mirror of https://github.com/facebook/tac_plus
574 lines
14 KiB
C
574 lines
14 KiB
C
/*
|
|
* $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 int is_valid_name(const char *name);
|
|
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")) {
|
|
if (is_valid_name(data->id->username)) {
|
|
return(tac_strdup(data->id->username));
|
|
}
|
|
}
|
|
if (STREQ(sym, "name")) {
|
|
if (is_valid_name(data->id->NAS_name)) {
|
|
return(tac_strdup(data->id->username));
|
|
}
|
|
}
|
|
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")) {
|
|
if (is_valid_name(data->id->NAC_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"));
|
|
}
|
|
|
|
/* is valid_name performs basic input santization for fields inside author_data that
|
|
* should be alphanumeric (NAC-address field, username)
|
|
*/
|
|
static int is_valid_name(const char *name) {
|
|
size_t len = strlen(name);
|
|
|
|
if (len > 100) {
|
|
report(LOG_DEBUG, "field %s is longer than allowed length 100", name);
|
|
return 0;
|
|
}
|
|
|
|
// character set check
|
|
for (size_t i = 0; i < len; i++) {
|
|
char c = name[i];
|
|
if (!isalnum(c) && c != '_' && c != '.' && c != ':' && c != '-') {
|
|
report(LOG_DEBUG, "invalid character '%c' inside field [%s]", c, name);
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|