(root)/
Linux-PAM-1.5.3/
modules/
pam_exec/
pam_exec.c
       1  /*
       2   * Copyright (c) 2006, 2008 Thorsten Kukuk <kukuk@thkukuk.de>
       3   *
       4   * Redistribution and use in source and binary forms, with or without
       5   * modification, are permitted provided that the following conditions
       6   * are met:
       7   * 1. Redistributions of source code must retain the above copyright
       8   *    notice, and the entire permission notice in its entirety,
       9   *    including the disclaimer of warranties.
      10   * 2. Redistributions in binary form must reproduce the above copyright
      11   *    notice, this list of conditions and the following disclaimer in the
      12   *    documentation and/or other materials provided with the distribution.
      13   * 3. The name of the author may not be used to endorse or promote
      14   *    products derived from this software without specific prior
      15   *    written permission.
      16   *
      17   * ALTERNATIVELY, this product may be distributed under the terms of
      18   * the GNU Public License, in which case the provisions of the GPL are
      19   * required INSTEAD OF the above restrictions.  (This clause is
      20   * necessary due to a potential bad interaction between the GPL and
      21   * the restrictions contained in a BSD-style copyright.)
      22   *
      23   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
      24   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
      25   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
      26   * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
      27   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
      28   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      29   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
      30   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
      31   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
      32   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
      33   * OF THE POSSIBILITY OF SUCH DAMAGE.
      34   */
      35  
      36  #if defined(HAVE_CONFIG_H)
      37  #include "config.h"
      38  #endif
      39  
      40  #include <time.h>
      41  #include <errno.h>
      42  #include <fcntl.h>
      43  #include <stdio.h>
      44  #include <string.h>
      45  #include <syslog.h>
      46  #include <unistd.h>
      47  #include <stdlib.h>
      48  #include <sys/wait.h>
      49  #include <sys/stat.h>
      50  #include <sys/types.h>
      51  #include <signal.h>
      52  
      53  #include <security/pam_modules.h>
      54  #include <security/pam_modutil.h>
      55  #include <security/pam_ext.h>
      56  #include <security/_pam_macros.h>
      57  #include "pam_inline.h"
      58  
      59  #define ENV_ITEM(n) { (n), #n }
      60  static struct {
      61    int item;
      62    const char *name;
      63  } env_items[] = {
      64    ENV_ITEM(PAM_SERVICE),
      65    ENV_ITEM(PAM_USER),
      66    ENV_ITEM(PAM_TTY),
      67    ENV_ITEM(PAM_RHOST),
      68    ENV_ITEM(PAM_RUSER),
      69  };
      70  
      71  /* move_fd_to_non_stdio copies the given file descriptor to something other
      72   * than stdin, stdout, or stderr.  Assumes that the caller will close all
      73   * unwanted fds after calling. */
      74  static int
      75  move_fd_to_non_stdio (pam_handle_t *pamh, int fd)
      76  {
      77    while (fd < 3)
      78      {
      79        fd = dup(fd);
      80        if (fd == -1)
      81  	{
      82  	  int err = errno;
      83  	  pam_syslog (pamh, LOG_ERR, "dup failed: %m");
      84  	  _exit (err);
      85  	}
      86      }
      87    return fd;
      88  }
      89  
      90  static int
      91  call_exec (const char *pam_type, pam_handle_t *pamh,
      92  	   int argc, const char **argv)
      93  {
      94    int debug = 0;
      95    int call_setuid = 0;
      96    int quiet = 0;
      97    int quiet_log = 0;
      98    int expose_authtok = 0;
      99    int use_stdout = 0;
     100    int optargc;
     101    const char *logfile = NULL;
     102    char authtok[PAM_MAX_RESP_SIZE] = {};
     103    pid_t pid;
     104    int fds[2];
     105    int stdout_fds[2];
     106    FILE *stdout_file = NULL;
     107    int retval;
     108    const char *name;
     109    struct sigaction newsa, oldsa;
     110  
     111    if (argc < 1) {
     112      pam_syslog (pamh, LOG_ERR,
     113  		"This module needs at least one argument");
     114      return PAM_SERVICE_ERR;
     115    }
     116  
     117    for (optargc = 0; optargc < argc; optargc++)
     118      {
     119        const char *str;
     120  
     121        if (argv[optargc][0] == '/') /* paths starts with / */
     122  	break;
     123  
     124        if (strcasecmp (argv[optargc], "debug") == 0)
     125  	debug = 1;
     126        else if (strcasecmp (argv[optargc], "stdout") == 0)
     127  	use_stdout = 1;
     128        else if ((str = pam_str_skip_icase_prefix (argv[optargc], "log=")) != NULL)
     129  	logfile = str;
     130        else if ((str = pam_str_skip_icase_prefix (argv[optargc], "type=")) != NULL)
     131  	{
     132  	  if (strcmp (pam_type, str) != 0)
     133  	    return PAM_IGNORE;
     134  	}
     135        else if (strcasecmp (argv[optargc], "seteuid") == 0)
     136  	call_setuid = 1;
     137        else if (strcasecmp (argv[optargc], "quiet") == 0)
     138  	quiet = 1;
     139        else if (strcasecmp (argv[optargc], "quiet_log") == 0)
     140  	quiet_log = 1;
     141        else if (strcasecmp (argv[optargc], "expose_authtok") == 0)
     142  	expose_authtok = 1;
     143        else
     144  	break; /* Unknown option, assume program to execute. */
     145      }
     146  
     147    /* Request user name to be available. */
     148  
     149    retval = pam_get_user(pamh, &name, NULL);
     150    if (retval != PAM_SUCCESS)
     151      {
     152        if (retval == PAM_CONV_AGAIN)
     153          retval = PAM_INCOMPLETE;
     154        return retval;
     155      }
     156  
     157    if (expose_authtok == 1)
     158      {
     159        if (strcmp (pam_type, "auth") != 0)
     160  	{
     161  	  pam_syslog (pamh, LOG_ERR,
     162  		      "expose_authtok not supported for type %s", pam_type);
     163  	  expose_authtok = 0;
     164  	}
     165        else
     166  	{
     167  	  const void *void_pass;
     168  
     169  	  retval = pam_get_item (pamh, PAM_AUTHTOK, &void_pass);
     170  	  if (retval != PAM_SUCCESS)
     171  	    {
     172  	      if (debug)
     173  		pam_syslog (pamh, LOG_DEBUG,
     174  			    "pam_get_item (PAM_AUTHTOK) failed, return %d",
     175  			    retval);
     176  	      return retval;
     177  	    }
     178  	  else if (void_pass == NULL)
     179  	    {
     180  	      char *resp = NULL;
     181  
     182  	      retval = pam_prompt (pamh, PAM_PROMPT_ECHO_OFF,
     183  				   &resp, _("Password: "));
     184  
     185  	      if (retval != PAM_SUCCESS)
     186  		{
     187  		  pam_overwrite_string (resp);
     188  		  _pam_drop (resp);
     189  		  if (retval == PAM_CONV_AGAIN)
     190  		    retval = PAM_INCOMPLETE;
     191  		  return retval;
     192  		}
     193  
     194  	      if (resp)
     195  		{
     196  		  pam_set_item (pamh, PAM_AUTHTOK, resp);
     197  		  strncpy (authtok, resp, sizeof(authtok) - 1);
     198  		  pam_overwrite_string (resp);
     199  		  _pam_drop (resp);
     200  		}
     201  	    }
     202  	  else
     203  	    strncpy (authtok, void_pass, sizeof(authtok) - 1);
     204  
     205  	  if (pipe(fds) != 0)
     206  	    {
     207  	      pam_overwrite_array(authtok);
     208  	      pam_syslog (pamh, LOG_ERR, "Could not create pipe: %m");
     209  	      return PAM_SYSTEM_ERR;
     210  	    }
     211  	}
     212      }
     213  
     214    if (use_stdout)
     215      {
     216        if (pipe(stdout_fds) != 0)
     217  	{
     218  	  pam_overwrite_array(authtok);
     219  	  pam_syslog (pamh, LOG_ERR, "Could not create pipe: %m");
     220  	  return PAM_SYSTEM_ERR;
     221  	}
     222        stdout_file = fdopen(stdout_fds[0], "r");
     223        if (!stdout_file)
     224  	{
     225  	  pam_overwrite_array(authtok);
     226  	  pam_syslog (pamh, LOG_ERR, "Could not fdopen pipe: %m");
     227  	  return PAM_SYSTEM_ERR;
     228  	}
     229      }
     230  
     231    if (optargc >= argc) {
     232      pam_overwrite_array(authtok);
     233      pam_syslog (pamh, LOG_ERR, "No path given as argument");
     234      return PAM_SERVICE_ERR;
     235    }
     236  
     237    memset(&newsa, '\0', sizeof(newsa));
     238    newsa.sa_handler = SIG_DFL;
     239    if (sigaction(SIGCHLD, &newsa, &oldsa) == -1) {
     240      pam_overwrite_array(authtok);
     241      pam_syslog(pamh, LOG_ERR, "failed to reset SIGCHLD handler: %m");
     242      return PAM_SYSTEM_ERR;
     243    }
     244  
     245    pid = fork();
     246    if (pid == -1) {
     247      pam_overwrite_array(authtok);
     248      return PAM_SYSTEM_ERR;
     249    }
     250    if (pid > 0) /* parent */
     251      {
     252        int status = 0;
     253        pid_t rc;
     254  
     255        if (expose_authtok) /* send the password to the child */
     256  	{
     257  	  if (debug)
     258  	    pam_syslog (pamh, LOG_DEBUG, "send password to child");
     259  	  if (write(fds[1], authtok, strlen(authtok)) == -1)
     260  	    pam_syslog (pamh, LOG_ERR,
     261  			      "sending password to child failed: %m");
     262  
     263            close(fds[0]);       /* close here to avoid possible SIGPIPE above */
     264            close(fds[1]);
     265  	}
     266  
     267        pam_overwrite_array(authtok);
     268  
     269        if (use_stdout)
     270  	{
     271  	  char buf[4096];
     272  	  close(stdout_fds[1]);
     273  	  while (fgets(buf, sizeof(buf), stdout_file) != NULL)
     274  	    {
     275  	      size_t len;
     276  	      len = strlen(buf);
     277  	      if (buf[len-1] == '\n')
     278  		buf[len-1] = '\0';
     279  	      pam_info(pamh, "%s", buf);
     280  	    }
     281  	  fclose(stdout_file);
     282  	}
     283  
     284        while ((rc = waitpid (pid, &status, 0)) == -1 &&
     285  	     errno == EINTR);
     286        sigaction(SIGCHLD, &oldsa, NULL);   /* restore old signal handler */
     287        if (rc == (pid_t)-1)
     288  	{
     289  	  pam_syslog (pamh, LOG_ERR, "waitpid returns with -1: %m");
     290  	  return PAM_SYSTEM_ERR;
     291  	}
     292        else if (status != 0)
     293  	{
     294  	  if (WIFEXITED(status))
     295  	    {
     296  		if (!quiet_log)
     297  	      pam_syslog (pamh, LOG_ERR, "%s failed: exit code %d",
     298  			  argv[optargc], WEXITSTATUS(status));
     299  		if (!quiet)
     300  	      pam_error (pamh, _("%s failed: exit code %d"),
     301  			 argv[optargc], WEXITSTATUS(status));
     302  	    }
     303  	  else if (WIFSIGNALED(status))
     304  	    {
     305  		if (!quiet_log)
     306  	      pam_syslog (pamh, LOG_ERR, "%s failed: caught signal %d%s",
     307  			  argv[optargc], WTERMSIG(status),
     308  			  WCOREDUMP(status) ? " (core dumped)" : "");
     309  		if (!quiet)
     310  	      pam_error (pamh, _("%s failed: caught signal %d%s"),
     311  			 argv[optargc], WTERMSIG(status),
     312  			 WCOREDUMP(status) ? " (core dumped)" : "");
     313  	    }
     314  	  else
     315  	    {
     316  		if (!quiet_log)
     317  	      pam_syslog (pamh, LOG_ERR, "%s failed: unknown status 0x%x",
     318  			  argv[optargc], status);
     319  		if (!quiet)
     320  	      pam_error (pamh, _("%s failed: unknown status 0x%x"),
     321  			 argv[optargc], status);
     322  	    }
     323  	  return PAM_SYSTEM_ERR;
     324  	}
     325        return PAM_SUCCESS;
     326      }
     327    else /* child */
     328      {
     329        const char **arggv;
     330        int i;
     331        char **envlist;
     332        int envlen, nitems;
     333        char *envstr;
     334        enum pam_modutil_redirect_fd redirect_stdin =
     335  	      expose_authtok ? PAM_MODUTIL_IGNORE_FD : PAM_MODUTIL_PIPE_FD;
     336        enum pam_modutil_redirect_fd redirect_stdout =
     337  	      (use_stdout || logfile) ? PAM_MODUTIL_IGNORE_FD : PAM_MODUTIL_NULL_FD;
     338  
     339        pam_overwrite_array(authtok);
     340  
     341        /* First, move all the pipes off of stdin, stdout, and stderr, to ensure
     342         * that calls to dup2 won't close them. */
     343  
     344        if (expose_authtok)
     345  	{
     346  	  fds[0] = move_fd_to_non_stdio(pamh, fds[0]);
     347  	  close(fds[1]);
     348  	}
     349  
     350        if (use_stdout)
     351  	{
     352  	  stdout_fds[1] = move_fd_to_non_stdio(pamh, stdout_fds[1]);
     353  	  close(stdout_fds[0]);
     354  	}
     355  
     356        /* Set up stdin. */
     357  
     358        if (expose_authtok)
     359  	{
     360  	  /* reopen stdin as pipe */
     361  	  if (dup2(fds[0], STDIN_FILENO) == -1)
     362  	    {
     363  	      int err = errno;
     364  	      pam_syslog (pamh, LOG_ERR, "dup2 of STDIN failed: %m");
     365  	      _exit (err);
     366  	    }
     367  	}
     368  
     369        /* Set up stdout. */
     370  
     371        if (use_stdout)
     372  	{
     373  	  if (dup2(stdout_fds[1], STDOUT_FILENO) == -1)
     374  	    {
     375  	      int err = errno;
     376  	      pam_syslog (pamh, LOG_ERR, "dup2 to stdout failed: %m");
     377  	      _exit (err);
     378  	    }
     379  	}
     380        else if (logfile)
     381  	{
     382  	  time_t tm = time (NULL);
     383  	  char *buffer = NULL;
     384  
     385  	  close (STDOUT_FILENO);
     386  	  if ((i = open (logfile, O_CREAT|O_APPEND|O_WRONLY,
     387  			 S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1)
     388  	    {
     389  	      int err = errno;
     390  	      pam_syslog (pamh, LOG_ERR, "open of %s failed: %m",
     391  			  logfile);
     392  	      _exit (err);
     393  	    }
     394  	  if (i != STDOUT_FILENO)
     395  	    {
     396  	      if (dup2 (i, STDOUT_FILENO) == -1)
     397  		{
     398  		  int err = errno;
     399  		  pam_syslog (pamh, LOG_ERR, "dup2 failed: %m");
     400  		  _exit (err);
     401  		}
     402  	      close (i);
     403  	    }
     404  	  if (asprintf (&buffer, "*** %s", ctime (&tm)) > 0)
     405  	    {
     406  	      pam_modutil_write (STDOUT_FILENO, buffer, strlen (buffer));
     407  	      free (buffer);
     408  	    }
     409  	}
     410  
     411        if ((use_stdout || logfile) &&
     412  	  dup2 (STDOUT_FILENO, STDERR_FILENO) == -1)
     413  	{
     414  	  int err = errno;
     415  	  pam_syslog (pamh, LOG_ERR, "dup2 failed: %m");
     416  	  _exit (err);
     417  	}
     418  
     419        if (pam_modutil_sanitize_helper_fds(pamh, redirect_stdin,
     420  					  redirect_stdout, redirect_stdout) < 0)
     421  	_exit(1);
     422  
     423        if (call_setuid)
     424  	if (setuid (geteuid ()) == -1)
     425  	  {
     426  	    int err = errno;
     427  	    pam_syslog (pamh, LOG_ERR, "setuid(%lu) failed: %m",
     428  			(unsigned long) geteuid ());
     429  	    _exit (err);
     430  	  }
     431  
     432        if (setsid () == -1)
     433  	{
     434  	  int err = errno;
     435  	  pam_syslog (pamh, LOG_ERR, "setsid failed: %m");
     436  	  _exit (err);
     437  	}
     438  
     439        arggv = calloc (argc + 4, sizeof (char *));
     440        if (arggv == NULL)
     441  	_exit (ENOMEM);
     442  
     443        for (i = 0; i < (argc - optargc); i++)
     444          arggv[i] = argv[i+optargc];
     445        arggv[i] = NULL;
     446  
     447        /*
     448         * Set up the child's environment list.  It consists of the PAM
     449         * environment, plus a few hand-picked PAM items.
     450         */
     451        envlist = pam_getenvlist(pamh);
     452        for (envlen = 0; envlist[envlen] != NULL; ++envlen)
     453          /* nothing */ ;
     454        nitems = PAM_ARRAY_SIZE(env_items);
     455        /* + 2 because of PAM_TYPE and NULL entry */
     456        envlist = realloc(envlist, (envlen + nitems + 2) * sizeof(*envlist));
     457        if (envlist == NULL)
     458        {
     459          pam_syslog (pamh, LOG_CRIT, "realloc environment failed: %m");
     460          _exit (ENOMEM);
     461        }
     462        for (i = 0; i < nitems; ++i)
     463        {
     464          const void *item;
     465  
     466          if (pam_get_item(pamh, env_items[i].item, &item) != PAM_SUCCESS || item == NULL)
     467            continue;
     468          if (asprintf(&envstr, "%s=%s", env_items[i].name, (const char *)item) < 0)
     469          {
     470            pam_syslog (pamh, LOG_CRIT, "prepare environment failed: %m");
     471            _exit (ENOMEM);
     472          }
     473          envlist[envlen++] = envstr;
     474          envlist[envlen] = NULL;
     475        }
     476  
     477        if (asprintf(&envstr, "PAM_TYPE=%s", pam_type) < 0)
     478          {
     479            pam_syslog (pamh, LOG_CRIT, "prepare environment failed: %m");
     480            _exit (ENOMEM);
     481          }
     482        envlist[envlen++] = envstr;
     483        envlist[envlen] = NULL;
     484  
     485        if (debug)
     486  	pam_syslog (pamh, LOG_DEBUG, "Calling %s ...", arggv[0]);
     487  
     488        DIAG_PUSH_IGNORE_CAST_QUAL;
     489        execve (arggv[0], (char **) arggv, envlist);
     490        DIAG_POP_IGNORE_CAST_QUAL;
     491        i = errno;
     492        pam_syslog (pamh, LOG_ERR, "execve(%s,...) failed: %m", arggv[0]);
     493        _exit (i);
     494      }
     495    return PAM_SYSTEM_ERR; /* will never be reached. */
     496  }
     497  
     498  int
     499  pam_sm_authenticate (pam_handle_t *pamh, int flags UNUSED,
     500  		     int argc, const char **argv)
     501  {
     502    return call_exec ("auth", pamh, argc, argv);
     503  }
     504  
     505  int
     506  pam_sm_setcred (pam_handle_t *pamh UNUSED, int flags UNUSED,
     507  		int argc UNUSED, const char **argv UNUSED)
     508  {
     509    return PAM_IGNORE;
     510  }
     511  
     512  /* password updating functions */
     513  
     514  int
     515  pam_sm_chauthtok(pam_handle_t *pamh, int flags,
     516  		 int argc, const char **argv)
     517  {
     518    if (flags & PAM_PRELIM_CHECK)
     519      return PAM_SUCCESS;
     520    return call_exec ("password", pamh, argc, argv);
     521  }
     522  
     523  int
     524  pam_sm_acct_mgmt(pam_handle_t *pamh, int flags UNUSED,
     525  		 int argc, const char **argv)
     526  {
     527    return call_exec ("account", pamh, argc, argv);
     528  }
     529  
     530  int
     531  pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED,
     532  		    int argc, const char **argv)
     533  {
     534    return call_exec ("open_session", pamh, argc, argv);
     535  }
     536  
     537  int
     538  pam_sm_close_session(pam_handle_t *pamh, int flags UNUSED,
     539  		     int argc, const char **argv)
     540  {
     541    return call_exec ("close_session", pamh, argc, argv);
     542  }