/* * tac_plus.c * * 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 "tac_plus.h" #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SYSLOG_H #include #endif #ifdef HAVE_SYS_SYSLOG_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_SYS_IOCTL_H #include #endif #include "main.h" #include "report.h" #include "utils.h" #include "cfgfile.h" #include "packet.h" #include "dump.h" #include "author.h" #include "acct.h" #include "authen.h" #include "do_acct.h" #include "parse.h" #ifdef MAXSESS #include "maxsess.h" #endif static void version TAC_ARGS((void)); static void start_session TAC_ARGS((void)); /* Configurable: */ #ifndef TAC_PLUS_PORT #define TAC_PLUS_PORT 49 #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 */ static 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 single = 0; /* single thread (for debugging) */ struct session session; /* session data */ static char pidfilebuf[75]; /* holds current name of the pidfile */ #ifndef REAPCHILD static RETSIGTYPE reapchild TAC_ARGS((int signo)); static RETSIGTYPE reapchild(signo) int signo; { #ifdef UNIONWAIT union wait status; #else int status; #endif int pid; signal(SIGCHLD, reapchild); for (;;) { pid = wait3(&status, WNOHANG, 0); if (pid <= 0) return; if (debug & DEBUG_FORK_FLAG) report(LOG_DEBUG, "%d reaped", pid); } } #endif /* REAPCHILD */ static void die TAC_ARGS((int signum)); static void die(signum) int signum; { report(LOG_INFO, "Received signal %d, shutting down", signum); unlink(pidfilebuf); tac_exit(0); } static void init TAC_ARGS((void)); static void init() { if (initialised) cfg_clean_config(); report(LOG_INFO, "Reading config"); session.acctfile = tac_strdup("/var/log/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); fprintf(stderr,"Config file not found!!\n"); tac_exit(1); } initialised++; report(LOG_INFO, "Version %s%s Initialized %d", VERSION, VERSION_TAIL, initialised); } /* 'handler()' will be called during initialization to setup signal handler, * keep it in mind when modifying it! */ static int handler_occured = 0; static RETSIGTYPE handler TAC_ARGS((int signum)); static RETSIGTYPE handler(signum) int signum; { /* never execute any non-trivial (=system-call) commands here * as it may immediately abort the whole 'handler()' (SYSV signal). * We hope that SA_RESTART is NOT set for our signals */ handler_occured = 1; /* It is never wrong to reinstall 'handler' just to be safe */ signal(SIGUSR1, handler); signal(SIGHUP , handler); /* DON'T interrupt! */ #ifdef HAVE_SIGINTERRUPT siginterrupt(SIGUSR1, 0 /* flag */); siginterrupt(SIGHUP , 0 /* flag */); #endif } static void check_handler_occured TAC_ARGS((void)); static RETSIGTYPE check_handler_occured() { if (!handler_occured) return; handler_occured = 0; report(LOG_INFO, "Signal detected, reloading configuration"); init(); } /* * Return a socket bound to an appropriate port number/address. Exits * the program on failure */ static int get_socket TAC_ARGS((void)); static int get_socket() { int s; struct sockaddr_in sin; struct servent *sp; int on = 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; sin.sin_addr.s_addr = htonl(INADDR_ANY); s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) { console++; report(LOG_ERR, "get_socket: socket: %s", sys_errlist[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 (bind(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) { console++; report(LOG_ERR, "get_socket: bind %d %s", ntohs(sin.sin_port), sys_errlist[errno]); tac_exit(1); } return (s); } static void open_logfile TAC_ARGS((void)); static void open_logfile() { #ifdef LOG_LOCAL6 openlog("tac_plus", LOG_PID, LOG_LOCAL6); #else openlog("tac_plus", LOG_PID); #endif setlogmask(LOG_UPTO(LOG_DEBUG)); } static void prep_session_peer TAC_ARGS((const struct sockaddr_in *from)); static void prep_session_peer(from) const struct sockaddr_in *from; { struct hostent *hp; if (session.peer_addr && session.peer_addr != session.peer) free(session.peer_addr); if (session.peer) free(session.peer); session.peer_addr = tac_strdup( (char *) inet_ntoa(from->sin_addr) ); hp = gethostbyaddr((char *) &from->sin_addr.s_addr, sizeof(from->sin_addr.s_addr), AF_INET); if (hp) session.peer = tac_strdup(hp->h_name); else session.peer = session.peer_addr; } /* * main * * We will eventually be called from inetd or via the rc scripts directly * Parse arguments and act appropiately. */ int main TAC_ARGS((int argc, char **argv)); int main(argc, argv) int argc; char **argv; { extern char *optarg; int childpid; int c; int s; FILE *fp; int lookup_peer = 0; debug = 0; /* no debugging */ standalone = 1; /* standalone */ single = 0; /* single threaded */ /* initialise global session data */ bzero(&session, sizeof(session)); session.peer = tac_strdup("unknown"); open_logfile(); #ifdef TAC_PLUS_PORT port = TAC_PLUS_PORT; #endif #ifdef MAINTAINER_MODE session.cfgfile = "/etc/tacacs/tac_plus.cfg"; #endif if (argc <= 1) { fprintf(stderr, "Usage: tac_plus -C \n"); fprintf(stderr, "\t[ -t ] [ -P ] [ -g ] [ -p ]\n"); fprintf(stderr, "\t[ -d ] [ -i ] [ -v ] [ -s ]\n"); fprintf(stderr, "\t[ -l logfile ]"); #ifdef MAXSESS fprintf(stderr, " [ -w whologfile ]"); #endif fprintf(stderr, "\n"); tac_exit(1); } while ((c = getopt(argc, argv, "td:C:ip:PgvsLl:w:u:")) != EOF) switch (c) { 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 */ version(); 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 'i': /* stand-alone */ 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); 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(); handler(-1 /* signum */); /* connect to the signals */ handler_occured = 0; /* post-fix cludge */ signal(SIGTERM, die); signal(SIGPIPE, SIG_IGN); if (parse_only) tac_exit(0); if (debug) report(LOG_DEBUG, "tac_plus server %s%s starting", VERSION, VERSION_TAIL); if (!standalone) { /* running under inetd */ struct sockaddr_in name; socklen_t name_len; #ifdef FIONBIO int fionbio_on = 1; #endif name_len = sizeof(name); if (getpeername(session.sock, (struct sockaddr *) &name, &name_len)) { report(LOG_ERR, "getpeername failure %s", sys_errlist[errno]); prep_session_peer(NULL); } else prep_session_peer(&name); session.sock = 0; #ifdef FIONBIO if (ioctl(session.sock, FIONBIO, &fionbio_on) < 0) { report(LOG_ERR, "ioctl(FIONBIO) %s", sys_errlist[errno]); tac_exit(1); } #endif start_session(); tac_exit(0); } if (!single) { /* Running standalone. Background ourselves, let go of controlling tty */ #ifdef SIGTTOU signal(SIGTTOU, SIG_IGN); #endif #ifdef SIGTTIN signal(SIGTTIN, SIG_IGN); #endif #ifdef SIGTSTP signal(SIGTSTP, SIG_IGN); #endif signal(SIGHUP, SIG_IGN); 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 #ifdef SETPGRP_VOID if (setpgrp() == -1) #else /* SETPGRP_VOID */ if (setpgrp(0, getpid()) == -1) #endif /* SETPGRP_VOID */ report(LOG_ERR, "Can't change process group"); #ifdef TIOCNOTTY c = open("/dev/tty", O_RDWR); if (c >= 0) { ioctl(c, TIOCNOTTY, (char *) 0); (void) close(c); } #endif signal(SIGCHLD, reapchild); #else /* REAPCHILD */ #ifdef SETPGRP_VOID if (setpgrp() == 1) #else /* SETPGRP_VOID */ if (setpgrp(0, getpid()) == 1) #endif /* SETPGRP_VOID */ report(LOG_ERR, "Can't change process group"); signal(SIGHUP, SIG_IGN); 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 */ 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(0); errno = 0; s = get_socket(); #ifndef SOMAXCONN #ifdef LINUX #define SOMAXCONN 128 #else #define SOMAXCONN 5 #endif /* LINUX */ #endif /* SOMAXCONN */ if (listen(s, SOMAXCONN) < 0) { console++; report(LOG_ERR, "listen: %s", sys_errlist[errno]); tac_exit(1); } if (port == TAC_PLUS_PORT) { strcpy(pidfilebuf, TACPLUS_PIDFILE); } else { sprintf(pidfilebuf, "%s.%d", TACPLUS_PIDFILE, port); } /* 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, sys_errlist[errno]); #ifdef TACPLUS_GROUPID if (setgid(TACPLUS_GROUPID)) report(LOG_ERR, "Cannot set group id to %d %s", TACPLUS_GROUPID, sys_errlist[errno]); #endif #ifdef TACPLUS_USERID if (setuid(TACPLUS_USERID)) report(LOG_ERR, "Cannot set user id to %d %s", TACPLUS_USERID, sys_errlist[errno]); #endif #ifdef MAXSESS maxsess_loginit(); #endif /* MAXSESS */ report(LOG_DEBUG, "uid=%d euid=%d gid=%d egid=%d s=%d", (int) getuid(), (int) geteuid(), (int) getgid(), (int) getegid(), s); for (;;) { int pid; struct sockaddr_in from; socklen_t from_len; int newsockfd; check_handler_occured(); bzero((char *) &from, sizeof(from)); from_len = sizeof(from); /* PERMIT interrupt of accept()! */ #ifdef HAVE_SIGINTERRUPT siginterrupt(SIGUSR1, 1 /* flag */); siginterrupt(SIGHUP , 1 /* flag */); #endif errno = 0; newsockfd = accept(s, (struct sockaddr *) &from, &from_len); /* DON'T interrupt! */ #ifdef HAVE_SIGINTERRUPT siginterrupt(SIGUSR1, 0 /* flag */); siginterrupt(SIGHUP , 0 /* flag */); #endif check_handler_occured(); if (newsockfd < 0) { /* sometimes we may get even 'errno==0' when 'handler()' signal occured */ if (errno == EINTR || errno == 0) continue; report(LOG_ERR, "accept: %s", sys_errlist[errno]); continue; } prep_session_peer(&from); 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; 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() { return(_NFILE); } #endif /* GETDTABLESIZE */ static int bad_version_check TAC_ARGS((u_char *pak)); /* Make sure version number is kosher. Return 0 if it is */ static int bad_version_check(pak) 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. * */ static void start_session TAC_ARGS((void)); static void start_session() { u_char *pak; HDR *hdr; session.seq_no = 0; session.aborted = 0; session.version = 0; /* Now we are starting our new 'request' cycle */ cfg_request_scan_begin(); 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; } } static void version TAC_ARGS((void)); static void version() { fprintf(stdout, "tac_plus version %s%s\n", VERSION, VERSION_TAIL); #ifdef AIX fprintf(stdout,"AIX\n"); #endif #ifdef ARAP_DES fprintf(stdout,"ARAP_DES\n"); #endif #ifdef BSDI fprintf(stdout,"BSDI\n"); #endif #ifdef CONST_SYSERRLIST fprintf(stdout,"CONST_SYSERRLIST\n"); #endif #ifdef DEBUG fprintf(stdout,"DEBUG\n"); #endif #ifdef DES_DEBUG fprintf(stdout,"DES_DEBUG\n"); #endif #ifdef FIONBIO fprintf(stdout,"FIONBIO\n"); #endif #ifdef FREEBSD fprintf(stdout,"FREEBSD\n"); #endif #ifdef GETDTABLESIZE fprintf(stdout,"GETDTABLESIZE\n"); #endif #ifdef HPUX fprintf(stdout,"HPUX\n"); #endif #ifdef LINUX fprintf(stdout,"LINUX\n"); #endif #ifdef LOG_LOCAL6 fprintf(stdout,"LOG_LOCAL6\n"); #endif #ifdef MAXSESS fprintf(stdout,"MAXSESS\n"); #endif #ifdef MIPS fprintf(stdout,"MIPS\n"); #endif #ifdef NEED_BZERO fprintf(stdout,"NEED_BZERO\n"); #endif #ifdef NETBSD fprintf(stdout,"NETBSD\n"); #endif #ifdef REAPCHILD fprintf(stdout,"REAPCHILD\n"); #endif #ifdef SHADOW_PASSWORDS fprintf(stdout,"SHADOW_PASSWORDS\n"); #endif #ifdef SIGTSTP fprintf(stdout,"SIGTSTP\n"); #endif #ifdef SIGTTIN fprintf(stdout,"SIGTTIN\n"); #endif #ifdef SIGTTOU fprintf(stdout,"SIGTTOU\n"); #endif #ifdef SKEY fprintf(stdout,"SKEY\n"); #endif #ifdef SOLARIS fprintf(stdout,"SOLARIS\n"); #endif #ifdef SO_REUSEADDR fprintf(stdout,"SO_REUSEADDR\n"); #endif #ifdef HAVE_STRCSPN fprintf(stdout,"HAVE_STRCSPN\n"); #endif #ifdef SYSV fprintf(stdout,"SYSV\n"); #endif #ifdef TACPLUS_GROUPID fprintf(stdout,"TACPLUS_GROUPID\n"); #endif #ifdef TAC_PLUS_PORT fprintf(stdout,"TAC_PLUS_PORT\n"); #endif #ifdef TACPLUS_USERID fprintf(stdout,"TACPLUS_USERID\n"); #endif #ifdef UNIONWAIT fprintf(stdout,"UNIONWAIT\n"); #endif #ifdef _BSD1 fprintf(stdout,"_BSD1\n"); #endif #ifdef _BSD_INCLUDES fprintf(stdout,"_BSD_INCLUDES\n"); #endif #ifdef __STDC__ fprintf(stdout,"__STDC__\n"); #endif }