]> granicus.if.org Git - linux-pam/blobdiff - modules/pam_exec/pam_exec.c
Introduce pam_modutil_sanitize_helper_fds
[linux-pam] / modules / pam_exec / pam_exec.c
index 49790d80323d0265d9d589c7b2faf59d3c15406b..12c44444c1ab145396ed2e5c3d21fcdc3e59a548 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2006 Thorsten Kukuk <kukuk@thkukuk.de>
+ * Copyright (c) 2006, 2008 Thorsten Kukuk <kukuk@thkukuk.de>
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #include <security/pam_modules.h>
 #include <security/pam_modutil.h>
 #include <security/pam_ext.h>
+#include <security/_pam_macros.h>
+
+#define ENV_ITEM(n) { (n), #n }
+static struct {
+  int item;
+  const char *name;
+} env_items[] = {
+  ENV_ITEM(PAM_SERVICE),
+  ENV_ITEM(PAM_USER),
+  ENV_ITEM(PAM_TTY),
+  ENV_ITEM(PAM_RHOST),
+  ENV_ITEM(PAM_RUSER),
+};
+
+/* move_fd_to_non_stdio copies the given file descriptor to something other
+ * than stdin, stdout, or stderr.  Assumes that the caller will close all
+ * unwanted fds after calling. */
+static int
+move_fd_to_non_stdio (pam_handle_t *pamh, int fd)
+{
+  while (fd < 3)
+    {
+      fd = dup(fd);
+      if (fd == -1)
+       {
+         int err = errno;
+         pam_syslog (pamh, LOG_ERR, "dup failed: %m");
+         _exit (err);
+       }
+    }
+  return fd;
+}
 
 static int
