diff options
-rw-r--r-- | examples/LDAP/samba.schema | 7 | ||||
-rw-r--r-- | source3/include/passdb.h | 8 | ||||
-rw-r--r-- | source3/include/smbldap.h | 5 | ||||
-rw-r--r-- | source3/lib/smbldap.c | 24 | ||||
-rw-r--r-- | source3/passdb/pdb_get_set.c | 90 | ||||
-rw-r--r-- | source3/passdb/pdb_ldap.c | 72 | ||||
-rw-r--r-- | source3/passdb/pdb_tdb.c | 1 | ||||
-rw-r--r-- | source3/smbd/chgpasswd.c | 85 |
8 files changed, 251 insertions, 41 deletions
diff --git a/examples/LDAP/samba.schema b/examples/LDAP/samba.schema index 71c954a0c0..0ad94f973d 100644 --- a/examples/LDAP/samba.schema +++ b/examples/LDAP/samba.schema @@ -251,6 +251,11 @@ attributetype ( 1.3.6.1.4.1.7165.2.1.47 NAME 'sambaMungedDial' 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 +334,7 @@ objectclass ( 1.3.6.1.4.1.7165.2.2.6 NAME 'sambaSamAccount' SUP top AUXILIARY displayName $ sambaHomePath $ sambaHomeDrive $ sambaLogonScript $ sambaProfilePath $ description $ sambaUserWorkstations $ sambaPrimaryGroupSID $ sambaDomainName $ sambaMungedDial $ - sambaBadPasswordCount $ sambaBadPasswordTime)) + sambaBadPasswordCount $ sambaBadPasswordTime $ sambaPasswordHistory)) ## ## Group mapping info diff --git a/source3/include/passdb.h b/source3/include/passdb.h index d08fd13a72..7d3e0014b6 100644 --- a/source3/include/passdb.h +++ b/source3/include/passdb.h @@ -99,6 +99,7 @@ enum pdb_elements { PDB_UNKNOWN6, PDB_LMPASSWD, PDB_NTPASSWD, + PDB_PWHISTORY, PDB_BACKEND_PRIVATE_DATA, /* this must be the last element */ @@ -165,16 +166,17 @@ typedef struct sam_passwd const char * dir_drive; /* home directory drive string */ const char * logon_script; /* logon script string */ const char * profile_path; /* profile path string */ - const char * acct_desc ; /* user description string */ + const char * acct_desc; /* user description string */ 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 char * unknown_str; /* don't know what this is, yet. */ + const char * munged_dial; /* munged path name and dial-back tel number */ DOM_SID user_sid; /* Primary User SID */ DOM_SID group_sid; /* Primary Group SID */ DATA_BLOB lm_pw; /* .data is Null if no password */ DATA_BLOB nt_pw; /* .data is Null if no password */ + DATA_BLOB nt_pw_his; /* nt hashed password history .data is Null if not available */ char* plaintext_pw; /* is Null if not available */ uint16 acct_ctrl; /* account info (ACB_xxxx bit-mask) */ diff --git a/source3/include/smbldap.h b/source3/include/smbldap.h index c7de7d84b3..b94577178b 100644 --- a/source3/include/smbldap.h +++ b/source3/include/smbldap.h @@ -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_SID_LIST 40 +#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; diff --git a/source3/lib/smbldap.c b/source3/lib/smbldap.c index d058613f00..9b6d597606 100644 --- a/source3/lib/smbldap.c +++ b/source3/lib/smbldap.c @@ -100,6 +100,8 @@ ATTRIB_MAP_ENTRY attrib_map_v30[] = { { 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 } }; @@ -345,19 +347,19 @@ static BOOL fetch_ldap_pw(char **dn, char** pw) /* sanity checks on the mod values */ - if (attribute == NULL || *attribute == '\0') + if (attribute == NULL || *attribute == '\0') { return; + } + #if 0 /* commented out after discussion with abartlet. Do not reenable. left here so other so re-add similar code --jerry */ if (value == NULL || *value == '\0') return; #endif - if (mods == NULL) - { + if (mods == NULL) { mods = (LDAPMod **) malloc(sizeof(LDAPMod *)); - if (mods == NULL) - { + if (mods == NULL) { DEBUG(0, ("make_a_mod: out of memory!\n")); return; } @@ -369,17 +371,14 @@ static BOOL fetch_ldap_pw(char **dn, char** pw) break; } - if (mods[i] == NULL) - { + if (mods[i] == NULL) { mods = (LDAPMod **) Realloc (mods, (i + 2) * sizeof (LDAPMod *)); - if (mods == NULL) - { + if (mods == NULL) { DEBUG(0, ("make_a_mod: out of memory!\n")); return; } mods[i] = (LDAPMod *) malloc(sizeof(LDAPMod)); - if (mods[i] == NULL) - { + if (mods[i] == NULL) { DEBUG(0, ("make_a_mod: out of memory!\n")); return; } @@ -389,8 +388,7 @@ static BOOL fetch_ldap_pw(char **dn, char** pw) mods[i + 1] = NULL; } - if (value != NULL) - { + if (value != NULL) { char *utf8_value = NULL; j = 0; diff --git a/source3/passdb/pdb_get_set.c b/source3/passdb/pdb_get_set.c index e69dac524f..4abb951d7b 100644 --- a/source3/passdb/pdb_get_set.c +++ b/source3/passdb/pdb_get_set.c @@ -150,6 +150,19 @@ const uint8* pdb_get_lanman_passwd (const SAM_ACCOUNT *sampass) return (NULL); } +const uint8* pdb_get_pw_history (const SAM_ACCOUNT *sampass, uint32 *current_hist_len) +{ + if (sampass) { + SMB_ASSERT((!sampass->private.nt_pw_his.data) + || ((sampass->private.nt_pw_his.length % NT_HASH_LEN) == 0)); + *current_hist_len = sampass->private.nt_pw_his.length / NT_HASH_LEN; + return ((uint8*)sampass->private.nt_pw_his.data); + } else { + *current_hist_len = 0; + 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 +995,30 @@ BOOL pdb_set_lanman_passwd (SAM_ACCOUNT *sampass, const uint8 pwd[LM_HASH_LEN], } /********************************************************************* + Set the user's password history hash. historyLen is the number of NT_HASH_LEN + entries to store in the history - this must match the size of the uint8 array + in pwd. +********************************************************************/ + +BOOL pdb_set_pw_history (SAM_ACCOUNT *sampass, const uint8 *pwd, uint32 historyLen, enum pdb_value_state flag) +{ + if (!sampass) + return False; + + if (historyLen && pwd){ + sampass->private.nt_pw_his = data_blob_talloc(sampass->mem_ctx, pwd, historyLen*NT_HASH_LEN); + if (!sampass->private.nt_pw_his.length) { + DEBUG(0, ("pdb_set_pw_history: data_blob_talloc() failed!\n")); + return False; + } + } else { + sampass->private.nt_pw_his = data_blob_talloc(sampass->mem_ctx, NULL, 0); + } + + return pdb_set_init_flags(sampass, PDB_PWHISTORY, flag); +} + +/********************************************************************* Set the user's plaintext password only (base procedure, see helper below) ********************************************************************/ @@ -1133,12 +1170,20 @@ BOOL pdb_set_pass_changed_now (SAM_ACCOUNT *sampass) BOOL pdb_set_plaintext_passwd (SAM_ACCOUNT *sampass, const char *plaintext) { - uchar new_lanman_p16[16]; - uchar new_nt_p16[16]; + uchar new_lanman_p16[LM_HASH_LEN]; + uchar new_nt_p16[NT_HASH_LEN]; + uchar current_ntpw_copy[NT_HASH_LEN]; + uchar *current_ntpw = NULL; if (!sampass || !plaintext) return False; - + + /* Store the current password for history purposes. */ + current_ntpw = (uint8 *)pdb_get_nt_passwd(sampass); + if (current_ntpw) { + memcpy (current_ntpw_copy, current_ntpw, NT_HASH_LEN); + } + /* Calculate the MD4 hash (NT compatible) of the password */ E_md4hash(plaintext, new_nt_p16); @@ -1164,6 +1209,45 @@ BOOL pdb_set_plaintext_passwd (SAM_ACCOUNT *sampass, const char *plaintext) if (!pdb_set_pass_changed_now (sampass)) return False; + /* Store the password history. */ + if (pdb_get_acct_ctrl(sampass) & ACB_NORMAL) { + uchar *pwhistory; + uint32 pwHistLen; + account_policy_get(AP_PASSWORD_HISTORY, &pwHistLen); + if (pwHistLen != 0){ + uint32 current_history_len; + /* We need to make sure we don't have a race condition here - the + account policy history length can change between when the pw_history + was first loaded into the SAM_ACCOUNT struct and now.... JRA. */ + pwhistory = (uchar *)pdb_get_pw_history(sampass, ¤t_history_len); + + if (current_history_len != pwHistLen) { + /* After closing and reopening SAM_ACCOUNT the history + values will sync up. We can't do this here. */ + + /* current_history_len > pwHistLen is not a problem - we + have more history than we need. */ + + if (current_history_len < pwHistLen) { + /* We only have room for current_history_len entries. */ + pwHistLen = current_history_len; + } + } + if (pwhistory && current_ntpw && pwHistLen){ + if (pwHistLen > 1) { + memmove(&pwhistory[NT_HASH_LEN], pwhistory, (pwHistLen -1)*NT_HASH_LEN ); + } + memcpy(pwhistory, current_ntpw_copy, NT_HASH_LEN); + pdb_set_pw_history(sampass, pwhistory, pwHistLen, PDB_CHANGED); + } else { + DEBUG (10,("pdb_get_set.c: pdb_set_plaintext_passwd: pwhistory was NULL!\n")); + } + } else { + /* Set the history length to zero. */ + pdb_set_pw_history(sampass, NULL, 0, PDB_CHANGED); + } + } + return True; } diff --git a/source3/passdb/pdb_ldap.c b/source3/passdb/pdb_ldap.c index d2ee9a2d9d..fed92cea56 100644 --- a/source3/passdb/pdb_ldap.c +++ b/source3/passdb/pdb_ldap.c @@ -81,8 +81,6 @@ #define SAM_ACCOUNT struct sam_passwd #endif -#define MODIFY_TIMESTAMP_STRING "modifyTimestamp" - #include "smbldap.h" struct ldapsam_privates { @@ -301,7 +299,9 @@ static NTSTATUS ldapsam_delete_entry(struct ldapsam_privates *ldap_state, 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,9 @@ static time_t ldapsam_get_entry_timestamp( 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 +449,7 @@ static BOOL init_sam_from_ldap (struct ldapsam_privates *ldap_state, uint8 hours[MAX_HOURS_LEN]; pstring temp; LOGIN_CACHE *cache_entry = NULL; + int pwHistLen; /* * do a little initialization @@ -694,6 +696,37 @@ static BOOL init_sam_from_ldap (struct ldapsam_privates *ldap_state, ZERO_STRUCT(smbntpwd); } + account_policy_get(AP_PASSWORD_HISTORY, &pwHistLen); + if (pwHistLen > 0){ + uint8 *pwhist = NULL; + int i; + + if ((pwhist = malloc(NT_HASH_LEN * pwHistLen)) == NULL){ + DEBUG(0, ("init_sam_from_ldap: malloc failed!\n")); + return False; + } + memset(pwhist, '\0', NT_HASH_LEN * pwHistLen); + + if (!smbldap_get_single_pstring (ldap_state->smbldap_state->ldap_struct, entry, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_PWD_HISTORY), temp)) { + /* leave as default - zeros */ + } else { + for (i = 0; i < pwHistLen; i++){ + if (!pdb_gethexpwd(&temp[i*32], smbntpwd)) { + break; + } + memset(&temp[i*32], '\0', 32); + memcpy(&pwhist[i*NT_HASH_LEN], smbntpwd, NT_HASH_LEN); + ZERO_STRUCT(smbntpwd); + } + } + if (!pdb_set_pw_history(sampass, pwhist, pwHistLen, PDB_SET)){ + SAFE_FREE(pwhist); + return False; + } + 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; @@ -781,7 +814,7 @@ static BOOL init_sam_from_ldap (struct ldapsam_privates *ldap_state, } /********************************************************************** - Initialize SAM_ACCOUNT from an LDAP query. + Initialize the ldap db from a SAM_ACCOUNT. Called on update. (Based on init_buffer_from_sam in pdb_tdb.c) *********************************************************************/ @@ -985,6 +1018,29 @@ static BOOL init_ldap_from_sam (struct ldapsam_privates *ldap_state, } } + if (need_update(sampass, PDB_PWHISTORY)) { + int pwHistLen = 0; + account_policy_get(AP_PASSWORD_HISTORY, &pwHistLen); + if (pwHistLen == 0) { + /* Remove any password history from the LDAP store. */ + pstrcpy(temp, "00000000000000000000000000000000"); + } else { + int i, currHistLen = 0; + const uint8 *pwhist = pdb_get_pw_history(sampass, &currHistLen); + if (pwhist != NULL) { + /* We can only store (sizeof(pstring)-1)/32 password history entries. */ + pwHistLen = MIN(pwHistLen, ((sizeof(temp)-1)/32)); + for (i=0; i< pwHistLen && i < currHistLen; i++) { + pdb_sethexpwd (&temp[i*32], &pwhist[i*NT_HASH_LEN], 0); + DEBUG(100, ("temp=%s\n", 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, @@ -1162,7 +1218,7 @@ static NTSTATUS ldapsam_getsampwnam(struct pdb_methods *my_methods, SAM_ACCOUNT int rc; attr_list = get_userattr_list( ldap_state->schema_ver ); - append_attr(&attr_list, MODIFY_TIMESTAMP_STRING); + append_attr(&attr_list, get_userattr_key2string(ldap_state->schema_ver,LDAP_ATTR_MOD_TIMESTAMP)); rc = ldapsam_search_suffix_by_name(ldap_state, sname, &result, attr_list); free_attr_list( attr_list ); @@ -1208,7 +1264,7 @@ static int ldapsam_get_ldap_user_by_sid(struct ldapsam_privates *ldap_state, switch ( ldap_state->schema_ver ) { case SCHEMAVER_SAMBASAMACCOUNT: attr_list = get_userattr_list(ldap_state->schema_ver); - append_attr(&attr_list, MODIFY_TIMESTAMP_STRING); + append_attr(&attr_list, get_userattr_key2string(ldap_state->schema_ver,LDAP_ATTR_MOD_TIMESTAMP)); rc = ldapsam_search_suffix_by_sid(ldap_state, sid, result, attr_list); free_attr_list( attr_list ); diff --git a/source3/passdb/pdb_tdb.c b/source3/passdb/pdb_tdb.c index 9bfb10c400..2cf7c55049 100644 --- a/source3/passdb/pdb_tdb.c +++ b/source3/passdb/pdb_tdb.c @@ -746,4 +746,3 @@ NTSTATUS pdb_tdbsam_init(void) { return smb_register_passdb(PASSDB_INTERFACE_VERSION, "tdbsam", pdb_init_tdbsam); } - diff --git a/source3/smbd/chgpasswd.c b/source3/smbd/chgpasswd.c index ca13a167fb..a1b90c8fed 100644 --- a/source3/smbd/chgpasswd.c +++ b/source3/smbd/chgpasswd.c @@ -933,6 +933,65 @@ static NTSTATUS check_oem_password(const char *user, } /*********************************************************** + This routine takes the given password and checks it against + the password history. Returns True if this password has been + found in the history list. +************************************************************/ + +static BOOL check_passwd_history(SAM_ACCOUNT *sampass, const char *plaintext) +{ + uchar new_nt_p16[NT_HASH_LEN]; + uchar zero_nt_pw[NT_HASH_LEN]; + const uint8 *nt_pw; + const uint8 *pwhistory; + BOOL found = False; + int i, pwHisLen, curr_pwHisLen; + + account_policy_get(AP_PASSWORD_HISTORY, &pwHisLen); + if (pwHisLen == 0) { + return False; + } + + pwhistory = pdb_get_pw_history(sampass, &curr_pwHisLen); + if (!pwhistory || curr_pwHisLen == 0) { + return False; + } + + /* Only examine the minimum of the current history len and + the stored history len. Avoids race conditions. */ + pwHisLen = MIN(pwHisLen,curr_pwHisLen); + + nt_pw = pdb_get_nt_passwd(sampass); + + E_md4hash(plaintext, new_nt_p16); + + if (!memcmp(nt_pw, new_nt_p16, NT_HASH_LEN)) { + DEBUG(10,("check_passwd_history: proposed new password for user %s is the same as the current password !\n", + pdb_get_username(sampass) )); + return True; + } + + dump_data(100, new_nt_p16, NT_HASH_LEN); + dump_data(100, pwhistory, NT_HASH_LEN*pwHisLen); + + memset(zero_nt_pw, '\0', NT_HASH_LEN); + for (i=0; i<pwHisLen; i++) { + if (!memcmp(&pwhistory[i*NT_HASH_LEN], zero_nt_pw, NT_HASH_LEN)) { + /* Ignore zero entries. */ + continue; + } + if (!memcmp(&pwhistory[i*NT_HASH_LEN], new_nt_p16, NT_HASH_LEN)) { + DEBUG(1,("check_passwd_history: proposed new password for user %s found in history list !\n", + pdb_get_username(sampass) )); + found = True; + break; + } + } + + 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 @@ -941,20 +1000,21 @@ static NTSTATUS check_oem_password(const char *user, NTSTATUS change_oem_password(SAM_ACCOUNT *hnd, char *old_passwd, char *new_passwd, BOOL as_root) { - struct passwd *pass; - BOOL ret; uint32 min_len; + struct passwd *pass = NULL; + const char *username = pdb_get_username(hnd); + time_t can_change_time = pdb_get_pass_can_change_time(hnd); - if (time(NULL) < pdb_get_pass_can_change_time(hnd)) { + if ((can_change_time != 0) && (time(NULL) < can_change_time)) { 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; + username, http_timestring(can_change_time))); + return NT_STATUS_ACCOUNT_RESTRICTION; } if (account_policy_get(AP_MIN_PASSWORD_LEN, &min_len) && (strlen(new_passwd) < min_len)) { DEBUG(1, ("user %s cannot change password - password too short\n", - pdb_get_username(hnd))); + username)); DEBUGADD(1, (" account policy min password len = %d\n", min_len)); return NT_STATUS_PASSWORD_RESTRICTION; /* return NT_STATUS_PWD_TOO_SHORT; */ @@ -965,14 +1025,19 @@ NTSTATUS change_oem_password(SAM_ACCOUNT *hnd, char *old_passwd, char *new_passw if (strlen(new_passwd) < lp_min_passwd_length()) { /* too short, must be at least MINPASSWDLENGTH */ DEBUG(1, ("Password Change: user %s, New password is shorter than minimum password length = %d\n", - pdb_get_username(hnd), lp_min_passwd_length())); + username, lp_min_passwd_length())); return NT_STATUS_PASSWORD_RESTRICTION; /* return NT_STATUS_PWD_TOO_SHORT; */ } - pass = Get_Pwnam(pdb_get_username(hnd)); + if (check_passwd_history(hnd,new_passwd)) { + return NT_STATUS_PASSWORD_RESTRICTION; + } + + pass = Get_Pwnam(username); if (!pass) { - DEBUG(1, ("check_oem_password: Username does not exist in system !?!\n")); + DEBUG(1, ("check_oem_password: Username %s does not exist in system !?!\n", username)); + return NT_STATUS_ACCESS_DENIED; } /* @@ -988,7 +1053,7 @@ NTSTATUS change_oem_password(SAM_ACCOUNT *hnd, char *old_passwd, char *new_passw */ if(lp_unix_password_sync() && - !chgpasswd(pdb_get_username(hnd), pass, old_passwd, new_passwd, as_root)) { + !chgpasswd(username, pass, old_passwd, new_passwd, as_root)) { return NT_STATUS_ACCESS_DENIED; } |