2008-10-10  Thorsten Kukuk  <kukuk@thkukuk.de>

	* 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/.

Index: configure.in
===================================================================
--- configure.in.orig
+++ configure.in
@@ -539,7 +539,7 @@ AC_OUTPUT(Makefile libpam/Makefile libpa
 	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: doc/sag/Linux-PAM_SAG.xml
===================================================================
--- doc/sag/Linux-PAM_SAG.xml.orig
+++ doc/sag/Linux-PAM_SAG.xml
@@ -443,6 +443,8 @@ session   required   pam_warn.so
     <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
      href="pam_permit.xml"/>
     <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+     href="pam_pwhistory.xml"/>
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
      href="pam_rhosts.xml"/>
     <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
      href="pam_rootok.xml"/>
Index: doc/sag/pam_pwhistory.xml
===================================================================
--- /dev/null
+++ doc/sag/pam_pwhistory.xml
@@ -0,0 +1,38 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+        "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<section id='sag-pam_pwhistory'>
+  <title>pam_pwhistory - grant access using .pwhistory file</title>
+  <cmdsynopsis>
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+     href="../../modules/pam_pwhistory/pam_pwhistory.8.xml" xpointer='xpointer(//cmdsynopsis[@id = "pam_pwhistory-cmdsynopsis"]/*)'/>
+  </cmdsynopsis>
+  <section id='sag-pam_pwhistory-description'>
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+     href="../../modules/pam_pwhistory/pam_pwhistory.8.xml" xpointer='xpointer(//refsect1[@id = "pam_pwhistory-description"]/*)'/>
+  </section>
+  <section id='sag-pam_pwhistory-options'>
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+     href="../../modules/pam_pwhistory/pam_pwhistory.8.xml" xpointer='xpointer(//refsect1[@id = "pam_pwhistory-options"]/*)'/>
+  </section>
+  <section id='sag-pam_pwhistory-types'>
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+     href="../../modules/pam_pwhistory/pam_pwhistory.8.xml" xpointer='xpointer(//refsect1[@id = "pam_pwhistory-types"]/*)'/>
+  </section>
+  <section id='sag-pam_pwhistory-return_values'>
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+     href="../../modules/pam_pwhistory/pam_pwhistory.8.xml" xpointer='xpointer(//refsect1[@id = "pam_pwhistory-return_values"]/*)'/>
+  </section>
+  <section id='sag-pam_pwhistory-files'>
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+     href="../../modules/pam_pwhistory/pam_pwhistory.8.xml" xpointer='xpointer(//refsect1[@id = "pam_pwhistory-files"]/*)'/>
+  </section>
+  <section id='sag-pam_pwhistory-examples'>
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+     href="../../modules/pam_pwhistory/pam_pwhistory.8.xml" xpointer='xpointer(//refsect1[@id = "pam_pwhistory-examples"]/*)'/>
+  </section>
+  <section id='sag-pam_pwhistory-author'>
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+     href="../../modules/pam_pwhistory/pam_pwhistory.8.xml" xpointer='xpointer(//refsect1[@id = "pam_pwhistory-author"]/*)'/>
+  </section>
+</section>
Index: libpam/pam_static_modules.h
===================================================================
--- libpam/pam_static_modules.h.orig
+++ libpam/pam_static_modules.h
@@ -61,6 +61,7 @@ extern struct pam_module _pam_namespace_
 #endif
 extern struct pam_module _pam_nologin_modstruct;
 extern struct pam_module _pam_permit_modstruct;
+extern struct pam_module _pam_pwhistory_modstruct;
 extern struct pam_module _pam_rhosts_modstruct;
 extern struct pam_module _pam_rhosts_auth_modstruct;
 extern struct pam_module _pam_rootok_modstruct;
@@ -119,6 +120,7 @@ static struct pam_module *static_modules
 #endif
   &_pam_nologin_modstruct,
   &_pam_permit_modstruct,
+  &_pam_pwhistory_modstruct,
   &_pam_rhosts_modstruct,
   &_pam_rhosts_auth_modstruct,
   &_pam_rootok_modstruct,
Index: modules/Makefile.am
===================================================================
--- modules/Makefile.am.orig
+++ modules/Makefile.am
@@ -1,15 +1,16 @@
 #
-# Copyright (c) 2005, 2006 Thorsten Kukuk <kukuk@thkukuk.de>
+# Copyright (c) 2005, 2006, 2008 Thorsten Kukuk <kukuk@thkukuk.de>
 #
 
 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 = *~
 
Index: modules/pam_pwhistory/.cvsignore
===================================================================
--- /dev/null
+++ modules/pam_pwhistory/.cvsignore
@@ -0,0 +1,8 @@
+*.la
+*.lo
+.deps
+.libs
+Makefile
+Makefile.in
+README
+pam_pwhistory.8
Index: modules/pam_pwhistory/Makefile.am
===================================================================
--- /dev/null
+++ modules/pam_pwhistory/Makefile.am
@@ -0,0 +1,35 @@
+#
+# Copyright (c) 2008 Thorsten Kukuk <kukuk@suse.de>
+#
+
+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
===================================================================
--- /dev/null
+++ modules/pam_pwhistory/README.xml
@@ -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
===================================================================
--- /dev/null
+++ modules/pam_pwhistory/opasswd.c
@@ -0,0 +1,473 @@
+/*
+ * Copyright (c) 2008 Thorsten Kukuk <kukuk@suse.de>
+ *
+ * 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"
+
+#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:%s:%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;
+}
Index: modules/pam_pwhistory/opasswd.h
===================================================================
--- /dev/null
+++ modules/pam_pwhistory/opasswd.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2008 Thorsten Kukuk <kukuk@suse.de>
+ *
+ * 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
===================================================================
--- /dev/null
+++ modules/pam_pwhistory/pam_pwhistory.8.xml
@@ -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 &lt;kukuk@thkukuk.de&gt;
+      </para>
+  </refsect1>
+
+</refentry>
Index: modules/pam_pwhistory/pam_pwhistory.c
===================================================================
--- /dev/null
+++ modules/pam_pwhistory/pam_pwhistory.c
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) 2008 Thorsten Kukuk
+ * Author: Thorsten Kukuk <kukuk@suse.de>
+ *
+ * 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 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
Index: modules/pam_pwhistory/tst-pam_pwhistory
===================================================================
--- /dev/null
+++ modules/pam_pwhistory/tst-pam_pwhistory
@@ -0,0 +1,2 @@
+#!/bin/sh
+../../tests/tst-dlopen .libs/pam_pwhistory.so
Index: po/POTFILES.in
===================================================================
--- po/POTFILES.in.orig
+++ po/POTFILES.in
@@ -58,6 +58,8 @@
 ./modules/pam_namespace/pam_namespace.c
 ./modules/pam_nologin/pam_nologin.c
 ./modules/pam_permit/pam_permit.c
+./modules/pam_pwhistory/opasswd.c
+./modules/pam_pwhistory/pam_pwhistory.c
 ./modules/pam_rhosts/pam_rhosts.c
 ./modules/pam_rootok/pam_rootok.c
 ./modules/pam_securetty/pam_securetty.c
Index: xtests/Makefile.am
===================================================================
--- xtests/Makefile.am.orig
+++ xtests/Makefile.am
@@ -30,7 +30,8 @@ EXTRA_DIST = run-xtests.sh tst-pam_dispa
 	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 \
@@ -38,7 +39,8 @@ XTESTS = tst-pam_dispatch1 tst-pam_dispa
 	tst-pam_unix1 tst-pam_unix2 tst-pam_unix3 tst-pam_unix4 \
 	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
===================================================================
--- xtests/run-xtests.sh.orig
+++ xtests/run-xtests.sh
@@ -23,6 +23,8 @@ cp /etc/security/group.conf /etc/securit
 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 @@ done
 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
===================================================================
--- /dev/null
+++ xtests/tst-pam_pwhistory1.c
@@ -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
===================================================================
--- /dev/null
+++ xtests/tst-pam_pwhistory1.pamd
@@ -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
===================================================================
--- /dev/null
+++ xtests/tst-pam_pwhistory1.sh
@@ -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/pam_pwhistory/pam_pwhistory.8
===================================================================
--- /dev/null
+++ modules/pam_pwhistory/pam_pwhistory.8
@@ -0,0 +1,127 @@
+.\"     Title: pam_pwhistory
+.\"    Author: 
+.\" Generator: DocBook XSL Stylesheets v1.73.2 <http://docbook.sf.net/>
+.\"      Date: 10/11/2008
+.\"    Manual: Linux-PAM Manual
+.\"    Source: Linux-PAM Manual
+.\"
+.TH "PAM_PWHISTORY" "8" "10/11/2008" "Linux-PAM Manual" "Linux\-PAM Manual"
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.SH "NAME"
+pam_pwhistory - PAM module to remember last passwords
+.SH "SYNOPSIS"
+.HP 17
+\fBpam_pwhistory\.so\fR [debug] [use_authtok] [enforce_for_root] [remember=\fIN\fR] [retry=\fIN\fR]
+.SH "DESCRIPTION"
+.PP
+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\.
+.PP
+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\.
+.SH "OPTIONS"
+.PP
+\fBdebug\fR
+.RS 4
+Turns on debugging via
+\fBsyslog\fR(3)\.
+.RE
+.PP
+\fBuse_authtok\fR
+.RS 4
+When password changing enforce the module to use the new password provided by a previously stacked
+\fBpassword\fR
+module (this is used in the example of the stacking of the
+\fBpam_cracklib\fR
+module documented below)\.
+.RE
+.PP
+\fBenforce_for_root\fR
+.RS 4
+If this option is set, the check is enforced for root, too\.
+.RE
+.PP
+\fBremember=\fR\fB\fIN\fR\fR
+.RS 4
+The last
+\fIN\fR
+passwords for each user are saved in
+\fI/etc/security/opasswd\fR\. The default is
+\fI10\fR\.
+.RE
+.PP
+\fBretry=\fR\fB\fIN\fR\fR
+.RS 4
+Prompt user at most
+\fIN\fR
+times before returning with error\. The default is
+\fI1\fR\.
+.RE
+.SH "MODULE TYPES PROVIDED"
+.PP
+Only the
+\fBpassword\fR
+module type is provided\.
+.SH "RETURN VALUES"
+.PP
+PAM_AUTHTOK_ERR
+.RS 4
+No new password was entered, the user aborted password change or new password couldn\'t be set\.
+.RE
+.PP
+PAM_IGNORE
+.RS 4
+Password history was disabled\.
+.RE
+.PP
+PAM_MAXTRIES
+.RS 4
+Password was rejected too often\.
+.RE
+.PP
+PAM_USER_UNKNOWN
+.RS 4
+User is not known to system\.
+.RE
+.SH "EXAMPLES"
+.PP
+An example password section would be:
+.sp
+.RS 4
+.nf
+#%PAM\-1\.0
+password     required       pam_pwhistory\.so
+password     required       pam_unix\.so        use_authtok
+      
+.fi
+.RE
+.PP
+In combination with
+\fBpam_cracklib\fR:
+.sp
+.RS 4
+.nf
+#%PAM\-1\.0
+password     required       pam_cracklib\.so    retry=3
+password     required       pam_pwhistory\.so   use_authtok
+password     required       pam_unix\.so        use_authtok
+      
+.fi
+.RE
+.sp
+.SH "FILES"
+.PP
+\fI/etc/security/opasswd\fR
+.RS 4
+File with password history
+.RE
+.SH "SEE ALSO"
+.PP
+
+\fBpam.conf\fR(5),
+\fBpam.d\fR(5),
+\fBpam\fR(8)
+.SH "AUTHOR"
+.PP
+pam_pwhistory was written by Thorsten Kukuk <kukuk@thkukuk\.de>
