(root)/
Linux-PAM-1.5.3/
modules/
pam_pwhistory/
opasswd.c
       1  /*
       2   * Copyright (c) 2008 Thorsten Kukuk <kukuk@suse.de>
       3   * Copyright (c) 2013 Red Hat, Inc.
       4   *
       5   * Redistribution and use in source and binary forms, with or without
       6   * modification, are permitted provided that the following conditions
       7   * are met:
       8   * 1. Redistributions of source code must retain the above copyright
       9   *    notice, and the entire permission notice in its entirety,
      10   *    including the disclaimer of warranties.
      11   * 2. Redistributions in binary form must reproduce the above copyright
      12   *    notice, this list of conditions and the following disclaimer in the
      13   *    documentation and/or other materials provided with the distribution.
      14   * 3. The name of the author may not be used to endorse or promote
      15   *    products derived from this software without specific prior
      16   *    written permission.
      17   *
      18   * ALTERNATIVELY, this product may be distributed under the terms of
      19   * the GNU Public License, in which case the provisions of the GPL are
      20   * required INSTEAD OF the above restrictions.  (This clause is
      21   * necessary due to a potential bad interaction between the GPL and
      22   * the restrictions contained in a BSD-style copyright.)
      23   *
      24   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
      25   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
      26   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
      27   * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
      28   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
      29   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      30   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
      31   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
      32   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
      33   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
      34   * OF THE POSSIBILITY OF SUCH DAMAGE.
      35   */
      36  
      37  #if defined(HAVE_CONFIG_H)
      38  #include <config.h>
      39  #endif
      40  
      41  #include <pwd.h>
      42  #include <shadow.h>
      43  #include <time.h>
      44  #include <ctype.h>
      45  #include <errno.h>
      46  #include <fcntl.h>
      47  #include <limits.h>
      48  #include <stdio.h>
      49  #include <unistd.h>
      50  #include <string.h>
      51  #include <stdlib.h>
      52  #include <syslog.h>
      53  #ifdef HELPER_COMPILE
      54  #include <stdarg.h>
      55  #endif
      56  #include <sys/stat.h>
      57  
      58  #ifdef HAVE_CRYPT_H
      59  #include <crypt.h>
      60  #endif
      61  
      62  #ifdef HELPER_COMPILE
      63  #define pam_modutil_getpwnam(h,n) getpwnam(n)
      64  #define pam_modutil_getspnam(h,n) getspnam(n)
      65  #define pam_syslog(h,a,...) helper_log_err(a,__VA_ARGS__)
      66  #else
      67  #include <security/pam_modutil.h>
      68  #include <security/pam_ext.h>
      69  #endif
      70  #include <security/pam_modules.h>
      71  #include "pam_inline.h"
      72  
      73  #include "opasswd.h"
      74  
      75  #ifndef RANDOM_DEVICE
      76  #define RANDOM_DEVICE "/dev/urandom"
      77  #endif
      78  
      79  #define DEFAULT_OLD_PASSWORDS_FILE SCONFIGDIR "/opasswd"
      80  
      81  #define DEFAULT_BUFLEN 4096
      82  
      83  typedef struct {
      84    char *user;
      85    char *uid;
      86    int count;
      87    char *old_passwords;
      88  } opwd;
      89  
      90  #ifdef HELPER_COMPILE
      91  PAM_FORMAT((printf, 2, 3))
      92  void
      93  helper_log_err(int err, const char *format, ...)
      94  {
      95    va_list args;
      96  
      97    va_start(args, format);
      98    openlog(HELPER_COMPILE, LOG_CONS | LOG_PID, LOG_AUTHPRIV);
      99    vsyslog(err, format, args);
     100    va_end(args);
     101    closelog();
     102  }
     103  #endif
     104  
     105  static int
     106  parse_entry (char *line, opwd *data)
     107  {
     108    const char delimiters[] = ":";
     109    char *endptr;
     110    char *count;
     111  
     112    data->user = strsep (&line, delimiters);
     113    data->uid = strsep (&line, delimiters);
     114    count = strsep (&line, delimiters);
     115    if (count == NULL)
     116        return 1;
     117  
     118    data->count = strtol (count, &endptr, 10);
     119    if (endptr != NULL && *endptr != '\0')
     120        return 1;
     121  
     122    data->old_passwords = strsep (&line, delimiters);
     123  
     124    return 0;
     125  }
     126  
     127  static int
     128  compare_password(const char *newpass, const char *oldpass)
     129  {
     130    char *outval;
     131  #ifdef HAVE_CRYPT_R
     132    struct crypt_data output;
     133    int retval;
     134  
     135    output.initialized = 0;
     136  
     137    outval = crypt_r (newpass, oldpass, &output);
     138  #else
     139    outval = crypt (newpass, oldpass);
     140  #endif
     141  
     142    retval = outval != NULL && strcmp(outval, oldpass) == 0;
     143    pam_overwrite_string(outval);
     144    return retval;
     145  }
     146  
     147  /* Check, if the new password is already in the opasswd file.  */
     148  PAMH_ARG_DECL(int
     149  check_old_pass, const char *user, const char *newpass, const char *filename, int debug)
     150  {
     151    int retval = PAM_SUCCESS;
     152    FILE *oldpf;
     153    char *buf = NULL;
     154    size_t buflen = 0;
     155    opwd entry;
     156    int found = 0;
     157  
     158  #ifndef HELPER_COMPILE
     159    if (SELINUX_ENABLED)
     160      return PAM_PWHISTORY_RUN_HELPER;
     161  #endif
     162  
     163    const char *opasswd_file =
     164  	  (filename != NULL ? filename : DEFAULT_OLD_PASSWORDS_FILE);
     165  
     166    if ((oldpf = fopen (opasswd_file, "r")) == NULL)
     167      {
     168        if (errno != ENOENT)
     169  	pam_syslog (pamh, LOG_ERR, "Cannot open %s: %m", opasswd_file);
     170        return PAM_SUCCESS;
     171      }
     172  
     173    while (!feof (oldpf))
     174      {
     175        char *cp, *tmp;
     176  #if defined(HAVE_GETLINE)
     177        ssize_t n = getline (&buf, &buflen, oldpf);
     178  #elif defined (HAVE_GETDELIM)
     179        ssize_t n = getdelim (&buf, &buflen, '\n', oldpf);
     180  #else
     181        ssize_t n;
     182  
     183        if (buf == NULL)
     184          {
     185            buflen = DEFAULT_BUFLEN;
     186            buf = malloc (buflen);
     187  	  if (buf == NULL)
     188  	    return PAM_BUF_ERR;
     189          }
     190        buf[0] = '\0';
     191        fgets (buf, buflen - 1, oldpf);
     192        n = strlen (buf);
     193  #endif /* HAVE_GETLINE / HAVE_GETDELIM */
     194        cp = buf;
     195  
     196        if (n < 1)
     197          break;
     198  
     199        tmp = strchr (cp, '#');  /* remove comments */
     200        if (tmp)
     201          *tmp = '\0';
     202        while (isspace ((int)*cp))    /* remove spaces and tabs */
     203          ++cp;
     204        if (*cp == '\0')        /* ignore empty lines */
     205          continue;
     206  
     207        if (cp[strlen (cp) - 1] == '\n')
     208          cp[strlen (cp) - 1] = '\0';
     209  
     210        if (strncmp (cp, user, strlen (user)) == 0 &&
     211            cp[strlen (user)] == ':')
     212          {
     213            /* We found the line we needed */
     214  	  if (parse_entry (cp, &entry) == 0)
     215  	    {
     216  	      found = 1;
     217  	      break;
     218  	    }
     219  	}
     220      }
     221  
     222    fclose (oldpf);
     223  
     224    if (found && entry.old_passwords)
     225      {
     226        const char delimiters[] = ",";
     227        char *running;
     228        char *oldpass;
     229  
     230        running = entry.old_passwords;
     231  
     232        do {
     233  	oldpass = strsep (&running, delimiters);
     234  	if (oldpass && strlen (oldpass) > 0 &&
     235  	    compare_password(newpass, oldpass) )
     236  	  {
     237  	    if (debug)
     238  	      pam_syslog (pamh, LOG_DEBUG, "New password already used");
     239  	    retval = PAM_AUTHTOK_ERR;
     240  	    break;
     241  	  }
     242        } while (oldpass != NULL);
     243      }
     244  
     245    pam_overwrite_n(buf, buflen);
     246    free (buf);
     247  
     248    return retval;
     249  }
     250  
     251  PAMH_ARG_DECL(int
     252  save_old_pass, const char *user, int howmany, const char *filename, int debug UNUSED)
     253  {
     254    struct stat opasswd_stat;
     255    FILE *oldpf, *newpf;
     256    int newpf_fd;
     257    int do_create = 0;
     258    int retval = PAM_SUCCESS;
     259    char *buf = NULL;
     260    size_t buflen = 0;
     261    int found = 0;
     262    struct passwd *pwd;
     263    const char *oldpass;
     264  
     265    /* Define opasswd file and temp file for opasswd */
     266    const char *opasswd_file =
     267  	  (filename != NULL ? filename : DEFAULT_OLD_PASSWORDS_FILE);
     268    char opasswd_tmp[PATH_MAX];
     269  
     270    if ((size_t) snprintf (opasswd_tmp, sizeof (opasswd_tmp), "%s.tmpXXXXXX",
     271  			 opasswd_file) >= sizeof (opasswd_tmp))
     272      return PAM_BUF_ERR;
     273  
     274    pwd = pam_modutil_getpwnam (pamh, user);
     275    if (pwd == NULL)
     276      return PAM_USER_UNKNOWN;
     277  
     278    if (howmany <= 0)
     279      return PAM_SUCCESS;
     280  
     281  #ifndef HELPER_COMPILE
     282    if (SELINUX_ENABLED)
     283      return PAM_PWHISTORY_RUN_HELPER;
     284  #endif
     285  
     286    if ((strcmp(pwd->pw_passwd, "x") == 0)  ||
     287        ((pwd->pw_passwd[0] == '#') &&
     288         (pwd->pw_passwd[1] == '#') &&
     289         (strcmp(pwd->pw_name, pwd->pw_passwd + 2) == 0)))
     290      {
     291        struct spwd *spw = pam_modutil_getspnam (pamh, user);
     292  
     293        if (spw == NULL)
     294          return PAM_USER_UNKNOWN;
     295        oldpass = spw->sp_pwdp;
     296      }
     297    else
     298        oldpass = pwd->pw_passwd;
     299  
     300    if (oldpass == NULL || *oldpass == '\0')
     301      return PAM_SUCCESS;
     302  
     303    if ((oldpf = fopen (opasswd_file, "r")) == NULL)
     304      {
     305        if (errno == ENOENT)
     306  	{
     307  	  pam_syslog (pamh, LOG_NOTICE, "Creating %s", opasswd_file);
     308  	  do_create = 1;
     309  	}
     310        else
     311  	{
     312  	  pam_syslog (pamh, LOG_ERR, "Cannot open %s: %m", opasswd_file);
     313  	  return PAM_AUTHTOK_ERR;
     314  	}
     315      }
     316    else if (fstat (fileno (oldpf), &opasswd_stat) < 0)
     317      {
     318        pam_syslog (pamh, LOG_ERR, "Cannot stat %s: %m", opasswd_file);
     319        fclose (oldpf);
     320        return PAM_AUTHTOK_ERR;
     321      }
     322  
     323    /* Open a temp passwd file */
     324    newpf_fd = mkstemp (opasswd_tmp);
     325    if (newpf_fd == -1)
     326      {
     327        pam_syslog (pamh, LOG_ERR, "Cannot create %s temp file: %m",
     328  		  opasswd_file);
     329        if (oldpf)
     330  	fclose (oldpf);
     331        return PAM_AUTHTOK_ERR;
     332      }
     333    if (do_create)
     334      {
     335        if (fchmod (newpf_fd, S_IRUSR|S_IWUSR) != 0)
     336  	pam_syslog (pamh, LOG_ERR,
     337  		    "Cannot set permissions of %s temp file: %m", opasswd_file);
     338        if (fchown (newpf_fd, 0, 0) != 0)
     339  	pam_syslog (pamh, LOG_ERR,
     340  		    "Cannot set owner/group of %s temp file: %m", opasswd_file);
     341      }
     342    else
     343      {
     344        if (fchmod (newpf_fd, opasswd_stat.st_mode) != 0)
     345  	pam_syslog (pamh, LOG_ERR,
     346  		    "Cannot set permissions of %s temp file: %m", opasswd_file);
     347        if (fchown (newpf_fd, opasswd_stat.st_uid, opasswd_stat.st_gid) != 0)
     348  	pam_syslog (pamh, LOG_ERR,
     349  		    "Cannot set owner/group of %s temp file: %m", opasswd_file);
     350      }
     351    newpf = fdopen (newpf_fd, "w+");
     352    if (newpf == NULL)
     353      {
     354        pam_syslog (pamh, LOG_ERR, "Cannot fdopen %s: %m", opasswd_tmp);
     355        if (oldpf)
     356  	fclose (oldpf);
     357        close (newpf_fd);
     358        retval = PAM_AUTHTOK_ERR;
     359        goto error_opasswd;
     360      }
     361  
     362    if (!do_create)
     363      while (!feof (oldpf))
     364        {
     365  	char *cp, *tmp, *save;
     366  #if defined(HAVE_GETLINE)
     367  	ssize_t n = getline (&buf, &buflen, oldpf);
     368  #elif defined (HAVE_GETDELIM)
     369  	ssize_t n = getdelim (&buf, &buflen, '\n', oldpf);
     370  #else
     371  	ssize_t n;
     372  
     373  	if (buf == NULL)
     374  	  {
     375  	    buflen = DEFAULT_BUFLEN;
     376  	    buf = malloc (buflen);
     377  	    if (buf == NULL)
     378                {
     379  		fclose (oldpf);
     380  		fclose (newpf);
     381  		retval = PAM_BUF_ERR;
     382  		goto error_opasswd;
     383                }
     384  	  }
     385  	buf[0] = '\0';
     386  	fgets (buf, buflen - 1, oldpf);
     387  	n = strlen (buf);
     388  #endif /* HAVE_GETLINE / HAVE_GETDELIM */
     389  
     390  	if (n < 1)
     391  	  break;
     392  
     393  	cp = buf;
     394  	save = strdup (buf); /* Copy to write the original data back.  */
     395  	if (save == NULL)
     396            {
     397  	    fclose (oldpf);
     398  	    fclose (newpf);
     399  	    retval = PAM_BUF_ERR;
     400  	    goto error_opasswd;
     401            }
     402  
     403  	tmp = strchr (cp, '#');  /* remove comments */
     404  	if (tmp)
     405  	  *tmp = '\0';
     406  	while (isspace ((int)*cp))    /* remove spaces and tabs */
     407  	  ++cp;
     408  	if (*cp == '\0')        /* ignore empty lines */
     409  	  goto write_old_data;
     410  
     411  	if (cp[strlen (cp) - 1] == '\n')
     412  	  cp[strlen (cp) - 1] = '\0';
     413  
     414  	if (strncmp (cp, user, strlen (user)) == 0 &&
     415  	    cp[strlen (user)] == ':')
     416  	  {
     417  	    /* We found the line we needed */
     418  	    opwd entry;
     419  
     420  	    if (parse_entry (cp, &entry) == 0)
     421  	      {
     422  		char *out = NULL;
     423  
     424  		found = 1;
     425  
     426  		/* Don't save the current password twice */
     427  		if (entry.old_passwords && entry.old_passwords[0] != '\0')
     428  		  {
     429  		    char *last = entry.old_passwords;
     430  
     431  		    cp = entry.old_passwords;
     432  		    entry.count = 1;  /* Don't believe the count */
     433  		    while ((cp = strchr (cp, ',')) != NULL)
     434  		      {
     435  			entry.count++;
     436  			last = ++cp;
     437  		      }
     438  
     439  		    /* compare the last password */
     440  		    if (strcmp (last, oldpass) == 0)
     441  		      goto write_old_data;
     442  		  }
     443  		else
     444  		  entry.count = 0;
     445  
     446  		/* increase count.  */
     447  		entry.count++;
     448  
     449  		/* check that we don't remember to many passwords.  */
     450  		while (entry.count > howmany && entry.count > 1)
     451  		  {
     452  		    char *p = strpbrk (entry.old_passwords, ",");
     453  		    if (p != NULL)
     454  		      entry.old_passwords = ++p;
     455  		    entry.count--;
     456  		  }
     457  
     458  		if (entry.count == 1)
     459  		  {
     460  		    if (asprintf (&out, "%s:%s:%d:%s\n",
     461  				  entry.user, entry.uid, entry.count,
     462  				  oldpass) < 0)
     463  		      {
     464  		        free (save);
     465  			retval = PAM_AUTHTOK_ERR;
     466  			fclose (oldpf);
     467  			fclose (newpf);
     468  			goto error_opasswd;
     469  		      }
     470  		  }
     471  		else
     472  		  {
     473  		    if (asprintf (&out, "%s:%s:%d:%s,%s\n",
     474  				  entry.user, entry.uid, entry.count,
     475  				  entry.old_passwords, oldpass) < 0)
     476  		      {
     477  		        free (save);
     478  			retval = PAM_AUTHTOK_ERR;
     479  			fclose (oldpf);
     480  			fclose (newpf);
     481  			goto error_opasswd;
     482  		      }
     483  		  }
     484  
     485  		if (fputs (out, newpf) < 0)
     486  		  {
     487  		    free (out);
     488  		    free (save);
     489  		    retval = PAM_AUTHTOK_ERR;
     490  		    fclose (oldpf);
     491  		    fclose (newpf);
     492  		    goto error_opasswd;
     493  		  }
     494  		free (out);
     495  	      }
     496  	  }
     497  	else
     498  	  {
     499  	  write_old_data:
     500  	    if (fputs (save, newpf) < 0)
     501  	      {
     502  		free (save);
     503  		retval = PAM_AUTHTOK_ERR;
     504  		fclose (oldpf);
     505  		fclose (newpf);
     506  		goto error_opasswd;
     507  	      }
     508  	  }
     509  	free (save);
     510        }
     511  
     512    if (!found)
     513      {
     514        char *out;
     515  
     516        if (asprintf (&out, "%s:%d:1:%s\n", user, pwd->pw_uid, oldpass) < 0)
     517  	{
     518  	  retval = PAM_AUTHTOK_ERR;
     519  	  if (oldpf)
     520  	    fclose (oldpf);
     521  	  fclose (newpf);
     522  	  goto error_opasswd;
     523  	}
     524        if (fputs (out, newpf) < 0)
     525  	{
     526  	  pam_overwrite_string(out);
     527  	  free (out);
     528  	  retval = PAM_AUTHTOK_ERR;
     529  	  if (oldpf)
     530  	    fclose (oldpf);
     531  	  fclose (newpf);
     532  	  goto error_opasswd;
     533  	}
     534        pam_overwrite_string(out);
     535        free (out);
     536      }
     537  
     538    if (oldpf)
     539      if (fclose (oldpf) != 0)
     540        {
     541  	pam_syslog (pamh, LOG_ERR, "Error while closing old opasswd file: %m");
     542  	retval = PAM_AUTHTOK_ERR;
     543  	fclose (newpf);
     544  	goto error_opasswd;
     545        }
     546  
     547    if (fflush (newpf) != 0 || fsync (fileno (newpf)) != 0)
     548      {
     549        pam_syslog (pamh, LOG_ERR,
     550  		  "Error while syncing temporary opasswd file: %m");
     551        retval = PAM_AUTHTOK_ERR;
     552        fclose (newpf);
     553        goto error_opasswd;
     554      }
     555  
     556    if (fclose (newpf) != 0)
     557      {
     558        pam_syslog (pamh, LOG_ERR,
     559  		  "Error while closing temporary opasswd file: %m");
     560        retval = PAM_AUTHTOK_ERR;
     561        goto error_opasswd;
     562      }
     563  
     564    char opasswd_backup[PATH_MAX];
     565    if ((size_t) snprintf (opasswd_backup, sizeof (opasswd_backup), "%s.old",
     566  			 opasswd_file) >= sizeof (opasswd_backup))
     567      {
     568        retval = PAM_BUF_ERR;
     569        goto error_opasswd;
     570      }
     571  
     572    unlink (opasswd_backup);
     573    if (link (opasswd_file, opasswd_backup) != 0 &&
     574        errno != ENOENT)
     575      pam_syslog (pamh, LOG_ERR, "Cannot create backup file of %s: %m",
     576  		opasswd_file);
     577    rename (opasswd_tmp, opasswd_file);
     578   error_opasswd:
     579    unlink (opasswd_tmp);
     580    pam_overwrite_n(buf, buflen);
     581    free (buf);
     582  
     583    return retval;
     584  }