/* tacc.c TACACS+ PAP authentication client * * Copyright 1997-98 by Pawel Krawczyk * Portions copyright (c) 1989 Carnegie Mellon University. * Copyright 2018 Cumulus Networks, Inc. All rights reserved. * * See http://www.ceti.com.pl/~kravietz/progs/tacacs.html * for details. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "tacplus.h" #include "libtac.h" /* Prompt displayed when asking for password */ #define PASSWORD_PROMPT "Password: " /* if defined, given command will be run after * successful authentication and proper wtmp * entries will be made */ #define DEFAULT_COMMAND "/usr/sbin/pppd -detach" /* message that will be displayed to user * before starting COMMAND */ #define COMMAND_MESSAGE "Starting PPP\n" /* timeout for reading password from user (seconds) */ #define GETPASS_TIMEOUT 60 /* end of CONFIGURABLE PARAMETERS */ /* prototypes */ void sighandler(int sig); void showusage(char *argv0); unsigned long getservername(char *serv); void showusage(char *progname); void showversion(char *progname); void authenticate(struct addrinfo *tac_server, const char *tac_secret, const char *user, char *pass, char *tty, char *remote_addr); void timeout_handler(int signum); #define EXIT_OK 0 #define EXIT_FAIL 1 /* AAA failure (or server error) */ #define EXIT_ERR 2 /* local error */ #define USE_SYSTEM 1 /* globals */ int tac_encryption = 1; typedef unsigned char flag; flag quiet = 0; char *user = NULL; /* global, because of signal handler */ char *iface = NULL; /* -I interface or VRF to use for connection */ struct sockaddr src_sockaddr; struct addrinfo src_addr_info; struct addrinfo *src_addr; int ip_addr_str_to_addr_info (const char *, struct addrinfo *); /* command line options */ static struct option long_options[] = { /* operation */ { "authenticate", no_argument, NULL, 'T' }, { "authorize", no_argument, NULL, 'R' }, { "account", no_argument, NULL, 'A' }, { "version", no_argument, NULL, 'V' }, { "help", no_argument, NULL, 'h' }, /* data */ { "username", required_argument, NULL, 'u' }, { "remote", required_argument, NULL, 'r' }, { "password", required_argument, NULL, 'p' }, { "server", required_argument, NULL, 's' }, { "secret", required_argument, NULL, 'k' }, { "command", required_argument, NULL, 'c' }, { "exec", required_argument, NULL, 'c' }, { "service", required_argument, NULL, 'S' }, { "protocol", required_argument, NULL, 'P' }, { "remote", required_argument, NULL, 'r' }, { "interface", required_argument, NULL, 'I' }, { "sourceip", required_argument, NULL, 'i' }, /* modifiers */ { "quiet", no_argument, NULL, 'q' }, { "silent", no_argument, NULL, 'q' }, { "no-wtmp", no_argument, NULL, 'w' }, { "no-encrypt", no_argument, NULL, 'n' }, { 0, 0, 0, 0 } }; /* command line letters */ char *opt_string = "TRAVIhu:p:s:k:c:qr:wnS:P:"; int main(int argc, char **argv) { char *pass = NULL; char *tty; char *command = NULL; char *remote_addr = NULL; char *service = NULL; char *protocol = NULL; struct addrinfo *tac_server; char *tac_server_name = NULL; char *tac_secret = NULL; char *tac_srcip = NULL; int tac_fd; short int task_id = 0; char buf[40]; int ret; #ifndef USE_SYSTEM pid_t pid; #endif char *msg; struct areply arep; /* options */ flag log_wtmp = 1; flag do_author = 0; flag do_authen = 0; flag do_account = 0; flag login_mode = 0; /* check argc */ if (argc < 2) { showusage(argv[0]); exit(EXIT_ERR); } /* check for login mode */ if (argc == 2 && isalpha(*argv[1])) { user = argv[1]; do_author = do_authen = do_account = 1; command = DEFAULT_COMMAND; login_mode = 1; } else { int c; int opt_index; while ((c = getopt_long(argc, argv, opt_string, long_options, &opt_index)) != EOF) { switch (c) { case 'T': do_authen = 1; break; case 'R': do_author = 1; break; case 'A': do_account = 1; break; case 'V': showversion(argv[0]); case 'h': showusage(argv[0]); case 'i': tac_srcip = optarg; break; case 'I': iface = optarg; break; case 'u': user = optarg; break; case 'r': remote_addr = optarg; break; case 'p': pass = optarg; break; case 's': tac_server_name = optarg; break; case 'k': tac_secret = optarg; break; case 'c': command = optarg; break; case 'S': service = optarg; break; case 'P': protocol = optarg; break; case 'q': quiet = 1; break; case 'w': log_wtmp = 0; break; case 'n': tac_encryption = 0; break; } } } /* check available information and set to defaults if needed */ if (do_authen + do_author + do_account == 0) { printf("error: one of -TRAVh options is required\n"); exit(EXIT_ERR); } if (user == NULL) { printf("error: username is required.\n"); exit(EXIT_ERR); } if (remote_addr == NULL) { printf("error: remote address is required.\n"); exit(EXIT_ERR); } if (service == NULL) { printf("error: service is required.\n"); exit(EXIT_ERR); } if (protocol == NULL) { printf("error: protocol is required.\n"); exit(EXIT_ERR); } if (tac_server_name == NULL) { printf("error: server name is required.\n"); exit(EXIT_ERR); } struct addrinfo hints; memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; ret = getaddrinfo(tac_server_name, "tacacs", &hints, &tac_server); if (ret != 0) { printf("error: resolving name %s: %s", tac_server_name, gai_strerror(ret)); exit(EXIT_ERR); } if (tac_srcip) { src_addr_info.ai_addr = &src_sockaddr; if (ip_addr_str_to_addr_info (tac_srcip, &src_addr_info) == 0) src_addr = &src_addr_info; else { printf("error: unable to convert sourceip %s to an IP addr\n", tac_srcip); exit(EXIT_ERR); } } if (tac_secret == NULL) { printf("error: server secret is required.\n"); exit(EXIT_ERR); } if (pass == NULL) { signal(SIGALRM, timeout_handler); alarm(GETPASS_TIMEOUT); pass = getpass(PASSWORD_PROMPT); alarm(0); signal(SIGALRM, SIG_DFL); if (!strlen(pass)) exit(EXIT_ERR); } tty = ttyname(0); if (strncmp(tty, "/dev/", 5) == 0) tty += 5; /* open syslog before any TACACS+ calls */ openlog("tacc", LOG_CONS | LOG_PID, LOG_AUTHPRIV); if (do_authen) authenticate(tac_server, tac_secret, user, pass, tty, remote_addr); if (do_author) { /* authorize user */ struct tac_attrib *attr = NULL; tac_add_attrib(&attr, "service", service); tac_add_attrib(&attr, "protocol", protocol); tac_fd = tac_connect_single(tac_server, tac_secret, src_addr, iface); if (tac_fd < 0) { if (!quiet) printf("Error connecting to TACACS+ server: %m\n"); exit(EXIT_ERR); } tac_author_send(tac_fd, user, tty, remote_addr, attr); tac_author_read(tac_fd, &arep); if (arep.status != AUTHOR_STATUS_PASS_ADD && arep.status != AUTHOR_STATUS_PASS_REPL) { if (!quiet) { struct tac_attrib *attr; printf("Authorization FAILED: %s\n", arep.msg); for (attr = arep.attr; attr != NULL; attr = attr->next) printf("Attribute %*s\n", attr->attr_len, attr->attr); } exit(EXIT_FAIL); } else { if (!quiet) printf("Authorization OK: %s\n", arep.msg); } tac_free_attrib(&attr); } /* we no longer need the password in our address space */ bzero(pass, strlen(pass)); pass = NULL; if (do_account) { /* start accounting */ struct tac_attrib *attr = NULL; sprintf(buf, "%lu", time(0)); tac_add_attrib(&attr, "start_time", buf); #if defined(HAVE_OPENSSL_RAND_H) && defined(HAVE_LIBCRYPTO) RAND_pseudo_bytes((unsigned char *) &task_id, sizeof(task_id)); #else task_id = tac_magic(); #endif sprintf(buf, "%hu", task_id); tac_add_attrib(&attr, "task_id", buf); tac_add_attrib(&attr, "service", service); tac_add_attrib(&attr, "protocol", protocol); tac_fd = tac_connect_single(tac_server, tac_secret, src_addr, iface); if (tac_fd < 0) { if (!quiet) printf("Error connecting to TACACS+ server: %m\n"); exit(EXIT_ERR); } tac_acct_send(tac_fd, TAC_PLUS_ACCT_FLAG_START, user, tty, remote_addr, attr); ret = tac_acct_read(tac_fd, &arep); if(ret == 0) { if (!quiet) printf("Accounting: START failed: %s\n", arep.msg); syslog(LOG_INFO, "TACACS+ accounting start failed: %s", arep.msg); } else if (!login_mode && !quiet) printf("Accounting: START OK\n"); close(tac_fd); tac_free_attrib(&attr); } /* log in local utmp */ #ifdef HAVE_LOGWTMP if (log_wtmp) logwtmp(tty, user, "dialup"); #endif if (command != NULL) { int ret; syslog(LOG_DEBUG, "starting %s for %s", command, user); signal(SIGHUP, SIG_IGN); signal(SIGTERM, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGCHLD, SIG_IGN); #ifdef COMMAND_MESSAGE printf(COMMAND_MESSAGE); #endif #if USE_SYSTEM ret = system(command); if (ret < 0) syslog(LOG_WARNING, "command failed: %m"); else syslog(LOG_NOTICE, "command exit code %u", ret); #else pid=fork(); if(pid == 0) { /* child */ execl(DEFAULT_COMMAND, DEFAULT_COMMAND, ARGS, NULL); syslog(LOG_ERR, "execl() failed: %m"); _exit(EXIT_FAIL); } if(pid < 0) { /* error */ syslog(LOG_ERR, "fork failed: %m"); exit(EXIT_FAIL); } if(pid > 0) { /* parent */ int st, r; r=wait(&st); } #endif } if (do_account) { /* stop accounting */ struct tac_attrib *attr = NULL; sprintf(buf, "%lu", time(0)); tac_add_attrib(&attr, "stop_time", buf); sprintf(buf, "%hu", task_id); tac_add_attrib(&attr, "task_id", buf); tac_fd = tac_connect_single(tac_server, tac_secret, src_addr, iface); if (tac_fd < 0) { if (!quiet) printf("Error connecting to TACACS+ server: %m\n"); exit(EXIT_ERR); } tac_acct_send(tac_fd, TAC_PLUS_ACCT_FLAG_STOP, user, tty, remote_addr, attr); ret = tac_acct_read(tac_fd, &arep); if (ret == 0) { if (!quiet) printf("Accounting: STOP failed: %s", arep.msg); syslog(LOG_INFO, "TACACS+ accounting stop failed: %s\n", arep.msg); } else if (!login_mode && !quiet) printf("Accounting: STOP OK\n"); close(tac_fd); tac_free_attrib(&attr); } /* logout from utmp */ #ifdef HAVE_LOGWTMP if (log_wtmp) logwtmp(tty, "", ""); #endif exit(EXIT_OK); } void sighandler(int sig) { TACDEBUG((LOG_DEBUG, "caught signal %d", sig)); } void authenticate(struct addrinfo *tac_server, const char *tac_secret, const char *user, char *pass, char *tty, char *remote_addr) { int tac_fd; char *msg; int ret; struct areply arep; tac_fd = tac_connect_single(tac_server, tac_secret, src_addr, iface); if (tac_fd < 0) { if (!quiet) printf("Error connecting to TACACS+ server: %m\n"); exit(EXIT_ERR); } /* start authentication */ if (tac_authen_send(tac_fd, user, pass, tty, remote_addr, TAC_PLUS_AUTHEN_LOGIN) < 0) { if (!quiet) printf("Error sending query to TACACS+ server\n"); exit(EXIT_ERR); } ret = tac_authen_read(tac_fd, &arep); if (ret != TAC_PLUS_AUTHEN_STATUS_PASS) { if (!quiet) printf("Authentication FAILED: %s\n", arep.msg); syslog(LOG_ERR, "authentication failed for %s: %s", user, arep.msg); exit(EXIT_FAIL); } if (!quiet) printf("Authentication OK\n"); syslog(LOG_INFO, "authentication OK for %s", user); close(tac_fd); } void showusage(char *progname) { char *a; a = rindex(progname, '/'); progname = (a == NULL) ? progname : ++a; printf("%s -- simple TACACS+ client and login, version %u.%u.%u\n", progname, tac_ver_major, tac_ver_minor, tac_ver_patch); printf("Copyright 1997-2016 by Pawel Krawczyk \n"); printf("Usage: %s option [option, ...]\n\n", progname); printf(" Action:\n"); printf(" -T, --authenticate perform authentication with username and password\n"); printf(" -R, --authorize perform authorization for requested service\n"); printf(" -A, --account account session beginning and end\n"); printf(" -h, --help display this help and exit\n"); printf(" -V, --version display version number and exit\n\n"); printf(" Data:\n"); printf(" -u, --username remote user name\n"); printf(" -p, --password remote user password\n"); printf(" -s, --server server IP address or FQDN\n"); printf(" -r, --remote remote client's IP address\n"); printf(" -S, --service requested service (e.g. ppp)\n"); printf(" -P, --protocol requested protocl (e.g. ip)\n"); printf(" -k, --secret server encryption key\n"); printf(" -c, --command command to execute after successful AAA\n"); printf(" --exec alias for --command\n\n"); printf(" Modifiers:\n"); printf(" -q, --quiet don't display messages to screen (but still\n"); printf(" --silent report them via syslog(3))\n"); printf(" -w, --no-wtmp don't write records to wtmp(5)\n"); printf(" -n, --no-encrypt don't encrypt AAA packets sent to servers\n\n"); printf("Example usage:\n\n"); printf(" tacc -TRA -u test1 -p test1 -s localhost -r 1.1.1.1 -k test1 -S ppp -P ip\n"); exit(EXIT_ERR); } void showversion(char *progname) { char *a; a = rindex(progname, '/'); progname = (a == NULL) ? progname : ++a; printf("%s %u.%u.%u\n", progname, tac_ver_major, tac_ver_minor, tac_ver_patch); exit(EXIT_OK); } unsigned long getservername(char *serv) { struct in_addr addr; struct hostent *h; if (inet_aton(serv, &addr) == 0) { if ((h = gethostbyname(serv)) == NULL) { herror("gethostbyname"); } else { bcopy(h->h_addr, (char *)&addr, sizeof(struct in_addr)); return(addr.s_addr); } } else return(addr.s_addr); return (-1); } void timeout_handler(int signum) { syslog(LOG_ERR, "timeout reading password from user %s", user); } /* Convert ip address string to address info. * It returns 0 on success, or -1 otherwise * It supports ipv4 only. */ int ip_addr_str_to_addr_info (const char *srcaddr, struct addrinfo *p_addr_info) { struct sockaddr_in *s_in; s_in = (struct sockaddr_in *)p_addr_info->ai_addr; s_in->sin_family = AF_INET; s_in->sin_addr.s_addr = INADDR_ANY; if (inet_pton(AF_INET, srcaddr, &(s_in->sin_addr)) == 1) { p_addr_info->ai_family = AF_INET; p_addr_info->ai_addrlen = sizeof (struct sockaddr_in); return 0; } return -1; }