/* * tac_plus.c * * TACACS_PLUS daemon suitable for using on Un*x systems. * * October 1994, Lol Grant * * Copyright (c) 1994-2000 by Cisco systems, Inc. * * Permission to use, copy, modify, and distribute modified and * unmodified copies of this software for any purpose and without fee is * hereby granted, provided that (a) this copyright and permission notice * appear on all copies of the software and supporting documentation, (b) * the name of Cisco Systems, Inc. not be used in advertising or * publicity pertaining to distribution of the program without specific * prior permission, and (c) notice be given in supporting documentation * that use, 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 #ifdef TAC_PLUS_USERNAME #include #endif #if defined(TAC_PLUS_GROUPID) || defined(TAC_PLUS_GROUPNAME) #include #include #endif #include #if defined(ALIASES) || defined(TTYS) #include "own_wtmp.h" #endif #include "allow.h" static int standalone = 1; /* running standalone (1) or under inetd (0) */ static int inter = 0; /* do not go to background */ 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 single = 0; /* single thread (for debugging) */ int wtmpfd = 0; /* for wtmp file logging */ char *wtmpfile = NULL; struct timeval started_at; struct session session; /* session data */ static char pidfilebuf[75]; /* holds current name of the pidfile */ unsigned long bindaddress; void start_session(); #ifndef REAPCHILD static RETSIGTYPE reapchild(int s) { int status; int pid; for (;;) { pid = wait3(&status, WNOHANG, 0); if (pid <= 0) { #ifdef REARMSIGNAL signal(SIGCHLD, reapchild); #endif return; } if (debug & DEBUG_FORK_FLAG) report(LOG_DEBUG, "%d reaped", pid); } } #endif /* REAPCHILD */ static RETSIGTYPE die(int s) { report(LOG_INFO, "Received signal %d, shutting down", s); #ifdef UTMP init_utmp("shutdown"); #endif unlink(pidfilebuf); tac_exit(0); } static void init() { if (initialised) cfg_clean_config(); report(LOG_INFO, "Reading config"); session.acctfile = tac_strdup(TAC_PLUS_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); } #ifdef UTMP if (init_utmp("reboot")) { report(LOG_ERR, "init_utmp(utmp=%s, wtmp=%s, ttys=%s) failed", TAC_PLUS_UTMPFILE, TAC_PLUS_WTMPFILE, TAC_PLUS_TTYSFILE); tac_exit(1); } #endif initialised++; report(LOG_INFO, "Version %s Initialized %d", TAC_VERSION, initialised); #ifdef REGNAS reg_nas_users(); #endif } #ifdef UDB /* static sigset_t nset = sigmask(SIGALRM) | sigmask(SIGUSR1); */ /* FIXME use sigprocmask(SIG_BLOCK, &nset, NULL); instead of signal(...,SIG_IGN)? in handler() too... */ static RETSIGTYPE alarm_hdl(int s) { db_checkmt(); alarm(60); /* sigprocmask(SIG_UNBLOCK, &nset, NULL); */ #if defined(REARMSIGNAL) signal(SIGALRM, alarm_hdl); #endif } #endif static RETSIGTYPE handler(int s) { report(LOG_INFO, "Received signal %d", s); init(); #if defined(REARMSIGNAL) signal(SIGUSR1, handler); signal(SIGHUP, handler); #endif } /* * Return a socket bound to an appropriate port number/address. Exits * the program on failure */ get_socket() { int s; struct sockaddr_in sin; struct servent *sp; #ifdef SO_REUSEADDR int on = 1; #endif 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 = bindaddress; /* 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() { #ifdef LOG_FACIL openlog("tac_plus", LOG_PID, LOG_FACIL); #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. */ main(argc, argv) int argc; char **argv; { extern char *optarg; int childpid; int c; int s = -1; FILE *fp; #ifdef LOOKUP_NAMES int lookup_peer = 0; #endif #ifdef TAC_PLUS_USERNAME struct passwd *pw; #ifdef TAC_PLUS_GROUPNAME struct group *gr; #endif #endif #ifdef TAC_PLUS_GROUPID gid_t groups[2]; #endif debug = 0; /* no debugging */ standalone = 1; /* standalone */ single = 0; /* single threaded */ inter = 0; /* initialise global session data */ bzero(&session, sizeof(session)); session.peer = tac_strdup("unknown"); #ifdef ALIASES session.peeralias = tac_strdup(session.peer); session.flags = B_FLAG_INIT; #endif session.cfgfile = tac_strdup(TAC_PLUS_CFGFILE); open_logfile(); #ifdef TAC_PLUS_PORT port = TAC_PLUS_PORT; #endif if (argc <= 1) { fprintf(stderr, "Usage: tac_plus -C \n" "\t[ -t ] [ -P ] [ -g ] [ -p ]\n" "\t[ -d ] [ -i ] [ -I ] [ -v ] [ -s ]\n" "\t[ -l logfile ]" #ifdef MAXSESS " [ -w whologfile ]" #endif #ifdef LOOKUP_NAMES " [ -L ]" #endif "\n"); tac_exit(1); } while ((c = getopt(argc, argv, "td:C:iIp:PgvsLln:w:u:")) != EOF) switch (c) { #ifdef LOOKUP_NAMES case 'L': /* lookup peer names via DNS */ lookup_peer++; break; #endif case 'n': /* do nothing */ break; case 's': /* don't respond to sendpass */ sendauth_only++; break; case 'v': /* print version and exit */ fprintf(stdout, "tac_plus version %s\n", TAC_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 */ free(session.cfgfile); session.cfgfile = tac_strdup(optarg); break; case 'i': /* stand-alone */ standalone = 0; break; case 'I': /* do not go to background */ inter = 1; 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" "Tac_plus is usually run as root\n"); #ifdef DXS1 { extern regex_t dxs1_re; regcomp(&dxs1_re, "dsx1\\(slot/unit/channel\\)=([0-9/]*),", REG_EXTENDED); } #endif #ifdef REGNAS { extern regex_t rship_re; regcomp(&rship_re, "Line is running PPP for address ([0-9.]+)\\.", REG_EXTENDED); } #endif parser_init(); init(); #ifdef UDB signal(SIGALRM, alarm_hdl); #endif signal(SIGUSR1, handler); signal(SIGHUP, handler); signal(SIGTERM, die); signal(SIGPIPE, SIG_IGN); if (parse_only) tac_exit(0); #ifdef UDB alarm(50); #endif if (debug) report(LOG_DEBUG, "tac_plus server %s starting", TAC_VERSION); if (!standalone) { /* running under inetd */ struct sockaddr_in name; size_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", sys_errlist[errno]); } else { #ifdef LOOKUP_NAMES struct hostent *hp; hp = gethostbyaddr((char *) &name.sin_addr.s_addr, sizeof(name.sin_addr.s_addr), AF_INET); #endif if (session.peer) { free(session.peer); } session.peer = tac_strdup( #ifdef LOOKUP_NAMES hp ? hp->h_name : #endif (char *) inet_ntoa(name.sin_addr) ); #ifdef ALIASES if (session.peeralias) { free(session.peeralias); } session.peeralias = get_peer_alias(session.peer, &session.flags); #endif session.nasaddr.s_addr = name.sin_addr.s_addr; } #ifdef FIONBIO if (ioctl(session.sock, 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 (!inter) { 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 (setsid() < 0) report(LOG_ERR, "Can't change process group: %s", strerror(errno)); #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 */ } #ifndef REAPCHILD signal(SIGCHLD, reapchild); #else signal(SIGCHLD, SIG_IGN); #endif closelog(); /* some systems require this */ for (c = 0; c < getdtablesize(); c++) #ifdef UDB if (!db_inuse(c)) #endif (void) close(c); /* make sure we can still log to syslog now we've closed everything */ open_logfile(); } /* ! single threaded */ ostream = NULL; /* chdir("/"); */ umask(077); errno = 0; s = get_socket(); #ifdef RAD_ROAMING buffer_server(); /* Start the buffer server */ #endif #ifndef SOMAXCONN #define SOMAXCONN 5 #endif if (listen(s, SOMAXCONN) < 0) { console++; report(LOG_ERR, "listen: %s", sys_errlist[errno]); tac_exit(1); } if (port == TAC_PLUS_PORT) { strcpy(pidfilebuf, TAC_PLUS_PIDFILE); } else { sprintf(pidfilebuf, "%s.%d", TAC_PLUS_PIDFILE, port); } /* write process id to pidfile */ if ((fp = fopen(pidfilebuf, "w")) != NULL) { fprintf(fp, "%d\n", getpid()); fclose(fp); } else report(LOG_ERR, "Cannot write pid to %s %s", pidfilebuf, sys_errlist[errno]); #ifdef TAC_PLUS_GROUPID if (setgid(TAC_PLUS_GROUPID)) report(LOG_ERR, "Cannot set group id to %d %s", TAC_PLUS_GROUPID, sys_errlist[errno]); #endif #ifdef TAC_PLUS_USERID if (setuid(TAC_PLUS_USERID)) report(LOG_ERR, "Cannot set user id to %d %s", TAC_PLUS_USERID, sys_errlist[errno]); #endif #ifdef TAC_PLUS_GROUPNAME if (!(gr = getgrnam(TAC_PLUS_GROUPNAME))) report(LOG_ERR, "Cannot obtain group information for %s", TAC_PLUS_GROUPNAME); else if (setgid(gr->gr_gid)) report(LOG_ERR, "Cannot set group id to %d %s", gr->gr_gid, sys_errlist[errno]); #endif #ifdef TAC_PLUS_USERNAME if (!(pw = getpwnam(TAC_PLUS_USERNAME))) report(LOG_ERR, "Cannot obtain user information for %s", TAC_PLUS_USERNAME); else { #ifndef TAC_PLUS_GROUPNAME if (setgid(pw->pw_gid)) report(LOG_ERR, "Cannot set group id to %d %s", pw->pw_gid, sys_errlist[errno]); #endif if (setuid(pw->pw_uid)) report(LOG_ERR, "Cannot set user id to %d %s", pw->pw_uid, sys_errlist[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; size_t from_len; int newsockfd = -1; #ifdef LOOKUP_NAMES struct hostent *hp = NULL; #endif 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", sys_errlist[errno]); continue; } if (!allowaddr(from.sin_addr.s_addr)) { report(LOG_WARNING, "Unauthorized call from %s", inet_ntoa(from.sin_addr)); close(newsockfd); if (errno == EINTR) continue; } session.key = defaultkey; for (nas_new = nas_list; nas_new; nas_new = nas_new->next) if (nas_new->addr.s_addr == from.sin_addr.s_addr) { if (nas_new->key) session.key = nas_new->key; session.nas = nas_new; } #ifdef LOOKUP_NAMES if (lookup_peer) { hp = gethostbyaddr((char *) &from.sin_addr.s_addr, sizeof(from.sin_addr.s_addr), AF_INET); } #endif if (session.peer) { free(session.peer); } session.peer = tac_strdup( #ifdef LOOKUP_NAMES hp ? hp->h_name : #endif (char *) inet_ntoa(from.sin_addr) ); #ifdef ALIASES if (session.peeralias) { free(session.peeralias); } session.peeralias = get_peer_alias(session.peer, &session.flags); #endif session.nasaddr.s_addr = from.sin_addr.s_addr; 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: %s", sys_errlist[errno]); 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); } } } #ifndef HAVE_GETDTABLESIZE int getdtablesize() { return(_NFILE); } #endif /* HAVE_GETDTABLESIZE */ /* Make sure version number is kosher. Return 0 if it is */ 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. * */ void start_session() { 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; } }