Patch password history for samba-3.0.5pre1, ldap backend

Jianliang Lu j.lu at tiesse.com
Wed Jun 9 09:44:35 GMT 2004


I ported the patch on 3.0.5pre1, the reason of this post is to complete the 
password policy, that was partially integrated on Samba. We are using this 
policy in production for about 2 years, and we'd like that the samba.schema 
will not be changed frequently. In this patch I've fixed also the 
bad_password_count update problem.

Jianliang Lu
TieSse s.p.a.     Ivrea (To) - Italy
j.lu at tiesse.com   luj at libero.it
http://www.tiesse.com


--------------------- Patch --------------------------

--- samba-3.0.5pre1/source/smbd/chgpasswd.c.orig	Wed Jun  9 09:23:20 
2004
+++ samba-3.0.5pre1/source/smbd/chgpasswd.c	Wed Jun  9 09:23:20 2004
@@ -933,6 +934,47 @@
 }
 
 /***********************************************************
+ This routine takes the given password and checks it against
+ the password history.
+************************************************************/
+static BOOL check_passwd_history(SAM_ACCOUNT *sampass, const char *plaintext)
+{
+    uchar new_lanman_p16[16];
+    uchar new_nt_p16[16];
+	uint8 *nt_pw;
+	uint8 *pwhistory;
+	BOOL found = False;
+	int i, pwHisLen;
+
+	account_policy_get(AP_PASSWORD_HISTORY, &pwHisLen); 
+	if (pwHisLen == 0)
+		return False;
+
+
+	pwhistory = (uint8 *) pdb_get_pw_history (sampass);
+
+	if (!pwhistory)
+		return False;
+
+	nt_pw = (uint8*) pdb_get_nt_passwd(sampass);
+
+	E_md4hash(plaintext, new_nt_p16);
+	
+	if (!memcmp(nt_pw, new_nt_p16, 16))
+			return True;
+
+	dump_data(10, new_nt_p16, 16);
+	dump_data(10, pwhistory, 16*pwHisLen);
+	for (i=0; i<pwHisLen; i++){
+		if (!memcmp((uint8 *) (pwhistory + i*16), new_nt_p16, 16))
+			found = True;
+	}
+
+	return found;
+}
+
+/***********************************************************
+/***********************************************************
  Code to change the oem password. Changes both the lanman
  and NT hashes.  Old_passwd is almost always NULL.
  NOTE this function is designed to be called as root. Check the old password
@@ -946,10 +988,10 @@
 	BOOL ret;
 	uint32 min_len;
 
-	if (time(NULL) < pdb_get_pass_can_change_time(hnd)) {
+	if ((time(NULL) < pdb_get_pass_can_change_time(hnd)) && 
(pdb_get_pass_must_change_time(hnd) != 0)) {
 		DEBUG(1, ("user %s cannot change password now, must wait 
until %s\n", 
 			  pdb_get_username(hnd), http_timestring
(pdb_get_pass_can_change_time(hnd))));
-		return NT_STATUS_PASSWORD_RESTRICTION;
+		return NT_STATUS_ACCOUNT_RESTRICTION;
 	}
 
 	if (account_policy_get(AP_MIN_PASSWORD_LEN, &min_len) && (strlen
(new_passwd) < min_len)) {
@@ -970,6 +1012,9 @@
 /* 		return NT_STATUS_PWD_TOO_SHORT; */
 	}
 
