/* * tac_plus.c * * TACACS_PLUS daemon suitable for using on Un*x systems. * * October 1994, Lol Grant * * Copyright (c) 1995 by Cisco systems, Inc. * All rights reserved. * Please NOTE: None of the TACACS code available here comes with any * warranty or support. */ #include "tac_plus.h" #include "sys/wait.h" #include "signal.h" static int standalone = 1; /* running standalone (1) or under inetd (0) */ static int initialised = 0; /* data structures have been allocated */ 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) */ struct session session; /* session data */ /* 22 December 2003 ---------- mleighty@harfordtechnology.com The following was added to allow the daemon to bind to all interfaces in the event a specific interface is not indicated. This is merely an extension of the product/source provided by Cisco Systems Questions to: security-tech@harfordtechnology.com */ /* ----------------------------------------------------------- */ char *address = "0.0.0.0"; /* ----------------------------------------------------------- */ /* 16 November 2003 ---------- mleighty@harfordtechnology.com The following was commented out to address the need to dynamically modify the PID file name when the tac_plus daemon is started on a non-standard port. Questions to: security-tech@harfordtechnology.com */ /* -------------------------------------------------------------- */ /* char *pidfile = TAC_PLUS_PIDFILE; */ /* -------------------------------------------------------------- */ /* 16 November 2003 ---------- mleighty@harfordtechnology.com The following was added to address the need to dynamically modify the PID file name whenthe tac_plus daemon is started on a non-standard port. This was taken from tac_plus.F4.0.2.alpha Questions to: security-tech@harfordtechnology.com */ /* -------------------------------------------------------------- */ static char pidfile[75]; /* holds current name of the pidfile */ /* -------------------------------------------------------------- */ static char rcsid[] = "$Id: tac_plus.c,v 1.67 1995/07/25 03:46:27 lol Exp $"; #ifndef REAPCHILD static #ifdef VOIDSIG void #else int #endif /* VOIDSIG */ reapchild() { #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 void die(signum) int signum; { report(LOG_INFO, "Received signal %d, shutting down", signum); tac_exit(0); } static void init(signum) int signum; { signal(SIGUSR1, SIG_IGN); if (initialised) cfg_clean_config(); report(LOG_INFO, "Reading config"); session.acctfile = tac_strdup("/var/tmp/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_INFO, "Initialized %d", initialised); signal(SIGUSR1, init); signal(SIGTERM, die); } /* * 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; 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; /* 22 December 2003 ---------- mleighty@harfordtechnology.com The following was commented out due to the need to bind to a specific interface as opposed to ALL interfaces. This is merely an extension of the product/source provided by Cisco Systems Questions to: security-tech@harfordtechnology.com */ /* ----------------------------------------------------------- */ /* sin.sin_addr.s_addr = htonl(INADDR_ANY); */ /* ----------------------------------------------------------- */ /* 22 December 2003 ---------- mleighty@harfordtechnology.com The following was added to address the need to bind to a specific interface as opposed to ALL interfaces. This is merely an extension of the product/source provided by Cisco Systems Questions to: security-tech@harfordtechnology.com */ /* ----------------------------------------------------------- */ sin.sin_addr.s_addr = inet_addr(address); /* ----------------------------------------------------------- */ 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); } /* * 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; int len; FILE *fp; debug = 0; /* no debugging */ standalone = 1; /* standalone */ single = 0; /* single threaded */ /* initialise global session data */ bzero(&session, sizeof(session)); session.peer = "unknown"; #ifdef LOG_LOCAL6 openlog("tac_plus", LOG_PID, LOG_LOCAL6); #else openlog("tac_plus", LOG_PID); #endif setlogmask(LOG_UPTO(LOG_DEBUG)); #ifdef TAC_PLUS_PORT port = TAC_PLUS_PORT; #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 ]\n"); /* 22 December 2003 ---------- mleighty@harfordtechnology.com The following was added to provide the option to bind to a specific interface as opposed to ALL interfaces. This is merely an extension of the product/source provided by Cisco Systems Questions to: security-tech@harfordtechnology.com */ /* ----------------------------------------------------------- */ fprintf(stderr, "\t[ -A < a.b.c.d IP Address to bind to > ]\n"); /* ----------------------------------------------------------- */ tac_exit(1); } /* 22 December 2003 ---------- mleighty@harfordtechnology.com The 'A:' was added to provide the option to bind to a specific interface as opposed to ALL interfaces. This section takes the designated IP address as from the command line arguments. This is merely an extension of the product/source provided by Cisco Systems Questions to: security-tech@harfordtechnology.com */ /* ----------------------------------------------------------- */ while ((c = getopt(argc, argv, "td:C:ip:PgvA:")) != EOF) switch (c) { case 'v': /* print version and exit */ fprintf(stdout, "\nTidePoint Corporation: tac_plus version %s\n", 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 */ len = strlen(optarg); session.cfgfile = (char *) tac_malloc(len + 1); bcopy(optarg, session.cfgfile, len); session.cfgfile[len] = '\0'; break; case 'i': /* stand-alone */ standalone = 0; break; case 'A': /* Address to bind to a.b.c.d */ /* 22 December 2003 ---------- mleighty@harfordtechnology.com The following was added to provide the option to bind to a specific interface as opposed to ALL interfaces. This section takes the designated IP address as from the command line arguments. This is merely an extension of the product/source provided by Cisco Systems Questions to: security-tech@harfordtechnology.com */ /* ----------------------------------------------------------- */ len =strlen(optarg); address = (char *) tac_malloc(len+1); bcopy(optarg, address, len); address[len] = '\0'; 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(0); if (parse_only) tac_exit(0); if (debug) report(LOG_DEBUG, "tac_plus server %s starting", rcsid); if (!standalone) { /* running under inetd */ struct sockaddr_in name; int name_len; int on = 1; 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 { struct hostent *hp; hp = gethostbyaddr((char *) &name.sin_addr.s_addr, sizeof(name.sin_addr.s_addr), AF_INET); session.peer = hp ? hp->h_name : (char *) inet_ntoa(name.sin_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 ((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 LINUX if (setpgrp() == -1) #else /* LINUX */ if (setpgrp(0, getpid()) == -1) #endif /* LINUX */ report(LOG_ERR, "Can't change process group"); c = open("/dev/tty", O_RDWR); if (c >= 0) { ioctl(c, TIOCNOTTY, (char *) 0); (void) close(c); } signal(SIGCHLD, reapchild); #else /* REAPCHILD */ if (setpgrp() == 1) 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 */ for (c = 0; c < getdtablesize(); c++) (void) close(c); } /* ! single threaded */ ostream = NULL; /* chdir("/"); */ umask(0); errno = 0; s = get_socket(); #ifndef SOMAXCONN #define SOMAXCONN 5 #endif if (listen(s, SOMAXCONN) < 0) { console++; report(LOG_ERR, "listen: %s", sys_errlist[errno]); tac_exit(1); } /* 16 November 2003 ---------- mleighty@harfordtechnology.com The following was added to address the need to dynamically modify the PID file name when the tac_plus daemon is started on a non-standard port or bound to a specific interface. This was taken from tac_plus.F4.0.2.alpha and extended to include the IP address when bound to a specific interface. The format of the file will be .... tac_plus.pid-a.b.c.d:p When the daemon is not bound to a specific interface, the format will be ... tac_plus.pid-0.0.0.0:49 Questions to: security-tech@harfordtechnology.com */ /* ----------------------------------------------------------- */ sprintf(pidfile, "%s-%s:%d", TAC_PLUS_PIDFILE, address, port); /* ----------------------------------------------------------- */ /* write process id to pidfile */ if ((fp = fopen(pidfile, "w")) != NULL) { fprintf(fp, "%d\n", getpid()); fclose(fp); } else report(LOG_ERR, "Cannot write pid to %s %s", pidfile, sys_errlist[errno]); /* 27 Dec 2000 ---------- mleighty@harfordtechnology.com The following was added to address the security of the PID file. Under previous versions of tac_plus the PID file is created with a mode of 666 owned by root:other. This change creates the PID file with a mode of 640 an ownership of root:tacacs. ( Assuming that the tacacs UID and GID have been created ( default UID 1500 and default GID 25 ). Questions to: security-tech@harfordtechnology.com */ /* ---------------------------------------------------------- */ chmod(pidfile,0640); /* ---------------------------------------------------------- */ #ifdef TAC_PLUS_GROUPID /* ----------------------------------------------------------- */ chown(pidfile, -1, 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 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; int from_len; int newsockfd; struct hostent *hp; 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]); tac_exit(1); } hp = gethostbyaddr((char *) &from.sin_addr.s_addr, sizeof(from.sin_addr.s_addr), AF_INET); session.peer = hp ? hp->h_name : (char *) inet_ntoa(from.sin_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"); 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 */ /* * Determine the packet type, read the rest of the packet data, * decrypt it and call the appropriate service routine. * * return 0 on success, 1 on failure * */ int start_session() { u_char *pak, *read_packet(); char msg[255]; HDR *hdr; void authen(); session.seq_no = 0; session.aborted = 0; pak = read_packet(); if (!pak) return (1); 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); switch (hdr->type) { case TAC_PLUS_AUTHEN: authen(pak); return (0); case TAC_PLUS_AUTHOR: author(pak); return (0); case TAC_PLUS_ACCT: accounting(pak); return (0); default: sprintf(msg, "Illegal Tacacs plus type code %d", hdr->type); /* FIXME: send_error_reply ??? */ return (1); } }