[PATCH] Password history support
Aurélien Degrémont
adegremont at idealx.com
Mon Dec 22 17:31:24 GMT 2003
Hi,
Here is a patch which implements password history support to Samba 3.
It add a new field pw_history[] to SAM_ACCOUNT struct. I think it's the
last missing and needed SAM_ACCOUNT field. This patch complete the
previous TDBSAM patch in order to create the new tdbsam format.
The user password history is stored in each sam_account struct. Each
password is coded in a "salted" format. It is done by the following
method : MD5( MD4_NT_PW_HASH + "salt") for security reasons. The
transformation is done by crypt_salted(), I put it util_pw.c (I didn't
know where put it, so it set it there, i let you move it if you know a
better place).
I modified all the password modification code in Samba to add the
password history call, maybe i miss some of it, please complete (the
samba password managing code is far from clear and it is ... scattered :)).
Presently, only ldapsam and tdbsam are supported. SambaSamAccount got a
new attribute.
Waiting for your comments.
Aurélien Degrémont
-------------- next part --------------
diff -ruN samba-3.0.1/examples/LDAP/samba.schema samba-3.0.1-patch/examples/LDAP/samba.schema
--- samba-3.0.1/examples/LDAP/samba.schema 2003-12-04 22:38:34.000000000 +0100
+++ samba-3.0.1-patch/examples/LDAP/samba.schema 2003-12-17 11:09:27.000000000 +0100
@@ -241,6 +241,11 @@
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.48 NAME 'sambaPasswordHistory'
+ DESC 'List of passwords from history'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{768} SINGLE-VALUE )
+
##
## SID, of any type
##
@@ -313,7 +318,8 @@
sambaPwdCanChange $ sambaPwdMustChange $ sambaAcctFlags $
displayName $ sambaHomePath $ sambaHomeDrive $ sambaLogonScript $
sambaProfilePath $ description $ sambaUserWorkstations $
- sambaPrimaryGroupSID $ sambaDomainName $ sambaMungedDial))
+ sambaPrimaryGroupSID $ sambaDomainName $ sambaMungedDial $
+ sambaPasswordHistory))
##
## Group mapping info
diff -ruN samba-3.0.1/source/include/passdb.h samba-3.0.1-patch/source/include/passdb.h
--- samba-3.0.1/source/include/passdb.h 2003-12-04 22:38:36.000000000 +0100
+++ samba-3.0.1-patch/source/include/passdb.h 2003-12-17 11:04:17.000000000 +0100
@@ -62,6 +62,7 @@
PDB_UNKNOWN6,
PDB_LMPASSWD,
PDB_NTPASSWD,
+ PDB_PASSWD_HISTORY,
PDB_BACKEND_PRIVATE_DATA,
/* this must be the last element */
@@ -89,6 +90,9 @@
#define IS_SAM_CHANGED(x, flag) (pdb_get_init_flags(x, flag) == PDB_CHANGED)
#define IS_SAM_DEFAULT(x, flag) (pdb_get_init_flags(x, flag) == PDB_DEFAULT)
+#define PW_HISTORY_SIZE NT_HASH_LEN /* Size, in bytes, of a password in the history */
+#define PW_HISTORY_NBR 24 /* Number of password stored in the history */
+
typedef struct sam_passwd
{
TALLOC_CTX *mem_ctx;
@@ -128,6 +132,7 @@
DATA_BLOB lm_pw; /* .data is Null if no password */
DATA_BLOB nt_pw; /* .data is Null if no password */
+ DATA_BLOB pw_history[PW_HISTORY_NBR]; /* .data is Null if no password */
char* plaintext_pw; /* is Null if not available */
uint16 acct_ctrl; /* account info (ACB_xxxx bit-mask) */
diff -ruN samba-3.0.1/source/include/smbldap.h samba-3.0.1-patch/source/include/smbldap.h
--- samba-3.0.1/source/include/smbldap.h 2003-12-04 22:38:36.000000000 +0100
+++ samba-3.0.1-patch/source/include/smbldap.h 2003-12-17 11:04:17.000000000 +0100
@@ -91,6 +91,7 @@
#define LDAP_ATTR_BAD_PASSWORD_COUNT 35
#define LDAP_ATTR_LOGON_COUNT 36
#define LDAP_ATTR_MUNGED_DIAL 37
+#define LDAP_ATTR_PASSWORD_HISTORY 39
typedef struct _attrib_map_entry {
int attrib;
@@ -145,4 +146,3 @@
struct smbldap_state;
#endif /* _SMBLDAP_H */
-
diff -ruN samba-3.0.1/source/lib/smbldap.c samba-3.0.1-patch/source/lib/smbldap.c
--- samba-3.0.1/source/lib/smbldap.c 2003-12-04 22:38:37.000000000 +0100
+++ samba-3.0.1-patch/source/lib/smbldap.c 2003-12-17 11:04:17.000000000 +0100
@@ -98,6 +98,7 @@
{ LDAP_ATTR_OBJCLASS, "objectClass" },
{ LDAP_ATTR_ACB_INFO, "sambaAcctFlags" },
{ LDAP_ATTR_MUNGED_DIAL, "sambaMungedDial" },
+ { LDAP_ATTR_PASSWORD_HISTORY, "sambaPasswordHistory" },
{ LDAP_ATTR_LIST_END, NULL }
};
diff -ruN samba-3.0.1/source/lib/util_pw.c samba-3.0.1-patch/source/lib/util_pw.c
--- samba-3.0.1/source/lib/util_pw.c 2003-06-07 19:57:33.000000000 +0200
+++ samba-3.0.1-patch/source/lib/util_pw.c 2003-12-17 11:04:17.000000000 +0100
@@ -87,3 +87,27 @@
return alloc_copy_passwd(temp);
}
+
+/**
+ * Crypt an NT password with some "salt" in MD5.
+ * @param nt_pw NT crypted password to be salted.
+ * @return a MD5 16-byte long hashed password salted. NULL if error.
+ */
+uint8 * crypt_salted(const uint8 *nt_pw)
+{
+ uint8 *crypt_pw = NULL;
+ struct MD5Context md5_ctx;
+
+ if ((crypt_pw = (uint8 *) malloc(PW_HISTORY_SIZE*sizeof(uint8))) == NULL) {
+ DEBUG(0,("crypt_salted: Unable to alloc crypt_pw\n"));
+ return(NULL);
+ }
+
+ MD5Init(&md5_ctx);
+ MD5Update(&md5_ctx, nt_pw, 16);
+ /* Salt the crypted password */
+ MD5Update(&md5_ctx, "salt", strlen("salt"));
+ MD5Final((uchar *)crypt_pw, &md5_ctx);
+
+ return(crypt_pw);
+}
diff -ruN samba-3.0.1/source/passdb/passdb.c samba-3.0.1-patch/source/passdb/passdb.c
--- samba-3.0.1/source/passdb/passdb.c 2003-12-17 11:09:48.000000000 +0100
+++ samba-3.0.1-patch/source/passdb/passdb.c 2003-12-17 11:07:16.000000000 +0100
@@ -115,9 +115,14 @@
static void destroy_pdb_talloc(SAM_ACCOUNT **user)
{
+ uint8 i;
+
if (*user) {
data_blob_clear_free(&((*user)->private.lm_pw));
data_blob_clear_free(&((*user)->private.nt_pw));
+ for (i=0; i<PW_HISTORY_NBR; i++)
+ if (pdb_get_passwd_history(*user,i) != NULL)
+ data_blob_clear_free(&((*user)->private.pw_history[i]));
if((*user)->private.plaintext_pw!=NULL)
memset((*user)->private.plaintext_pw,'\0',strlen((*user)->private.plaintext_pw));
@@ -343,6 +348,7 @@
static void pdb_free_sam_contents(SAM_ACCOUNT *user)
{
+ uint8 i;
/* Kill off sensitive data. Free()ed by the
talloc mechinism */
@@ -352,6 +358,10 @@
if (user->private.plaintext_pw!=NULL)
memset(user->private.plaintext_pw,'\0',strlen(user->private.plaintext_pw));
+ for (i=0; i<PW_HISTORY_NBR; i++)
+ if (pdb_get_passwd_history(user, i) != NULL)
+ data_blob_clear_free(&(user->private.pw_history[i]));
+
if (user->private.backend_private_data && user->private.backend_private_data_free_fn) {
user->private.backend_private_data_free_fn(&user->private.backend_private_data);
}
@@ -1033,6 +1043,12 @@
pdb_free_sam(&sam_pass);
return False;
}
+
+ if (!pdb_add_passwd_history (sam_pass, pdb_get_nt_passwd(sam_pass), PDB_CHANGED)) {
+ slprintf(err_str, err_str_len-1, "Failed to set password history for user %s.\n", user_name);
+ pdb_free_sam(&sam_pass);
+ return False;
+ }
}
if (local_flags & LOCAL_ADD_USER) {
@@ -1296,7 +1312,7 @@
*********************************************************************/
#define TDB_FORMAT_STRING_V0 "ddddddBBBBBBBBBBBBddBBwdwdBwwd"
-#define TDB_FORMAT_STRING_V1 "dddddddBBBBBBBBBBBBddBBwdwdBwwd"
+#define TDB_FORMAT_STRING_V1 "dddddddBBBBBBBBBBBBddBBBwdwdBwwd"
/**********************************************************************
Intialize a SAM_ACCOUNT struct from a BYTE buffer of size len
@@ -1766,10 +1782,12 @@
uint16 acct_ctrl, logon_divs;
uint16 bad_password_count, logon_count;
uint8 *hours;
- static uint8 *lm_pw_ptr, *nt_pw_ptr;
+ static uint8 *lm_pw_ptr, *nt_pw_ptr, *pw_history_ptr;
uint32 len = 0;
- uint32 lm_pw_len, nt_pw_len, hourslen;
+ uint32 lm_pw_len, nt_pw_len, hourslen, pw_history_len;
+ uint8 *pw_history=NULL;
BOOL ret = True;
+ uint8 i=0;
if(sampass == NULL || buf == NULL) {
DEBUG(0, ("init_sam_from_buffer: NULL parameters found!\n"));
@@ -1801,6 +1819,7 @@
&group_rid,
&lm_pw_len, &lm_pw_ptr,
&nt_pw_len, &nt_pw_ptr,
+ &pw_history_len, &pw_history_ptr,
&acct_ctrl,
&unknown_3,
&logon_divs,
@@ -1878,6 +1897,13 @@
}
}
+ if (pw_history_len && pw_history_ptr) {
+ pw_history_len /= PW_HISTORY_SIZE;
+ for (i=0; i < pw_history_len; i++) {
+ pdb_set_passwd_history(sampass, pw_history_ptr + i*PW_HISTORY_SIZE, i, PDB_SET);
+ }
+ }
+
pdb_set_user_sid_from_rid(sampass, user_rid, PDB_SET);
pdb_set_group_sid_from_rid(sampass, group_rid, PDB_SET);
pdb_set_unknown_3(sampass, unknown_3, PDB_SET);
@@ -1904,6 +1930,7 @@
SAFE_FREE(munged_dial);
SAFE_FREE(unknown_str);
SAFE_FREE(hours);
+ SAFE_FREE(pw_history_ptr);
return ret;
}
@@ -1945,8 +1972,11 @@
const uint8 *lm_pw;
const uint8 *nt_pw;
+ uint8 *pw_history=NULL, *pw=NULL;
uint32 lm_pw_len = 16;
uint32 nt_pw_len = 16;
+ uint32 pw_history_len = 1;
+ uint8 i=0;
/* do we have a valid SAM_ACCOUNT pointer? */
if (sampass == NULL) {
@@ -2061,6 +2091,27 @@
else
munged_dial_len = 0;
+
+ /* Alloc pointer to create the password history buffer */
+ if ((pw_history = (uint8 *) malloc(pw_history_len)) == NULL) {
+ DEBUG(0,("init_buffer_from_sam: Unable to malloc() memory for password history!\n"));
+ return (-1);
+ }
+ /* Store each password of the history into the buffer */
+ for(i=0; i < PW_HISTORY_NBR && ((pw = (uint8 *) pdb_get_passwd_history(sampass, i)) != NULL); i++) {
+ pw_history_len += PW_HISTORY_SIZE;
+ if ((pw_history = (uint8 *) realloc(pw_history, pw_history_len)) == NULL) {
+ DEBUG(0,("init_buffer_from_sam: Unable to realloc() memory for password history!\n"));
+ return (-1);
+ }
+ if (memcpy(pw_history + pw_history_len - PW_HISTORY_SIZE - 1, pw, PW_HISTORY_SIZE) == NULL) {
+ DEBUG(0,("init_buffer_from_sam: Unable to memcpy() memory of password history!\n"));
+ SAFE_FREE(pw_history);
+ return (-1);
+ }
+ }
+ pw_history[pw_history_len - 1] = '\0';
+
/* one time to get the size needed */
len = tdb_pack(NULL, 0, TDB_FORMAT_STRING_v1,
logon_time,
@@ -2086,6 +2137,7 @@
group_rid,
lm_pw_len, lm_pw,
nt_pw_len, nt_pw,
+ pw_history_len, pw_history,
pdb_get_acct_ctrl(sampass),
pdb_get_unknown_3(sampass),
pdb_get_logon_divs(sampass),
@@ -2102,6 +2154,7 @@
/* malloc the space needed */
if ( (*buf=(uint8*)malloc(len)) == NULL) {
DEBUG(0,("init_buffer_from_sam: Unable to malloc() memory for buffer!\n"));
+ SAFE_FREE(pw_history);
return (-1);
}
@@ -2130,6 +2183,7 @@
group_rid,
lm_pw_len, lm_pw,
nt_pw_len, nt_pw,
+ pw_history_len, pw_history,
pdb_get_acct_ctrl(sampass),
pdb_get_unknown_3(sampass),
pdb_get_logon_divs(sampass),
@@ -2139,6 +2193,7 @@
pdb_get_logon_count(sampass),
pdb_get_unknown_6(sampass));
+ SAFE_FREE(pw_history);
/* check to make sure we got it correct */
if (buflen != len) {
diff -ruN samba-3.0.1/source/passdb/pdb_get_set.c samba-3.0.1-patch/source/passdb/pdb_get_set.c
--- samba-3.0.1/source/passdb/pdb_get_set.c 2003-11-07 18:37:36.000000000 +0100
+++ samba-3.0.1-patch/source/passdb/pdb_get_set.c 2003-12-17 11:04:17.000000000 +0100
@@ -338,6 +338,16 @@
return (-1);
}
+const uint8 * pdb_get_passwd_history (const SAM_ACCOUNT *sampass, uint8 index)
+{
+
+ if (sampass && index < PW_HISTORY_NBR) {
+ return (sampass->private.pw_history[index].data);
+ }
+ else
+ return (NULL);
+}
+
void *pdb_get_backend_private_data (const SAM_ACCOUNT *sampass, const struct pdb_methods *my_methods)
{
if (sampass && my_methods == sampass->private.backend_private_methods)
@@ -1128,3 +1138,51 @@
return True;
}
+
+
+/**
+ * Set a user's history password. The provided password must be correctly crypted.
+ * @param pwd must be a 16-byte NT_HASH already salted.
+ **/
+
+BOOL pdb_set_passwd_history (SAM_ACCOUNT *sampass, const uint8 *pwd, uint8 index, enum pdb_value_state flag)
+{
+
+ if (!sampass)
+ return False;
+
+ if (index >= PW_HISTORY_NBR)
+ return False;
+
+ data_blob_clear_free(&sampass->private.pw_history[index]);
+ sampass->private.pw_history[index] = data_blob(pwd, pwd ? PW_HISTORY_SIZE : 0);
+
+ return pdb_set_init_flags(sampass, PDB_PASSWD_HISTORY, flag);
+}
+
+
+/**
+ * Add a password to the password history. The provided password will be salted.
+ * @param pwd must be a 16-byte NT_HASH non salted.
+ **/
+
+BOOL pdb_add_passwd_history (SAM_ACCOUNT *sampass, const uint8 *pwd, enum pdb_value_state flag)
+{
+ uint8 *salt_pw = NULL;
+
+ if (!sampass)
+ return False;
+
+ data_blob_clear_free(&sampass->private.pw_history[PW_HISTORY_NBR - 1]);
+
+ memmove(&sampass->private.pw_history[1], sampass->private.pw_history, (sizeof(sampass->private.pw_history) - sizeof(sampass->private.pw_history[0])));
+
+ if (pwd)
+ salt_pw = crypt_salted(pwd);
+
+ sampass->private.pw_history[0] = data_blob(salt_pw, salt_pw ? PW_HISTORY_SIZE : 0);
+
+ SAFE_FREE(salt_pw);
+
+ return pdb_set_init_flags(sampass, PDB_PASSWD_HISTORY, flag);
+}
diff -ruN samba-3.0.1/source/passdb/pdb_ldap.c samba-3.0.1-patch/source/passdb/pdb_ldap.c
--- samba-3.0.1/source/passdb/pdb_ldap.c 2003-12-04 22:38:38.000000000 +0100
+++ samba-3.0.1-patch/source/passdb/pdb_ldap.c 2003-12-17 11:04:17.000000000 +0100
@@ -426,7 +426,8 @@
logon_count = 0;
uint32 hours_len;
uint8 hours[MAX_HOURS_LEN];
- pstring temp;
+ pstring temp, pw_history;
+ uint8 i=0, pw_history_len=0;
/*
* do a little initialization
@@ -696,6 +697,20 @@
}
if (!smbldap_get_single_attribute (ldap_state->smbldap_state->ldap_struct, entry,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_PASSWORD_HISTORY), temp)) {
+ /* leave as default */
+ } else {
+ pw_history_len = strlen(temp) / 32;
+ for(i=0; i<pw_history_len; i++) {
+ pdb_gethexpwd(temp+i*32, pw_history);
+ if (!pdb_set_passwd_history(sampass, pw_history, i, PDB_SET))
+ return False;
+ }
+ memset((char *)temp, '\0', strlen(temp)+1);
+ ZERO_STRUCT(pw_history);
+ }
+
+ if (!smbldap_get_single_attribute (ldap_state->smbldap_state->ldap_struct, entry,
get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_ACB_INFO), temp)) {
acct_ctrl |= ACB_NORMAL;
} else {
@@ -750,6 +765,7 @@
{
pstring temp;
uint32 rid;
+ uint8 i=0;
if (mods == NULL || sampass == NULL) {
DEBUG(0, ("init_ldap_from_sam: NULL parameters found!\n"));
@@ -928,6 +944,15 @@
get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_NTPW),
temp);
+ for(i=0;pdb_get_passwd_history(sampass,i) != NULL; i++)
+ pdb_sethexpwd (temp+i*32, pdb_get_passwd_history(sampass, i),
+ pdb_get_acct_ctrl(sampass));
+
+ if (need_update(sampass, PDB_PASSWD_HISTORY))
+ smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_PASSWORD_HISTORY),
+ temp);
+
slprintf (temp, sizeof (temp) - 1, "%li", pdb_get_pass_last_set_time(sampass));
if (need_update(sampass, PDB_PASSLASTSET))
smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods,
diff -ruN samba-3.0.1/source/rpc_server/srv_samr_nt.c samba-3.0.1-patch/source/rpc_server/srv_samr_nt.c
--- samba-3.0.1/source/rpc_server/srv_samr_nt.c 2003-12-10 22:59:18.000000000 +0100
+++ samba-3.0.1-patch/source/rpc_server/srv_samr_nt.c 2003-12-17 11:04:17.000000000 +0100
@@ -2667,6 +2667,11 @@
return False;
}
+ if (!pdb_add_passwd_history(pwd, id12->nt_pwd, PDB_CHANGED)) {
+ pdb_free_sam(&pwd);
+ return False;
+ }
+
if(!pdb_update_sam_account(pwd)) {
pdb_free_sam(&pwd);
return False;
@@ -2825,6 +2830,11 @@
return False;
}
+ if (!pdb_add_passwd_history(pwd, pdb_get_nt_passwd(pwd), PDB_CHANGED)) {
+ pdb_free_sam(&pwd);
+ return False;
+ }
+
copy_id23_to_sam_passwd(pwd, id23);
/* if it's a trust account, don't update /etc/passwd */
@@ -2891,6 +2901,11 @@
return False;
}
+ if (!pdb_add_passwd_history(pwd, pdb_get_nt_passwd(pwd), PDB_CHANGED)) {
+ pdb_free_sam(&pwd);
+ return False;
+ }
+
/* if it's a trust account, don't update /etc/passwd */
if ( ( (acct_ctrl & ACB_DOMTRUST) == ACB_DOMTRUST ) ||
( (acct_ctrl & ACB_WSTRUST) == ACB_WSTRUST) ||
diff -ruN samba-3.0.1/source/smbd/chgpasswd.c samba-3.0.1-patch/source/smbd/chgpasswd.c
--- samba-3.0.1/source/smbd/chgpasswd.c 2003-12-04 22:38:38.000000000 +0100
+++ samba-3.0.1-patch/source/smbd/chgpasswd.c 2003-12-17 11:04:17.000000000 +0100
@@ -908,8 +908,12 @@
NTSTATUS change_oem_password(SAM_ACCOUNT *hnd, char *old_passwd, char *new_passwd, BOOL as_root)
{
BOOL ret;
- uint32 min_len;
+ uint32 min_len, nbr_pw;
+ uint8 crypt_pw[PW_HISTORY_SIZE];
+ uint8 *hist_pw=NULL, *salt_pw=NULL;
+ uint8 i=0;
+
if (time(NULL) < pdb_get_pass_can_change_time(hnd)) {
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))));
@@ -934,6 +938,24 @@
/* return NT_STATUS_PWD_TOO_SHORT; */
}
+ /* Test the password against the password history */
+ if (account_policy_get(AP_PASSWORD_HISTORY, &nbr_pw) && nbr_pw > 0) {
+
+ /* Crypt and salt the clear password */
+ E_md4hash(new_passwd, crypt_pw);
+ salt_pw = crypt_salted(crypt_pw);
+
+ for(i=0; i<nbr_pw && ((hist_pw = (uint8*) pdb_get_passwd_history(hnd, i)) != NULL); i++) {
+ if (hist_pw != NULL && memcmp(salt_pw, hist_pw, PW_HISTORY_SIZE) == 0) {
+ DEBUG(1, ("Password Change: the new password for user %s is already present in history, it cannot be used.\n", pdb_get_username(hnd)));
+ SAFE_FREE(salt_pw);
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+ }
+ SAFE_FREE(salt_pw);
+ }
+
+
/* TODO: Add cracklib support here */
/*
@@ -957,6 +979,11 @@
return NT_STATUS_ACCESS_DENIED;
}
+ /* Add the password to the history */
+ if (!pdb_add_passwd_history(hnd, crypt_pw, PDB_CHANGED)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
/* Now write it into the file. */
ret = pdb_update_sam_account (hnd);
diff -ruN samba-3.0.1/source/utils/pdbedit.c samba-3.0.1-patch/source/utils/pdbedit.c
--- samba-3.0.1/source/utils/pdbedit.c 2003-12-04 22:38:40.000000000 +0100
+++ samba-3.0.1-patch/source/utils/pdbedit.c 2003-12-17 11:04:17.000000000 +0100
@@ -358,6 +358,7 @@
}
pdb_set_plaintext_passwd(sam_pwent, password1);
+ pdb_add_passwd_history(sam_pwent, pdb_get_nt_passwd(sam_pwent), PDB_CHANGED);
memset(password1, 0, strlen(password1));
SAFE_FREE(password1);
memset(password2, 0, strlen(password2));
More information about the samba-technical
mailing list