+    if (check_passwd_history(hnd,new_passwd))
+	        return NT_STATUS_PASSWORD_RESTRICTION;
+
 	pass = Get_Pwnam(pdb_get_username(hnd));
 	if (!pass) {
 		DEBUG(1, ("check_oem_password: Username does not exist in 
system !?!\n"));

--- samba-3.0.5pre1/source/include/passdb.h.orig	Wed Jun  9 09:23:20 
2004
+++ samba-3.0.5pre1/source/include/passdb.h	Wed Jun  9 15:11:32 2004
@@ -99,6 +99,7 @@
 	PDB_UNKNOWN6,
 	PDB_LMPASSWD,
 	PDB_NTPASSWD,
+	PDB_PWHISTORY,
 	PDB_BACKEND_PRIVATE_DATA,
 
 	/* this must be the last element */
@@ -169,6 +170,7 @@
 		const char * workstations; /* login from workstations string 
*/
 		const char * unknown_str ; /* don't know what this is, yet. */
 		const char * munged_dial ; /* munged path name and dial-back 
tel number */
+		const uint8 * nt_pw_his; /* nt hashed password history */
 		
 		DOM_SID user_sid;    /* Primary User SID */
 		DOM_SID group_sid;   /* Primary Group SID */

--- samba-3.0.5pre1/source/passdb/pdb_get_set.c.orig	Wed Jun  9 09:23:20 
2004
+++ samba-3.0.5pre1/source/passdb/pdb_get_set.c	Wed Jun  9 15:12:21 2004
@@ -150,6 +150,15 @@
 		return (NULL);
 }
 
+const uint8* pdb_get_pw_history (const SAM_ACCOUNT *sampass)
+{
+	if (sampass) {
+		return ((uint8*)sampass->private.nt_pw_his);
+	}
+	else
+		return (NULL);
+}
+
 /* Return the plaintext password if known.  Most of the time
    it isn't, so don't assume anything magic about this function.
    
@@ -982,6 +991,31 @@
 }
 
 /*********************************************************************
+ Set the user's password history hash.
+ ********************************************************************/
+
+BOOL pdb_set_pw_history (SAM_ACCOUNT *sampass, const uint8 * pwd, int 
historyLen, enum pdb_value_state flag)
+{
+	if (!sampass)
+		return False;
+	
+	if (historyLen > 0){
+		if (pwd){
+			sampass->private.nt_pw_his = (uint8 *) talloc_memdup
(sampass->mem_ctx, pwd, historyLen*16);
+
+			if (!sampass->private.nt_pw_his) {
+				DEBUG(0, ("pdb_set_pw_his: talloc_memdup() 
failed!\n")); 
+				return False;
+			}
+		} else {
+			sampass->private.nt_pw_his = NULL;  
+		}
+	}
+
+	return pdb_set_init_flags(sampass, PDB_PWHISTORY, flag);
+}
+
+/*********************************************************************
  Set the user's plaintext password only (base procedure, see helper
  below)
  ********************************************************************/
@@ -1135,10 +1169,18 @@
 {
 	uchar new_lanman_p16[16];
 	uchar new_nt_p16[16];
+	uchar *pwhistory;
+    uchar nt_pw[16];
+    uchar * ntpw;
+    int pwHistLen;
 
 	if (!sampass || !plaintext)
 		return False;
 	
+    ntpw = (uint8 *)pdb_get_nt_passwd(sampass);
+    if (ntpw)
+        memcpy ((uchar *)nt_pw, (uchar *)ntpw, 16);
+
 	/* Calculate the MD4 hash (NT compatible) of the password */
 	E_md4hash(plaintext, new_nt_p16);
 
@@ -1164,6 +1206,20 @@
 	if (!pdb_set_pass_changed_now (sampass))
 		return False;
 
+	if (pdb_get_acct_ctrl(sampass) & ACB_NORMAL) { 
+		account_policy_get(AP_PASSWORD_HISTORY, &pwHistLen);
+			if (pwHistLen > 0){
+			pwhistory = (uint8 *)pdb_get_pw_history (sampass);
+			if (pwhistory){
+				memmove((uchar *) (pwhistory + 16), 
pwhistory, (pwHistLen -1)*16 );
+				memcpy((uchar *)pwhistory, (uchar *)nt_pw, 
16);
+				pdb_set_pw_history (sampass, pwhistory, 
pwHistLen, PDB_CHANGED);
+			}
+			else {
+				DEBUG (100,("pdb_get_set.c: 
pdb_set_plaintext_passwd: pwhistory was NULL!\n"));
+			}
+		}
+	}
 	return True;
 }
 
--- samba-3.0.5pre1/examples/LDAP/samba.schema.orig	Wed Jun  9 15:03:03 
2004
+++ samba-3.0.5pre1/examples/LDAP/samba.schema	Wed Jun  9 15:14:07 2004
@@ -212,7 +212,6 @@
 	EQUALITY integerMatch
 	SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
 
-
 ##
 ## string settings
 ##
@@ -251,6 +250,10 @@
 	EQUALITY caseExactMatch
 	SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} )
 
+attributetype ( 1.3.6.1.4.1.7165.2.1.50 NAME 'sambaPasswordHistory'
+	DESC 'MD4 hash of the unicode password'
+	EQUALITY caseIgnoreIA5Match
+	SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} )
 ##
 ## SID, of any type
 ##
@@ -329,7 +332,7 @@
                displayName $ sambaHomePath $ sambaHomeDrive $ 
sambaLogonScript $
 	       sambaProfilePath $ description $ sambaUserWorkstations $
 	       sambaPrimaryGroupSID $ sambaDomainName $ sambaMungedDial $
-	       sambaBadPasswordCount $ sambaBadPasswordTime))
+		   sambaBadPasswordCount $ sambaBadPasswordTime $ 
sambaPasswordHistory))
 
 ##
 ## Group mapping info

