tacacs-F4.0.4.28/tac_plus.c (891 lines of code) (raw):
/*
* $Id: tac_plus.c,v 1.59 2009-07-16 23:31:26 heas Exp $
*
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
* TACACS_PLUS daemon suitable for using on Unix systems.
*
* October 1994, Lol Grant
*
* Copyright (c) 1994-1998 by Cisco systems, Inc.
* Permission to use, copy, modify, and distribute this software for
* any purpose and without fee is hereby granted, provided that this
* copyright and permission notice appear on all copies of the
* software and supporting documentation, the name of Cisco Systems,
* Inc. not be used in advertising or publicity pertaining to
* distribution of the program without specific prior permission, and
* notice be given in supporting documentation that modification,
* copying and distribution is by permission of Cisco Systems, Inc.
*
* Cisco Systems, Inc. makes no representations about the suitability
* of this software for any purpose. THIS SOFTWARE IS PROVIDED ``AS
* IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE.
*/
#include "version.h"
#include "tac_plus.h"
#include <grp.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <pwd.h>
#include <sys/wait.h>
#include <signal.h>
#ifdef LIBWRAP
# include <tcpd.h>
int allow_severity = 0;
int deny_severity = 0;
#endif
char *progname; /* program name */
static int standalone = 1; /* running standalone (1) or under inetd (0) */
static int initialised; /* data structures have been allocated */
volatile sig_atomic_t reinitialize; /* schedule config reinitialization */
int sendauth_only; /* don't respond to sendpass requests */
int debug; /* debugging flags */
int facility = LOG_DAEMON; /* syslog facility */
int port = TAC_PLUS_PORT; /* port we're listening on */
char *portstr = TAC_PLUS_PORTSTR;
int console; /* write all syslog messages to console */
int parse_only; /* exit after verbose parsing */
int lookup_peer; /* look-up peer names from addresses */
#if HAVE_PID_T
pid_t childpid; /* child pid, global for unlink(PIDFILE) */
#else
int childpid;
#endif
int single; /* single thread (for debugging) */
int opt_G; /* foreground */
int opt_S; /* enable single-connection */
int wtmpfd; /* for wtmp file logging */
char *opt_Q;
char *opt_U;
int total_child_count = 0;
volatile sig_atomic_t dump_client_table = 0;
volatile sig_atomic_t reap_children = 0;
char *wtmpfile = NULL;
char *bind_address = NULL;
struct timeval started_at;
struct session session; /* session data */
#define PIDSZ 75
static char pidfilebuf[PIDSZ]; /* holds current name of the pidfile */
#define MSGBUFSZ 1024
static char msgbuf[MSGBUFSZ];
static RETSIGTYPE die(int);
static int get_socket(int **, int *);
static int init(void);
#if defined(REAPCHILD) && defined(REAPSIGIGN)
static RETSIGTYPE reapchild(int);
#endif
void start_session(void);
void vers(void);
void usage(void);
static RETSIGTYPE
die(int signum)
{
report(LOG_NOTICE, "Received signal %d, shutting down", signum);
if (childpid > 0)
unlink(pidfilebuf);
tac_exit(0);
}
static int
init(void)
{
if (initialised)
cfg_clean_config();
report(LOG_NOTICE, "Reading config");
if (!session.cfgfile) {
report(LOG_ERR, "no config file specified");
tac_exit(1);
}
/* read the config file */
if (cfg_read_config(session.cfgfile)) {
report(LOG_ERR, "Parsing %s", session.cfgfile);
tac_exit(1);
}
if (session.acctfile == NULL && !(session.flags & SESS_FLAG_ACCTSYSL))
session.acctfile = tac_strdup(TACPLUS_ACCTFILE);
initialised++;
reinitialize = 0;
report(LOG_NOTICE, "Version %s Initialized %d", version, initialised);
return 0;
}
static RETSIGTYPE
handler(int signum)
{
/* report() is not reentrant-safe */
#define RCVSIG_STR "Received signal\n"
(void)write(fileno(stderr), RCVSIG_STR, strlen(RCVSIG_STR));
reinitialize = 1;
#ifdef REARMSIGNAL
signal(SIGUSR1, handler);
signal(SIGHUP, handler);
#endif
}
static RETSIGTYPE
dump_clients_handler(int signum)
{
dump_client_table = 1;
#ifdef REARMSIGNAL
signal(SIGUSR2, dump_clients_handler);
#endif
}
#if defined(REAPCHILD) && defined(REAPSIGIGN)
RETSIGTYPE
reapchild(int notused)
{
reap_children = 1;
}
#endif
void
reapchildren()
{
#ifdef UNIONWAIT
union wait status;
#else
int status;
#endif
#if HAVE_PID_T
pid_t pid;
#else
int pid;
#endif
int procs_for_client;
for (;;) {
pid = waitpid(-1, &status, WNOHANG);
if (pid <= 1)
return;
snprintf(msgbuf, MSGBUFSZ, "Clening up session for pid %lu", pid);
report(LOG_DEBUG, msgbuf);
procs_for_client = decrement_client_count_for_proc(pid);
total_child_count--;
}
reap_children = 0;
}
/*
* Return a socket bound to an appropriate port number/address. Exits
* the program on failure.
*/
static int
get_socket(int **sa, int *nsa)
{
char host[NI_MAXHOST], serv[NI_MAXHOST];
struct addrinfo hint, *res, *rp;
int ecode,
flag,
kalive = 1,
s;
memset(&hint, 0, sizeof(struct addrinfo));
hint.ai_family = AF_UNSPEC;
hint.ai_socktype = SOCK_STREAM;
hint.ai_protocol = IPPROTO_TCP;
hint.ai_flags = AI_PASSIVE;
#ifdef AI_ADDRCONFIG
hint.ai_flags |= AI_ADDRCONFIG;
#endif
if (bind_address)
ecode = getaddrinfo(bind_address, portstr, &hint, &res);
else
ecode = getaddrinfo(NULL, portstr, &hint, &res);
if (ecode != 0) {
report(LOG_ERR, "getaddrinfo: %s\n", gai_strerror(ecode));
tac_exit(1);
}
*sa = NULL;
*nsa = 0;
for (rp = res; rp != NULL; rp = rp->ai_next) {
s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (s == -1)
continue;
if (1 || debug & DEBUG_PACKET_FLAG)
report(LOG_DEBUG, "socket FD %d AF %d", s, rp->ai_family);
flag = 1;
#ifdef IPV6_V6ONLY
if (rp->ai_family == AF_INET6)
setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag));
#endif
#ifdef SO_REUSEADDR
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&flag,
sizeof(flag)) < 0)
perror("setsockopt - SO_REUSEADDR");
#endif
if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&kalive,
sizeof(kalive)) < 0)
perror("setsockopt - SO_KEEPALIVE");
flag = 0;
if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *)&flag,
sizeof(flag)) < 0)
perror("setsockopt - SO_NODELAY");
if (bind(s, rp->ai_addr, rp->ai_addrlen) < 0) {
console = 1;
ecode = errno;
if (lookup_peer)
flag = 0;
else
flag = NI_NUMERICHOST | NI_NUMERICSERV;
if (getnameinfo(rp->ai_addr, rp->ai_addrlen, host, NI_MAXHOST,
serv, NI_MAXHOST, flag)) {
strncpy(host, "unknown", NI_MAXHOST - 1);
host[NI_MAXHOST - 1] = '\0';
strncpy(serv, "unknown", NI_MAXHOST - 1);
serv[NI_MAXHOST - 1] = '\0';
}
report(LOG_ERR, "get_socket: bind %s:%s %s", host, serv,
strerror(ecode));
console = 0;
close(s);
s = -1;
continue;
}
if (*sa == NULL)
*sa = malloc(sizeof(int) * ++(*nsa));
else
*sa = realloc(*sa, sizeof(int) * ++(*nsa));
if (*sa == NULL) {
report(LOG_ERR, "malloc failure: %s", strerror(errno));
tac_exit(1);
}
(*sa)[*nsa - 1] = s;
}
freeaddrinfo(res);
if (*nsa < 1) {
console = 1;
report(LOG_ERR, "get_socket: could not bind a listening socket");
tac_exit(1);
}
return(0);
}
void
open_logfile(void)
{
openlog("tac_plus", LOG_PID, facility);
setlogmask(LOG_UPTO(LOG_DEBUG));
}
/*
* We will eventually be called from inetd or via the rc scripts directly
* Parse arguments and act appropiately.
*/
int
main(int argc, char **argv)
{
extern char *optarg;
FILE *fp;
int c, *s, ns, somaxconn;
struct pollfd *pfds;
#ifndef SOMAXCONN
# define SOMAXCONN 64
#endif
somaxconn = SOMAXCONN;
#if PROFILE
moncontrol(0);
#endif
if ((progname = strrchr(*argv, '/')) != NULL) {
progname++;
} else
progname = *argv;
/* initialise global session data */
memset(&session, 0, sizeof(session));
session.peer = tac_strdup("unknown");
#if defined(REAPCHILD) && defined(REAPSIGIGN)
client_count_init();
#endif
open_logfile();
if (argc <= 1) {
usage();
tac_exit(1);
}
while ((c = getopt(argc, argv, "B:C:d:hiPp:Q:tGgvSsLl:m:w:U:u:")) != EOF)
switch (c) {
case 'B': /* bind() address*/
bind_address = optarg;
break;
case 'L': /* lookup peer names via DNS */
lookup_peer = 1;
break;
case 's': /* don't respond to sendpass */
sendauth_only = 1;
break;
case 'v': /* print version and exit */
vers();
tac_exit(1);
case 't':
console = 1; /* log to console too */
break;
case 'P': /* Parse config file only */
parse_only = 1;
break;
case 'G': /* foreground */
opt_G = 1;
break;
case 'g': /* single threaded */
single = 1;
break;
case 'p': /* port */
port = atoi(optarg);
portstr = optarg;
break;
case 'd': /* debug */
debug |= atoi(optarg);
break;
case 'C': /* config file name */
session.cfgfile = tac_strdup(optarg);
break;
case 'h': /* usage */
usage();
tac_exit(0);
case 'i': /* inetd mode */
standalone = 0;
break;
case 'l': /* logfile */
logfile = tac_strdup(optarg);
break;
case 'm': /* SOMAXCONN */
somaxconn = atoi(optarg);
break;
case 'Q': /* setgid */
opt_Q = tac_strdup(optarg);
break;
case 'S': /* enable single-connection */
opt_S = 1;
break;
#ifdef MAXSESS
case 'w': /* wholog file */
wholog = tac_strdup(optarg);
break;
#endif
case 'U': /* setuid */
opt_U = tac_strdup(optarg);
break;
case 'u':
wtmpfile = tac_strdup(optarg);
break;
default:
fprintf(stderr, "%s: bad switch %c\n", progname, c);
usage();
tac_exit(1);
}
parser_init();
/* read the configuration/etc */
init();
signal(SIGUSR1, handler);
signal(SIGHUP, handler);
signal(SIGUSR2, dump_clients_handler);
signal(SIGTERM, die);
signal(SIGPIPE, SIG_IGN);
if (parse_only)
tac_exit(0);
if (debug)
report(LOG_DEBUG, "tac_plus server %s starting", version);
if (!standalone) {
/* running under inetd */
char host[NI_MAXHOST];
int on;
struct sockaddr_in name;
socklen_t name_len;
name_len = sizeof(name);
session.flags |= SESS_NO_SINGLECONN;
session.sock = 0;
if (getpeername(session.sock, (struct sockaddr *)&name, &name_len)) {
report(LOG_ERR, "getpeername failure %s", strerror(errno));
} else {
if (getnameinfo((struct sockaddr *)&name, name_len, host,
NI_MAXHOST, NULL, 0, NI_NUMERICHOST)) {
strncpy(host, "unknown", NI_MAXHOST - 1);
host[NI_MAXHOST - 1] = '\0';
}
if (session.peer) {
free(session.peer);
}
session.peer = tac_strdup(host);
if (session.peerip)
free(session.peerip);
session.peerip = tac_strdup((char *)host);
if (debug & DEBUG_AUTHEN_FLAG)
report(LOG_INFO, "session.peerip is %s", session.peerip);
}
#ifdef FIONBIO
on = 1;
if (ioctl(session.sock, FIONBIO, &on) < 0) {
report(LOG_ERR, "ioctl(FIONBIO) %s", strerror(errno));
tac_exit(1);
}
#endif
start_session();
tac_exit(0);
}
if (single) {
session.flags |= SESS_NO_SINGLECONN;
} else {
/*
* Running standalone; background ourselves and release controlling
* tty, unless -G option was specified to keep the parent in the
* foreground.
*/
#ifdef SIGTTOU
signal(SIGTTOU, SIG_IGN);
#endif
#ifdef SIGTTIN
signal(SIGTTIN, SIG_IGN);
#endif
#ifdef SIGTSTP
signal(SIGTSTP, SIG_IGN);
#endif
if (!opt_S)
session.flags |= SESS_NO_SINGLECONN;
if (!opt_G) {
if ((childpid = fork()) < 0)
report(LOG_ERR, "Can't fork first child");
else if (childpid > 0)
exit(0); /* parent */
if (debug)
report(LOG_DEBUG, "Backgrounded");
#if SETPGRP_VOID
if (setpgrp() == -1)
#else
if (setpgrp(0, getpid()) == -1)
#endif /* SETPGRP_VOID */
report(LOG_ERR, "Can't change process group: %s",
strerror(errno));
/* XXX What does "REAPCHILD" have to do with TIOCNOTTY? */
#ifndef REAPCHILD
c = open("/dev/tty", O_RDWR);
if (c >= 0) {
ioctl(c, TIOCNOTTY, (char *)0);
(void) close(c);
}
#else /* REAPCHILD */
if ((childpid = fork()) < 0)
report(LOG_ERR, "Can't fork second child");
else if (childpid > 0)
exit(0);
if (debug & DEBUG_FORK_FLAG)
report(LOG_DEBUG, "Forked grandchild");
#endif /* REAPCHILD */
/* some systems require this */
closelog();
for (c = getdtablesize(); c >= 0; c--)
(void)close(c);
/*
* make sure we can still log to syslog now that we have closed
* everything
*/
open_logfile();
}
}
#if REAPCHILD
#if REAPSIGIGN
signal(SIGCHLD, reapchild);
#else
signal(SIGCHLD, SIG_IGN);
#endif
#endif
ostream = NULL;
/* chdir("/"); */
umask(022);
errno = 0;
get_socket(&s, &ns);
for (c = 0; c < ns; c++) {
if (listen(s[c], somaxconn) < 0) {
console = 1;
report(LOG_ERR, "listen: %s", strerror(errno));
tac_exit(1);
}
}
if (port == TAC_PLUS_PORT) {
if (bind_address == NULL) {
strncpy(pidfilebuf, TACPLUS_PIDFILE, PIDSZ);
if (pidfilebuf[PIDSZ - 1] != '\0')
c = PIDSZ;
else
c = PIDSZ - 1;
} else
c = snprintf(pidfilebuf, PIDSZ, "%s.%s", TACPLUS_PIDFILE,
bind_address);
} else {
if (bind_address == NULL)
c = snprintf(pidfilebuf, PIDSZ, "%s.%d", TACPLUS_PIDFILE, port);
else
c = snprintf(pidfilebuf, PIDSZ, "%s.%s.%d", TACPLUS_PIDFILE,
bind_address, port);
}
if (c >= PIDSZ) {
pidfilebuf[PIDSZ - 1] = '\0';
report(LOG_ERR, "pid filename truncated: %s", pidfilebuf);
childpid = 0;
} else {
/* write process id to pidfile */
if ((fp = fopen(pidfilebuf, "w")) != NULL) {
fprintf(fp, "%d\n", (int)getpid());
fclose(fp);
/*
* After forking to disassociate; make sure we know we're the
* mother so that we remove our pid file upon exit in die().
*/
childpid = 1;
} else {
report(LOG_ERR, "Cannot write pid to %s %s", pidfilebuf,
strerror(errno));
childpid = 0;
}
}
if (opt_Q) {
struct group *gr;
if ((gr = getgrnam(opt_Q)) == NULL) {
report(LOG_ERR, "Could set groupid to %s: %s", opt_Q,
strerror(errno));
} else if (setgid(gr->gr_gid)) {
report(LOG_ERR, "Cannot set group id to %d %s",
gr->gr_gid, strerror(errno));
}
}
#ifdef TACPLUS_GROUPID
else if (setgid(TACPLUS_GROUPID))
report(LOG_ERR, "Cannot set group id to %d %s",
TACPLUS_GROUPID, strerror(errno));
#endif
if (opt_U) {
struct passwd *pw;
if ((pw = getpwnam(opt_U)) == NULL) {
report(LOG_ERR, "Could not find username %s: %s", opt_U,
strerror(errno));
} else if (setuid(pw->pw_uid)) {
report(LOG_ERR, "Cannot set user id to %d %s",
pw->pw_uid, strerror(errno));
}
}
#ifdef TACPLUS_USERID
else if (setuid(TACPLUS_USERID))
report(LOG_ERR, "Cannot set user id to %d %s",
TACPLUS_USERID, strerror(errno));
#endif
#ifdef MAXSESS
maxsess_loginit();
#endif /* MAXSESS */
report(LOG_DEBUG, "uid=%d euid=%d gid=%d egid=%d s=%d",
getuid(), geteuid(), getgid(), getegid(), s);
pfds = malloc(sizeof(struct pollfd) * ns);
if (pfds == NULL) {
report(LOG_ERR, "malloc failure: %s", strerror(errno));
tac_exit(1);
}
for (c = 0; c < ns; c++) {
pfds[c].fd = s[c];
pfds[c].events = POLLIN | POLLERR | POLLHUP | POLLNVAL;
}
for (;;) {
#if HAVE_PID_T
pid_t pid;
#else
int pid;
#endif
char host[NI_MAXHOST];
struct sockaddr_storage from;
socklen_t from_len = sizeof(from);
int newsockfd = -1;
int flags, status;
int procs_for_client;
#if defined(REAPCHILD) && defined(REAPSIGIGN)
if (reap_children)
reapchildren();
#endif
if (reinitialize)
init();
if (dump_client_table) {
report(LOG_ALERT, "Dumping Client Tables");
dump_client_tables();
dump_client_table = 0;
}
status = poll(pfds, ns, TAC_PLUS_ACCEPT_TIMEOUT * 1000);
if (status == 0)
continue;
if (status == -1)
if (errno == EINTR)
continue;
memset((char *)&from, 0, sizeof(from));
for (c = 0; c < ns; c++) {
if (pfds[c].revents & POLLIN)
newsockfd = accept(s[c], (struct sockaddr *)&from, &from_len);
/* New connection, break out of for loop to fork, avoids
accepting 2 connections then forking, leaving one stranded */
if (newsockfd >= 0)
break;
else if (pfds[c].revents & (POLLERR | POLLHUP | POLLNVAL)) {
report(LOG_ERR, "exception on listen FD %d", s[c]);
tac_exit(1);
}
}
if (newsockfd < 0) {
if (errno == EINTR)
continue;
report(LOG_ERR, "accept: %s", strerror(errno));
continue;
}
if (getnameinfo((struct sockaddr *)&from, from_len, host, NI_MAXHOST,
NULL, 0, NI_NUMERICHOST)) {
strncpy(host, "unknown", NI_MAXHOST - 1);
host[NI_MAXHOST - 1] = '\0';
}
if (session.peer) {
free(session.peer);
}
session.peer = tac_strdup(host);
if (session.peerip)
free(session.peerip);
session.peerip = tac_strdup((char *)host);
if (debug & DEBUG_PACKET_FLAG)
report(LOG_DEBUG, "session request from %s sock=%d",
session.peer, newsockfd);
if (!single) {
#if defined(REAPCHILD) && defined(REAPSIGIGN)
/* first we check the tocal process count to see if we are at the limit */
if (total_child_count >= cfg_get_maxprocs()) {
report(LOG_ALERT, "refused connection from %s [%s] at global max procs [%d]",
session.peer, session.peerip, total_child_count);
shutdown(newsockfd, 2);
close(newsockfd);
continue;
}
/* no we check the process count per client */
procs_for_client = get_client_count(session.peerip);
report(LOG_ALERT, "connection [%d] from %s [%s]", procs_for_client + 1, session.peer, session.peerip);
if (procs_for_client >= cfg_get_maxprocsperclt()) {
report(LOG_ALERT, "refused connection from %s [%s] at client max procs [%d]",
session.peer, session.peerip, procs_for_client);
shutdown(newsockfd, 2);
close(newsockfd);
continue;
}
#endif
pid = fork();
if (pid < 0) {
report(LOG_ERR, "fork error");
tac_exit(1);
}
} else {
pid = 0;
}
if (pid == 0) {
/* child */
if (!single)
for (c = 0; c < ns; c++)
close(s[c]);
session.sock = newsockfd;
#ifdef LIBWRAP
if (! hosts_ctl(progname,session.peer,session.peerip,progname)) {
report(LOG_ALERT, "refused connection from %s [%s]",
session.peer, session.peerip);
shutdown(session.sock, 2);
close(session.sock);
if (!single) {
tac_exit(0);
} else {
close(session.sock);
continue;
}
}
report(LOG_INFO, "connect from %s [%s]", session.peer,
session.peerip);
#endif
#if PROFILE
moncontrol(1);
#endif
start_session();
shutdown(session.sock, 2);
close(session.sock);
if (!single)
tac_exit(0);
} else {
if (debug & DEBUG_FORK_FLAG)
report(LOG_DEBUG, "forked %ld", (long)pid);
/* parent */
#if defined(REAPCHILD) && defined(REAPSIGIGN)
total_child_count++;
procs_for_client = increment_client_count_for_proc(pid, session.peerip);
snprintf(msgbuf, MSGBUFSZ, "forked %lu for %s, procs %d, procs for client %d",
(long)pid, session.peerip, total_child_count, procs_for_client);
report(LOG_DEBUG, msgbuf);
#endif
close(newsockfd);
}
}
}
#ifndef HAVE_GETDTABLESIZE
int
getdtablesize(void)
{
return(_NFILE);
}
#endif /* HAVE_GETDTABLESIZE */
/* Make sure version number is kosher. Return 0 if it is */
int
bad_version_check(u_char *pak)
{
HDR *hdr = (HDR *)pak;
switch (hdr->type) {
case TAC_PLUS_AUTHEN:
/*
* Let authen routines take care of more sophisticated version
* checking as its now a bit involved.
*/
return(0);
case TAC_PLUS_AUTHOR:
case TAC_PLUS_ACCT:
if (hdr->version != TAC_PLUS_VER_0) {
send_error_reply(hdr->type, "Illegal packet version");
return(1);
}
return(0);
default:
return(1);
}
}
/*
* Determine the packet type, read the rest of the packet data,
* decrypt it and call the appropriate service routine.
*/
void
start_session(void)
{
u_char *pak;
HDR *hdr;
do {
session.seq_no = 0;
session.aborted = 0;
session.version = 0;
pak = read_packet();
if (pak == NULL)
break;
if (debug & DEBUG_PACKET_FLAG) {
report(LOG_DEBUG, "validation request from %s", session.peer);
dump_nas_pak(pak);
}
hdr = (HDR *)pak;
session.session_id = ntohl(hdr->session_id);
/* Do some version checking */
if (bad_version_check(pak)) {
free(pak);
break;
}
switch (hdr->type) {
case TAC_PLUS_AUTHEN:
authen(pak);
free(pak);
break;
case TAC_PLUS_AUTHOR:
author(pak);
free(pak);
break;
case TAC_PLUS_ACCT:
accounting(pak);
free(pak);
break;
default:
/* Note: can't send error reply if type is unknown */
report(LOG_ERR, "Illegal type %d in received packet", hdr->type);
free(pak);
goto OUT;
}
} while (!(session.flags & SESS_NO_SINGLECONN) &&
session.peerflags & TAC_PLUS_SINGLE_CONNECT_FLAG);
OUT:
if (debug & DEBUG_PACKET_FLAG)
report(LOG_DEBUG, "%s: disconnect", session.peer);
}
void
usage(void)
{
fprintf(stderr, "Usage: tac_plus -C <config_file> [-GghiLPstv]"
" [-B <bind address>]"
" [-d <debug level>]"
" [-l <logfile>]"
" [-p <port>]"
" [-Q <setgid groupname>]"
" [-U <setuid username>]"
" [-u <wtmpfile>]"
#ifdef MAXSESS
" [-w <whologfile>]"
#endif
"\n");
fprintf(stderr, "\t-G\tstay in foreground; do not detach from the tty\n"
"\t-g\tsingle thread mode\n"
"\t-h\tdisplay this message\n"
"\t-i\tinetd mode\n"
"\t-L\tlookup peer addresses for logs\n"
"\t-P\tparse the configuration file and exit\n"
"\t-S\tenable single-connection\n"
"\t-s\trefuse SENDPASS\n"
"\t-t\talso log to /dev/console\n"
"\t-v\tdisplay version information\n");
return;
}
void
vers(void)
{
fprintf(stdout, "tac_plus version %s\n", version);
#if ACECLNT
fprintf(stdout, "ACECLNT\n");
#endif
#if ACLS
fprintf(stdout, "ACLS\n");
#endif
#if AIX
fprintf(stdout, "AIX\n");
#endif
#if ARAP_DES
fprintf(stdout, "ARAP_DES\n");
#endif
#if BSDI
fprintf(stdout, "BSDI\n");
#endif
#if DEBUG
fprintf(stdout, "DEBUG\n");
#endif
#if DES_DEBUG
fprintf(stdout, "DES_DEBUG\n");
#endif
#ifdef FIONBIO
fprintf(stdout, "FIONBIO\n");
#endif
#if FREEBSD
fprintf(stdout, "FREEBSD\n");
#endif
#ifndef HAVE_GETDTABLESIZE
fprintf(stdout, "GETDTABLESIZE\n");
#endif
#if HPUX
fprintf(stdout, "HPUX\n");
#endif
#if LIBWRAP
fprintf(stdout, "LIBWRAP\n");
#endif
#if LINUX
fprintf(stdout, "LINUX\n");
#endif
#if LITTLE_ENDIAN
fprintf(stdout, "LITTLE_ENDIAN\n");
#endif
#if LOG_DAEMON
fprintf(stdout, "LOG_DAEMON\n");
#endif
#ifdef MAXSESS
fprintf(stdout, "MAXSESS\n");
#endif
#ifdef MAXSESS_FINGER
fprintf(stdout, "MAXSESS_FINGER\n");
#endif
#if MIPS
fprintf(stdout, "MIPS\n");
#endif
#if NETBSD
fprintf(stdout, "NETBSD\n");
#endif
#ifdef HAVE_PAM
fprintf(stdout, "PAM\n");
#endif
#ifdef NO_PWAGE
fprintf(stdout, "NO_PWAGE\n");
#endif
#ifdef REAPCHILD
fprintf(stdout, "REAPCHILD\n");
#endif
#ifdef REAPSIGIGN
fprintf(stdout, "REAPSIGIGN\n");
#endif
#ifdef REARMSIGNAL
fprintf(stdout, "REARMSIGNAL\n");
#endif
#ifdef RETSIGTYPE
# define _RETSIGTYPE(a) #a
fprintf(stdout, "RETSIGTYPE %s\n", _RETSIGTYPE(RETSIGTYPE));
#endif
#ifdef SHADOW_PASSWORDS
fprintf(stdout, "SHADOW_PASSWORDS\n");
#endif
#if SIGTSTP
fprintf(stdout, "SIGTSTP\n");
#endif
#if SIGTTIN
fprintf(stdout, "SIGTTIN\n");
#endif
#if SIGTTOU
fprintf(stdout, "SIGTTOU\n");
#endif
#if SKEY
fprintf(stdout, "SKEY\n");
#endif
#if SOLARIS
fprintf(stdout, "SOLARIS\n");
#endif
#if SO_REUSEADDR
fprintf(stdout, "SO_REUSEADDR\n");
#endif
#if STDLIB_MALLOC
fprintf(stdout, "STDLIB_MALLOC\n");
#endif
#if STRCSPN
fprintf(stdout, "STRCSPN\n");
#endif
#if HAVE_STRERROR
fprintf(stdout, "STRERROR\n");
#else
fprintf(stdout, "SYSERRLIST\n");
/* XXX: fprintf(stdout,"CONST_SYSERRLIST\n"); */
#endif
#if SYSLOG_IN_SYS
fprintf(stdout, "SYSLOG_IN_SYS\n");
#endif
#if TACPLUS_GROUPID
fprintf(stdout, "TACPLUS_GROUPID\n");
#endif
#if TAC_PLUS_PORT
fprintf(stdout, "TAC_PLUS_PORT\n");
#endif
#if TACPLUS_USERID
fprintf(stdout, "TACPLUS_USERID\n");
#endif
#if TRACE
fprintf(stdout, "TRACE\n");
#endif
#if UENABLE
fprintf(stdout, "UENABLE\n");
#endif
#if UNIONWAIT
fprintf(stdout, "UNIONWAIT\n");
#endif
#if _BSD1
fprintf(stdout, "_BSD1\n");
#endif
#if _BSD_INCLUDES
fprintf(stdout, "_BSD_INCLUDES\n");
#endif
#if __STDC__
fprintf(stdout, "__STDC__\n");
#endif
return;
}