/*
* $Id: tac_plus.c,v 1.34 2006/08/15 22:13:44 heas Exp $
*
* TACACS_PLUS daemon suitable for using on Un*x 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 "sys/wait.h"
#include "signal.h"
#ifdef LIBWRAP
# include <tcpd.h>
int allow_severity = 0;
int deny_severity = 0;
#endif
static int standalone = 1; /* running standalone (1) or under inetd (0) */
static int initialised = 0; /* data structures have been allocated */
int sendauth_only = 0; /* don't respond to sendpass requests */
int debug = 0; /* debugging flags */
int port = 0; /* port we're listening on */
int console = 0; /* write all syslog messages to console */
int parse_only = 0; /* exit after verbose parsing */
int childpid = 1; /* child pid, global for unlink(PIDFILE) */
int single = 0; /* single thread (for debugging) */
int wtmpfd = 0; /* for wtmp file logging */
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 */
#ifndef REAPCHILD
static RETSIGTYPE reapchild(int);
#endif
void start_session(void);
void vers(void);
void usage(void);
#ifndef REAPCHILD
static
RETSIGTYPE
reapchild(int notused)
{
#ifdef UNIONWAIT
union wait status;
#else
int status;
#endif
int pid;
for (;;) {
pid = wait3(&status, WNOHANG, 0);
if (pid <= 0)
return;
if (debug & DEBUG_FORK_FLAG)
report(LOG_DEBUG, "%d reaped", pid);
}
}
#endif /* REAPCHILD */
static RETSIGTYPE
die(int signum)
{
report(LOG_NOTICE, "Received signal %d, shutting down", signum);
if (childpid > 0)
unlink(pidfilebuf);
tac_exit(0);
}
static RETSIGTYPE
init(void)
{
if (initialised)
cfg_clean_config();
report(LOG_NOTICE, "Reading config");
session.acctfile = tac_strdup(TACPLUS_ACCTFILE);
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);
}
initialised++;
report(LOG_NOTICE, "Version %s Initialized %d", version, initialised);
}
static RETSIGTYPE
handler(int signum)
{
report(LOG_NOTICE, "Received signal %d", signum);
init();
#ifdef REARMSIGNAL
signal(SIGUSR1, handler);
signal(SIGHUP, handler);
#endif
}
/*
* Return a socket bound to an appropriate port number/address. Exits
* the program on failure.
*/
int
get_socket(void)
{
int s;
struct sockaddr_in sin;
struct servent *sp;
u_long inaddr;
int on = 1,
kalive = 1;
bzero((char *) &sin, sizeof(sin));
if (port) {
sin.sin_port = htons(port);
} else {
sp = getservbyname("tacacs", "tcp");
if (sp)
sin.sin_port = sp->s_port;
else {
report(LOG_ERR, "Cannot find socket port");
tac_exit(1);
}
}
sin.sin_family = AF_INET;
if (! bind_address) {
sin.sin_addr.s_addr = htonl(INADDR_ANY);
} else {
if ((inaddr = inet_addr(bind_address)) != -1) {
/* A dotted decimal address */
bcopy(&inaddr, &sin.sin_addr, sizeof(inaddr));
sin.sin_family = AF_INET;
} else {
report(LOG_ERR, "Invalid bind address specification: '%s'",
bind_address);
tac_exit(1);
}
}
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
console++;
report(LOG_ERR, "get_socket: socket: %s", strerror(errno));
tac_exit(1);
}
#ifdef SO_REUSEADDR
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
sizeof(on)) < 0)
perror("setsockopt - SO_REUSEADDR");
#endif /* SO_REUSEADDR */
if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &kalive,
sizeof(kalive)) < 0)
perror("setsockopt - SO_KEEPALIVE");
if (bind(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
console++;
report(LOG_ERR, "get_socket: bind %d %s", ntohs(sin.sin_port),
strerror(errno));
tac_exit(1);
}
return(s);
}
static void
open_logfile(void)
{
#ifdef LOG_DAEMON
openlog("tac_plus", LOG_PID, LOG_DAEMON);
#else
openlog("tac_plus", LOG_PID);
#endif
setlogmask(LOG_UPTO(LOG_DEBUG));
}
/*
* main
*
* 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;
int c;
int s;
FILE *fp;
int lookup_peer = 0;
#ifdef LIBWRAP
char *progname;
#endif
debug = 0; /* no debugging */
standalone = 1; /* standalone */
single = 0; /* single threaded */
#if PROFILE
moncontrol(0);
#endif
#ifdef LIBWRAP
if ((progname = strrchr(*argv, '/')) != NULL) {
progname++;
} else
progname = *argv;
#endif
/* initialise global session data */
bzero(&session, sizeof(session));
session.peer = tac_strdup("unknown");
open_logfile();
#ifdef TAC_PLUS_PORT
port = TAC_PLUS_PORT;
#endif
if (argc <= 1) {
usage();
tac_exit(1);
}
while ((c = getopt(argc, argv, "B:C:d:hiPp:tgvsLl:w:u:")) != EOF)
switch (c) {
case 'B': /* bind() address*/
bind_address = optarg;
break;
case 'L': /* lookup peer names via DNS */
lookup_peer++;
break;
case 's': /* don't respond to sendpass */
sendauth_only++;
break;
case 'v': /* print version and exit */
vers();
tac_exit(1);
case 't':
console++; /* log to console too */
break;
case 'P': /* Parse config file only */
parse_only++;
break;
case 'g': /* single threaded */
single++;
break;
case 'p': /* port */
port = atoi(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;
#ifdef MAXSESS
case 'w': /* wholog file */
wholog = tac_strdup(optarg);
break;
#endif
case 'u':
wtmpfile = tac_strdup(optarg);
break;
default:
fprintf(stderr, "%s: bad switch %c\n", argv[0], c);
usage();
tac_exit(1);
}
if (geteuid() != 0) {
fprintf(stderr, "Warning, not running as uid 0\n");
fprintf(stderr, "Tac_plus is usually run as root\n");
}
parser_init();
init();
signal(SIGUSR1, handler);
signal(SIGHUP, 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 */
struct sockaddr_in name;
socklen_t name_len;
#ifdef FIONBIO
int on = 1;
#endif
name_len = sizeof(name);
session.sock = 0;
if (getpeername(session.sock, (struct sockaddr *) &name, &name_len)) {
report(LOG_ERR, "getpeername failure %s", strerror(errno));
} else {
struct hostent *hp = NULL;
if (lookup_peer) {
hp = gethostbyaddr((char *) &name.sin_addr.s_addr,
sizeof(name.sin_addr.s_addr), AF_INET);
}
if (session.peer) {
free(session.peer);
}
session.peer = tac_strdup(hp ? hp->h_name :
(char *) inet_ntoa(name.sin_addr));
if (session.peerip)
free(session.peerip);
session.peerip = tac_strdup((char *) inet_ntoa(name.sin_addr));
if (debug & DEBUG_AUTHEN_FLAG)
report(LOG_INFO, "session.peerip is %s", session.peerip);
}
#ifdef FIONBIO
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) {
/* Running standalone. Background ourselves, release controlling tty */
#ifdef SIGTTOU
signal(SIGTTOU, SIG_IGN);
#endif
#ifdef SIGTTIN
signal(SIGTTIN, SIG_IGN);
#endif
#ifdef SIGTSTP
signal(SIGTSTP, SIG_IGN);
#endif
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");
#ifndef REAPCHILD
#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));
c = open("/dev/tty", O_RDWR);
if (c >= 0) {
ioctl(c, TIOCNOTTY, (char *) 0);
(void) close(c);
}
signal(SIGCHLD, reapchild);
#else /* REAPCHILD */
#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));
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");
signal(SIGCHLD, SIG_IGN);
#endif /* REAPCHILD */
/*
* 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;
closelog(); /* some systems require this */
for (c = 0; c < getdtablesize(); c++)
(void) close(c);
/* make sure we can still log to syslog now we've closed everything */
open_logfile();
} /* ! single threaded */
ostream = NULL;
/* chdir("/"); */
umask(022);
errno = 0;
s = get_socket();
#ifndef SOMAXCONN
#define SOMAXCONN 5
#endif
if (listen(s, SOMAXCONN) < 0) {
console++;
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);
}
/* write process id to pidfile */
if ((fp = fopen(pidfilebuf, "w")) != NULL) {
fprintf(fp, "%d\n", (int) getpid());
fclose(fp);
} else
report(LOG_ERR, "Cannot write pid to %s %s",
pidfilebuf, strerror(errno));
#ifdef TACPLUS_GROUPID
if (setgid(TACPLUS_GROUPID))
report(LOG_ERR, "Cannot set group id to %d %s",
TACPLUS_GROUPID, strerror(errno));
#endif
#ifdef TACPLUS_USERID
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);
for (;;) {
int pid;
struct sockaddr_in from;
socklen_t from_len;
int newsockfd;
struct hostent *hp = NULL;
bzero((char *) &from, sizeof(from));
from_len = sizeof(from);
newsockfd = accept(s, (struct sockaddr *) &from, &from_len);
if (newsockfd < 0) {
if (errno == EINTR)
continue;
report(LOG_ERR, "accept: %s", strerror(errno));
continue;
}
if (lookup_peer) {
hp = gethostbyaddr((char *) &from.sin_addr.s_addr,
sizeof(from.sin_addr.s_addr), AF_INET);
}
if (session.peer) {
free(session.peer);
}
session.peer = tac_strdup(hp ? hp->h_name :
(char *) inet_ntoa(from.sin_addr));
if (session.peerip)
free(session.peerip);
session.peerip = tac_strdup((char *) inet_ntoa(from.sin_addr));
if (debug & DEBUG_AUTHEN_FLAG)
report(LOG_INFO, "session.peerip is %s", session.peerip);
if (debug & DEBUG_PACKET_FLAG)
report(LOG_DEBUG, "session request from %s sock=%d",
session.peer, newsockfd);
if (!single) {
pid = fork();
if (pid < 0) {
report(LOG_ERR, "fork error");
tac_exit(1);
}
} else {
pid = 0;
}
if (pid == 0) {
/* child */
if (!single)
close(s);
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);
tac_exit(0);
}
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 %d", pid);
/* parent */
close(newsockfd);
}
}
}
#ifdef GETDTABLESIZE
int
getdtablesize(void)
{
return(_NFILE);
}
#endif /* 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, *read_packet();
HDR *hdr;
void authen();
session.seq_no = 0;
session.aborted = 0;
session.version = 0;
pak = read_packet();
if (!pak) {
return;
}
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);
return;
}
switch (hdr->type) {
case TAC_PLUS_AUTHEN:
authen(pak);
free(pak);
return;
case TAC_PLUS_AUTHOR:
author(pak);
free(pak);
return;
case TAC_PLUS_ACCT:
accounting(pak);
return;
default:
/* Note: can't send error reply if type is unknown */
report(LOG_ERR, "Illegal type %d in received packet", hdr->type);
free(pak);
return;
}
}
void
usage(void)
{
fprintf(stderr, "Usage: tac_plus -C <config_file> [-ghiLPstv]"
" [-B <bind address>]"
" [-d <debug level>]"
" [-l <logfile>]"
" [-p <port>]"
" [-u <wtmpfile>]"
#ifdef MAXSESS
" [-w <whologfile>]"
#endif
"\n");
fprintf(stderr, "\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\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 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
#ifdef 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 ! HAVE_BZERO
fprintf(stdout, "NEED_BZERO\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 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
#ifdef SYSV
fprintf(stdout, "SYSV\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;
}
syntax highlighted by Code2HTML, v. 0.9.1