(root)/
Linux-PAM-1.5.3/
modules/
pam_pwhistory/
pam_pwhistory.c
       1  /*
       2   * pam_pwhistory module
       3   *
       4   * Copyright (c) 2008, 2012 Thorsten Kukuk
       5   * Author: Thorsten Kukuk <kukuk@thkukuk.de>
       6   * Copyright (c) 2013 Red Hat, Inc.
       7   *
       8   * Redistribution and use in source and binary forms, with or without
       9   * modification, are permitted provided that the following conditions
      10   * are met:
      11   * 1. Redistributions of source code must retain the above copyright
      12   *    notice, and the entire permission notice in its entirety,
      13   *    including the disclaimer of warranties.
      14   * 2. Redistributions in binary form must reproduce the above copyright
      15   *    notice, this list of conditions and the following disclaimer in the
      16   *    documentation and/or other materials provided with the distribution.
      17   * 3. The name of the author may not be used to endorse or promote
      18   *    products derived from this software without specific prior
      19   *    written permission.
      20   *
      21   * ALTERNATIVELY, this product may be distributed under the terms of
      22   * the GNU Public License, in which case the provisions of the GPL are
      23   * required INSTEAD OF the above restrictions.  (This clause is
      24   * necessary due to a potential bad interaction between the GPL and
      25   * the restrictions contained in a BSD-style copyright.)
      26   *
      27   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
      28   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
      29   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
      30   * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
      31   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
      32   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      33   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
      34   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
      35   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
      36   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
      37   * OF THE POSSIBILITY OF SUCH DAMAGE.
      38   */
      39  
      40  #if defined(HAVE_CONFIG_H)
      41  #include <config.h>
      42  #endif
      43  
      44  #include <pwd.h>
      45  #include <errno.h>
      46  #include <stdio.h>
      47  #include <stdlib.h>
      48  #include <string.h>
      49  #include <unistd.h>
      50  #include <syslog.h>
      51  #include <sys/types.h>
      52  #include <sys/stat.h>
      53  #include <sys/time.h>
      54  #include <sys/resource.h>
      55  #include <sys/wait.h>
      56  #include <signal.h>
      57  #include <fcntl.h>
      58  
      59  #include <security/pam_modules.h>
      60  #include <security/pam_modutil.h>
      61  #include <security/pam_ext.h>
      62  #include <security/_pam_macros.h>
      63  
      64  #include "opasswd.h"
      65  #include "pam_inline.h"
      66  #include "pwhistory_config.h"
      67  
      68  
      69  
      70  static void
      71  parse_option (pam_handle_t *pamh, const char *argv, options_t *options)
      72  {
      73    const char *str;
      74  
      75    if (strcasecmp (argv, "try_first_pass") == 0)
      76      /* ignore */;
      77    else if (strcasecmp (argv, "use_first_pass") == 0)
      78      /* ignore */;
      79    else if (strcasecmp (argv, "use_authtok") == 0)
      80      /* ignore, handled by pam_get_authtok */;
      81    else if (strcasecmp (argv, "debug") == 0)
      82      options->debug = 1;
      83    else if ((str = pam_str_skip_icase_prefix(argv, "remember=")) != NULL)
      84      {
      85        options->remember = strtol(str, NULL, 10);
      86        if (options->remember < 0)
      87          options->remember = 0;
      88        if (options->remember > 400)
      89          options->remember = 400;
      90      }
      91    else if ((str = pam_str_skip_icase_prefix(argv, "retry=")) != NULL)
      92      {
      93        options->tries = strtol(str, NULL, 10);
      94        if (options->tries < 0)
      95          options->tries = 1;
      96      }
      97    else if (strcasecmp (argv, "enforce_for_root") == 0)
      98      options->enforce_for_root = 1;
      99    else if (pam_str_skip_icase_prefix(argv, "authtok_type=") != NULL)
     100      { /* ignore, for pam_get_authtok */; }
     101    else if ((str = pam_str_skip_icase_prefix(argv, "file=")) != NULL)
     102      {
     103        if (*str != '/')
     104          {
     105            pam_syslog (pamh, LOG_ERR,
     106                        "pam_pwhistory: file path should be absolute: %s", argv);
     107          }
     108        else
     109          options->filename = str;
     110      }
     111    else
     112      pam_syslog (pamh, LOG_ERR, "pam_pwhistory: unknown option: %s", argv);
     113  }
     114  
     115  static int
     116  run_save_helper(pam_handle_t *pamh, const char *user,
     117  		int howmany, const char *filename, int debug)
     118  {
     119    int retval, child;
     120    struct sigaction newsa, oldsa;
     121  
     122    memset(&newsa, '\0', sizeof(newsa));
     123    newsa.sa_handler = SIG_DFL;
     124    sigaction(SIGCHLD, &newsa, &oldsa);
     125  
     126    child = fork();
     127    if (child == 0)
     128      {
     129        static char *envp[] = { NULL };
     130        char *args[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
     131  
     132        if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_PIPE_FD,
     133            PAM_MODUTIL_PIPE_FD,
     134            PAM_MODUTIL_PIPE_FD) < 0)
     135          {
     136            _exit(PAM_SYSTEM_ERR);
     137          }
     138  
     139        /* exec binary helper */
     140        DIAG_PUSH_IGNORE_CAST_QUAL;
     141        args[0] = (char *)PWHISTORY_HELPER;
     142        args[1] = (char *)"save";
     143        args[2] = (char *)user;
     144        args[3] = (char *)filename;
     145        DIAG_POP_IGNORE_CAST_QUAL;
     146        if (asprintf(&args[4], "%d", howmany) < 0 ||
     147            asprintf(&args[5], "%d", debug) < 0)
     148          {
     149            pam_syslog(pamh, LOG_ERR, "asprintf: %m");
     150            _exit(PAM_SYSTEM_ERR);
     151          }
     152  
     153        execve(args[0], args, envp);
     154  
     155        pam_syslog(pamh, LOG_ERR, "helper binary execve failed: %s: %m", args[0]);
     156  
     157        _exit(PAM_SYSTEM_ERR);
     158      }
     159    else if (child > 0)
     160      {
     161        /* wait for child */
     162        int rc = 0;
     163        while ((rc = waitpid (child, &retval, 0)) == -1 &&
     164                errno == EINTR);
     165        if (rc < 0)
     166          {
     167            pam_syslog(pamh, LOG_ERR, "pwhistory_helper save: waitpid: %m");
     168            retval = PAM_SYSTEM_ERR;
     169          }
     170        else if (!WIFEXITED(retval))
     171          {
     172            pam_syslog(pamh, LOG_ERR, "pwhistory_helper save abnormal exit: %d", retval);
     173            retval = PAM_SYSTEM_ERR;
     174          }
     175        else
     176          {
     177            retval = WEXITSTATUS(retval);
     178          }
     179      }
     180    else
     181      {
     182        pam_syslog(pamh, LOG_ERR, "fork failed: %m");
     183        retval = PAM_SYSTEM_ERR;
     184      }
     185  
     186    sigaction(SIGCHLD, &oldsa, NULL);   /* restore old signal handler */
     187  
     188    return retval;
     189  }
     190  
     191  static int
     192  run_check_helper(pam_handle_t *pamh, const char *user,
     193  		 const char *newpass, const char *filename, int debug)
     194  {
     195    int retval, child, fds[2];
     196    struct sigaction newsa, oldsa;
     197  
     198    /* create a pipe for the password */
     199    if (pipe(fds) != 0)
     200      return PAM_SYSTEM_ERR;
     201  
     202    memset(&newsa, '\0', sizeof(newsa));
     203    newsa.sa_handler = SIG_DFL;
     204    sigaction(SIGCHLD, &newsa, &oldsa);
     205  
     206    child = fork();
     207    if (child == 0)
     208      {
     209        static char *envp[] = { NULL };
     210        char *args[] = { NULL, NULL, NULL, NULL, NULL, NULL };
     211  
     212        /* reopen stdin as pipe */
     213        if (dup2(fds[0], STDIN_FILENO) != STDIN_FILENO)
     214          {
     215            pam_syslog(pamh, LOG_ERR, "dup2 of %s failed: %m", "stdin");
     216            _exit(PAM_SYSTEM_ERR);
     217          }
     218  
     219        if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_IGNORE_FD,
     220            PAM_MODUTIL_PIPE_FD,
     221            PAM_MODUTIL_PIPE_FD) < 0)
     222          {
     223            _exit(PAM_SYSTEM_ERR);
     224          }
     225  
     226        /* exec binary helper */
     227        DIAG_PUSH_IGNORE_CAST_QUAL;
     228        args[0] = (char *)PWHISTORY_HELPER;
     229        args[1] = (char *)"check";
     230        args[2] = (char *)user;
     231        args[3] = (char *)filename;
     232        DIAG_POP_IGNORE_CAST_QUAL;
     233        if (asprintf(&args[4], "%d", debug) < 0)
     234          {
     235            pam_syslog(pamh, LOG_ERR, "asprintf: %m");
     236            _exit(PAM_SYSTEM_ERR);
     237          }
     238  
     239        execve(args[0], args, envp);
     240  
     241        pam_syslog(pamh, LOG_ERR, "helper binary execve failed: %s: %m", args[0]);
     242  
     243        _exit(PAM_SYSTEM_ERR);
     244      }
     245    else if (child > 0)
     246      {
     247        /* wait for child */
     248        int rc = 0;
     249        if (newpass == NULL)
     250          newpass = "";
     251  
     252        /* send the password to the child */
     253        if (write(fds[1], newpass, strlen(newpass)+1) == -1)
     254          {
     255            pam_syslog(pamh, LOG_ERR, "Cannot send password to helper: %m");
     256            retval = PAM_SYSTEM_ERR;
     257          }
     258        newpass = NULL;
     259        close(fds[0]);       /* close here to avoid possible SIGPIPE above */
     260        close(fds[1]);
     261        while ((rc = waitpid (child, &retval, 0)) == -1 &&
     262                errno == EINTR);
     263        if (rc < 0)
     264          {
     265            pam_syslog(pamh, LOG_ERR, "pwhistory_helper check: waitpid: %m");
     266            retval = PAM_SYSTEM_ERR;
     267          }
     268        else if (!WIFEXITED(retval))
     269          {
     270            pam_syslog(pamh, LOG_ERR, "pwhistory_helper check abnormal exit: %d", retval);
     271            retval = PAM_SYSTEM_ERR;
     272          }
     273        else
     274          {
     275            retval = WEXITSTATUS(retval);
     276          }
     277      }
     278    else
     279      {
     280        pam_syslog(pamh, LOG_ERR, "fork failed: %m");
     281        close(fds[0]);
     282        close(fds[1]);
     283        retval = PAM_SYSTEM_ERR;
     284      }
     285  
     286    sigaction(SIGCHLD, &oldsa, NULL);   /* restore old signal handler */
     287  
     288    return retval;
     289  }
     290  
     291  /* This module saves the current hashed password in /etc/security/opasswd
     292     and then compares the new password with all entries in this file. */
     293  
     294  int
     295  pam_sm_chauthtok (pam_handle_t *pamh, int flags, int argc, const char **argv)
     296  {
     297    const char *newpass;
     298    const char *user;
     299      int retval, tries;
     300    options_t options;
     301  
     302    memset (&options, 0, sizeof (options));
     303  
     304    /* Set some default values, which could be overwritten later.  */
     305    options.remember = 10;
     306    options.tries = 1;
     307  
     308    parse_config_file(pamh, argc, argv, &options);
     309  
     310    /* Parse parameters for module */
     311    for ( ; argc-- > 0; argv++)
     312      parse_option (pamh, *argv, &options);
     313  
     314    if (options.debug)
     315      pam_syslog (pamh, LOG_DEBUG, "pam_sm_chauthtok entered");
     316  
     317    if (options.remember == 0)
     318      return PAM_IGNORE;
     319  
     320    retval = pam_get_user (pamh, &user, NULL);
     321    if (retval != PAM_SUCCESS)
     322      return retval;
     323  
     324    if (flags & PAM_PRELIM_CHECK)
     325      {
     326        if (options.debug)
     327  	pam_syslog (pamh, LOG_DEBUG,
     328  		    "pam_sm_chauthtok(PAM_PRELIM_CHECK)");
     329  
     330        return PAM_SUCCESS;
     331      }
     332  
     333    retval = save_old_pass (pamh, user, options.remember, options.filename, options.debug);
     334  
     335    if (retval == PAM_PWHISTORY_RUN_HELPER)
     336        retval = run_save_helper(pamh, user, options.remember, options.filename, options.debug);
     337  
     338    if (retval != PAM_SUCCESS)
     339      return retval;
     340  
     341    newpass = NULL;
     342    tries = 0;
     343    while ((newpass == NULL) && (tries < options.tries))
     344      {
     345        retval = pam_get_authtok (pamh, PAM_AUTHTOK, &newpass, NULL);
     346        if (retval != PAM_SUCCESS && retval != PAM_TRY_AGAIN)
     347  	{
     348  	  if (retval == PAM_CONV_AGAIN)
     349  	    retval = PAM_INCOMPLETE;
     350  	  return retval;
     351  	}
     352        tries++;
     353  
     354        if (options.debug)
     355  	{
     356  	  if (newpass)
     357  	    pam_syslog (pamh, LOG_DEBUG, "got new auth token");
     358  	  else
     359  	    pam_syslog (pamh, LOG_DEBUG, "got no auth token");
     360  	}
     361  
     362        if (newpass == NULL || retval == PAM_TRY_AGAIN)
     363  	continue;
     364  
     365        if (options.debug)
     366  	pam_syslog (pamh, LOG_DEBUG, "check against old password file");
     367  
     368        retval = check_old_pass (pamh, user, newpass, options.filename, options.debug);
     369        if (retval == PAM_PWHISTORY_RUN_HELPER)
     370  	  retval = run_check_helper(pamh, user, newpass, options.filename, options.debug);
     371  
     372        if (retval != PAM_SUCCESS)
     373  	{
     374  	  if (getuid() || options.enforce_for_root ||
     375  	      (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
     376  	    {
     377  	      pam_error (pamh,
     378  		         _("Password has been already used. Choose another."));
     379  	      newpass = NULL;
     380  	      /* Remove password item, else following module will use it */
     381  	      pam_set_item (pamh, PAM_AUTHTOK, (void *) NULL);
     382  	    }
     383  	  else
     384  	    pam_info (pamh,
     385  		       _("Password has been already used."));
     386  	}
     387      }
     388  
     389    if (newpass == NULL && tries >= options.tries)
     390      {
     391        if (options.debug)
     392  	pam_syslog (pamh, LOG_DEBUG, "Aborted, too many tries");
     393        return PAM_MAXTRIES;
     394      }
     395  
     396    return PAM_SUCCESS;
     397  }