s4-auth: Support password history correctly, including allowing NTLM logins using... s4-bwdPwdCount-01
authorAndrew Bartlett <abartlet@samba.org>
Sun, 10 Nov 2013 21:38:03 +0000 (10:38 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Sun, 9 Feb 2014 23:33:59 +0000 (12:33 +1300)
This is only done during a 1 hour grace period, by default.

We only update bad password count when not one of the last 3 passwords

Andrew Bartlett

Signed-off-by: Andrew Bartlett <abartlet@samba.org>
selftest/knownfail
source4/auth/ntlm/auth_sam.c
source4/auth/sam.c

index 65f9fd274f616f182c4209079eb7705db1016d8c..bc536660ced3c54b4b1b46d6b69a2cff586263af 100644 (file)
@@ -96,7 +96,6 @@
 ^samba4.rpc.netlogon.*.GetTrustPasswords
 ^samba4.rpc.netlogon.*.DatabaseRedo
 ^samba4.rpc.netlogon.*.ServerGetTrustInfo
-^samba4.rpc.samr.passwords.badpwdcount # Not provided by Samba 4 yet
 ^samba4.rpc.samr.passwords.lockout
 ^samba4.base.charset.*.Testing partial surrogate
 ^samba4.*.base.maximum_allowed         # broken until we implement NTCREATEX_OPTIONS_BACKUP_INTENT
index 9a93fbb069f57f46788a7feb97e310ddb69e6b9a..3bb6a7876b8a23f8717e0f93f77bf4366c5d3107 100644 (file)
@@ -33,6 +33,7 @@
 #include "param/param.h"
 #include "librpc/gen_ndr/ndr_irpc_c.h"
 #include "lib/messaging/irpc.h"
+#include "libcli/auth/libcli_auth.h"
 
 NTSTATUS auth_sam_init(void);
 
@@ -216,9 +217,140 @@ static NTSTATUS authsam_authenticate(struct auth4_context *auth_context,
                                        acct_flags, lm_pwd, nt_pwd,
                                        user_info, user_sess_key, lm_sess_key);
        if (NT_STATUS_EQUAL(nt_status, NT_STATUS_WRONG_PASSWORD)) {
-               NTSTATUS update_bad_pwd_count_status = authsam_update_bad_pwd_count(auth_context->sam_ctx, msg, domain_dn);
-               if (!NT_STATUS_IS_OK(update_bad_pwd_count_status)) {
-                       /* bo! (what can we do here? */
+               NTSTATUS update_bad_pwd_count_status = nt_status;
+               int history_len;
+               const char *attrs[] = { "pwdHistoryLength", NULL };
+               struct ldb_message *dom_msg;
+               /* pull the  attributes */
+               int ret = dsdb_search_one(sam_ctx, mem_ctx, &dom_msg, domain_dn, LDB_SCOPE_BASE,
+                                         attrs,
+                                         0,
+                                         "objectClass=domain");
+               if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+                       DEBUG(3,("Couldn't find domain %s!\n",
+                                ldb_dn_get_linearized(domain_dn)));
+                       TALLOC_FREE(tmp_ctx);
+                       /* This looks odd, but we just want to return the original wrong password */
+                       return NT_STATUS_WRONG_PASSWORD;
+               }
+               if (ret != LDB_SUCCESS) {
+                       TALLOC_FREE(tmp_ctx);
+                       /* This looks odd, but we just want to return the original wrong password */
+                       return NT_STATUS_WRONG_PASSWORD;
+               }
+
+
+               history_len = ldb_msg_find_attr_as_uint(dom_msg, "pwdHistoryLength", -1);
+               if (history_len > 0) {
+                       int i;
+                       for (i = 1; i < MIN(history_len, 3); i++) {
+                               DATA_BLOB user_sess_key_from_history, lm_sess_key_from_history;
+                               static const struct samr_Password zero_hash;
+                               struct samr_Password zero_string_hash;
+                               struct samr_Password zero_string_des_hash;
+
+                               /*
+                                * We choose to avoid any issues
+                                * around different LM and NT history
+                                * lengths by only checking the NT
+                                * history
+                                */
+                               struct samr_Password *nt_history_pwd;
+                               struct samr_Password *lm_history_pwd;
+                               update_bad_pwd_count_status
+                                       = samdb_result_passwords_from_history(tmp_ctx,  auth_context->lp_ctx, msg, i,
+                                                                             &lm_history_pwd, &nt_history_pwd);
+
+                               if (!NT_STATUS_IS_OK(update_bad_pwd_count_status)) {
+                                       continue;
+                               }
+
+                               /* Skip over NULL (not present) or all-zero hashes in the history */
+                               if (!nt_history_pwd || memcmp(nt_history_pwd->hash, zero_hash.hash, 16) == 0) {
+                                       continue;
+                               }
+
+                               /*
+                                * This looks odd, but the password_hash module writes this in if
+                                * (somehow) we didn't have an old NT hash
+                                */
+
+                               E_md4hash("", zero_string_hash.hash);
+                               if (memcmp(nt_history_pwd->hash, zero_string_hash.hash, 16) == 0) {
+                                       continue;
+                               }
+
+                               E_deshash("", zero_string_des_hash.hash);
+                               if (!lm_history_pwd || memcmp(lm_history_pwd->hash, zero_string_des_hash.hash, 16) == 0) {
+                                       lm_history_pwd = NULL;
+                               }
+
+                               update_bad_pwd_count_status = authsam_password_ok(auth_context, tmp_ctx,
+                                                                                 acct_flags, lm_history_pwd,
+                                                                                 nt_history_pwd,
+                                                                                 user_info,
+                                                                                 &user_sess_key_from_history,
+                                                                                 &lm_sess_key_from_history);
+
+
+                               /*
+                                * If the password was OK, came from
+                                * NTLM, and it was the previous
+                                * password, then see if it is within
+                                * the grace period, so that we don't
+                                * break cached sessions on other
+                                * computers before the user can lock
+                                * and unlock their other screens
+                                * (resetting their cached password)
+                                */
+                               if (NT_STATUS_IS_OK(update_bad_pwd_count_status)
+                                   && i == 1
+                                   && user_info->password_state == AUTH_PASSWORD_RESPONSE) {
+
+                                       NTTIME pwdLastSet = samdb_result_nttime(msg,
+                                                                               "pwdLastSet", 0);
+                                       NTTIME now;
+                                       NTTIME grace_period = lpcfg_old_password_grace_period(auth_context->lp_ctx)
+                                               * 60 * 1000*1000*10;
+                                       /* Test account expire time */
+                                       unix_to_nt_time(&now, time(NULL));
+                                       if (now >= pwdLastSet && ((now - pwdLastSet) < grace_period)) {
+                                               nt_status = NT_STATUS_OK;
+                                               *user_sess_key = user_sess_key_from_history;
+                                               *lm_sess_key = lm_sess_key_from_history;
+                                               break;
+                                       }
+                               }
+
+                               /*
+                                * This looks odd, but we just want to
+                                * return the original wrong password.
+                                * This just ensures we skip the
+                                * update of the bad pwd count,
+                                * because this is almost certainly user error, not an attack.
+                                */
+
+                               if (NT_STATUS_IS_OK(update_bad_pwd_count_status)) {
+                                       TALLOC_FREE(tmp_ctx);
+                                       return NT_STATUS_WRONG_PASSWORD;
+                               }
+                       }
+               }
+
+               /*
+                * If we are not in the grace period (which resets
+                * nt_status), and we didn't return early because we
+                * matched an old password, update the badPwdCount et
+                * al.
+                */
+               if (NT_STATUS_EQUAL(nt_status, NT_STATUS_WRONG_PASSWORD)) {
+                       update_bad_pwd_count_status
+                               = authsam_update_bad_pwd_count(auth_context->sam_ctx, msg, domain_dn);
+                       if (!NT_STATUS_IS_OK(update_bad_pwd_count_status)) {
+                               /* This looks odd, but we just want to return the original wrong password */
+                               TALLOC_FREE(tmp_ctx);
+                               return NT_STATUS_WRONG_PASSWORD;
+                       }
                }
        }
 
index 4d3b50b01210e437a2210c73e4b05f0d2d90d6af..8854bcf3713db5d4a7eb009d05a385181619fd1e 100644 (file)
@@ -84,6 +84,8 @@ const char *user_attrs[] = {
        "primaryGroupID",
        "memberOf",
        "badPasswordTime",
+       "lmPwdHistory",
+       "ntPwdHistory",
        NULL,
 };