/* * $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 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 [-ghiLPstv]" " [-B ]" " [-d ]" " [-l ]" " [-p ]" " [-u ]" #ifdef MAXSESS " [-w ]" #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; }