|
From: Tomas M. <tm...@re...> - 2008-09-30 12:17:13
|
On Thu, 2008-09-04 at 09:15 +0200, Thorsten Kukuk wrote:
> Hi,
>
> I thought again about using the plaintext password and maybe calling
> pam_pwhistory twice in the stack. But in the end I don't think that it
> makes sense. pam_pwhistory makes clearly no sense at all for central
> stored passwords (NIS, LDAP, kerberos), if the password strength
> checking is done on the client. You can always choose another client
> for changing the password, which means you can reuse your old
> passwords.
> So what stay left are local stored passwords, here we always have
> a crypted password. Or systems uses something else than passwords,
> but then pam_pwhistory is useless, too.
> So I'm still in favour of let it simple.
>
> So, here is the current implementation including a first test case.
> Any comments?
>
> Thorsten
Here is the first review:
>
> Index: configure.in
> ===================================================================
> RCS file: /cvsroot/pam/Linux-PAM/configure.in,v
> retrieving revision 1.126
> diff -u -u -r1.126 configure.in
> --- configure.in 18 Aug 2008 13:29:21 -0000 1.126
> +++ configure.in 3 Sep 2008 21:58:47 -0000
> @@ -542,7 +542,7 @@
> modules/pam_mkhomedir/Makefile modules/pam_motd/Makefile \
> modules/pam_namespace/Makefile \
> modules/pam_nologin/Makefile modules/pam_permit/Makefile \
> - modules/pam_rhosts/Makefile \
> + modules/pam_pwhistory/Makefile modules/pam_rhosts/Makefile \
> modules/pam_rootok/Makefile modules/pam_exec/Makefile \
> modules/pam_securetty/Makefile modules/pam_selinux/Makefile \
> modules/pam_sepermit/Makefile \
> Index: xtests/Makefile.am
> ===================================================================
> RCS file: /cvsroot/pam/Linux-PAM/xtests/Makefile.am,v
> retrieving revision 1.18
> diff -u -u -r1.18 Makefile.am
> --- xtests/Makefile.am 18 Feb 2008 17:57:34 -0000 1.18
> +++ xtests/Makefile.am 3 Sep 2008 21:58:47 -0000
> @@ -28,7 +28,8 @@
> tst-pam_substack3.pamd tst-pam_substack3a.pamd tst-pam_substack3.sh \
> tst-pam_substack4.pamd tst-pam_substack4a.pamd tst-pam_substack4.sh \
> tst-pam_substack5.pamd tst-pam_substack5a.pamd tst-pam_substack5.sh \
> - tst-pam_assemble_line1.pamd tst-pam_assemble_line1.sh
> + tst-pam_assemble_line1.pamd tst-pam_assemble_line1.sh \
> + tst-pam_pwhistory1.pamd tst-pam_pwhistory1.sh
>
> XTESTS = tst-pam_dispatch1 tst-pam_dispatch2 tst-pam_dispatch3 \
> tst-pam_dispatch4 tst-pam_dispatch5 \
> @@ -36,7 +37,8 @@
> tst-pam_unix1 tst-pam_unix2 tst-pam_unix3 \
> tst-pam_access1 tst-pam_access2 tst-pam_access3 \
> tst-pam_access4 tst-pam_limits1 tst-pam_succeed_if1 \
> - tst-pam_group1 tst-pam_authfail tst-pam_authsucceed
> + tst-pam_group1 tst-pam_authfail tst-pam_authsucceed \
> + tst-pam_pwhistory1
>
> NOSRCTESTS = tst-pam_substack1 tst-pam_substack2 tst-pam_substack3 \
> tst-pam_substack4 tst-pam_substack5 tst-pam_assemble_line1
> Index: xtests/run-xtests.sh
> ===================================================================
> RCS file: /cvsroot/pam/Linux-PAM/xtests/run-xtests.sh,v
> retrieving revision 1.8
> diff -u -u -r1.8 run-xtests.sh
> --- xtests/run-xtests.sh 19 Oct 2007 17:06:29 -0000 1.8
> +++ xtests/run-xtests.sh 3 Sep 2008 21:58:47 -0000
> @@ -23,6 +23,8 @@
> install -m 644 "${SRCDIR}"/group.conf /etc/security/group.conf
> cp /etc/security/limits.conf /etc/security/limits.conf-pam-xtests
> install -m 644 "${SRCDIR}"/limits.conf /etc/security/limits.conf
> +mv /etc/security/opasswd /etc/security/opasswd-pam-xtests
> +
> for testname in $XTESTS ; do
> for cfg in "${SRCDIR}"/$testname*.pamd ; do
> install -m 644 $cfg /etc/pam.d/$(basename $cfg .pamd)
> @@ -49,6 +51,7 @@
> mv /etc/security/access.conf-pam-xtests /etc/security/access.conf
> mv /etc/security/group.conf-pam-xtests /etc/security/group.conf
> mv /etc/security/limits.conf-pam-xtests /etc/security/limits.conf
> +mv /etc/security/opasswd-pam-xtests /etc/security/opasswd
> if test "$failed" -ne 0; then
> echo "==================="
> echo "$failed of $all tests failed"
> Index: xtests/tst-pam_pwhistory1.c
> ===================================================================
> RCS file: xtests/tst-pam_pwhistory1.c
> diff -N xtests/tst-pam_pwhistory1.c
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ xtests/tst-pam_pwhistory1.c 3 Sep 2008 21:58:47 -0000
> @@ -0,0 +1,169 @@
> +/*
> + * 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.
> + */
> +
> +/*
> + * Check remember handling
> + * Change ten times the password
> + * Try the ten passwords again, should always be rejected
> + * Try a new password, should succeed
> + */
> +
> +#ifdef HAVE_CONFIG_H
> +#include <config.h>
> +#endif
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <security/pam_appl.h>
> +
> +static int in_test;
> +
> +static const char *passwords[] = {
> + "pamhistory01", "pamhistory02", "pamhistory03",
> + "pamhistory04", "pamhistory05", "pamhistory06",
> + "pamhistory07", "pamhistory08", "pamhistory09",
> + "pamhistory10",
> + "pamhistory01", "pamhistory02", "pamhistory03",
> + "pamhistory04", "pamhistory05", "pamhistory06",
> + "pamhistory07", "pamhistory08", "pamhistory09",
> + "pamhistory10",
> + "pamhistory11",
> + "pamhistory01", "pamhistory02", "pamhistory03",
> + "pamhistory04", "pamhistory05", "pamhistory06",
> + "pamhistory07", "pamhistory08", "pamhistory09",
> + "pamhistory10"};
> +
> +static int debug;
> +
> +/* A conversation function which uses an internally-stored value for
> + the responses. */
> +static int
> +fake_conv (int num_msg, const struct pam_message **msgm,
> + struct pam_response **response, void *appdata_ptr UNUSED)
> +{
> + struct pam_response *reply;
> + int count;
> +
> + /* Sanity test. */
> + if (num_msg <= 0)
> + return PAM_CONV_ERR;
> +
> + if (debug)
> + fprintf (stderr, "msg_style=%d, msg=%s\n", msgm[0]->msg_style,
> + msgm[0]->msg);
> +
> + if (msgm[0]->msg_style != 1)
> + return PAM_SUCCESS;
> +
> + /* Allocate memory for the responses. */
> + reply = calloc (num_msg, sizeof (struct pam_response));
> + if (reply == NULL)
> + return PAM_CONV_ERR;
> +
> + /* Each prompt elicits the same response. */
> + for (count = 0; count < num_msg; ++count)
> + {
> + reply[count].resp_retcode = 0;
> + reply[count].resp = strdup (passwords[in_test]);
> + if (debug)
> + fprintf (stderr, "send password %s\n", reply[count].resp);
> + }
> +
> + /* Set the pointers in the response structure and return. */
> + *response = reply;
> + return PAM_SUCCESS;
> +}
> +
> +static struct pam_conv conv = {
> + fake_conv,
> + NULL
> +};
> +
> +
> +int
> +main(int argc, char *argv[])
> +{
> + pam_handle_t *pamh=NULL;
> + const char *user="tstpampwhistory";
> + int retval;
> +
> + if (argc > 1 && strcmp (argv[1], "-d") == 0)
> + debug = 1;
> +
> + for (in_test = 0;
> + in_test < (int)(sizeof (passwords)/sizeof (char *)); in_test++)
> + {
> +
> + retval = pam_start("tst-pam_pwhistory1", user, &conv, &pamh);
> + if (retval != PAM_SUCCESS)
> + {
> + if (debug)
> + fprintf (stderr, "pwhistory1-%d: pam_start returned %d\n",
> + in_test, retval);
> + return 1;
> + }
> +
> + retval = pam_chauthtok (pamh, 0);
> + if (in_test < 10 || in_test == 20)
> + {
> + if (retval != PAM_SUCCESS)
> + {
> + if (debug)
> + fprintf (stderr, "pwhistory1-%d: pam_chauthtok returned %d\n",
> + in_test, retval);
> + return 1;
> + }
> + }
> + else if (in_test < 20)
> + {
> + if (retval != PAM_MAXTRIES)
> + {
> + if (debug)
> + fprintf (stderr, "pwhistory1-%d: pam_chauthtok returned %d\n",
> + in_test, retval);
> + return 1;
> + }
> + }
> +
> + retval = pam_end (pamh,retval);
> + if (retval != PAM_SUCCESS)
> + {
> + if (debug)
> + fprintf (stderr, "pwhistory1: pam_end returned %d\n", retval);
> + return 1;
> + }
> + }
> +
> + return 0;
> +}
> Index: xtests/tst-pam_pwhistory1.pamd
> ===================================================================
> RCS file: xtests/tst-pam_pwhistory1.pamd
> diff -N xtests/tst-pam_pwhistory1.pamd
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ xtests/tst-pam_pwhistory1.pamd 3 Sep 2008 21:58:47 -0000
> @@ -0,0 +1,7 @@
> +#%PAM-1.0
> +auth required pam_permit.so
> +account required pam_permit.so
> +password required pam_pwhistory.so remember=10 retry=1 debug
> +password required pam_unix.so use_authtok md5
> +session required pam_permit.so
> +
> Index: xtests/tst-pam_pwhistory1.sh
> ===================================================================
> RCS file: xtests/tst-pam_pwhistory1.sh
> diff -N xtests/tst-pam_pwhistory1.sh
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ xtests/tst-pam_pwhistory1.sh 3 Sep 2008 21:58:47 -0000
> @@ -0,0 +1,7 @@
> +#!/bin/bash
> +
> +/usr/sbin/useradd tstpampwhistory
> +./tst-pam_pwhistory1
> +RET=$?
> +/usr/sbin/userdel -r tstpampwhistory 2> /dev/null
> +exit $RET
> Index: modules/Makefile.am
> ===================================================================
> RCS file: /cvsroot/pam/Linux-PAM/modules/Makefile.am,v
> retrieving revision 1.14
> diff -u -u -r1.14 Makefile.am
> --- modules/Makefile.am 4 Feb 2008 14:00:20 -0000 1.14
> +++ modules/Makefile.am 3 Sep 2008 21:58:47 -0000
> @@ -1,15 +1,16 @@
> #
> -# Copyright (c) 2005, 2006 Thorsten Kukuk <ku...@th...>
> +# Copyright (c) 2005, 2006, 2008 Thorsten Kukuk <ku...@th...>
> #
>
> 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_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 = *~
>
> RCS file: modules/pam_pwhistory/Makefile.am
> diff -N modules/pam_pwhistory/Makefile.am
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ modules/pam_pwhistory/Makefile.am 3 Sep 2008 21:58:47 -0000
> @@ -0,0 +1,35 @@
> +#
> +# Copyright (c) 2008 Thorsten Kukuk <ku...@su...>
> +#
> +
> +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
> +
> Index: modules/pam_pwhistory/README.xml
> ===================================================================
> RCS file: modules/pam_pwhistory/README.xml
> diff -N modules/pam_pwhistory/README.xml
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ modules/pam_pwhistory/README.xml 3 Sep 2008 21:58:47 -0000
> @@ -0,0 +1,41 @@
> +<?xml version="1.0" encoding='UTF-8'?>
> +<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
> +"http://www.docbook.org/xml/4.3/docbookx.dtd"
> +[
> +<!--
> +<!ENTITY pamaccess SYSTEM "pam_pwhistory.8.xml">
> +-->
> +]>
> +
> +<article>
> +
> + <articleinfo>
> +
> + <title>
> + <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
> + href="pam_pwhistory.8.xml" xpointer='xpointer(//refnamediv[@id = "pam_pwhistory-name"]/*)'/>
> + </title>
> +
> + </articleinfo>
> +
> + <section>
> + <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
> + href="pam_pwhistory.8.xml" xpointer='xpointer(//refsect1[@id = "pam_pwhistory-description"]/*)'/>
> + </section>
> +
> + <section>
> + <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
> + href="pam_pwhistory.8.xml" xpointer='xpointer(//refsect1[@id = "pam_pwhistory-options"]/*)'/>
> + </section>
> +
> + <section>
> + <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
> + href="pam_pwhistory.8.xml" xpointer='xpointer(//refsect1[@id = "pam_pwhistory-examples"]/*)'/>
> + </section>
> +
> + <section>
> + <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
> + href="pam_pwhistory.8.xml" xpointer='xpointer(//refsect1[@id = "pam_pwhistory-author"]/*)'/>
> + </section>
> +
> +</article>
> Index: modules/pam_pwhistory/opasswd.c
> ===================================================================
> RCS file: modules/pam_pwhistory/opasswd.c
> diff -N modules/pam_pwhistory/opasswd.c
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ modules/pam_pwhistory/opasswd.c 3 Sep 2008 21:58:47 -0000
> @@ -0,0 +1,456 @@
> +/*
> + * Copyright (c) 2008 Thorsten Kukuk <ku...@su...>
> + *
> + * 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 <config.h>
> +#endif
> +
> +#include <pwd.h>
> +#include <time.h>
> +#include <ctype.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <stdio.h>
> +#include <unistd.h>
> +#include <string.h>
> +#include <stdlib.h>
> +#include <syslog.h>
> +#include <sys/stat.h>
> +
> +#if defined (HAVE_XCRYPT_H)
> +#include <xcrypt.h>
> +#elif defined (HAVE_CRYPT_H)
> +#include <crypt.h>
> +#endif
> +
> +#include <security/pam_ext.h>
> +#include <security/pam_modules.h>
> +
> +#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"
> +
> +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')
> + {
> + fprintf (stderr, "endptr = '%c'\n", *endptr);
You must not call fprintf(stderr) in pam module.
> + 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 = 8096;
Perhaps make that #define somewhere?
> + buf = malloc (buflen);
> + if (buf == NULL)
> + return PAM_BUF_ERR;
> + }
> + buf[0] = '\0';
> + fgets (buf, buflen - 1, oldpf);
> + if (buf != NULL)
This is superfluous if.
> + n = strlen (buf);
> + else
> + n = 0;
> +#endif /* HAVE_GETLINE / HAVE_GETDELIM */
> + cp = buf;
> +
> + if (n < 1)
> + break;
> +
> + tmp = strchr (cp, '#'); /* remove comments */
Is it really correct to recognize # comments in opasswd?
> + 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 (strncasecmp (cp, user, strlen (user)) == 0 &&
> + cp[strlen (user)] == ':')
Why case insensitive compare? Are glibc getpwnam() and similar functions
case insensitive? I don't think so.
> + {
> + /* 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;
Why duplicate the string at all? And note that strdupa doesn't return
NULL on stack overflow - the behavior is undefined.
> +
> + 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)
> +{
> + 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 open %s: %m", opasswd_tmp);
Perhaps this message should be: "Cannot fdopen %s: %m"
> + fclose (oldpf);
> + close (newpf_fd);
> + retval = PAM_AUTHTOK_ERR;
> + goto error_opasswd;
> + }
> +
> + if (!do_create)
For clarity the following loop code perhaps could be split off into a
separate function and even better it could be shared with the
check_old_password code?
> + 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 = 8096;
> + buf = malloc (buflen);
> + if (buf == NULL)
> + return PAM_BUF_ERR;
> +
> + }
> + buf[0] = '\0';
> + fgets (buf, buflen - 1, oldpf);
> + if (buf != NULL)
> + n = strlen (buf);
> + else
> + n = 0;
> +#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 */
As above.
> + if (tmp)
> + *tmp = '\0';
> + while (isspace ((int)*cp)) /* remove spaces and tabs */
> + ++cp;
> + if (*cp == '\0') /* ignore empty lines */
> + goto write_data;
> +
> + if (cp[strlen (cp) - 1] == '\n')
> + cp[strlen (cp) - 1] = '\0';
> +
> + if (strncasecmp (cp, user, strlen (user)) == 0 &&
> + cp[strlen (user)] == ':')
As above.
> + {
> + /* 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_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_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)
add || entry.old_passwords[0] == '\0' ?
> + asprintf (&out, "%s:%s:%d:%s\n",
> + entry.user, entry.uid, entry.count,
> + oldpass);
> + else
> + asprintf (&out, "%s:%s:%d:%s,%s\n",
> + entry.user, entry.uid, entry.count,
> + entry.old_passwords, oldpass);
asprintf should be tested for return value != -1
> +
> + if (fputs (out, newpf) < 0)
> + {
> + free (out);
> + free (save);
> + retval = PAM_AUTHTOK_ERR;
> + fclose (oldpf);
> + fclose (newpf);
> + goto error_opasswd;
> + }
> + free (out);
> + }
> + }
> + else
> + {
> + write_data:
Perhaps the label should be named 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;
> +
> + asprintf (&out, "%s:%d:1:%s\n", user, uid, oldpass);
Again test asprintf return value.
> + 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");
Is it really necessary to test for retval on oldpf fclose() - it is
opened just for read anyway. On the other hand there is no harm doing
that so OK.
> + 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;
> +}
> Index: modules/pam_pwhistory/opasswd.h
> ===================================================================
> RCS file: modules/pam_pwhistory/opasswd.h
> diff -N modules/pam_pwhistory/opasswd.h
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ modules/pam_pwhistory/opasswd.h 3 Sep 2008 21:58:47 -0000
> @@ -0,0 +1,45 @@
> +/*
> + * Copyright (c) 2008 Thorsten Kukuk <ku...@su...>
> + *
> + * 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__ */
> Index: modules/pam_pwhistory/pam_pwhistory.8.xml
> ===================================================================
> RCS file: modules/pam_pwhistory/pam_pwhistory.8.xml
> diff -N modules/pam_pwhistory/pam_pwhistory.8.xml
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ modules/pam_pwhistory/pam_pwhistory.8.xml 3 Sep 2008 21:58:47 -0000
> @@ -0,0 +1,226 @@
> +<?xml version="1.0" encoding='UTF-8'?>
> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
> + "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd">
> +
> +<refentry id="pam_pwhistory">
> +
> + <refmeta>
> + <refentrytitle>pam_pwhistory</refentrytitle>
> + <manvolnum>8</manvolnum>
> + <refmiscinfo class="sectdesc">Linux-PAM Manual</refmiscinfo>
> + </refmeta>
> +
> + <refnamediv id="pam_pwhistory-name">
> + <refname>pam_pwhistory</refname>
> + <refpurpose>PAM module to remember last passwords</refpurpose>
> + </refnamediv>
> +
> + <refsynopsisdiv>
> + <cmdsynopsis id="pam_pwhistory-cmdsynopsis">
> + <command>pam_pwhistory.so</command>
> + <arg choice="opt">
> + debug
> + </arg>
> + <arg choice="opt">
> + use_authtok
> + </arg>
> + <arg choice="opt">
> + enforce_for_root
> + </arg>
> + <arg choice="opt">
> + remember=<replaceable>N</replaceable>
> + </arg>
> + <arg choice="opt">
> + retry=<replaceable>N</replaceable>
> + </arg>
> +
> + </cmdsynopsis>
> + </refsynopsisdiv>
> +
> + <refsect1 id="pam_pwhistory-description">
> +
> + <title>DESCRIPTION</title>
> +
> + <para>
> + 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.
> + </para>
> + <para>
> + 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.
> + </para>
> + </refsect1>
> +
> + <refsect1 id="pam_pwhistory-options">
> + <title>OPTIONS</title>
> + <variablelist>
> + <varlistentry>
> + <term>
> + <option>debug</option>
> + </term>
> + <listitem>
> + <para>
> + Turns on debugging via
> + <citerefentry>
> + <refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum>
> + </citerefentry>.
> + </para>
> + </listitem>
> + </varlistentry>
> + <varlistentry>
> + <term>
> + <option>use_authtok</option>
> + </term>
> + <listitem>
> + <para>
> + When password changing enforce the module to use the new password
> + provided by a previously stacked <option>password</option>
> + module (this is used in the example of the stacking of the
> + <command>pam_cracklib</command> module documented below).
> + </para>
> + </listitem>
> + </varlistentry>
> + <varlistentry>
> + <term>
> + <option>enforce_for_root</option>
> + </term>
> + <listitem>
> + <para>
> + If this option is set, the check is enforced for root, too.
> + </para>
> + </listitem>
> + </varlistentry>
> + <varlistentry>
> + <term>
> + <option>remember=<replaceable>N</replaceable></option>
> + </term>
> + <listitem>
> + <para>
> + The last <replaceable>N</replaceable> passwords for each
> + user are saved in <filename>/etc/security/opasswd</filename>.
> + The default is <emphasis>10</emphasis>.
> + </para>
> + </listitem>
> + </varlistentry>
> + <varlistentry>
> + <term>
> + <option>retry=<replaceable>N</replaceable></option>
> + </term>
> + <listitem>
> + <para>
> + Prompt user at most <replaceable>N</replaceable> times
> + before returning with error. The default is
> + <emphasis>1</emphasis>.
> + </para>
> + </listitem>
> + </varlistentry>
> +
> + </variablelist>
> + </refsect1>
> +
> + <refsect1 id="pam_pwhistory-types">
> + <title>MODULE TYPES PROVIDED</title>
> + <para>
> + Only the <option>password</option> module type is provided.
> + </para>
> + </refsect1>
> +
> + <refsect1 id='pam_pwhistory-return_values'>
> + <title>RETURN VALUES</title>
> + <variablelist>
> + <varlistentry>
> + <term>PAM_AUTHTOK_ERR</term>
> + <listitem>
> + <para>
> + No new password was entered, the user aborted password
> + change or new password couldn't be set.
> + </para>
> + </listitem>
> + </varlistentry>
> + <varlistentry>
> + <term>PAM_IGNORE</term>
> + <listitem>
> + <para>
> + Password history was disabled.
> + </para>
> + </listitem>
> + </varlistentry>
> + <varlistentry>
> + <term>PAM_MAXTRIES</term>
> + <listitem>
> + <para>
> + Password was rejected too often.
> + </para>
> + </listitem>
> + </varlistentry>
> + <varlistentry>
> + <term>PAM_USER_UNKNOWN</term>
> + <listitem>
> + <para>
> + User is not known to system.
> + </para>
> + </listitem>
> + </varlistentry>
> + </variablelist>
> + </refsect1>
> +
> + <refsect1 id='pam_pwhistory-examples'>
> + <title>EXAMPLES</title>
> + <para>
> + An example password section would be:
> + <programlisting>
> +#%PAM-1.0
> +password required pam_pwhistory.so
> +password required pam_unix.so use_authtok
> + </programlisting>
> + </para>
> + <para>
> + In combination with <command>pam_cracklib</command>:
> + <programlisting>
> +#%PAM-1.0
> +password required pam_cracklib.so retry=3
> +password required pam_pwhistory.so use_authtok
> +password required pam_unix.so use_authtok
> + </programlisting>
> + </para>
> + </refsect1>
> +
> + <refsect1 id="pam_pwhistory-files">
> + <title>FILES</title>
> + <variablelist>
> + <varlistentry>
> + <term><filename>/etc/security/opasswd</filename></term>
> + <listitem>
> + <para>File with password history</para>
> + </listitem>
> + </varlistentry>
> + </variablelist>
> + </refsect1>
> +
> + <refsect1 id='pam_pwhistory-see_also'>
> + <title>SEE ALSO</title>
> + <para>
> + <citerefentry>
> + <refentrytitle>pam.conf</refentrytitle><manvolnum>5</manvolnum>
> + </citerefentry>,
> + <citerefentry>
> + <refentrytitle>pam.d</refentrytitle><manvolnum>5</manvolnum>
> + </citerefentry>,
> + <citerefentry>
> + <refentrytitle>pam</refentrytitle><manvolnum>8</manvolnum>
> + </citerefentry>
> + </para>
> + </refsect1>
> +
> + <refsect1 id='pam_pwhistory-author'>
> + <title>AUTHOR</title>
> + <para>
> + pam_pwhistory was written by Thorsten Kukuk <ku...@th...>
> + </para>
> + </refsect1>
> +
> +</refentry>
> Index: modules/pam_pwhistory/pam_pwhistory.c
> ===================================================================
> RCS file: modules/pam_pwhistory/pam_pwhistory.c
> diff -N modules/pam_pwhistory/pam_pwhistory.c
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ modules/pam_pwhistory/pam_pwhistory.c 3 Sep 2008 21:58:47 -0000
> @@ -0,0 +1,318 @@
> +/*
> + * Copyright (c) 2008 Thorsten Kukuk
> + * Author: Thorsten Kukuk <ku...@su...>
> + *
> + * 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 <config.h>
> +#endif
> +
> +#define PAM_SM_PASSWORD
> +
> +#include <pwd.h>
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <shadow.h>
> +#include <syslog.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +
> +#include <security/pam_modules.h>
> +#include <security/pam_modutil.h>
> +#include <security/pam_ext.h>
> +#include <security/_pam_macros.h>
> +
> +#include "opasswd.h"
> +
> +#define OLD_PASSWORD_PROMPT _("Old Password: ")
> +#define NEW_PASSWORD_PROMPT _("New Password: ")
> +#define AGAIN_PASSWORD_PROMPT _("Reenter New Password: ")
> +#define MISTYPED_PASSWORD _("Passwords do not match.")
> +
> +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 */;
Shouldn't use_first_pass be rather alias for use_authtok?
> + 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);
> + 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 used already. 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);
> + 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 used already. 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
> Index: modules/pam_pwhistory/tst-pam_pwhistory
> ===================================================================
> RCS file: modules/pam_pwhistory/tst-pam_pwhistory
> diff -N modules/pam_pwhistory/tst-pam_pwhistory
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ modules/pam_pwhistory/tst-pam_pwhistory 3 Sep 2008 21:58:47 -0000
> @@ -0,0 +1,2 @@
> +#!/bin/sh
> +../../tests/tst-dlopen .libs/pam_pwhistory.so
--
Tomas Mraz
No matter how far down the wrong road you've gone, turn back.
Turkish proverb
|