--- samba-3.0.5pre1/source/lib/smbldap.c.orig	Wed Jun  9 09:23:20 2004
+++ samba-3.0.5pre1/source/lib/smbldap.c	Wed Jun  9 15:14:55 2004
@@ -100,6 +100,8 @@
 	{ LDAP_ATTR_MUNGED_DIAL,	"sambaMungedDial"	},
 	{ LDAP_ATTR_BAD_PASSWORD_COUNT,	"sambaBadPasswordCount" },
 	{ LDAP_ATTR_BAD_PASSWORD_TIME,	"sambaBadPasswordTime" 	},
+	{ LDAP_ATTR_PWD_HISTORY,    "sambaPasswordHistory"  },
+	{ LDAP_ATTR_MOD_TIMESTAMP,  "modifyTimestamp"   },
 	{ LDAP_ATTR_LIST_END,		NULL 			}
 };
 
@@ -330,6 +332,45 @@
 					    sizeof(pstring));
 }
 
+
+/*******************************************************************
+ Search an attribute and return the multivalues found.
+******************************************************************/
+
+BOOL smbldap_get_single_attribute_multivalue (LDAP * ldap_struct, 
LDAPMessage * entry,
+				   const char *attribute, char *value, int 
len, int * count)
+{
+	char **values;
+	int i;  
+	
+	if ( !attribute )
+		return False;
+		
+	value[0] = '\0';
+
+	if ((values = ldap_get_values (ldap_struct, entry, attribute)) == 
NULL) {
+		DEBUG (10, ("smbldap_get_single_attribute_multivalue: [%s] = 
[<does not exist>]\n", attribute));
+		
+		return False;
+	}
+	
+	for (i = 0; (values[i] != NULL) && (i < len); i++){
+		if (convert_string(CH_UTF8, CH_UNIX,values[i], -1,(value + 
i*sizeof(pstring)), sizeof(pstring), False) == (size_t)-1) {
+			DEBUG(1, ("smbldap_get_single_attribute_multivalue: 
string conversion of [%s] = [%s] failed!\n", 
+				  attribute, values[i]));
+			ldap_value_free(values);
+			return False;
+		}
+	}
+	
+	*count = i;
+	ldap_value_free(values);
+#ifdef DEBUG_PASSWORDS
+	DEBUG (100, ("smbldap_get_single_attribute: [%s] = [%s]\n", 
attribute, value));
+#endif	
+	return True;
+}
+
 /************************************************************************
  Routine to manage the LDAPMod structure array
  manage memory used by the array, by each struct, and values

--- samba-3.0.5pre1/source/include/smbldap.h.orig	Wed Jun  9 09:23:20 
2004
+++ samba-3.0.5pre1/source/include/smbldap.h	Wed Jun  9 15:16:22 2004
@@ -93,8 +93,9 @@
 #define LDAP_ATTR_LOGON_COUNT		36
 #define LDAP_ATTR_MUNGED_DIAL		37
 #define LDAP_ATTR_BAD_PASSWORD_TIME	38
-
+#define LDAP_ATTR_PWD_HISTORY      39 
 #define LDAP_ATTR_SID_LIST              40
+#define LDAP_ATTR_MOD_TIMESTAMP     41
 
 typedef struct _attrib_map_entry {
 	int		attrib;

--- samba-3.0.5pre1/source/passdb/pdb_ldap.c.orig	Wed Jun  9 09:23:20 
2004
+++ samba-3.0.5pre1/source/passdb/pdb_ldap.c	Wed Jun  9 15:13:16 2004
@@ -81,7 +81,6 @@
 #define SAM_ACCOUNT struct sam_passwd
 #endif
 
-#define MODIFY_TIMESTAMP_STRING "modifyTimestamp"
 
 #include "smbldap.h"
 
@@ -301,7 +300,8 @@
 		   really exist. */
 
 		for (attrib = attrs; *attrib != NULL; attrib++) {
-			if (StrCaseCmp(*attrib, name) == 0) {
+			if ((StrCaseCmp(*attrib, name) == 0) && 
+			!(StrCaseCmp(*attrib, get_userattr_key2string
(ldap_state->schema_ver, LDAP_ATTR_MOD_TIMESTAMP)))) {
 				DEBUG(10, ("ldapsam_delete_entry: deleting 
attribute %s\n", name));
 				smbldap_set_mod(&mods, LDAP_MOD_DELETE, name, 
NULL);
 			}
@@ -400,8 +400,11 @@
 	pstring temp;	
 	struct tm tm;
 
-	if (!smbldap_get_single_pstring(ldap_state->smbldap_state-
>ldap_struct,
-					entry, MODIFY_TIMESTAMP_STRING, 
temp)) 
+	if (!smbldap_get_single_pstring(
+	            ldap_state->smbldap_state->ldap_struct, entry,
+	            get_userattr_key2string(ldap_state->schema_ver,
+                LDAP_ATTR_MOD_TIMESTAMP),
+                temp))
 		return (time_t) 0;
 
 	strptime(temp, "%Y%m%d%H%M%SZ", &tm);
@@ -448,6 +451,9 @@
 	uint8 		hours[MAX_HOURS_LEN];
 	pstring temp;
 	LOGIN_CACHE	*cache_entry = NULL;
+    uint8   *pwhist;
+    int     i, pwcount, pwHistLen;
+    char    *temp1;
 
 	/*
 	 * do a little initialization
@@ -694,6 +700,37 @@
 		ZERO_STRUCT(smbntpwd);
 	}
 
+
+	account_policy_get(AP_PASSWORD_HISTORY, &pwHistLen);
+	if (pwHistLen > 0){
+		if ((temp1 = (char  *) malloc(sizeof(pstring) * pwHistLen)) 
== NULL) {
+			DEBUG(0, ("pdb_ldap.c: init_sam_from_ldap: malloc 
failed!\n"));
+			return False;
+		}
+		memset(temp1, 0, sizeof(pstring)*pwHistLen);
+		if ((pwhist = (uint8 *) malloc(16 * pwHistLen)) == NULL){
+			DEBUG(0, ("pdb_ldap.c: init_sam_from_ldap: malloc 
failed!\n"));
+			return False;
+		}
+		memset((uint8 *) pwhist, 0, 16*pwHistLen);
+		if (!smbldap_get_single_attribute_multivalue (ldap_state-
>smbldap_state->ldap_struct, entry, get_userattr_key2string(ldap_state-
>schema_ver, LDAP_ATTR_PWD_HISTORY), temp1, pwHistLen, &pwcount)) {
+			/* leave as default */
+		} else {
+			for (i = 0; (i < pwHistLen) && (i < pwcount); i++){
+				pdb_gethexpwd((char *)(temp1 + i*sizeof
(pstring)), smbntpwd);
+				memcpy((unsigned char *) (pwhist + i*16), 
smbntpwd, 16);
+				ZERO_STRUCT(smbntpwd);
+			}
+		}
+		if (!pdb_set_pw_history(sampass, pwhist, pwHistLen, PDB_SET)){
+			SAFE_FREE (temp1);
+			SAFE_FREE (pwhist);
+			return False;
+		}
+		SAFE_FREE (temp1);
+		SAFE_FREE (pwhist);
+	}
+
 	if (!smbldap_get_single_pstring (ldap_state->smbldap_state-
>ldap_struct, entry,
 			get_userattr_key2string(ldap_state->schema_ver, 
LDAP_ATTR_ACB_INFO), temp)) {
 		acct_ctrl |= ACB_NORMAL;
@@ -793,6 +830,8 @@
 {
 	pstring temp;
 	uint32 rid;
+    uint8 * temp1;
+    int pwHistLen, i;
 
 	if (mods == NULL || sampass == NULL) {
 		DEBUG(0, ("init_ldap_from_sam: NULL parameters found!\n"));
@@ -985,6 +1024,26 @@
 			}
 		}
 
+
+		if (need_update(sampass, PDB_PWHISTORY)) {
+			account_policy_get(AP_PASSWORD_HISTORY, &pwHistLen);
+			temp1 = (uint8 *) pdb_get_pw_history(sampass);
+			if (temp1 != NULL)
+				for (i=0; i< pwHistLen; i++){
+					pdb_sethexpwd (temp, (uint8 *)(temp1 
+ i*16), 0);
+					DEBUG(100, ("temp1=%s, temp=%s\n", 
temp1, temp));
+					if (!strncmp(temp,"0000000000",10))
+						break;
+					smbldap_set_mod 
(mods,LDAP_MOD_REPLACE, get_userattr_key2string(ldap_state-
>schema_ver,LDAP_ATTR_PWD_HISTORY),temp);
+					/*
+					smbldap_make_mod(ldap_state-
>smbldap_state->ldap_struct, existing,
+								mods, 
get_userattr_key2string(ldap_state->schema_ver, 
+									
						 LDAP_ATTR_PWD_HISTORY), 
+									
						 temp);
+									
						 */
+				}
+		}
+
 		if (need_update(sampass, PDB_PASSLASTSET)) {
 			slprintf (temp, sizeof (temp) - 1, "%li", 
pdb_get_pass_last_set_time(sampass));
 			smbldap_make_mod(ldap_state->smbldap_state-
>ldap_struct, existing, mods,




More information about the samba-technical mailing list