diff options
Diffstat (limited to 'tacc.c')
-rw-r--r-- | tacc.c | 601 |
1 files changed, 601 insertions, 0 deletions
@@ -0,0 +1,601 @@ +/* tacc.c TACACS+ PAP authentication client + * + * Copyright 1997-98 by Pawel Krawczyk <kravietz@ceti.com.pl> + * 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 <stdio.h> +#include <pwd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <errno.h> +#include <utmp.h> +#include <sys/file.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include <time.h> +#include <getopt.h> +#include <ctype.h> +#include <openssl/rand.h> + +#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 <pawel.krawczyk@hush.com>\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; +} |