split exec functionality into 3 parts
authorPhil Mayers <p.mayers@imperial.ac.uk>
Tue, 19 Apr 2011 14:10:11 +0000 (15:10 +0100)
committerAlan T. DeKok <aland@freeradius.org>
Fri, 13 May 2011 08:10:17 +0000 (10:10 +0200)
src/include/radiusd.h
src/main/exec.c

index 9e07dc6..503fb58 100644 (file)
@@ -641,6 +641,13 @@ int                rad_authenticate (REQUEST *);
 int            rad_postauth(REQUEST *);
 
 /* exec.c */
+pid_t radius_start_program(const char *cmd, REQUEST *request,
+                       int exec_wait,
+                       int *input_fd,
+                       int *output_fd,
+                       VALUE_PAIR *input_pairs,
+                       int shell_escape);
+int radius_readfrom_program(int fd, pid_t pid, int timeout, char *answer, int left);
 int            radius_exec_program(const char *,  REQUEST *, int,
                                    char *user_msg, int msg_len,
                                    VALUE_PAIR *input_pairs,
index 3c50269..196cdfc 100644 (file)
@@ -66,41 +66,29 @@ static void tv_sub(struct timeval *end, struct timeval *start,
 
 
 /*
- *     Execute a program on successful authentication.
- *     Return 0 if exec_wait == 0.
- *     Return the exit code of the called program if exec_wait != 0.
- *     Return -1 on fork/other errors in the parent process.
+ *     Start a process
  */
-int radius_exec_program(const char *cmd, REQUEST *request,
+pid_t radius_start_program(const char *cmd, REQUEST *request,
                        int exec_wait,
-                       char *user_msg, int msg_len,
+                       int *input_fd,
+                       int *output_fd,
                        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 to_child[2] = {-1, -1};
+       int from_child[2] = {-1, -1};
+       pid_t pid;
        int argc = -1;
-       int comma = 0;
-       int status;
        int i;
-       int n, left, done;
+       int n, left;
        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");
@@ -239,18 +227,23 @@ int radius_exec_program(const char *cmd, REQUEST *request,
         *      Open a pipe for child/parent communication, if necessary.
         */
        if (exec_wait) {
-               if (pipe(pd) != 0) {
-                       radlog(L_ERR|L_CONS, "Couldn't open pipe: %s",
-                              strerror(errno));
-                       return -1;
+               if (input_fd) {
+                       if (pipe(to_child) != 0) {
+                               radlog(L_ERR|L_CONS, "Couldn't open pipe to child: %s",
+                                      strerror(errno));
+                               return -1;
+                       }
+               }
+               if (output_fd) {
+                       if (pipe(from_child) != 0) {
+                               radlog(L_ERR|L_CONS, "Couldn't open pipe from child: %s",
+                                      strerror(errno));
+                               /* safe because these either need closing or are == -1 */
+                               close(to_child[0]);
+                               close(to_child[1]);
+                               return -1;
+                       }
                }
-       } else {
-               /*
-                *      We're not waiting, so we don't look for a
-                *      message, or VP's.
-                */
-               user_msg = NULL;
-               output_pairs = NULL;
        }
 
        envp[0] = NULL;
@@ -322,34 +315,29 @@ int radius_exec_program(const char *cmd, REQUEST *request,
                               strerror(errno));
                        exit(1);
                }
-               dup2(devnull, STDIN_FILENO);
 
                /*
                 *      Only massage the pipe handles if the parent
                 *      has created them.
                 */
                if (exec_wait) {
-                       /*
-                        *      pd[0] is the FD the child will read from,
-                        *      which we don't want.
-                        */
-                       if (close(pd[0]) != 0) {
-                               radlog(L_ERR|L_CONS, "Can't close pipe: %s",
-                                      strerror(errno));
-                               exit(1);
+
+                       if (input_fd) {
+                               close(to_child[1]);
+                               dup2(to_child[0], STDIN_FILENO);
+                       } else {
+                               dup2(devnull, STDIN_FILENO);
                        }
 
-                       /*
-                        *      pd[1] is the FD that the child will write to,
-                        *      so we make it STDOUT.
-                        */
-                       if (dup2(pd[1], STDOUT_FILENO) != 1) {
-                               radlog(L_ERR|L_CONS, "Can't dup stdout: %s",
-                                      strerror(errno));
-                               exit(1);
+                       if (output_fd) {
+                               close(from_child[0]);
+                               dup2(from_child[1], STDOUT_FILENO);
+                       } else {
+                               dup2(devnull, STDOUT_FILENO);
                        }
 
                } else {        /* no pipe, STDOUT should be /dev/null */
+                       dup2(devnull, STDIN_FILENO);
                        dup2(devnull, STDOUT_FILENO);
                }
 
@@ -393,8 +381,11 @@ int radius_exec_program(const char *cmd, REQUEST *request,
                radlog(L_ERR|L_CONS, "Couldn't fork %s: %s",
                       argv[0], strerror(errno));
                if (exec_wait) {
-                       close(pd[0]);
-                       close(pd[1]);
+                       /* safe because these either need closing or are == -1 */
+                       close(to_child[0]);
+                       close(to_child[1]);
+                       close(from_child[0]);
+                       close(from_child[0]);
                }
                return -1;
        }
@@ -402,21 +393,36 @@ int radius_exec_program(const char *cmd, REQUEST *request,
        /*
         *      We're not waiting, exit, and ignore any child's status.
         */
-       if (!exec_wait) {
-               return 0;
+       if (exec_wait) {
+               /*
+                *      Close the ends of the pipe(s) the child is using
+                *      return the ends of the pipe(s) our caller wants
+                *
+                */
+               if (input_fd) {
+                       *input_fd = to_child[1];
+                       close(to_child[0]);
+               }
+               if (output_fd) {
+                       *output_fd = from_child[0];
+                       close(from_child[1]);
+               }
        }
 
-       /*
-        *      Close the FD to which the child writes it's data.
-        *
-        *      If we can't close it, then we close pd[0], and return an
-        *      error.
-        */
-       if (close(pd[1]) != 0) {
-               radlog(L_ERR|L_CONS, "Can't close pipe: %s", strerror(errno));
-               close(pd[0]);
-               return -1;
-       }
+       return pid;
+}
+
+/*
+ * read from the child process into buffer "answer" of length "left"
+ */
+int radius_readfrom_program(int fd, pid_t pid, int timeout, char *answer, int left) {
+
+       int done;
+       int status;
+       struct timeval start;
+#ifdef O_NONBLOCK
+       int nonblock = TRUE;
+#endif
 
 #ifdef O_NONBLOCK
        /*
@@ -425,13 +431,13 @@ int radius_exec_program(const char *cmd, REQUEST *request,
        do {
                int flags;
                
-               if ((flags = fcntl(pd[0], F_GETFL, NULL)) < 0)  {
+               if ((flags = fcntl(fd, F_GETFL, NULL)) < 0)  {
                        nonblock = FALSE;
                        break;
                }
                
                flags |= O_NONBLOCK;
-               if( fcntl(pd[0], F_SETFL, flags) < 0) {
+               if( fcntl(fd, F_SETFL, flags) < 0) {
                        nonblock = FALSE;
                        break;
                }
@@ -444,7 +450,6 @@ int radius_exec_program(const char *cmd, REQUEST *request,
         *      until the message is full.
         */
        done = 0;
-       left = sizeof(answer) - 1;
        gettimeofday(&start, NULL);
        while (1) {
                int rcode;
@@ -452,28 +457,28 @@ int radius_exec_program(const char *cmd, REQUEST *request,
                struct timeval when, elapsed, wake;
 
                FD_ZERO(&fds);
-               FD_SET(pd[0], &fds);
+               FD_SET(fd, &fds);
 
                gettimeofday(&when, NULL);
                tv_sub(&when, &start, &elapsed);
-               if (elapsed.tv_sec >= 10) goto too_long;
+               if (elapsed.tv_sec >= timeout) goto too_long;
                
-               when.tv_sec = 10;
+               when.tv_sec = timeout;
                when.tv_usec = 0;
                tv_sub(&when, &elapsed, &wake);
 
-               rcode = select(pd[0] + 1, &fds, NULL, NULL, &wake);
+               rcode = select(fd + 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]); /* should give SIGPIPE to child, too */
+                       close(fd); /* should give SIGPIPE to child, too */
 
                        /*
                         *      Clean up the child entry.
                         */
                        rad_waitpid(pid, &status);
-                       return 1;                       
+                       return -1;
                }
                if (rcode < 0) {
                        if (errno == EINTR) continue;
@@ -486,13 +491,13 @@ int radius_exec_program(const char *cmd, REQUEST *request,
                 *      will return the number of bytes available.
                 */
                if (nonblock) {
-                       status = read(pd[0], answer + done, left);
+                       status = read(fd, answer + done, left);
                } else 
 #endif
                        /*
                         *      There's at least 1 byte ready: read it.
                         */
-                       status = read(pd[0], answer + done, 1);
+                       status = read(fd, answer + done, 1);
 
                /*
                 *      Nothing more to read: stop.
@@ -524,13 +529,58 @@ int radius_exec_program(const char *cmd, REQUEST *request,
                left -= status;
                if (left <= 0) break;
        }
+
+       return done;
+}
+
+/*
+ *     Execute a program on successful authentication.
+ *     Return 0 if exec_wait == 0.
+ *     Return the exit code of the called program if exec_wait != 0.
+ *     Return -1 on fork/other errors in the parent process.
+ */
+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 *p;
+       int from_child;
+       pid_t pid, child_pid;
+       int comma = 0;
+       int status;
+       int n, done;
+       char answer[4096];
+
+       pid = radius_start_program(cmd, request, exec_wait, NULL, &from_child, input_pairs, shell_escape);
+       if (pid < 0) {
+               return -1;
+       }
+
+       if (!exec_wait)
+               return 0;
+
+       done = radius_readfrom_program(from_child, pid, 10, answer, sizeof(answer));
+       if (done < 0) {
+               /*
+                * failure - radius_readfrom_program will
+                * have called close(from_child) for us
+                */
+               DEBUG("failed to read from child output");
+               return 1;
+
+       }
        answer[done] = 0;
 
+
        /*
         *      Make sure that the writer can't block while writing to
         *      a pipe that no one is reading from anymore.
         */
-       close(pd[0]);
+       close(from_child);
 
        DEBUG2("Exec-Program output: %s", answer);