From dad5bd7c146a842e11da19c5715db117d62f5677 Mon Sep 17 00:00:00 2001 From: Thorsten Kukuk Date: Fri, 10 Oct 2008 06:53:45 +0000 Subject: Relevant BUGIDs: Purpose of commit: new feature Commit summary: --------------- 2008-10-10 Thorsten Kukuk * configure.in: add modules/pam_pwhistory/Makefile. * doc/sag/Linux-PAM_SAG.xml: Include pam_pwhistory.xml. * doc/sag/pam_pwhistory.xml: New. * libpam/pam_static_modules.h: Add pam_pwhistory data. * modules/Makefile.am: Add pam_pwhistory directory. * modules/pam_pwhistory/Makefile.am: New. * modules/pam_pwhistory/README.xml: New. * modules/pam_pwhistory/opasswd.c: New. * modules/pam_pwhistory/opasswd.h: New. * modules/pam_pwhistory/pam_pwhistory.8.xml: New. * modules/pam_pwhistory/pam_pwhistory.c: New. * modules/pam_pwhistory/tst-pam_pwhistory: New. * xtests/Makefile.am: New. * xtests/run-xtests.sh: New. * xtests/tst-pam_pwhistory1.c: New. * xtests/tst-pam_pwhistory1.pamd: New. * xtests/tst-pam_pwhistory1.sh: New. * po/POTFILES.in: Add modules/pam_pwhistory/. * po/de.po: Update translations. --- modules/Makefile.am | 15 +- modules/pam_pwhistory/.cvsignore | 8 + modules/pam_pwhistory/Makefile.am | 35 +++ modules/pam_pwhistory/README.xml | 41 +++ modules/pam_pwhistory/opasswd.c | 473 ++++++++++++++++++++++++++++++ modules/pam_pwhistory/opasswd.h | 45 +++ modules/pam_pwhistory/pam_pwhistory.8.xml | 226 ++++++++++++++ modules/pam_pwhistory/pam_pwhistory.c | 319 ++++++++++++++++++++ modules/pam_pwhistory/tst-pam_pwhistory | 2 + 9 files changed, 1157 insertions(+), 7 deletions(-) create mode 100644 modules/pam_pwhistory/.cvsignore create mode 100644 modules/pam_pwhistory/Makefile.am create mode 100644 modules/pam_pwhistory/README.xml create mode 100644 modules/pam_pwhistory/opasswd.c create mode 100644 modules/pam_pwhistory/opasswd.h create mode 100644 modules/pam_pwhistory/pam_pwhistory.8.xml create mode 100644 modules/pam_pwhistory/pam_pwhistory.c create mode 100755 modules/pam_pwhistory/tst-pam_pwhistory (limited to 'modules') diff --git a/modules/Makefile.am b/modules/Makefile.am index c79f5957..f21d52e8 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -1,15 +1,16 @@ # -# Copyright (c) 2005, 2006 Thorsten Kukuk +# Copyright (c) 2005, 2006, 2008 Thorsten Kukuk # SUBDIRS = pam_access pam_cracklib pam_debug pam_deny pam_echo \ - pam_env pam_filter pam_ftp pam_group pam_issue pam_keyinit \ - pam_lastlog pam_limits pam_listfile pam_localuser pam_mail \ - pam_mkhomedir pam_motd pam_nologin pam_permit pam_rhosts pam_rootok \ - pam_securetty pam_selinux pam_sepermit pam_shells pam_stress \ + pam_env pam_exec pam_faildelay pam_filter pam_ftp \ + pam_group pam_issue pam_keyinit pam_lastlog pam_limits \ + pam_listfile pam_localuser pam_loginuid pam_mail \ + pam_mkhomedir pam_motd pam_namespace pam_nologin \ + pam_permit pam_pwhistory pam_rhosts pam_rootok pam_securetty \ + pam_selinux pam_sepermit pam_shells pam_stress \ pam_succeed_if pam_tally pam_time pam_tty_audit pam_umask \ - pam_unix pam_userdb pam_warn pam_wheel pam_xauth pam_exec \ - pam_namespace pam_loginuid pam_faildelay + pam_unix pam_userdb pam_warn pam_wheel pam_xauth CLEANFILES = *~ diff --git a/modules/pam_pwhistory/.cvsignore b/modules/pam_pwhistory/.cvsignore new file mode 100644 index 00000000..c0d3c72c --- /dev/null +++ b/modules/pam_pwhistory/.cvsignore @@ -0,0 +1,8 @@ +*.la +*.lo +.deps +.libs +Makefile +Makefile.in +README +pam_pwhistory.8 diff --git a/modules/pam_pwhistory/Makefile.am b/modules/pam_pwhistory/Makefile.am new file mode 100644 index 00000000..018d0b52 --- /dev/null +++ b/modules/pam_pwhistory/Makefile.am @@ -0,0 +1,35 @@ +# +# Copyright (c) 2008 Thorsten Kukuk +# + +CLEANFILES = *~ + +EXTRA_DIST = README $(MANS) $(XMLS) tst-pam_pwhistory + +TESTS = tst-pam_pwhistory + +man_MANS = pam_pwhistory.8 + +XMLS = README.xml pam_pwhistory.8.xml + +securelibdir = $(SECUREDIR) +secureconfdir = $(SCONFIGDIR) + +AM_CFLAGS = -I$(top_srcdir)/libpam/include -I$(top_srcdir)/libpamc/include +AM_LDFLAGS = -no-undefined -avoid-version -module +if HAVE_VERSIONING + AM_LDFLAGS += -Wl,--version-script=$(srcdir)/../modules.map +endif + +noinst_HEADERS = opasswd.h + +securelib_LTLIBRARIES = pam_pwhistory.la +pam_pwhistory_la_LIBADD = -L$(top_builddir)/libpam -lpam @LIBCRYPT@ +pam_pwhistory_la_SOURCES = pam_pwhistory.c opasswd.c + +if ENABLE_REGENERATE_MAN +noinst_DATA = README +README: pam_pwhistory.8.xml +-include $(top_srcdir)/Make.xml.rules +endif + diff --git a/modules/pam_pwhistory/README.xml b/modules/pam_pwhistory/README.xml new file mode 100644 index 00000000..f048e321 --- /dev/null +++ b/modules/pam_pwhistory/README.xml @@ -0,0 +1,41 @@ + + +--> +]> + +
+ + + + + <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" + href="pam_pwhistory.8.xml" xpointer='xpointer(//refnamediv[@id = "pam_pwhistory-name"]/*)'/> + + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
diff --git a/modules/pam_pwhistory/opasswd.c b/modules/pam_pwhistory/opasswd.c new file mode 100644 index 00000000..89452d3f --- /dev/null +++ b/modules/pam_pwhistory/opasswd.c @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2008 Thorsten Kukuk + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined (HAVE_XCRYPT_H) +#include +#elif defined (HAVE_CRYPT_H) +#include +#endif + +#include +#include + +#include "opasswd.h" + +#ifndef RANDOM_DEVICE +#define RANDOM_DEVICE "/dev/urandom" +#endif + +#define OLD_PASSWORDS_FILE "/etc/security/opasswd" +#define TMP_PASSWORDS_FILE OLD_PASSWORDS_FILE".tmpXXXXXX" + +#define DEFAULT_BUFLEN 4096 + +typedef struct { + char *user; + char *uid; + int count; + char *old_passwords; +} opwd; + + +static int +parse_entry (char *line, opwd *data) +{ + const char delimiters[] = ":"; + char *endptr; + + data->user = strsep (&line, delimiters); + data->uid = strsep (&line, delimiters); + data->count = strtol (strsep (&line, delimiters), &endptr, 10); + if (endptr != NULL && *endptr != '\0') + return 1; + + data->old_passwords = strsep (&line, delimiters); + + return 0; +} + +/* Check, if the new password is already in the opasswd file. */ +int +check_old_password (pam_handle_t *pamh, const char *user, + const char *newpass, int debug) +{ + int retval = PAM_SUCCESS; + FILE *oldpf; + char *buf = NULL; + size_t buflen = 0; + opwd entry; + int found = 0; + + if ((oldpf = fopen (OLD_PASSWORDS_FILE, "r")) == NULL) + { + if (errno != ENOENT) + pam_syslog (pamh, LOG_ERR, "Cannot open %s: %m", OLD_PASSWORDS_FILE); + return PAM_SUCCESS; + } + + while (!feof (oldpf)) + { + char *cp, *tmp; +#if defined(HAVE_GETLINE) + ssize_t n = getline (&buf, &buflen, oldpf); +#elif defined (HAVE_GETDELIM) + ssize_t n = getdelim (&buf, &buflen, '\n', oldpf); +#else + ssize_t n; + + if (buf == NULL) + { + buflen = DEFAULT_BUFLEN; + buf = malloc (buflen); + if (buf == NULL) + return PAM_BUF_ERR; + } + buf[0] = '\0'; + fgets (buf, buflen - 1, oldpf); + n = strlen (buf); +#endif /* HAVE_GETLINE / HAVE_GETDELIM */ + cp = buf; + + if (n < 1) + break; + + tmp = strchr (cp, '#'); /* remove comments */ + if (tmp) + *tmp = '\0'; + while (isspace ((int)*cp)) /* remove spaces and tabs */ + ++cp; + if (*cp == '\0') /* ignore empty lines */ + continue; + + if (cp[strlen (cp) - 1] == '\n') + cp[strlen (cp) - 1] = '\0'; + + if (strncmp (cp, user, strlen (user)) == 0 && + cp[strlen (user)] == ':') + { + /* We found the line we needed */ + if (parse_entry (cp, &entry) == 0) + { + found = 1; + break; + } + } + } + + fclose (oldpf); + + if (found) + { + const char delimiters[] = ","; + struct crypt_data output; + char *running; + char *oldpass; + + memset (&output, 0, sizeof (output)); + + running = strdupa (entry.old_passwords); + if (running == NULL) + return PAM_BUF_ERR; + + do { + oldpass = strsep (&running, delimiters); + if (oldpass && strlen (oldpass) > 0 && + strcmp (crypt_r (newpass, oldpass, &output), oldpass) == 0) + { + if (debug) + pam_syslog (pamh, LOG_DEBUG, "New password already used"); + retval = PAM_AUTHTOK_ERR; + break; + } + } while (oldpass != NULL); + } + + if (buf) + free (buf); + + return retval; +} + +int +save_old_password (pam_handle_t *pamh, const char *user, uid_t uid, + const char *oldpass, int howmany, int debug UNUSED) +{ + char opasswd_tmp[] = TMP_PASSWORDS_FILE; + struct stat opasswd_stat; + FILE *oldpf, *newpf; + int newpf_fd; + int do_create = 0; + int retval = PAM_SUCCESS; + char *buf = NULL; + size_t buflen = 0; + int found = 0; + + if (howmany <= 0) + return PAM_SUCCESS; + + if (oldpass == NULL || *oldpass == '\0') + return PAM_SUCCESS; + + if ((oldpf = fopen (OLD_PASSWORDS_FILE, "r")) == NULL) + { + if (errno == ENOENT) + { + pam_syslog (pamh, LOG_NOTICE, "Creating %s", + OLD_PASSWORDS_FILE); + do_create = 1; + } + else + { + pam_syslog (pamh, LOG_ERR, "Cannot open %s: %m", + OLD_PASSWORDS_FILE); + return PAM_AUTHTOK_ERR; + } + } + else if (fstat (fileno (oldpf), &opasswd_stat) < 0) + { + pam_syslog (pamh, LOG_ERR, "Cannot stat %s: %m", OLD_PASSWORDS_FILE); + fclose (oldpf); + return PAM_AUTHTOK_ERR; + } + + /* Open a temp passwd file */ + newpf_fd = mkstemp (opasswd_tmp); + if (newpf_fd == -1) + { + pam_syslog (pamh, LOG_ERR, "Cannot create %s temp file: %m", + OLD_PASSWORDS_FILE); + fclose (oldpf); + return PAM_AUTHTOK_ERR; + } + if (do_create) + { + if (fchmod (newpf_fd, S_IRUSR|S_IWUSR) != 0) + pam_syslog (pamh, LOG_ERR, + "Cannot set permissions of %s temp file: %m", + OLD_PASSWORDS_FILE); + if (fchown (newpf_fd, 0, 0) != 0) + pam_syslog (pamh, LOG_ERR, + "Cannot set owner/group of %s temp file: %m", + OLD_PASSWORDS_FILE); + } + else + { + if (fchmod (newpf_fd, opasswd_stat.st_mode) != 0) + pam_syslog (pamh, LOG_ERR, + "Cannot set permissions of %s temp file: %m", + OLD_PASSWORDS_FILE); + if (fchown (newpf_fd, opasswd_stat.st_uid, opasswd_stat.st_gid) != 0) + pam_syslog (pamh, LOG_ERR, + "Cannot set owner/group of %s temp file: %m", + OLD_PASSWORDS_FILE); + } + newpf = fdopen (newpf_fd, "w+"); + if (newpf == NULL) + { + pam_syslog (pamh, LOG_ERR, "Cannot fdopen %s: %m", opasswd_tmp); + fclose (oldpf); + close (newpf_fd); + retval = PAM_AUTHTOK_ERR; + goto error_opasswd; + } + + if (!do_create) + while (!feof (oldpf)) + { + char *cp, *tmp, *save; +#if defined(HAVE_GETLINE) + ssize_t n = getline (&buf, &buflen, oldpf); +#elif defined (HAVE_GETDELIM) + ssize_t n = getdelim (&buf, &buflen, '\n', oldpf); +#else + ssize_t n; + + if (buf == NULL) + { + buflen = DEFAULT_BUFLEN; + buf = malloc (buflen); + if (buf == NULL) + return PAM_BUF_ERR; + + } + buf[0] = '\0'; + fgets (buf, buflen - 1, oldpf); + n = strlen (buf); +#endif /* HAVE_GETLINE / HAVE_GETDELIM */ + + cp = buf; + save = strdup (buf); /* Copy to write the original data back. */ + if (save == NULL) + return PAM_BUF_ERR; + + if (n < 1) + break; + + tmp = strchr (cp, '#'); /* remove comments */ + if (tmp) + *tmp = '\0'; + while (isspace ((int)*cp)) /* remove spaces and tabs */ + ++cp; + if (*cp == '\0') /* ignore empty lines */ + goto write_old_data; + + if (cp[strlen (cp) - 1] == '\n') + cp[strlen (cp) - 1] = '\0'; + + if (strncmp (cp, user, strlen (user)) == 0 && + cp[strlen (user)] == ':') + { + /* We found the line we needed */ + opwd entry; + + if (parse_entry (cp, &entry) == 0) + { + char *out = NULL; + + found = 1; + + /* Don't save the current password twice */ + if (entry.old_passwords) + { + /* there is only one password */ + if (strcmp (entry.old_passwords, oldpass) == 0) + goto write_old_data; + else + { + /* check last entry */ + cp = strstr (entry.old_passwords, oldpass); + + if (cp && strcmp (cp, oldpass) == 0) + { /* the end is the same, check that there + is a "," before. */ + --cp; + if (*cp == ',') + goto write_old_data; + } + } + } + + /* increase count. */ + entry.count++; + + /* check that we don't remember to many passwords. */ + while (entry.count > howmany) + { + char *p = strpbrk (entry.old_passwords, ","); + if (p != NULL) + entry.old_passwords = ++p; + entry.count--; + } + + if (entry.old_passwords == NULL) + { + if (asprintf (&out, "%s:%s:%d:%s\n", + entry.user, entry.uid, entry.count, + oldpass) < 0) + { + retval = PAM_AUTHTOK_ERR; + fclose (oldpf); + fclose (newpf); + goto error_opasswd; + } + } + else + { + if (asprintf (&out, "%s:%si%d:%s,%s\n", + entry.user, entry.uid, entry.count, + entry.old_passwords, oldpass) < 0) + { + retval = PAM_AUTHTOK_ERR; + fclose (oldpf); + fclose (newpf); + goto error_opasswd; + } + } + + if (fputs (out, newpf) < 0) + { + free (out); + free (save); + retval = PAM_AUTHTOK_ERR; + fclose (oldpf); + fclose (newpf); + goto error_opasswd; + } + free (out); + } + } + else + { + write_old_data: + if (fputs (save, newpf) < 0) + { + free (save); + retval = PAM_AUTHTOK_ERR; + fclose (oldpf); + fclose (newpf); + goto error_opasswd; + } + } + free (save); + } + + if (!found) + { + char *out; + + if (asprintf (&out, "%s:%d:1:%s\n", user, uid, oldpass) < 0) + { + retval = PAM_AUTHTOK_ERR; + if (oldpf) + fclose (oldpf); + fclose (newpf); + goto error_opasswd; + } + if (fputs (out, newpf) < 0) + { + free (out); + retval = PAM_AUTHTOK_ERR; + if (oldpf) + fclose (oldpf); + fclose (newpf); + goto error_opasswd; + } + free (out); + } + + if (oldpf) + if (fclose (oldpf) != 0) + { + pam_syslog (pamh, LOG_ERR, "Error while closing old opasswd file: %m"); + retval = PAM_AUTHTOK_ERR; + fclose (newpf); + goto error_opasswd; + } + + if (fclose (newpf) != 0) + { + pam_syslog (pamh, LOG_ERR, + "Error while closing temporary opasswd file: %m"); + retval = PAM_AUTHTOK_ERR; + goto error_opasswd; + } + + unlink (OLD_PASSWORDS_FILE".old"); + if (link (OLD_PASSWORDS_FILE, OLD_PASSWORDS_FILE".old") != 0 && + errno != ENOENT) + pam_syslog (pamh, LOG_ERR, "Cannot create backup file of %s: %m", + OLD_PASSWORDS_FILE); + rename (opasswd_tmp, OLD_PASSWORDS_FILE); + error_opasswd: + unlink (opasswd_tmp); + + return retval; +} diff --git a/modules/pam_pwhistory/opasswd.h b/modules/pam_pwhistory/opasswd.h new file mode 100644 index 00000000..e8a20139 --- /dev/null +++ b/modules/pam_pwhistory/opasswd.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2008 Thorsten Kukuk + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __OPASSWD_H__ +#define __OPASSWD_H__ + +extern int check_old_password (pam_handle_t *pamh, const char *user, + const char *newpass, int debug); +extern int save_old_password (pam_handle_t *pamh, const char *user, + uid_t uid, const char *oldpass, + int howmany, int debug); + +#endif /* __OPASSWD_H__ */ diff --git a/modules/pam_pwhistory/pam_pwhistory.8.xml b/modules/pam_pwhistory/pam_pwhistory.8.xml new file mode 100644 index 00000000..26d6bd15 --- /dev/null +++ b/modules/pam_pwhistory/pam_pwhistory.8.xml @@ -0,0 +1,226 @@ + + + + + + + pam_pwhistory + 8 + Linux-PAM Manual + + + + pam_pwhistory + PAM module to remember last passwords + + + + + pam_pwhistory.so + + debug + + + use_authtok + + + enforce_for_root + + + remember=N + + + retry=N + + + + + + + + DESCRIPTION + + + This module saves the last passwords for each user in order + to force password change history and keep the user from + alternating between the same password too frequently. + + + This module does not work togehter with kerberos. In general, + it does not make much sense to use this module in conjuction + with NIS or LDAP, since the old passwords are stored on the + local machine and are not available on another machine for + password history checking. + + + + + OPTIONS + + + + + + + + Turns on debugging via + + syslog3 + . + + + + + + + + + + When password changing enforce the module to use the new password + provided by a previously stacked + module (this is used in the example of the stacking of the + pam_cracklib module documented below). + + + + + + + + + + If this option is set, the check is enforced for root, too. + + + + + + + + + + The last N passwords for each + user are saved in /etc/security/opasswd. + The default is 10. + + + + + + + + + + Prompt user at most N times + before returning with error. The default is + 1. + + + + + + + + + MODULE TYPES PROVIDED + + Only the module type is provided. + + + + + RETURN VALUES + + + PAM_AUTHTOK_ERR + + + No new password was entered, the user aborted password + change or new password couldn't be set. + + + + + PAM_IGNORE + + + Password history was disabled. + + + + + PAM_MAXTRIES + + + Password was rejected too often. + + + + + PAM_USER_UNKNOWN + + + User is not known to system. + + + + + + + + EXAMPLES + + An example password section would be: + +#%PAM-1.0 +password required pam_pwhistory.so +password required pam_unix.so use_authtok + + + + In combination with pam_cracklib: + +#%PAM-1.0 +password required pam_cracklib.so retry=3 +password required pam_pwhistory.so use_authtok +password required pam_unix.so use_authtok + + + + + + FILES + + + /etc/security/opasswd + + File with password history + + + + + + + SEE ALSO + + + pam.conf5 + , + + pam.d5 + , + + pam8 + + + + + + AUTHOR + + pam_pwhistory was written by Thorsten Kukuk <kukuk@thkukuk.de> + + + + diff --git a/modules/pam_pwhistory/pam_pwhistory.c b/modules/pam_pwhistory/pam_pwhistory.c new file mode 100644 index 00000000..d3cce728 --- /dev/null +++ b/modules/pam_pwhistory/pam_pwhistory.c @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2008 Thorsten Kukuk + * Author: Thorsten Kukuk + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#define PAM_SM_PASSWORD + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "opasswd.h" + +#define NEW_PASSWORD_PROMPT _("New %s%spassword: ") +#define AGAIN_PASSWORD_PROMPT _("Retype new %s%spassword: ") +#define MISTYPED_PASSWORD _("Sorry, passwords do not match.") + +#define DEFAULT_BUFLEN 2048 + +struct options_t { + int debug; + int use_authtok; + int enforce_for_root; + int remember; + int tries; +}; +typedef struct options_t options_t; + + +static void +parse_option (pam_handle_t *pamh, const char *argv, options_t *options) +{ + if (strcasecmp (argv, "use_first_pass") == 0) + /* ignore */; + else if (strcasecmp (argv, "use_first_pass") == 0) + /* ignore */; + else if (strcasecmp (argv, "use_authtok") == 0) + options->use_authtok = 1; + else if (strcasecmp (argv, "debug") == 0) + options->debug = 1; + else if (strncasecmp (argv, "remember=", 9) == 0) + { + options->remember = strtol(&argv[9], NULL, 10); + if (options->remember < 0) + options->remember = 0; + if (options->remember > 400) + options->remember = 400; + } + else if (strncasecmp (argv, "retry=", 6) == 0) + { + options->tries = strtol(&argv[6], NULL, 10); + if (options->tries < 0) + options->tries = 1; + } + else if (strcasecmp (argv, "enforce_for_root") == 0) + options->enforce_for_root = 1; + else + pam_syslog (pamh, LOG_ERR, "pam_pwhistory: unknown option: %s", argv); +} + + +PAM_EXTERN int +pam_sm_chauthtok (pam_handle_t *pamh, int flags, int argc, const char **argv) +{ + struct passwd *pwd; + char *newpass; + const char *user; + void *newpass_void; + int retval, tries; + options_t options; + + memset (&options, 0, sizeof (options)); + + /* Set some default values, which could be overwritten later. */ + options.remember = 10; + options.tries = 1; + + /* Parse parameters for module */ + for ( ; argc-- > 0; argv++) + parse_option (pamh, *argv, &options); + + if (options.debug) + pam_syslog (pamh, LOG_DEBUG, "pam_sm_chauthtok entered"); + + + if (options.remember == 0) + return PAM_IGNORE; + + retval = pam_get_user (pamh, &user, NULL); + if (retval != PAM_SUCCESS) + return retval; + + if (user == NULL || strlen (user) == 0) + { + if (options.debug) + pam_syslog (pamh, LOG_DEBUG, + "User is not known to system"); + + return PAM_USER_UNKNOWN; + } + + if (flags & PAM_PRELIM_CHECK) + { + if (options.debug) + pam_syslog (pamh, LOG_DEBUG, + "pam_sm_chauthtok(PAM_PRELIM_CHECK)"); + + return PAM_SUCCESS; + } + + pwd = pam_modutil_getpwnam (pamh, user); + if (pwd == NULL) + return PAM_USER_UNKNOWN; + + /* Ignore root if not enforced */ + if (pwd->pw_uid == 0 && !options.enforce_for_root) + return PAM_SUCCESS; + + if ((strcmp(pwd->pw_passwd, "x") == 0) || + ((pwd->pw_passwd[0] == '#') && + (pwd->pw_passwd[1] == '#') && + (strcmp(pwd->pw_name, pwd->pw_passwd + 2) == 0))) + { + struct spwd *spw = pam_modutil_getspnam (pamh, user); + if (spw == NULL) + return PAM_USER_UNKNOWN; + + retval = save_old_password (pamh, user, pwd->pw_uid, spw->sp_pwdp, + options.remember, options.debug); + if (retval != PAM_SUCCESS) + return retval; + } + else + { + retval = save_old_password (pamh, user, pwd->pw_uid, pwd->pw_passwd, + options.remember, options.debug); + if (retval != PAM_SUCCESS) + return retval; + } + + retval = pam_get_item (pamh, PAM_AUTHTOK, (const void **) &newpass_void); + newpass = (char *) newpass_void; + if (retval != PAM_SUCCESS) + return retval; + if (options.debug) + { + if (newpass) + pam_syslog (pamh, LOG_DEBUG, "got new auth token"); + else + pam_syslog (pamh, LOG_DEBUG, "new auth token not set"); + } + + /* If we haven't been given a password yet, prompt for one... */ + if (newpass == NULL) + { + if (options.use_authtok) + /* We are not allowed to ask for a new password */ + return PAM_AUTHTOK_ERR; + + tries = 0; + + while ((newpass == NULL) && (tries++ < options.tries)) + { + retval = pam_prompt (pamh, PAM_PROMPT_ECHO_OFF, &newpass, + NEW_PASSWORD_PROMPT, "UNIX", " "); + if (retval != PAM_SUCCESS) + { + _pam_drop (newpass); + if (retval == PAM_CONV_AGAIN) + retval = PAM_INCOMPLETE; + return retval; + } + + if (newpass == NULL) + { + /* We want to abort the password change */ + pam_error (pamh, _("Password change aborted.")); + return PAM_AUTHTOK_ERR; + } + + if (options.debug) + pam_syslog (pamh, LOG_DEBUG, "check against old password file"); + + if (check_old_password (pamh, user, newpass, + options.debug) != PAM_SUCCESS) + { + pam_error (pamh, + _("Password has been already used. Choose another.")); + _pam_overwrite (newpass); + _pam_drop (newpass); + if (tries >= options.tries) + { + if (options.debug) + pam_syslog (pamh, LOG_DEBUG, + "Aborted, too many tries"); + return PAM_MAXTRIES; + } + } + else + { + int failed; + char *new2; + + retval = pam_prompt (pamh, PAM_PROMPT_ECHO_OFF, &new2, + AGAIN_PASSWORD_PROMPT, "UNIX", " "); + if (retval != PAM_SUCCESS) + return retval; + + if (new2 == NULL) + { /* Aborting password change... */ + pam_error (pamh, _("Password change aborted.")); + return PAM_AUTHTOK_ERR; + } + + failed = (strcmp (newpass, new2) != 0); + + _pam_overwrite (new2); + _pam_drop (new2); + + if (failed) + { + pam_error (pamh, MISTYPED_PASSWORD); + _pam_overwrite (newpass); + _pam_drop (newpass); + if (tries >= options.tries) + { + if (options.debug) + pam_syslog (pamh, LOG_DEBUG, + "Aborted, too many tries"); + return PAM_MAXTRIES; + } + } + } + } + + /* Remember new password */ + pam_set_item (pamh, PAM_AUTHTOK, (void *) newpass); + } + else /* newpass != NULL, we found an old password */ + { + if (options.debug) + pam_syslog (pamh, LOG_DEBUG, "look in old password file"); + + if (check_old_password (pamh, user, newpass, + options.debug) != PAM_SUCCESS) + { + pam_error (pamh, + _("Password has been already used. Choose another.")); + /* We are only here, because old password was set. + So overwrite it, else it will be stored! */ + pam_set_item (pamh, PAM_AUTHTOK, (void *) NULL); + + return PAM_AUTHTOK_ERR; + } + } + + return PAM_SUCCESS; +} + + +#ifdef PAM_STATIC +/* static module data */ +struct pam_module _pam_pwhistory_modstruct = { + "pam_pwhistory", + NULL, + NULL, + NULL, + NULL, + NULL, + pam_sm_chauthtok +}; +#endif diff --git a/modules/pam_pwhistory/tst-pam_pwhistory b/modules/pam_pwhistory/tst-pam_pwhistory new file mode 100755 index 00000000..3531a88a --- /dev/null +++ b/modules/pam_pwhistory/tst-pam_pwhistory @@ -0,0 +1,2 @@ +#!/bin/sh +../../tests/tst-dlopen .libs/pam_pwhistory.so -- cgit v1.2.3