-call_exec (pam_handle_t *pamh, int argc, const char **argv)
+call_exec (const char *pam_type, pam_handle_t *pamh,
+          int argc, const char **argv)
 {
   int debug = 0;
   int call_setuid = 0;
+  int quiet = 0;
+  int expose_authtok = 0;
+  int use_stdout = 0;
   int optargc;
   const char *logfile = NULL;
+  const char *authtok = NULL;
   pid_t pid;
+  int fds[2];
+  int stdout_fds[2];
+  FILE *stdout_file = NULL;
 
   if (argc < 1) {
     pam_syslog (pamh, LOG_ERR,
@@ -81,14 +121,91 @@ call_exec (pam_handle_t *pamh, int argc, const char **argv)
 
       if (strcasecmp (argv[optargc], "debug") == 0)
        debug = 1;
+      else if (strcasecmp (argv[optargc], "stdout") == 0)
+       use_stdout = 1;
       else if (strncasecmp (argv[optargc], "log=", 4) == 0)
        logfile = &argv[optargc][4];
+      else if (strncasecmp (argv[optargc], "type=", 5) == 0)
+       {
+         if (strcmp (pam_type, &argv[optargc][5]) != 0)
+           return PAM_IGNORE;
+       }
       else if (strcasecmp (argv[optargc], "seteuid") == 0)
        call_setuid = 1;
+      else if (strcasecmp (argv[optargc], "quiet") == 0)
+       quiet = 1;
+      else if (strcasecmp (argv[optargc], "expose_authtok") == 0)
+       expose_authtok = 1;
       else
        break; /* Unknown option, assume program to execute. */
     }
 
+  if (expose_authtok == 1)
+    {
+      if (strcmp (pam_type, "auth") != 0)
+       {
+         pam_syslog (pamh, LOG_ERR,
+                     "expose_authtok not supported for type %s", pam_type);
+         expose_authtok = 0;
+       }
+      else
+       {
+         const void *void_pass;
+         int retval;
+
+         retval = pam_get_item (pamh, PAM_AUTHTOK, &void_pass);
+         if (retval != PAM_SUCCESS)
+           {
+             if (debug)
+               pam_syslog (pamh, LOG_DEBUG,
+                           "pam_get_item (PAM_AUTHTOK) failed, return %d",
+                           retval);
+             return retval;
+           }
+         else if (void_pass == NULL)
+           {
+             char *resp = NULL;
+
+             retval = pam_prompt (pamh, PAM_PROMPT_ECHO_OFF,
+                                  &resp, _("Password: "));
+
+             if (retval != PAM_SUCCESS)
+               {
+                 _pam_drop (resp);
+                 if (retval == PAM_CONV_AGAIN)
+                   retval = PAM_INCOMPLETE;
+                 return retval;
+               }
+
+             pam_set_item (pamh, PAM_AUTHTOK, resp);
+             authtok = strdupa (resp);
+             _pam_drop (resp);
+           }
+         else
+           authtok = void_pass;
+
+         if (pipe(fds) != 0)
+           {
+             pam_syslog (pamh, LOG_ERR, "Could not create pipe: %m");
+             return PAM_SYSTEM_ERR;
+           }
+       }
+    }
+
+  if (use_stdout)
+    {
+      if (pipe(stdout_fds) != 0)
+       {
+         pam_syslog (pamh, LOG_ERR, "Could not create pipe: %m");
+         return PAM_SYSTEM_ERR;
+       }
+      stdout_file = fdopen(stdout_fds[0], "r");
+      if (!stdout_file)
+       {
+         pam_syslog (pamh, LOG_ERR, "Could not fdopen pipe: %m");
+         return PAM_SYSTEM_ERR;
+       }
+    }
 
   if (optargc >= argc) {
     pam_syslog (pamh, LOG_ERR, "No path given as argument");
@@ -102,6 +219,43 @@ call_exec (pam_handle_t *pamh, int argc, const char **argv)
     {
       int status = 0;
       pid_t retval;
+
+      if (expose_authtok) /* send the password to the child */
+       {
+         if (authtok != NULL)
+           {            /* send the password to the child */
+             if (debug)
+               pam_syslog (pamh, LOG_DEBUG, "send password to child");
+             if (write(fds[1], authtok, strlen(authtok)+1) == -1)
+               pam_syslog (pamh, LOG_ERR,
+                           "sending password to child failed: %m");
+             authtok = NULL;
+           }
+         else
+           {
+             if (write(fds[1], "", 1) == -1)   /* blank password */
+               pam_syslog (pamh, LOG_ERR,
+                           "sending password to child failed: %m");
+           }
+        close(fds[0]);       /* close here to avoid possible SIGPIPE above */
+        close(fds[1]);
+       }
+
+      if (use_stdout)
+       {
+         char buf[4096];
+         close(stdout_fds[1]);
+         while (fgets(buf, sizeof(buf), stdout_file) != NULL)
+           {
+             size_t len;
+             len = strlen(buf);
+             if (buf[len-1] == '\n')
+               buf[len-1] = '\0';
+             pam_info(pamh, "%s", buf);
+           }
+         fclose(stdout_file);
+       }
+
       while ((retval = waitpid (pid, &status, 0)) == -1 &&
             errno == EINTR);
       if (retval == (pid_t)-1)
@@ -115,6 +269,7 @@ call_exec (pam_handle_t *pamh, int argc, const char **argv)
            {
              pam_syslog (pamh, LOG_ERR, "%s failed: exit code %d",
                          argv[optargc], WEXITSTATUS(status));
+               if (!quiet)
              pam_error (pamh, _("%s failed: exit code %d"),
                         argv[optargc], WEXITSTATUS(status));
            }
@@ -123,6 +278,7 @@ call_exec (pam_handle_t *pamh, int argc, const char **argv)
              pam_syslog (pamh, LOG_ERR, "%s failed: caught signal %d%s",
                          argv[optargc], WTERMSIG(status),
                          WCOREDUMP(status) ? " (core dumped)" : "");
+               if (!quiet)
              pam_error (pamh, _("%s failed: caught signal %d%s"),
                         argv[optargc], WTERMSIG(status),
                         WCOREDUMP(status) ? " (core dumped)" : "");
@@ -131,6 +287,7 @@ call_exec (pam_handle_t *pamh, int argc, const char **argv)
            {
              pam_syslog (pamh, LOG_ERR, "%s failed: unknown status 0x%x",
                          argv[optargc], status);
+               if (!quiet)
              pam_error (pamh, _("%s failed: unknown status 0x%x"),
                         argv[optargc], status);
            }
@@ -142,30 +299,66 @@ call_exec (pam_handle_t *pamh, int argc, const char **argv)
     {
       char **arggv;
       int i;
+      char **envlist, **tmp;
+      int envlen, nitems;
+      char *envstr;
+      enum pam_modutil_redirect_fd redirect_stdin =
+             expose_authtok ? PAM_MODUTIL_IGNORE_FD : PAM_MODUTIL_PIPE_FD;
+      enum pam_modutil_redirect_fd redirect_stdout =
+             (use_stdout || logfile) ? PAM_MODUTIL_IGNORE_FD : PAM_MODUTIL_NULL_FD;
 
-      for (i = 0; i < sysconf (_SC_OPEN_MAX); i++)
-       close (i);
+      /* First, move all the pipes off of stdin, stdout, and stderr, to ensure
+       * that calls to dup2 won't close them. */
 
-      /* New stdin.  */
-      if ((i = open ("/dev/null", O_RDWR)) < 0)
+      if (expose_authtok)
        {
-         int err = errno;
-         pam_syslog (pamh, LOG_ERR, "open of /dev/null failed: %m");
-         exit (err);
+         fds[0] = move_fd_to_non_stdio(pamh, fds[0]);
+         close(fds[1]);
+       }
+
+      if (use_stdout)
+       {
+         stdout_fds[1] = move_fd_to_non_stdio(pamh, stdout_fds[1]);
+         close(stdout_fds[0]);
        }
-      /* New stdout and stderr.  */
-      if (logfile)
+
+      /* Set up stdin. */
+
+      if (expose_authtok)
+       {
+         /* reopen stdin as pipe */
+         if (dup2(fds[0], STDIN_FILENO) == -1)
+           {
+             int err = errno;
+             pam_syslog (pamh, LOG_ERR, "dup2 of STDIN failed: %m");
+             _exit (err);
+           }
+       }
+
+      /* Set up stdout. */
+
+      if (use_stdout)
+       {
+         if (dup2(stdout_fds[1], STDOUT_FILENO) == -1)
+           {
+             int err = errno;
+             pam_syslog (pamh, LOG_ERR, "dup2 to stdout failed: %m");
+             _exit (err);
+           }
+       }
+      else if (logfile)
        {
          time_t tm = time (NULL);
          char *buffer = NULL;
 
+         close (STDOUT_FILENO);
          if ((i = open (logfile, O_CREAT|O_APPEND|O_WRONLY,
-                        S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1)
+                        S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1)
            {
              int err = errno;
              pam_syslog (pamh, LOG_ERR, "open of %s failed: %m",
                          logfile);
-             exit (err);
+             _exit (err);
            }
          if (asprintf (&buffer, "*** %s", ctime (&tm)) > 0)
            {
@@ -173,19 +366,18 @@ call_exec (pam_handle_t *pamh, int argc, const char **argv)
              free (buffer);
            }
        }
-      else
-       if (dup (i) == -1)
-         {
-           int err = errno;
-           pam_syslog (pamh, LOG_ERR, "dup failed: %m");
-           exit (err);
-         }
-      if (dup (i) == -1)
-        {
+
+      if ((use_stdout || logfile) &&
+         dup2 (STDOUT_FILENO, STDERR_FILENO) == -1)
+       {
          int err = errno;
-         pam_syslog (pamh, LOG_ERR, "dup failed: %m");
-          exit (err);
-        }
+         pam_syslog (pamh, LOG_ERR, "dup2 failed: %m");
+         _exit (err);
+       }
+
+      if (pam_modutil_sanitize_helper_fds(pamh, redirect_stdin,
+                                         redirect_stdout, redirect_stdout) < 0)
+       _exit(1);
 
       if (call_setuid)
        if (setuid (geteuid ()) == -1)
@@ -193,44 +385,83 @@ call_exec (pam_handle_t *pamh, int argc, const char **argv)
            int err = errno;
            pam_syslog (pamh, LOG_ERR, "setuid(%lu) failed: %m",
                        (unsigned long) geteuid ());
-           exit (err);
+           _exit (err);
          }
 
       if (setsid () == -1)
        {
          int err = errno;
          pam_syslog (pamh, LOG_ERR, "setsid failed: %m");
-         exit (err);
+         _exit (err);
        }
 
       arggv = calloc (argc + 4, sizeof (char *));
       if (arggv == NULL)
-       exit (ENOMEM);
+       _exit (ENOMEM);
 
       for (i = 0; i < (argc - optargc); i++)
        arggv[i] = strdup(argv[i+optargc]);
       arggv[i] = NULL;
 
+      /*
+       * Set up the child's environment list.  It consists of the PAM
+       * environment, plus a few hand-picked PAM items.
+       */
+      envlist = pam_getenvlist(pamh);
+      for (envlen = 0; envlist[envlen] != NULL; ++envlen)
+        /* nothing */ ;
+      nitems = sizeof(env_items) / sizeof(*env_items);
+      /* + 2 because of PAM_TYPE and NULL entry */
+      tmp = realloc(envlist, (envlen + nitems + 2) * sizeof(*envlist));
+      if (tmp == NULL)
+      {
+        free(envlist);
+        pam_syslog (pamh, LOG_ERR, "realloc environment failed: %m");
+        _exit (ENOMEM);
+      }
+      envlist = tmp;
+      for (i = 0; i < nitems; ++i)
+      {
+        const void *item;
+
+        if (pam_get_item(pamh, env_items[i].item, &item) != PAM_SUCCESS || item == NULL)
+          continue;
+        if (asprintf(&envstr, "%s=%s", env_items[i].name, (const char *)item) < 0)
+        {
+          free(envlist);
+          pam_syslog (pamh, LOG_ERR, "prepare environment failed: %m");
+          _exit (ENOMEM);
+        }
+        envlist[envlen++] = envstr;
+        envlist[envlen] = NULL;
+      }
+
+      if (asprintf(&envstr, "PAM_TYPE=%s", pam_type) < 0)
+        {
+          free(envlist);
+          pam_syslog (pamh, LOG_ERR, "prepare environment failed: %m");
+          _exit (ENOMEM);
+        }
+      envlist[envlen++] = envstr;
+      envlist[envlen] = NULL;
+
       if (debug)
        pam_syslog (pamh, LOG_DEBUG, "Calling %s ...", arggv[0]);
 
-      if (execv (arggv[0], arggv) == -1)
-       {
-         int err = errno;
-         pam_syslog (pamh, LOG_ERR, "execv(%s,...) failed: %m",
-                     arggv[0]);
-         exit (err);
-       }
-      exit (1); /* should never be reached. */
+      execve (arggv[0], arggv, envlist);
+      i = errno;
+      pam_syslog (pamh, LOG_ERR, "execve(%s,...) failed: %m", arggv[0]);
+      free(envlist);
+      _exit (i);
     }
-  return PAM_SYSTEM_ERR;
+  return PAM_SYSTEM_ERR; /* will never be reached. */
 }
 
 PAM_EXTERN int
 pam_sm_authenticate (pam_handle_t *pamh, int flags UNUSED,
                     int argc, const char **argv)
 {
-  return call_exec (pamh, argc, argv);
+  return call_exec ("auth", pamh, argc, argv);
 }
 
 PAM_EXTERN int
@@ -248,28 +479,28 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags,
 {
   if (flags & PAM_PRELIM_CHECK)
     return PAM_SUCCESS;
-  return call_exec (pamh, argc, argv);
+  return call_exec ("password", pamh, argc, argv);
 }
 
 PAM_EXTERN int
 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags UNUSED,
                 int argc, const char **argv)
 {
-  return call_exec (pamh, argc, argv);
+  return call_exec ("account", pamh, argc, argv);
 }
 
 PAM_EXTERN int
 pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED,
                    int argc, const char **argv)
 {
-  return call_exec (pamh, argc, argv);
+  return call_exec ("open_session", pamh, argc, argv);
 }
 
 PAM_EXTERN int
 pam_sm_close_session(pam_handle_t *pamh, int flags UNUSED,
                     int argc, const char **argv)
 {
-  return call_exec (pamh, argc, argv);
+  return call_exec ("close_session", pamh, argc, argv);
 }
 
 #ifdef PAM_STATIC