tacacs-F4.0.4.28/programs.c (383 lines of code) (raw):

/* * $Id: programs.c,v 1.13 2009-06-02 18:08:00 heas Exp $ * * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved * Copyright (c) 1995-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. */ /* Routines to fork children and communicate with them via pipes */ #include "tac_plus.h" #include <sys/wait.h> #ifdef HAVE_CTYPE_H # include <ctype.h> #endif #include <unistd.h> #include <signal.h> static void close_fds(int, int, int); static char *lookup(char *, struct author_data *); #if HAVE_PID_T static pid_t my_popen(char *, int *, int *, int *); #else static int my_popen(char *, int *, int *, int *); #endif static char **read_args(int, int); static int read_string(int, char *, int); static char *substitute(char *, struct author_data *); #if HAVE_PID_T static int waitfor(pid_t); #else static int waitfor(int); #endif static int write_args(int, char **, int); /* * Support for dollar variables. Look in the authorization data and return * strings representing values found there. If not found, return "unknown". * Recognized strings and their interpolated value types are: * * user -- user name * name -- NAS name * port -- NAS port * ip -- NAS ip * address -- NAC address (remote user location) * priv -- privilege level (0 to 15) * method -- (1 to 4) * type -- (1 to 4) * service -- (1 to 7) * status -- (pass, fail, error, unknown) */ static char * lookup(char *sym, struct author_data *data) { static char buf[5]; if (STREQ(sym, "user")) { return(tac_strdup(data->id->username)); } if (STREQ(sym, "name")) { return(tac_strdup(data->id->NAS_name)); } if (STREQ(sym, "ip")) { return(tac_strdup(data->id->NAS_ip)); } if (STREQ(sym, "port")) { return(tac_strdup(data->id->NAS_port)); } if (STREQ(sym, "address")) { return(tac_strdup(data->id->NAC_address)); } if (STREQ(sym, "priv")) { snprintf(buf, sizeof(buf), "%d", data->id->priv_lvl); return(tac_strdup(buf)); } if (STREQ(sym, "method")) { snprintf(buf, sizeof(buf), "%d", data->authen_method); return(tac_strdup(buf)); } if (STREQ(sym, "type")) { snprintf(buf, sizeof(buf), "%d", data->authen_type); return(tac_strdup(buf)); } if (STREQ(sym, "service")) { snprintf(buf, sizeof(buf), "%d", data->service); return(tac_strdup(buf)); } if (STREQ(sym, "status")) { switch (data->status) { default: return(tac_strdup("unknown")); case AUTHOR_STATUS_PASS_ADD: case AUTHOR_STATUS_PASS_REPL: return(tac_strdup("pass")); case AUTHOR_STATUS_FAIL: return(tac_strdup("fail")); case AUTHOR_STATUS_ERROR: return(tac_strdup("error")); } } return(tac_strdup("unknown")); } /* * Interpolate values of dollar variables into a string. Determine values * for the various $ variables by looking in the authorization data. */ static char * substitute(char *string, struct author_data *data) { char *cp; char out[MAX_INPUT_LINE_LEN], *outp; char sym[MAX_INPUT_LINE_LEN], *symp; char *value, *valuep; if (debug & DEBUG_AUTHOR_FLAG) report(LOG_DEBUG, "substitute: %s", string); cp = string; outp = out; while (*cp) { if (*cp != DOLLARSIGN) { *outp++ = *cp++; continue; } cp++; /* skip dollar sign */ symp = sym; /* does it have curly braces e.g. ${foo} ? */ if (*cp == '{') { cp++; /* skip { */ while (*cp && *cp != '}') *symp++ = *cp++; cp++; /* skip } */ } else { /* copy symbol into sym */ while (*cp && isalpha((int) *cp)) *symp++ = *cp++; } *symp = '\0'; /* lookup value */ if (debug & DEBUG_SUBST_FLAG) report(LOG_DEBUG, "Lookup %s", sym); valuep = value = lookup(sym, data); if (debug & DEBUG_SUBST_FLAG) report(LOG_DEBUG, "Expands to: %s", value); /* copy value into output */ while (valuep && *valuep) *outp++ = *valuep++; free(value); } *outp++ = '\0'; if (debug & DEBUG_AUTHOR_FLAG) report(LOG_DEBUG, "Dollar substitution: %s", out); return(tac_strdup(out)); } /* * Wait for a (child) pid to terminate. Return its status. Probably * horribly implementation dependent. */ static int #if HAVE_PID_T waitfor(pid_t pid) #else waitfor(int pid) #endif { #if HAVE_PID_T pid_t ret; #else int ret; #endif #ifdef UNIONWAIT union wait status; #else int status; #endif ret = waitpid(pid, &status, 0); if (ret < 0) { report(LOG_ERR, "%s: pid %ld no child exists", session.peer, (long)pid); return(-1); } if (!WIFEXITED(status)) { report(LOG_ERR, "%s: pid %ld child in illegal state", session.peer, (long)pid); return(-1); } if (debug & DEBUG_AUTHOR_FLAG) report(LOG_DEBUG, "pid %ld child exited status %ld", (long)pid, (long)WEXITSTATUS(status)); return(WEXITSTATUS(status)); } /* Write an argv array of strings to fd, adding a newline to each one */ static int write_args(int fd, char **args, int arg_cnt) { int i, m, n, o; for (i = 0; i < arg_cnt; i++) { n = strlen(args[i]); for (m = 0; m < n; ) { if ((o = write(fd, args[i], n)) == -1) { if (errno != EINTR) { report(LOG_ERR, "%s: Process write failure: %s", session.peer, strerror(errno)); return(-1); } } else m += o; } while ((m = write(fd, "\n", 1)) != 1) { if (m == -1 && errno != EINTR) { report(LOG_ERR, "%s: Process write failure: %s", session.peer, strerror(errno)); return(-1); } } } return(0); } /* Close the three given file-descruptors */ static void close_fds(int fd1, int fd2, int fd3) { if (fd1 >= 0) { close(fd1); } if (fd2 >= 0) { close(fd2); } if (fd3 >= 0) { close(fd3); } return; } /* * Fork a command. Return read and write file descriptors in readfdp and * writefdp. Return the pid or -1 if unsuccessful */ #if HAVE_PID_T static pid_t #else static int #endif my_popen(char *cmd, int *readfdp, int *writefdp, int *errorfdp) { int fd1[2], fd2[2], fd3[2]; #if HAVE_PID_T pid_t pid; #else int pid; #endif fd1[0] = fd1[1] = fd2[0] = fd2[1] = fd3[0] = fd3[1] = -1; *readfdp = *writefdp = *errorfdp = -1; if (pipe(fd1) < 0 || pipe(fd2) < 0 || pipe(fd3) < 0) { report(LOG_ERR, "%s: Cannot create pipes", session.peer); close_fds(fd1[0], fd2[0], fd3[0]); close_fds(fd1[1], fd2[1], fd3[1]); return(-1); } /* The parent who forked us is set to reap all children automatically. We disable this so we can explicitly reap our children to read their status */ signal(SIGCHLD, SIG_DFL); pid = fork(); if (pid < 0) { report(LOG_ERR, "%s: fork failure", session.peer); close_fds(fd1[0], fd2[0], fd3[0]); close_fds(fd1[1], fd2[1], fd3[1]); return(-1); } if (pid > 0) { /* parent */ close_fds(fd1[0], fd2[1], fd3[1]); *writefdp = fd1[1]; *readfdp = fd2[0]; *errorfdp = fd3[0]; return(pid); } /* child */ closelog(); close(session.sock); close_fds(fd1[1], fd2[0], fd3[0]); if (fd1[0] != STDIN_FILENO) { if (dup2(fd1[0], STDIN_FILENO) < 0) exit(-1); close(fd1[0]); } if (fd2[1] != STDOUT_FILENO) { if (dup2(fd2[1], STDOUT_FILENO) < 0) exit(-1); close(fd2[1]); } if (fd3[1] != STDERR_FILENO) { if (dup2(fd3[1], STDERR_FILENO) < 0) exit(-1); close(fd3[1]); } (void) execl("/bin/sh", "sh", "-c", cmd, (char *) NULL); _exit(-1); return(0); /* keep Codecenter quiet */ } /* * read the file descriptor and stuff the data into the given array for the * number of bytes given. Throw the rest away. */ static int read_string(int fd, char *string, int len) { uint i, ret; char c; i = 0; do { ret = read(fd, &c, 1); if (ret > 0 && (i + 1) < len) { string[i++] = c; string[i] = '\0'; } } while (i < len && ret > 0); return(ret); } /* * Read lines from fd and place them into an argv style array. Highly * recursive so we do not have to count lines in advance. Uses "n" as the * count of lines seen so far. When eof is read, the array is allocated, * and the recursion unravels */ static char ** read_args(int n, int fd) { char buf[255], *bufp, c, **out; bufp = buf; while (read(fd, &c, 1) > 0) { if (c != '\n') { *bufp++ = c; continue; } *bufp = '\0'; out = read_args(n + 1, fd); out[n] = (char *) tac_malloc(strlen(buf) + 1); strcpy(out[n], buf); return(out); } /* eof */ out = (char **) tac_malloc(sizeof(char *) * (n + 1)); out[n] = NULL; return(out); } /* * Do variable interpolation on a string, then invoke it as a shell command. * Write an appropriate set of AV pairs to standard input of the command and * read its standard output into outarray. Return the commands final status * when it terminates */ int call_pre_process(char *string, struct author_data *data, char ***outargsp, int *outargs_cntp, char *error, int err_len) { char **new_args; int readfd, writefd, errorfd; int status, i; char *cmd = substitute(string, data); #if HAVE_PID_T pid_t pid; #else int pid; #endif pid = my_popen(cmd, &readfd, &writefd, &errorfd); memset(error, '\0', err_len); free(cmd); if (pid < 0) { close_fds(readfd, writefd, errorfd); return(1); /* deny */ } for (i = 0; i < data->num_in_args; i++) { if (debug & DEBUG_AUTHOR_FLAG) report(LOG_DEBUG, "input %s", data->input_args[i]); } if (write_args(writefd, data->input_args, data->num_in_args)) { close_fds(readfd, writefd, errorfd); return(1); /* deny */ } close(writefd); writefd = -1; new_args = read_args(0, readfd); *outargsp = new_args; if (debug & DEBUG_AUTHOR_FLAG) { for (i = 0; new_args[i]; i++) { report(LOG_DEBUG, "output %s", new_args[i]); } } read_string(errorfd, error, err_len); if (error[0] != '\0') { report(LOG_ERR, "Error from program (%d): \"%s\" ", strlen(error), error); } /* count the args */ for (i = 0; new_args[i]; i++) /* NULL stmt */ ; *outargs_cntp = i; status = waitfor(pid); close_fds(readfd, writefd, errorfd); return(status); } /* * Do variable interpolation on a string, then invoke it as a shell command. * Write an appropriate set of AV pairs to standard input of the command and * read its standard output into outarray. Return the commands final status * when it terminates */ int call_post_process(char *string, struct author_data *data, char ***outargsp, int *outargs_cntp) { char **new_args; int status; int readfd, writefd, errorfd; int i; char *cmd = substitute(string, data); #if HAVE_PID_T pid_t pid; #else int pid; #endif pid = my_popen(cmd, &readfd, &writefd, &errorfd); free(cmd); if (pid < 0) { close_fds(readfd, writefd, errorfd); return(1); /* deny */ } /* If the status is AUTHOR_STATUS_PASS_ADD then the current output args * represent *additions* to the input args, not the full set */ if (data->status == AUTHOR_STATUS_PASS_ADD) { for (i = 0; i < data->num_in_args; i++) { if (debug & DEBUG_AUTHOR_FLAG) report(LOG_DEBUG, "input %s", data->input_args[i]); } if (write_args(writefd, data->input_args, data->num_in_args)) { close_fds(readfd, writefd, errorfd); return(1); /* deny */ } } for (i = 0; i < data->num_out_args; i++) { if (debug & DEBUG_AUTHOR_FLAG) report(LOG_DEBUG, "input %s", data->output_args[i]); } if (write_args(writefd, data->output_args, data->num_out_args)) { close_fds(readfd, writefd, errorfd); return(1); /* deny */ } close(writefd); writefd = -1; new_args = read_args(0, readfd); *outargsp = new_args; if (debug & DEBUG_AUTHOR_FLAG) { for (i = 0; new_args[i]; i++) { report(LOG_DEBUG, "output %s", new_args[i]); } } /* count the output args */ for (i = 0; new_args[i]; i++) /* NULL stmt */ ; *outargs_cntp = i; status = waitfor(pid); close_fds(readfd, writefd, errorfd); return(status); }