From scott@mail.medsp.com  Thu Jan 20 21:58:19 2000
Return-Path: <scott@mail.medsp.com>
Received: from mail.medsp.com (medsp.com [209.203.250.120])
	by hub.freebsd.org (Postfix) with ESMTP id 9414B15461
	for <FreeBSD-gnats-submit@freebsd.org>; Thu, 20 Jan 2000 21:58:17 -0800 (PST)
	(envelope-from scott@mail.medsp.com)
Received: (from scott@localhost)
	by mail.medsp.com (8.9.3/8.9.3) id WAA92779;
	Thu, 20 Jan 2000 22:03:06 -0800 (PST)
	(envelope-from scott)
Message-Id: <200001210603.WAA92779@mail.medsp.com>
Date: Thu, 20 Jan 2000 22:03:06 -0800 (PST)
From: Scott Gasch <scott@mail.medsp.com>
Reply-To: scott@wannabe.guru.org
To: FreeBSD-gnats-submit@freebsd.org
Subject:
X-Send-Pr-Version: 3.2

>Number:         16244
>Category:       bin
>Synopsis:       [PATCH] don't allow password re-use when changing passwords
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Thu Jan 20 22:00:01 PST 2000
>Closed-Date:    Sat Jul 21 11:50:41 PDT 2001
>Last-Modified:  Sat Jul 21 11:51:29 PDT 2001
>Originator:     
>Release:        
>Organization:
>Environment:

 Patch is against CURRENT as of 1/19/00.
 
>Description:
 
 Hello,
 
 Enclosed is a patch to /usr/bin/passwd.  The purpose of this code is
 to not allow users to reuse the same password again when they change
 their password (either voluntarilly or by force).
                                                                                
 The code works by maintaining a password history (HIST_FILE, default
 to /etc/passwd.hist) file where the last HIST_COUNT encrypted
 passwords of users are stored (HIST_COUNT being configurable).  Only
 the passwords of users whose login class includes the boolean
 "usepasswordhist" capability are tracked in this history file.  The
 file is created uid 0 gid 0 mode 0600 and every time it is
 read/written this setting is checked and, if needed, reset.
                                                                                
 When a user whose login class includes "usepasswordhist" in it changes
 his/her password the new password is compared to the ones already on
 record in the HIST_FILE.  If it matches any of the old passwords the
 new password is refused.
                                                                                
 Like the other password restrictions this mechanism will allow root to
 insist on the new password after warning once.  There is code in place
 to handle users who write scripts to quickly rotate their passwords in
 an attempt to beat the system.  Specifically, if a user's old password
 list is full (contains HIST_COUNT old passowords) and the newest of
 the old passwords is less than HIST_TIME seconds old (default to 3600)
 when the user changes his password again, the new password simply
 replaces the newest old password on the history list.  Of course if
 the newest old password on the list is older than HIST_TIME seconds,
 the list is rippled down and the oldest entry is lost.  Also, I
 understand that individual users can specify their own personal
 login.conf files and could probably override the "usepasswordhist"
 capability as it is not protected.
 
 I understand that this code might not be for everyone.  There are
 several reasons for this: First, it creates another file with
 encrypted passwords in it.  Even though it keeps an eye on the
 security of this file and this file only contains old passwords in
 encrypted format it is not something one would want to share with bad
 guys.  Also, with this new code, the clear text new password stays in
 memory longer.  To me this is a minor issue but I suppose it might
 bother some people.  Indeed the entire password history file (and all
 the encrypted passwords therein) is in memory during the execution of
 passwd.  Finally it might be argued that a mechanism such as this one
 where a user is forced to change their password to a unique one
 coupled with a policy where users must change their passwords every N
 days may decrease overall security by encouraging people to write
 passwords down or use passwords in a logical progression.  I posted on
 hackers about this idea and no one seemed very interested...
                                                                                
 Still, this code may be useful and may be something you are interested
 in.  Users in login classes without the "usepasswordhist" capability
 defined are, of course, do not use this mechanism and their old
 passwords are not tracked in the HIST_FILE.  Also, the code is only
 included if you define PASS_HIST while building passwd.
                                                                                
 Anyway, here's the patch along with two new files (passhist.c and
 passhist.h).  The patch is against CURRENT as of today... it's in shar
 format in the How-To-Repeat section.  The two new files are meant to
 be dropped in /usr/src/usr.bin/passwd along with the rest.  I have not
 tried this on a YP system but suspect it would be pretty useless as it
 will store the passwd.hist file on each individual machine.  I do not
 have a YP system setup here but I suspect adapting this to YP would be
 simple.
                                                                                
 Hope this is something useful and thanks for your time (and a great
 OS!).
 
 Scott                                                                           

