Massively cleaned up #include's, so they're in a consistent
[freeradius.git] / src / main / exec.c
index cb0e9e4..aa982b0 100644 (file)
  *
  *   You should have received a copy of the GNU General Public License
  *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  *
- * Copyright 2000  The FreeRADIUS server project
- * Copyright 2000  Michael J. Hartwick <hartwick@hartwick.com>
+ * Copyright 2000-2004,2006  The FreeRADIUS server project
  */
-static const char rcsid[] = "$Id$";
 
-#include "autoconf.h"
-#include "libradius.h"
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/rad_assert.h>
 
 #include <sys/file.h>
 
-#include <stdlib.h>
-#include <string.h>
 #include <fcntl.h>
 #include <ctype.h>
 #include <signal.h>
 
-#if HAVE_SYS_WAIT_H
+#ifdef HAVE_SYS_WAIT_H
 #      include <sys/wait.h>
 #endif
 #ifndef WEXITSTATUS
@@ -43,189 +42,401 @@ static const char rcsid[] = "$Id$";
 #      define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
 #endif
 
-#include "radiusd.h"
-
+#define MAX_ARGV (256)
 /*
  *     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, const char **user_msg)
+                       int exec_wait,
+                       char *user_msg, int msg_len,
+                       VALUE_PAIR *input_pairs,
+                       VALUE_PAIR **output_pairs,
+                       int shell_escape)
 {
        VALUE_PAIR *vp;
-       static char message[256];
+       char mycmd[1024];
        char answer[4096];
-       char *argv[32];
-       char *buf, *p;
+       char argv_buf[4096];
+       char *argv[MAX_ARGV];
+       const char *from;
+       char *p, *to;
        int pd[2];
-       pid_t pid;
+       pid_t pid, child_pid;
        int argc = -1;
        int comma = 0;
        int status;
+       int i;
        int n, left, done;
-       void (*oldsig)(int) = NULL;
+
+       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;
+       }
+
+       /*
+        *      Check for bad escapes.
+        */
+       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));
 
        /*
-        *      (hs)    - Open a pipe for child/parent communication.
-        *              - Reset the signal handler for SIGCHLD, so
-        *                we have a chance to notice the dead child here and
-        *                not in some signal handler.
-        *                This has to be done for the exec_wait case only, since
-        *                if we don't wait we aren't interested in any
-        *                gone children ...
-        */     
+        *      Split the string into argv's BEFORE doing radius_xlat...
+        */
+       from = cmd;
+       to = mycmd;
+       argc = 0;
+       while (*from) {
+               int length;
+
+               /*
+                *      Skip spaces.
+                */
+               if ((*from == ' ') || (*from == '\t')) {
+                       from++;
+                       continue;
+               }
+
+               argv[argc] = to;
+               argc++;
+
+               if (argc >= (MAX_ARGV - 1)) break;
+
+               /*
+                *      Copy the argv over to our buffer.
+                */
+               while (*from && (*from != ' ') && (*from != '\t')) {
+                       if (to >= mycmd + sizeof(mycmd) - 1) {
+                               return -1; /* ran out of space */
+                       }
+
+                       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 { /* FIXME: catch %%{ ? */
+                                       *(to++) = *(from++);
+                               }
+                               break;
+
+                       default:
+                               *(to++) = *(from++);
+                       }
+               } /* end of string, or found a space */
+
+               *(to++) = '\0'; /* terminate the string */
+       }
+
+       /*
+        *      We have to have SOMETHING, at least.
+        */
+       if (argc <= 0) {
+               radlog(L_ERR, "Exec-Program: empty command line.");
+               return -1;
+       }
+
+       /*
+        *      Expand each string, as appropriate.
+        */
+       to = argv_buf;
+       left = sizeof(argv_buf);
+       for (i = 0; i < argc; i++) {
+               int sublen;
+
+               /*
+                *      Don't touch argv's which won't be translated.
+                */
+               if (strchr(argv[i], '%') == NULL) continue;
+
+               sublen = radius_xlat(to, left - 1, argv[i], request, NULL);
+               if (sublen <= 0) {
+                       /*
+                        *      Fail to be backwards compatible.
+                        *
+                        *      It's yucky, but it won't break anything,
+                        *      and it won't cause security problems.
+                        */
+                       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;
+
+       /*
+        *      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: %m");
-                       pd[0] = pd[1] = 0;
-               }
-               if ((oldsig = signal(SIGCHLD, SIG_DFL)) == SIG_ERR) {
-                       radlog(L_ERR|L_CONS, "Can't reset SIGCHLD: %m");
-                       oldsig = NULL;
+                       radlog(L_ERR|L_CONS, "Couldn't open pipe: %s",
+                              strerror(errno));
+                       return -1;
                }
+       } else {
+               /*
+                *      We're not waiting, so we don't look for a
+                *      message, or VP's.
+                */
+               user_msg = NULL;
+               output_pairs = NULL;
        }
 
-       if ((pid = fork()) == 0) {
+       if (exec_wait) {
+               pid = rad_fork();       /* remember PID */
+       } else {
+               pid = fork();           /* don't wait */
+       }
+
+       if (pid == 0) {
 #define MAX_ENVP 1024
+               int devnull;
                char *envp[MAX_ENVP];
                int envlen;
                char buffer[1024];
 
-               /*      
-                *      Child
-                */
-               radius_xlat2(answer, sizeof(answer), cmd, request);
-               buf = answer;
-
                /*
-                *      Log the command if we are debugging something
+                *      Child process.
+                *
+                *      We try to be fail-safe here. So if ANYTHING
+                *      goes wrong, we exit with status 1.
                 */
-               DEBUG("Exec-Program: %s", buf);
 
                /*
-                *      Build vector list and execute.
+                *      Open STDIN to /dev/null
                 */
-               p = strtok(buf, " \t");
-               if (p) do {
-                       argv[++argc] = p;
-                       p = strtok(NULL, " \t");
-               } while(p != NULL);
-               argv[++argc] = p;
-               if (argc == 0) {
-                       radlog(L_ERR, "Exec-Program: empty command line.");
+               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);
 
+               /*
+                *      Only massage the pipe handles if the parent
+                *      has created them.
+                */
                if (exec_wait) {
-                       if (close(pd[0]) != 0)
-                               radlog(L_ERR|L_CONS, "Can't close pipe: %m");
-                       if (dup2(pd[1], 1) != 1)
-                               radlog(L_ERR|L_CONS, "Can't dup stdout: %m");
+                       /*
+                        *      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);
+                       }
+
+                       /*
+                        *      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);
+                       }
+
+               } else {        /* no pipe, STDOUT should be /dev/null */
+                       dup2(devnull, STDOUT_FILENO);
                }
 
                /*
+                *      If we're not debugging, then we can't do
+                *      anything with the error messages, so we throw
+                *      them away.
+                *
+                *      If we are debugging, then we want the error
+                *      messages to go to the STDERR of the server.
+                */
+               if (debug_flag == 0) {
+                       dup2(devnull, STDERR_FILENO);
+               }
+               close(devnull);
+
+               /*
+                *      The server may have MANY FD's open.  We don't
+                *      want to leave dangling FD's for the child process
+                *      to play funky games with, so we close them.
+                */
+               closefrom(3);
+
+               /*
                 *      Set up the environment variables.
                 *      We're in the child, and it will exit in 4 lines
                 *      anyhow, so memory allocation isn't an issue.
                 */
                envlen = 0;
 
-               for (vp = request->packet->vps; vp->next; vp = vp->next) {
+               for (vp = input_pairs; vp != NULL; vp = vp->next) {
                        /*
                         *      Hmm... maybe we shouldn't pass the
                         *      user's password in an environment
                         *      variable...
                         */
                        snprintf(buffer, sizeof(buffer), "%s=", vp->name);
-                       for (p = buffer; *p != '='; p++) {
-                               if (*p == '-') {
-                                       *p = '_';
-                               } else if (isalpha(*p)) {
-                                       *p = toupper(*p);
+                       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, 1);
+                       vp_prints_value(buffer+n, sizeof(buffer) - n, vp, shell_escape);
 
                        envp[envlen++] = strdup(buffer);
-               }
 
+                       /*
+                        *      Don't add too many attributes.
+                        */
+                       if (envlen == (MAX_ENVP - 1)) break;
+               }
                envp[envlen] = NULL;
-               
-
-
-
-               for(n = 32; n >= 3; n--)
-                       close(n);
 
                execve(argv[0], argv, envp);
-
-               radlog(L_ERR, "Exec-Program: %s: %m", argv[0]);
+               radlog(L_ERR, "Exec-Program: FAILED to execute %s: %s",
+                      argv[0], strerror(errno));
                exit(1);
        }
 
        /*
-        *      Parent 
+        *      Parent process.
         */
        if (pid < 0) {
-               radlog(L_ERR|L_CONS, "Couldn't fork: %m");
+               radlog(L_ERR|L_CONS, "Couldn't fork %s: %s",
+                      argv[0], strerror(errno));
                return -1;
        }
-       if (!exec_wait)
+
+       /*
+        *      We're not waiting, exit, and ignore any child's status.
+        */
+       if (!exec_wait) {
                return 0;
+       }
 
        /*
-        *      (hs) Do we have a pipe?
-        *      --> Close the write side of the pipe 
-        *      --> Read from it.
+        *      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.
         */
-       done = 0;
-       if (pd[0] || pd[1]) {
-               if (close(pd[1]) != 0)
-                       radlog(L_ERR|L_CONS, "Can't close pipe: %m");
+       if (close(pd[1]) != 0) {
+               radlog(L_ERR|L_CONS, "Can't close pipe: %s", strerror(errno));
+               close(pd[0]);
+               return -1;
+       }
 
+       /*
+        *      Read from the pipe until we doesn't get any more or
+        *      until the message is full.
+        */
+       done = 0;
+       left = sizeof(answer) - 1;
+       while (1) {
+               status = read(pd[0], answer + done, left);
                /*
-                *      (hs) Read until we doesn't get any more
-                *      or until the message is full.
+                *      Nothing more to read: stop.
                 */
-               done = 0;
-               left = sizeof(answer) - 1;
-               while ((n = read(pd[0], answer + done, left)) > 0) {
-                       done += n;
-                       left -= n;
-                       if (left <= 0) break;
+               if (status == 0) {
+                       break;
                }
-               answer[done] = 0;
 
                /*
-                *      (hs) Make sure that the writer can't block
-                *      while writing in a pipe that isn't read anymore.
+                *      Error: See if we have to continue.
                 */
-               close(pd[0]);
+               if (status < 0) {
+                       /*
+                        *      We were interrupted: continue reading.
+                        */
+                       if (errno == EINTR) {
+                               continue;
+                       }
+
+                       /*
+                        *      There was another error.  Most likely
+                        *      The child process has finished, and
+                        *      exited.
+                        */
+                       break;
+               }
+
+               done += status;
+               left -= status;
+               if (left <= 0) break;
        }
+       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]);
+
+       DEBUG2("Exec-Program output: %s", answer);
 
        /*
         *      Parse the output, if any.
         */
        if (done) {
-               /*
-                *      For backwards compatibility, first check
-                *      for plain text (user_msg).
-                */
-               vp = NULL;
-               n = userparse(answer, &vp);
-               if (vp)
-                       pairfree(&vp);
+               n = T_OP_INVALID;
+               if (output_pairs) {
+                       /*
+                        *      For backwards compatibility, first check
+                        *      for plain text (user_msg).
+                        */
+                       vp = NULL;
+                       n = userparse(answer, &vp);
+                       if (vp) {
+                               pairfree(&vp);
+                       }
+               }
 
-               if (n != 0) {
-                       radlog(L_DBG, "Exec-Program-Wait: plaintext: %s", answer);
+               if (n == T_OP_INVALID) {
+                       DEBUG("Exec-Program-Wait: plaintext: %s", answer);
                        if (user_msg) {
-                               strncpy(message, answer, sizeof(message));
-                               message[sizeof(message) - 1] = 0;
-                               *user_msg = message;
+                               strlcpy(user_msg, answer, msg_len);
                        }
                } else {
                        /*
@@ -244,41 +455,45 @@ int radius_exec_program(const char *cmd, REQUEST *request,
                        }
 
                        /*
-                        *  Replace any trailing comma by a NUL.
+                        *      Replace any trailing comma by a NUL.
                         */
-                       if (answer[strlen(answer) - 1] == ',')
+                       if (answer[strlen(answer) - 1] == ',') {
                                answer[strlen(answer) - 1] = '\0';
+                       }
 
                        radlog(L_DBG,"Exec-Program-Wait: value-pairs: %s", answer);
-                       if (userparse(answer, &vp) != 0)
-                               radlog(L_ERR,
-               "Exec-Program-Wait: %s: unparsable reply", cmd);
-                       else {
-                               pairmove(&request->reply->vps, &vp);
-                               pairfree(&vp);
+                       if (userparse(answer, &vp) == T_OP_INVALID) {
+                               radlog(L_ERR, "Exec-Program-Wait: %s: unparsable reply", cmd);
+
+                       } else {
+                               /*
+                                *      Tell the caller about the value
+                                *      pairs.
+                                */
+                               *output_pairs = vp;
                        }
-               }
-       }
-
-       while(waitpid(pid, &status, 0) != pid)
-               ;
+               } /* else the answer was a set of VP's, not a text message */
+       } /* else we didn't read anything from the child */
 
        /*
-        *      (hs) Now we let our cleanup_sig handler take care for
-        *      all signals that will arise.
+        *      Call rad_waitpid (should map to waitpid on non-threaded
+        *      or single-server systems).
         */
-       if (oldsig && (signal(SIGCHLD, oldsig) == SIG_ERR))
-               radlog(L_ERR|L_CONS,
-                       "Can't set SIGCHLD to the cleanup handler: %m");
-       sig_cleanup(SIGCHLD);
-
-       if (WIFEXITED(status)) {
-               status = WEXITSTATUS(status);
-               radlog(L_INFO, "Exec-Program: returned: %d", status);
-               return status;
+       child_pid = rad_waitpid(pid, &status);
+       if (child_pid == 0) {
+               radlog(L_DBG, "Exec-Program: Timeout waiting for child");
+               return 2;
        }
-       radlog(L_ERR|L_CONS, "Exec-Program: Abnormal child exit (killed or coredump)");
 
+       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;
 }
-