#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/rad_assert.h>
#include <sys/file.h>
#include <fcntl.h>
#include <ctype.h>
#include <signal.h>
#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#ifndef WEXITSTATUS
# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
#endif
#ifndef WIFEXITED
# define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
#endif
#define MAX_ARGV (256)
#define USEC 1000000
static void tv_sub(struct timeval *end, struct timeval *start,
struct timeval *elapsed)
{
elapsed->tv_sec = end->tv_sec - start->tv_sec;
if (elapsed->tv_sec > 0) {
elapsed->tv_sec--;
elapsed->tv_usec = USEC;
} else {
elapsed->tv_usec = 0;
}
elapsed->tv_usec += end->tv_usec;
elapsed->tv_usec -= start->tv_usec;
if (elapsed->tv_usec >= USEC) {
elapsed->tv_usec -= USEC;
elapsed->tv_sec++;
}
}
int radius_exec_program(const char *cmd, REQUEST *request,
int exec_wait,
char *user_msg, int msg_len,
VALUE_PAIR *input_pairs,
VALUE_PAIR **output_pairs,
int shell_escape)
{
VALUE_PAIR *vp;
char mycmd[1024];
const char *from;
char *p, *to;
int pd[2];
pid_t pid, child_pid;
int argc = -1;
int comma = 0;
int status;
int i;
int n, left, done;
char *argv[MAX_ARGV];
char answer[4096];
char argv_buf[4096];
#define MAX_ENVP 1024
char *envp[MAX_ENVP];
struct timeval start;
#ifdef O_NONBLOCK
int nonblock = TRUE;
#endif
if (user_msg) *user_msg = '\0';
if (output_pairs) *output_pairs = NULL;
if (strlen(cmd) > (sizeof(mycmd) - 1)) {
radlog(L_ERR|L_CONS, "Command line is too long");
return -1;
}
if (cmd[strlen(cmd) - 1] == '\\') {
radlog(L_ERR|L_CONS, "Command line has final backslash, without a following character");
return -1;
}
strlcpy(mycmd, cmd, sizeof(mycmd));
from = cmd;
to = mycmd;
argc = 0;
while (*from) {
int length;
if ((*from == ' ') || (*from == '\t')) {
from++;
continue;
}
argv[argc] = to;
argc++;
if (argc >= (MAX_ARGV - 1)) break;
while (*from && (*from != ' ') && (*from != '\t')) {
if (to >= mycmd + sizeof(mycmd) - 1) {
return -1;
}
switch (*from) {
case '"':
case '\'':
length = rad_copy_string(to, from);
if (length < 0) {
radlog(L_ERR|L_CONS, "Invalid string passed as argument for external program");
return -1;
}
from += length;
to += length;
break;
case '%':
if (from[1] == '{') {
*(to++) = *(from++);
length = rad_copy_variable(to, from);
if (length < 0) {
radlog(L_ERR|L_CONS, "Invalid variable expansion passed as argument for external program");
return -1;
}
from += length;
to += length;
} else {
*(to++) = *(from++);
}
break;
case '\\':
if (from[1] == ' ') from++;
default:
*(to++) = *(from++);
}
}
*(to++) = '\0';
}
if (argc <= 0) {
radlog(L_ERR, "Exec-Program: empty command line.");
return -1;
}
to = argv_buf;
left = sizeof(argv_buf);
for (i = 0; i < argc; i++) {
int sublen;
if (strchr(argv[i], '%') == NULL) continue;
if (!request) continue;
sublen = radius_xlat(to, left - 1, argv[i], request, NULL);
if (sublen <= 0) {
sublen = 0;
}
argv[i] = to;
to += sublen;
*(to++) = '\0';
left -= sublen;
left--;
if (left <= 0) {
radlog(L_ERR, "Exec-Program: Ran out of space while expanding arguments.");
return -1;
}
}
argv[argc] = NULL;
#ifndef __MINGW32__
if (exec_wait) {
if (pipe(pd) != 0) {
radlog(L_ERR|L_CONS, "Couldn't open pipe: %s",
strerror(errno));
return -1;
}
} else {
user_msg = NULL;
output_pairs = NULL;
}
envp[0] = NULL;
if (input_pairs) {
int envlen;
char buffer[1024];
envlen = 0;
for (vp = input_pairs; vp != NULL; vp = vp->next) {
snprintf(buffer, sizeof(buffer), "%s=", vp->name);
if (shell_escape) {
for (p = buffer; *p != '='; p++) {
if (*p == '-') {
*p = '_';
} else if (isalpha((int) *p)) {
*p = toupper(*p);
}
}
}
n = strlen(buffer);
vp_prints_value(buffer+n, sizeof(buffer) - n, vp, shell_escape);
envp[envlen++] = strdup(buffer);
if (envlen == (MAX_ENVP - 1)) break;
}
envp[envlen] = NULL;
}
if (exec_wait) {
pid = rad_fork();
} else {
pid = fork();
}
if (pid == 0) {
int devnull;
devnull = open("/dev/null", O_RDWR);
if (devnull < 0) {
radlog(L_ERR|L_CONS, "Failed opening /dev/null: %s\n",
strerror(errno));
exit(1);
}
dup2(devnull, STDIN_FILENO);
if (exec_wait) {
if (close(pd[0]) != 0) {
radlog(L_ERR|L_CONS, "Can't close pipe: %s",
strerror(errno));
exit(1);
}
if (dup2(pd[1], STDOUT_FILENO) != 1) {
radlog(L_ERR|L_CONS, "Can't dup stdout: %s",
strerror(errno));
exit(1);
}
} else {
dup2(devnull, STDOUT_FILENO);
}
if (debug_flag == 0) {
dup2(devnull, STDERR_FILENO);
}
close(devnull);
closefrom(3);
execve(argv[0], argv, envp);
radlog(L_ERR, "Exec-Program: FAILED to execute %s: %s",
argv[0], strerror(errno));
exit(1);
}
for (i = 0; envp[i] != NULL; i++) {
free(envp[i]);
}
if (pid < 0) {
radlog(L_ERR|L_CONS, "Couldn't fork %s: %s",
argv[0], strerror(errno));
if (exec_wait) {
close(pd[0]);
close(pd[1]);
}
return -1;
}
if (!exec_wait) {
return 0;
}
if (close(pd[1]) != 0) {
radlog(L_ERR|L_CONS, "Can't close pipe: %s", strerror(errno));
close(pd[0]);
return -1;
}
#ifdef O_NONBLOCK
do {
int flags;
if ((flags = fcntl(pd[0], F_GETFL, NULL)) < 0) {
nonblock = FALSE;
break;
}
flags |= O_NONBLOCK;
if( fcntl(pd[0], F_SETFL, flags) < 0) {
nonblock = FALSE;
break;
}
} while (0);
#endif
done = 0;
left = sizeof(answer) - 1;
gettimeofday(&start, NULL);
while (1) {
int rcode;
fd_set fds;
struct timeval when, elapsed, wake;
FD_ZERO(&fds);
FD_SET(pd[0], &fds);
gettimeofday(&when, NULL);
tv_sub(&when, &start, &elapsed);
if (elapsed.tv_sec >= 10) goto too_long;
when.tv_sec = 10;
when.tv_usec = 0;
tv_sub(&when, &elapsed, &wake);
rcode = select(pd[0] + 1, &fds, NULL, NULL, &wake);
if (rcode == 0) {
too_long:
radlog(L_ERR, "Child PID %u is taking too much time: forcing failure and killing child.", pid);
kill(pid, SIGTERM);
close(pd[0]);
rad_waitpid(pid, &status);
return 1;
}
if (rcode < 0) {
if (errno == EINTR) continue;
break;
}
#ifdef O_NONBLOCK
if (nonblock) {
status = read(pd[0], answer + done, left);
} else
#endif
status = read(pd[0], answer + done, 1);
if (status == 0) {
break;
}
if (status < 0) {
if (errno == EINTR) {
continue;
}
break;
}
done += status;
left -= status;
if (left <= 0) break;
}
answer[done] = 0;
close(pd[0]);
DEBUG2("Exec-Program output: %s", answer);
if (done) {
n = T_OP_INVALID;
if (output_pairs) {
vp = NULL;
n = userparse(answer, &vp);
if (vp) {
pairfree(&vp);
}
}
if (n == T_OP_INVALID) {
DEBUG("Exec-Program-Wait: plaintext: %s", answer);
if (user_msg) {
strlcpy(user_msg, answer, msg_len);
}
} else {
for (p = answer; *p; p++) {
if (*p == '\n') {
*p = comma ? ' ' : ',';
p++;
comma = 0;
}
if (*p == ',') comma++;
}
if (answer[strlen(answer) - 1] == ',') {
answer[strlen(answer) - 1] = '\0';
}
radlog(L_DBG,"Exec-Program-Wait: value-pairs: %s", answer);
if (userparse(answer, &vp) == T_OP_INVALID) {
radlog(L_ERR, "Exec-Program-Wait: %s: unparsable reply", cmd);
} else {
*output_pairs = vp;
}
}
}
child_pid = rad_waitpid(pid, &status);
if (child_pid == 0) {
radlog(L_DBG, "Exec-Program: Timeout waiting for child");
return 2;
}
if (child_pid == pid) {
if (WIFEXITED(status)) {
status = WEXITSTATUS(status);
radlog(L_DBG, "Exec-Program: returned: %d", status);
return status;
}
}
radlog(L_ERR|L_CONS, "Exec-Program: Abnormal child exit: %s",
strerror(errno));
return 1;
#else
msg_len = msg_len;
if (exec_wait) {
radlog(L_ERR, "Exec-Program-Wait is not supported");
return -1;
}
user_msg = NULL;
output_pairs = NULL;
{
_spawnve(_P_NOWAIT, argv[0], argv, envp);
}
return 0;
#endif
}