>How-To-Repeat:
>Fix:
 
# This is a shell archive.  Save it in a file, remove anything before
# this line, and then unpack it by entering "sh file".  Note, it may
# create directories; files and directories will be owned by you and
# have default permissions.
#
# This archive contains:
#
#	passhist.h
#	passhist.c
#	passwd.diff
#
echo x - passhist.h
sed 's/^X//' >passhist.h << 'END-of-passhist.h'
X//+----------------------------------------------------------------------------
X//
X// File:     passhist.h
X//
X// Module:   passwd patch, FreeBSD.
X//
X// Synopsis: See passhist.c
X//
X//+----------------------------------------------------------------------------
X
X#ifndef _PASSHIST_H_
X#define _PASSHIST_H_
X
X#include <limits.h>
X
X//
X// How many old passwords to remember, per user.
X//
X#define HIST_COUNT                            3
X
X//
X// Where to store encrupted old passwords.
X//
X#define HIST_FILE                             "/etc/passwd.hist"
X
X//
X// How long a password must be active (in sec) before it counts... this
X// is to stop people from writing scripts to change their password 
X// HIST_COUNT times in a row and then reset the same original password.
X//
X#define HIST_TIME                             3600
X
Xtypedef struct _HIST_NODE
X{
X	uid_t uid;
X	char szPass[HIST_COUNT][PASS_MAX];
X	time_t timeLastUpdate;
X	int iNumPass;
X}
XHIST_NODE;
X
Xint InitializeList(void);
Xint ReadList(void);
Xint AddPass(uid_t uidUser, char *szPass);
Xint CheckPass(uid_t uidUser, char *szPass);
Xint WriteList(void);
Xvoid CloseList(void);
X
X#endif /* _PASSHIST_H_ */
END-of-passhist.h
echo x - passhist.c
sed 's/^X//' >passhist.c << 'END-of-passhist.c'
X//+----------------------------------------------------------------------------
X//
X// File:     passhist.c
X//
X// Module:   passwd patch, FreeBSD.
X//
X// Synopsis: A set of functions that maintain a password history file.
X//           This file (HIST_FILE in passhist.h) stores a configurable
X//           number of prior passwords for each user (HIST_COUNT in 
X//           passhist.h).  These passwords are stored in encrypted format.
X//           HIST_FILE's permissions and ownership are checked and reset
X//           at every read/write operation so as to self-correct any
X//           insecure setting an administrator has created.
X//
X//           InitializeList and ReadList must be called before anything
X//           else.
X//
X//           Subsequent calls to CheckPass will determine whether a
X//           proposed new password for a certain user has been used
X//           by said user in his last PASS_COUNT passwords.
X//
X//           Subsequent calls to AddPass will add a new password to
X//           a specific user's list so long as he has had his current
X//           password in effect for at least HIST_TIME seconds.  This
X//           is to prevent scripts from changing passwords HIST_COUNT
X//           times in a row and then resetting the original password
X//           again.
X//
X// Copyright (c) 2000 Scott Gasch (scott@wannabe.guru.org)
X// All rights reserved.
X//
X// Redistribution and use in source and binary forms, with or without
X// modification, are permitted provided that the following conditions
X// are met:
X// 1. Redistributions of source code must retain the above copyright
X//    notice, this list of conditions and the following disclaimer.
X// 2. Redistributions in binary form must reproduce the above copyright
X//    notice, this list of conditions and the following disclaimer in the
X//    documentation and/or other materials provided with the distribution.
X//
X// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
X// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
X// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
X// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
X// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
X// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
X// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
X// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
X// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
X// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
X// SUCH DAMAGE.
X//
X//+----------------------------------------------------------------------------
X
X#include <sys/types.h>
X#include <sys/param.h>
X#include <sys/stat.h>
X#include <unistd.h>
X#include <limits.h>
X#include <pwd.h>
X#include <stdlib.h>
X#include <stdio.h>
X#include <string.h>
X#include <time.h>
X
X#include "passhist.h"
X
X//
X// Local function protos.
X//
Xstatic HIST_NODE *GetHistEntry(uid_t uidTarg);
Xint CompareUids(const void *pA, const void *pB);
X
X//
X// History table and count of items in it.
X//
XHIST_NODE *g_pHist = NULL;
Xunsigned int g_iHistCount = 0;
X
X
X//+----------------------------------------------------------------------------
X//
X// Function:  CompareUids
X//
X// Synopsis:  Compare two history table records (used by qsort).
X//
X// Arguments: const void *pA - ptr to first hist table record.
X//            const void *pB - ptr to second hist table record.
X//            
X// Returns:   int - difference between records.
X//
X//+----------------------------------------------------------------------------
Xint CompareUids(const void *pA, const void *pB)
X{
X	return (((HIST_NODE *) pA)->uid - ((HIST_NODE *) pB)->uid);
X}
X
X
X//+----------------------------------------------------------------------------
X//
X// Function:  InitializeList
X//
X// Synopsis:  Prepare the memory history list for population by ReadList.
X//            To do this we need to count the number of uids on the sys-
X//            tem and allocate a big enough hunk of memory to hold the 
X//            list.  We also populate the uid field of each record and
X//            sort the memory list so that it is fast/easy for ReadList
X//            to determine which uid is good later.
X//
X// Arguments: void
X//            
X// Returns:   int - 0 for failure, number of accounts (>0) for success.
X//
X//+----------------------------------------------------------------------------
Xint InitializeList(void)
X{
X	struct passwd *pPwd;
X	unsigned int iIndex = 0;
X
X	//
X	// Count the number of user account in the system.
X	//
X	setpwent();
X	g_iHistCount = 0;
X	while ((pPwd = getpwent()))
X	{
X		g_iHistCount++;
X	}
X
X	//
X	// Allocate a buffer large enough to hold a password history of N
X	// user accounts.
X	//
X	if (NULL != g_pHist) free(g_pHist);
X	g_pHist = (HIST_NODE *) malloc(g_iHistCount * sizeof(HIST_NODE));
X	if (NULL == g_pHist)
X	{
X		fprintf(stderr, "Out of memory.\n");
X		return(0);
X	}
X	memset(g_pHist, 0, g_iHistCount * sizeof(HIST_NODE));
X
X	//
X	// Fill in the UID field in the history list.
X	//
X	setpwent();
X	while ((pPwd = getpwent()))
X	{
X		g_pHist[iIndex++].uid = pPwd->pw_uid;
X	}
X	endpwent();
X	
X	//
X	// Sort the list
X	//
X	qsort(g_pHist, g_iHistCount, sizeof(HIST_NODE), CompareUids);
X	return(g_iHistCount);
X}
X
X
X//+----------------------------------------------------------------------------
X//
X// Function:  CloseList
X//
X// Synopsis:  Cleanup... deallocate history table etc...
X//
X// Arguments: void
X//            
X// Returns:   void
X//
X//+----------------------------------------------------------------------------
Xvoid CloseList(void)
X{
X	if (g_pHist) free(g_pHist);
X	g_iHistCount = 0;
X}
X
X
X//+----------------------------------------------------------------------------
X//
X// Function:  CheckFilePerms
X//
X// Synopsis: Check that the HIST_FILE's owner is root and mode is
X//           0600.
X//
X// Arguments: void
X//            
X// Returns:   int - 0 if file perms are insecure, 1 if secure.
X//
X//+----------------------------------------------------------------------------
Xint CheckFilePerms(void)
X{
X	struct stat sStat;                        // Stat buffer
X
X	//
X	// Stat it and check the info
X	//
X	if (0 != stat(HIST_FILE, &sStat))
X	{
X		return(0);
X	}
X
X	if (sStat.st_uid)
X	{
X		return(0);
X	}
X
X	if (sStat.st_mode & (S_IRWXG | S_IRWXO))
X	{
X		return(0);
X	}
X
X	return(1);
X}
X
X
X//+----------------------------------------------------------------------------
X//
X// Function:  ReadList
X//
X// Synopsis:  Read in the HIST_FILE file... create a history list in 
X//            memory.  For HIST_FILE line format see WriteList below.
X//            Do not bother reading information about uids not valid
X//            on the system anymore.  
X//
X//            Note: a HIST_PASS file line cannot be more than 2048
X//            characters wide.
X//
X// Arguments: void
X//            
X// Returns:   int - 0 on failure, 1 on success.
X//
X//+----------------------------------------------------------------------------
Xint ReadList(void)
X{
X	FILE *fp = fopen(HIST_FILE, "r");         // File ptr.
X	char buf[2048];                           // A line from the file
X	uid_t uid;                                // A uid from the file
X	HIST_NODE *pEntry;                        // A hist entry
X	char *pch;                                // Misc char ptr.
X
X	//
X	// If the file does not exist or there is some other read error,
X	// don't bother.
X	//
X	if (NULL == fp)
X	{
X		return(0);
X	}
X
X	//
X	// Print a nasty message to the user about his lax security... this
X	// will notify him that something is wrong and that someone could 
X	// potentially be running a crack program on his user's old passwds.
X	// Also, reset the file permissions.
X	//
X	if (0 == CheckFilePerms())
X	{
X		fprintf(stderr, "Warning: resetting insecure owner/mode of %s.\n", 
X				HIST_FILE);
X		chmod(HIST_FILE, S_IRUSR | S_IWUSR);
X		chown(HIST_FILE, 0, 0);
X	}
X
X	//
X	// Read a line from the file... we have a history table with
X	// just uids in it, sorted, courtesy of InitializeList.  This
X	// lets us determine where the data from this line should be 
X	// put very quickly.
X	//
X	while(fgets(buf, 2048, fp))
X	{
X		//
X		// The uid should be the first thing on the line...
X		//
X		uid = (uid_t)atoi(buf);
X
X		//
X		// If there is no such user (we do not have a hist table node
X		// already for him) skip it.
X		//
X		if (NULL == (pEntry = GetHistEntry(uid))) continue;
X
X		//
X		// Skip past the uid number to the first NULL and one char
X		// beyond.
X		//
X		pch = buf;
X		while (*pch) pch++;
X		pch++;
X
X		//
X		// Read timestamp of last update.. the next thing on the line.
X		//
X		pEntry->timeLastUpdate = (time_t)atoi(pch);
X
X		//
X		// Skip until NULL and one beyond.
X		//
X		while (*pch) pch++;
X		pch++;
X
X		//
X		// Read password list.
X		//
X		while (*pch)
X		{
X			if (pEntry->iNumPass < HIST_COUNT)
X			{
X				strncpy(pEntry->szPass[pEntry->iNumPass], pch, PASS_MAX);
X				pEntry->iNumPass++;
X			}
X
X			while(*pch) pch++;
X			pch++;
X		}
X	}
X	fclose(fp);
X
X	return(1);
X}
X
X
X//+----------------------------------------------------------------------------
X//
X// Function:  WriteList
X//
X// Synopsis:  Write the list currently in memory out to the HIST_FILE file.
X//            Each record is in the format:
X//
X//            uid\0timestamp\0pass1\0pass2\0 ... passn\0\0\n
X//
X//            uid: numeric uid for the user.
X//            timestamp: seconds since epoch of last passwd update.
X//            pass1..passn: encrypted old passwords.
X//
X// Arguments: void
X//            
X// Returns:   int - 0 on error, 1 on success.
X//
X//+----------------------------------------------------------------------------
Xint WriteList(void)
X{
X	int i, j;                                 // Loop control
X	FILE *fp = fopen(HIST_FILE, "w");         // File ptr.
X
X	//
X	// Make sure the settings on the file are secure.
X	//
X	chmod(HIST_FILE, S_IRUSR | S_IWUSR);
X	chown(HIST_FILE, 0, 0);
X
X	if (!fp) return(0);
X
X	for (i = 0; i < g_iHistCount; i++)
X	{
X		//
X		// Only bother writing accounts for which we have saved passwd
X		// info.
X		//
X		if (g_pHist[i].iNumPass)
X		{
X			fprintf(fp, "%d%c%d%c", g_pHist[i].uid, 0, 
X					(int)g_pHist[i].timeLastUpdate, 0);
X			for (j = 0; j < g_pHist[i].iNumPass; j++)
X			{
X				fprintf(fp, "%s%c", g_pHist[i].szPass[j], 0);
X			}
X			fprintf(fp, "%c\n", 0);
X		}
X	}
X
X	fclose(fp);
X	return(1);
X}
X
X
X//+----------------------------------------------------------------------------
X//
X// Function:  AddPass
X//
X// Synopsis:  Add a new (encrypted) password to a user's history.
X//
X// Arguments: uid_t uidUser - The uid of the guy whose history we update.
X//            char *szPass - The (encrypted) password he has set.
X//            
X// Returns:   int - 1 on success and 0 on failure.
X//
X//+----------------------------------------------------------------------------
Xint AddPass(uid_t uidUser, char *szPass)
X{
X	HIST_NODE *pEntry = GetHistEntry(uidUser); // User's hist node
X	int i;                                     // Loop control
X	time_t timeNow = time(NULL);               // Sec since epoch, now.
X
X	//
X	// I dunno why this would fail bug handle it if it does.
X	//
X	if (-1 == timeNow)
X	{
X		return(0);
X	}
X
X	//
X	// This user has no history as far as we know.  Make one.. do not
X	// bother inserting it into the right place in the list as we will
X	// not be querying the list again anyway... it will get sorted in
X	// next time we read/sort the list (next time someone runs
X	// passwd).
X	//
X	if (NULL == pEntry)
X	{
X		g_iHistCount++;
X		g_pHist = realloc(g_pHist, g_iHistCount * sizeof(HIST_NODE));
X		pEntry = &(g_pHist[g_iHistCount - 1]);
X		memset(pEntry, 0, sizeof(HIST_NODE));
X		pEntry->uid = uidUser;
X	}
X	
X	//
X	// Depending on whether we have a full (HIST_COUNT) list of
X	// old passwords we will either add a new one or ripple the
X	// oldest saved one off the back to make room for a new one.
X	//
X	if (pEntry->iNumPass < HIST_COUNT)
X	{
X		strncpy(pEntry->szPass[pEntry->iNumPass], szPass, PASS_MAX);
X		pEntry->iNumPass++;
X	}
X	else
X	{
X		//
X		// If we get here this guy's history is full.  If the newest
X		// password in the history is over HIST_TIME seconds old then
X		// ripple-shift the passwords in his list and bump the oldest
X		// one off the "no use" history list.
X		//
X		// However if the newest password is less than HIST_TIME
X		// seconds old then just replace *it* with the new password.
X		// That way if the guy is messing with us and has a script to
X		// cycle through HIST_COUNT passwords in an effort to be able
X		// to reuse the original one again it will not work.
X		// Moreover, if the newest password has hardly been used
X		// (since it is so young) it is safer to lose it than an older
X		// one that has been used longer (presumably).
X		//
X		if (timeNow - pEntry->timeLastUpdate > HIST_TIME)
X		{
X			for (i = 1; i < HIST_COUNT; i++)
X			{
X				strncpy(pEntry->szPass[i - 1], pEntry->szPass[i], PASS_MAX);
X			}
X		}
X		strncpy(pEntry->szPass[HIST_COUNT - 1], szPass, PASS_MAX);
X	}
X
X	//
X	// Last update time is now!
X	//
X	pEntry->timeLastUpdate = timeNow;
X		
X	return(1);
X}
X
X
X//+----------------------------------------------------------------------------
X//
X// Function:  CheckPass
X//
X// Synopsis:  Determine whether a given (cleartext) password that the user
X//            wants to use matches any of the passwords in the history file.
X//
X// Arguments: uid_t uidUser - The uid of the user trying to change password
X//            char *szPassword - The (cleartext) proposed new password.
X//            
X// Returns:   int - 1 if the password is good (not present in the user's
X//                  password history) or 0 if the password has already been
X//                  used by this user.
X//
X//+----------------------------------------------------------------------------
Xint CheckPass(uid_t uidUser, char *szPassword)
X{
X	HIST_NODE *pEntry = GetHistEntry(uidUser); // History node pointer 
X	int i;                                     // Loop control
X
X	//
X	// If we do not have a history for this particular user (they have
X	// never had their password set before?) we will let them use this
X	// password.
X	//
X	if (NULL == pEntry)
X	{
X		return(1);
X	}
X	else
X	{
X		//
X		// Otherwise check out this guy's history for matches.
X		//
X		for (i = 0; i < pEntry->iNumPass; i++)
X		{
X			if (!strcmp(crypt(szPassword, pEntry->szPass[i]),
X						pEntry->szPass[i]))
X			{
X				return(0);
X			}
X		}
X	}
X	//
X	// No matches, this is a good password as far as the history is
X	// concerned.
X	//
X	return(1);
X}
X
X
X//+----------------------------------------------------------------------------
X//
X// Function:  GetHistEntry
X//
X// Synopsis:  A binary search on the history table looking for a record
X//            that matches a particular uid target (param).
X//
X// Arguments: uid_t uidTarg - The uid we are seeking
X//            
X// Returns:   HIST_NODE* - A ptr to the record cooresponding to this uid,
X//                         NULL if uid was not found in the table.
X//
X//+----------------------------------------------------------------------------
Xstatic HIST_NODE *GetHistEntry(uid_t uidTarg)
X{
X	unsigned int iLeft = 0;                   // The left (bottom) range idx
X	unsigned int iRight = g_iHistCount;       // The right (high) range idx
X	unsigned int iMid = 0;                    // The midpoint
X	int fFound = 0;                           // Did we find a match?
X	uid_t uidPossible;                        // Value of a possible match.
X
X	//
X	// Pre: we should never have left > right.
X	//
X	if (iLeft > iRight) return(NULL);
X
X	while ((iLeft <= iRight) && !fFound)
X	{
X		iMid = (iLeft + iRight) / 2;
X
X		uidPossible = g_pHist[iMid].uid;
X		if (uidPossible == uidTarg) 
X		{
X			fFound = 1;
X		}
X		else
X		{
X			if (uidTarg < uidPossible)
X			{
X				iRight = iMid - 1;
X			}
X			else
X			{
X				iLeft = iMid + 1;
X			}
X		}
X	}
X
X	if (fFound)
X	{
X		return(&(g_pHist[iMid]));
X	}
X	else
X	{
X		return(NULL);
X	}
X}
X
X
X
X
X
X
X
X
X
X
END-of-passhist.c
echo x - passwd.diff
sed 's/^X//' >passwd.diff << 'END-of-passwd.diff'
X*** passwd/Makefile	Sat Dec 18 05:55:15 1999
X--- passwd/Makefile	Thu Jan 20 20:01:41 2000
X***************
X*** 26,32 ****
X  
X  PROG=	passwd
X  SRCS=	local_passwd.c passwd.c pw_copy.c pw_util.c pw_yp.c \
X! 	yp_passwd.c ypxfr_misc.c ${GENSRCS}
X  GENSRCS=yp.h yp_clnt.c yppasswd.h yppasswd_clnt.c \
X  	yppasswd_private.h yppasswd_private_clnt.c yppasswd_private_xdr.c
X  CFLAGS+=-Wall
X--- 26,32 ----
X  
X  PROG=	passwd
X  SRCS=	local_passwd.c passwd.c pw_copy.c pw_util.c pw_yp.c \
X! 	yp_passwd.c ypxfr_misc.c passhist.c ${GENSRCS}
X  GENSRCS=yp.h yp_clnt.c yppasswd.h yppasswd_clnt.c \
X  	yppasswd_private.h yppasswd_private_clnt.c yppasswd_private_xdr.c
X  CFLAGS+=-Wall
X***************
X*** 42,48 ****
X  	-I${.CURDIR}/../../usr.bin/chpass \
X  	-I${.CURDIR}/../../libexec/ypxfr \
X  	-I${.CURDIR}/../../usr.sbin/rpc.yppasswdd \
X! 	-Dyp_error=warnx -DLOGGING
X  
X  .endif
X  
X--- 42,48 ----
X  	-I${.CURDIR}/../../usr.bin/chpass \
X  	-I${.CURDIR}/../../libexec/ypxfr \
X  	-I${.CURDIR}/../../usr.sbin/rpc.yppasswdd \
X! 	-Dyp_error=warnx -DLOGGING -DPASS_HIST
X  
X  .endif
X  
X*** passwd/local_passwd.c	Fri Aug 27 18:04:51 1999
X--- passwd/local_passwd.c	Thu Jan 20 19:27:54 2000
X***************
X*** 68,73 ****
X--- 68,76 ----
X  #endif
X  
X  #include "extern.h"
X+ #ifdef PASS_HIST
X+ #include "passhist.h"
X+ #endif
X  
X  static uid_t uid;
X  int randinit;
X***************
X*** 101,106 ****
X--- 104,110 ----
X  #endif
X  	char buf[_PASSWORD_LEN+1], salt[10];
X  	struct timeval tv;
X+ 	int fUsePasswdHist = 0;
X  
X  	if (!nis)
X  		(void)printf("Changing local password for %s.\n", pw->pw_name);
X***************
X*** 128,137 ****
X--- 132,161 ----
X  		if (period > (time_t)0) {
X  			pw->pw_change = time(NULL) + period;
X  		}
X+ 
X+ #ifdef PASS_HIST
X+ 		/* 
X+ 		 * Should this user be forced to use password history
X+ 		 * mechanism?
X+ 		 */
X+ 		fUsePasswdHist = (int)login_getcapbool(lc, "usepasswordhist", 0);
X+ #endif
X+ 
X  		login_close(lc);
X  	}
X  #endif
X  
X+ #ifdef PASS_HIST
X+ 	/*
X+ 	 * If we are using password histories, initialize the list now.
X+ 	 */
X+ 	if (fUsePasswdHist)
X+ 	{
X+ 		InitializeList();
X+ 		ReadList();
X+ 	}
X+ #endif
X+ 
X  	for (buf[0] = '\0', tries = 0;;) {
X  		p = getpass("New password:");
X  		if (!*p) {
X***************
X*** 147,152 ****
X--- 171,189 ----
X  			(void)printf("Please don't use an all-lower case password.\nUnusual capitalization, control characters or digits are suggested.\n");
X  			continue;
X  		}
X+ 
X+ #ifdef PASS_HIST
X+ 		if (fUsePasswdHist)
X+ 		{
X+ 			if ((0 == CheckPass(pw->pw_uid, p)) && (uid != 0 || ++tries < 2))
X+ 			{
X+ 				printf("Please do not reuse passwords you have already used "
X+ 					   "recently.\n");
X+ 				continue;
X+ 			}
X+ 		}
X+ #endif
X+ 
X  		(void)strcpy(buf, p);
X  		if (!strcmp(buf, getpass("Retype new password:")))
X  			break;
X***************
X*** 180,185 ****
X--- 217,237 ----
X  	    salt[8] = '\0';
X  	}
X  #endif
X+ 
X+ #ifdef PASS_HIST
X+ 	/*
X+ 	 * If we are using password histories, add this new password to the
X+ 	 * user's history list (so he can't re-use it later for a while) and
X+ 	 * write/close the list.
X+ 	 */
X+ 	if (fUsePasswdHist)
X+ 	{
X+ 		AddPass(pw->pw_uid, crypt(buf, salt));
X+ 		WriteList();
X+ 		CloseList();	
X+ 	}
X+ #endif
X+ 
X  	return (crypt(buf, salt));
X  }
X  
X*** passwd/passwd.1	Fri Aug 27 18:04:51 1999
X--- passwd/passwd.1	Thu Jan 20 20:04:42 2000
X***************
X*** 90,96 ****
X  is set according to 
X  .if t ``passwordtime''
X  .if n "passwordtime"
X! capability in the user's login class.
X  .Pp
X  To change another user's Kerberos password, one must first
X  run
X--- 90,101 ----
X  is set according to 
X  .if t ``passwordtime''
X  .if n "passwordtime"
X! capability in the user's login class.  The boolean 
X! .if t ``usepasswordhist''
X! .if n "usepasswordhist"
X! capability's presence in a user's login class indicates that
X! the user's new password will be checked by the password history 
X! mechanism, which will not allow the reuse of recent old passwords.
X  .Pp
X  To change another user's Kerberos password, one must first
X  run
X***************
X*** 199,204 ****
X--- 204,211 ----
X  The user database
X  .It Pa /etc/passwd 
X  A Version 7 format password file
X+ .It Pa /etc/passwd.hist
X+ Password history file 
X  .It Pa /etc/passwd.XXXXXX
X  Temporary copy of the password file
X  .It Pa /etc/login.conf
END-of-passwd.diff
exit
 
 
>Release-Note:
>Audit-Trail:
Responsible-Changed-From-To: gnats-admin->freebsd-bugs 
Responsible-Changed-By: asmodai 
Responsible-Changed-When: Tue Feb 1 07:40:27 PST 2000 
Responsible-Changed-Why:  
gnats-admin is not a valid person to have a responsibility. 
State-Changed-From-To: open->closed 
State-Changed-By: mike 
State-Changed-When: Sat Jul 21 11:50:41 PDT 2001 
State-Changed-Why:  

FreeBSD: "Tools, not policy." 

http://www.FreeBSD.org/cgi/query-pr.cgi?pr=16244 
>Unformatted:
