From 3f07737fd4a92fc948cfc432bc46098d6dd5269a Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Wed, 6 Nov 2013 10:39:42 +1300 Subject: [PATCH] s4:auth: Add password lockout support to the AD DC Including a fix by Arvid Requate Change-Id: I25d10da50dd6119801cd37349cce970599531c6b Signed-off-by: Andrew Bartlett Reviewed-by: Stefan Metzmacher --- source4/auth/ntlm/auth_sam.c | 7 ++ source4/auth/sam.c | 117 ++++++++++++++++++++++++ source4/kdc/hdb-samba4.c | 18 +++- source4/rpc_server/samr/samr_password.c | 16 +++- source4/rpc_server/wscript_build | 2 +- 5 files changed, 156 insertions(+), 4 deletions(-) diff --git a/source4/auth/ntlm/auth_sam.c b/source4/auth/ntlm/auth_sam.c index d07f9301fb4..8f7f5448e84 100644 --- a/source4/auth/ntlm/auth_sam.c +++ b/source4/auth/ntlm/auth_sam.c @@ -213,6 +213,13 @@ static NTSTATUS authsam_authenticate(struct auth4_context *auth_context, nt_status = authsam_password_ok(auth_context, mem_ctx, 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? */ + } + } + NT_STATUS_NOT_OK_RETURN(nt_status); nt_status = authsam_account_ok(mem_ctx, auth_context->sam_ctx, diff --git a/source4/auth/sam.c b/source4/auth/sam.c index 1c3b81ad0c6..789ff19d15c 100644 --- a/source4/auth/sam.c +++ b/source4/auth/sam.c @@ -83,6 +83,7 @@ const char *user_attrs[] = { "logonCount", "primaryGroupID", "memberOf", + "badPasswordTime", NULL, }; @@ -634,3 +635,119 @@ NTSTATUS authsam_get_user_info_dc_principal(TALLOC_CTX *mem_ctx, return NT_STATUS_OK; } + +NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx, + struct ldb_message *msg, + struct ldb_dn *domain_dn) +{ + const char *attrs[] = { "lockoutThreshold", + "lockOutObservationWindow", + "lockoutDuration", + "pwdProperties", + NULL }; + int ret, badPwdCount; + int64_t lockoutThreshold, lockOutObservationWindow, badPasswordTime; + struct dom_sid *sid; + struct ldb_result *domain_res; + struct ldb_message *msg_mod; + struct timeval tv_now = timeval_current(); + NTTIME now = timeval_to_nttime(&tv_now); + NTSTATUS status; + uint32_t pwdProperties, rid = 0; + TALLOC_CTX *mem_ctx; + + mem_ctx = talloc_new(msg); + if (mem_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid"); + + ret = dsdb_search_dn(sam_ctx, mem_ctx, &domain_res, domain_dn, attrs, 0); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + pwdProperties = ldb_msg_find_attr_as_uint(domain_res->msgs[0], + "pwdProperties", -1); + if (sid && !(pwdProperties & DOMAIN_PASSWORD_LOCKOUT_ADMINS)) { + status = dom_sid_split_rid(NULL, sid, NULL, &rid); + if (!NT_STATUS_IS_OK(status)) { + /* + * This can't happen anyway, but always try + * and update the badPwdCount on failure + */ + rid = 0; + } + } + + /* + * Work out if we are doing password lockout on the domain. + * Also, the built in administrator account is exempt: + * http://msdn.microsoft.com/en-us/library/windows/desktop/aa375371%28v=vs.85%29.aspx + */ + lockoutThreshold = ldb_msg_find_attr_as_int(domain_res->msgs[0], + "lockoutThreshold", 0); + if (lockoutThreshold == 0 || (rid == DOMAIN_RID_ADMINISTRATOR)) { + DEBUG(5, ("Not updating badPwdCount on %s after wrong password\n", + ldb_dn_get_linearized(msg->dn))); + TALLOC_FREE(mem_ctx); + return NT_STATUS_OK; + } + + lockOutObservationWindow = ldb_msg_find_attr_as_int64(domain_res->msgs[0], + "lockOutObservationWindow", 0); + + badPasswordTime = ldb_msg_find_attr_as_int64(msg, "badPasswordTime", 0); + + msg_mod = ldb_msg_new(mem_ctx); + if (msg_mod == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + msg_mod->dn = msg->dn; + + if (badPasswordTime - lockOutObservationWindow >= now) { + badPwdCount = ldb_msg_find_attr_as_int(msg, "badPwdCount", 0); + } else { + badPwdCount = 0; + } + + badPwdCount++; + + ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, "badPwdCount", badPwdCount); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + ret = samdb_msg_add_int64(sam_ctx, msg_mod, msg_mod, "badPasswordTime", now); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + if (badPwdCount >= lockoutThreshold) { + ret = samdb_msg_add_int64(sam_ctx, msg_mod, msg_mod, "lockoutTime", now); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + DEBUG(5, ("Locked out user %s after %d wrong passwords\n", + ldb_dn_get_linearized(msg->dn), badPwdCount)); + } + + ret = dsdb_replace(sam_ctx, msg_mod, 0); + if (ret != LDB_SUCCESS) { + DEBUG(0, ("Failed to upate badPwdCount, badPasswordTime or set lockoutTime on %s: %s\n", + ldb_dn_get_linearized(msg_mod->dn), ldb_errstring(sam_ctx))); + TALLOC_FREE(mem_ctx); + return NT_STATUS_INTERNAL_ERROR; + } + + DEBUG(5, ("Updated badPwdCount on %s after %d wrong passwords\n", + ldb_dn_get_linearized(msg->dn), badPwdCount)); + + TALLOC_FREE(mem_ctx); + return NT_STATUS_OK; +} diff --git a/source4/kdc/hdb-samba4.c b/source4/kdc/hdb-samba4.c index 4519fb99dcc..7b03588c6ea 100644 --- a/source4/kdc/hdb-samba4.c +++ b/source4/kdc/hdb-samba4.c @@ -35,6 +35,8 @@ #include "includes.h" #include "kdc/kdc-glue.h" #include "kdc/db-glue.h" +#include "auth/auth_sam.h" +#include static krb5_error_code hdb_samba4_open(krb5_context context, HDB *db, int flags, mode_t mode) { @@ -165,6 +167,20 @@ hdb_samba4_check_s4u2self(krb5_context context, HDB *db, target_principal); } +static krb5_error_code hdb_samba4_auth_status(krb5_context context, HDB *db, + hdb_entry_ex *entry, + int hdb_auth_status) +{ + struct samba_kdc_db_context *kdc_db_ctx = talloc_get_type_abort(db->hdb_db, + struct samba_kdc_db_context); + struct samba_kdc_entry *p = talloc_get_type(entry->ctx, struct samba_kdc_entry); + + if (hdb_auth_status == HDB_AUTH_WRONG_PASSWORD) { + authsam_update_bad_pwd_count(kdc_db_ctx->samdb, p->msg, ldb_get_default_basedn(kdc_db_ctx->samdb)); + } + return 0; +} + /* This interface is to be called by the KDC and libnet_keytab_dump, * which is expecting Samba calling conventions. * It is also called by a wrapper (hdb_samba4_create) from the @@ -216,7 +232,7 @@ NTSTATUS hdb_samba4_create_kdc(struct samba_kdc_base_context *base_ctx, (*db)->hdb__del = NULL; (*db)->hdb_destroy = hdb_samba4_destroy; - (*db)->hdb_auth_status = NULL; + (*db)->hdb_auth_status = hdb_samba4_auth_status; (*db)->hdb_check_constrained_delegation = hdb_samba4_check_constrained_delegation; (*db)->hdb_check_pkinit_ms_upn_match = hdb_samba4_check_pkinit_ms_upn_match; (*db)->hdb_check_s4u2self = hdb_samba4_check_s4u2self; diff --git a/source4/rpc_server/samr/samr_password.c b/source4/rpc_server/samr/samr_password.c index 0c4f3384604..383fce1223f 100644 --- a/source4/rpc_server/samr/samr_password.c +++ b/source4/rpc_server/samr/samr_password.c @@ -30,6 +30,7 @@ #include "libcli/auth/libcli_auth.h" #include "../lib/util/util_ldb.h" #include "rpc_server/samr/proto.h" +#include "auth/auth_sam.h" /* samr_ChangePasswordUser @@ -64,6 +65,7 @@ NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, const char * const attrs[] = { "objectSid", "dBCSPwd", "userAccountControl", "msDS-User-Account-Control-Computed", + "badPwdCount", "badPasswordTime", NULL }; struct samr_Password *lm_pwd; DATA_BLOB lm_pwd_blob; @@ -123,6 +125,7 @@ NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, if (!extract_pw_from_buffer(mem_ctx, pwbuf->data, &new_password)) { DEBUG(3,("samr: failed to decode password buffer\n")); + authsam_update_bad_pwd_count(sam_ctx, res[0], ldb_get_default_basedn(sam_ctx)); return NT_STATUS_WRONG_PASSWORD; } @@ -132,6 +135,7 @@ NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, new_password.length, (void **)&new_pass, &converted_size)) { DEBUG(3,("samr: failed to convert incoming password buffer to unix charset\n")); + authsam_update_bad_pwd_count(sam_ctx, res[0], ldb_get_default_basedn(sam_ctx)); return NT_STATUS_WRONG_PASSWORD; } @@ -141,6 +145,7 @@ NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, new_password.length, (void **)&new_unicode_password.data, &unicode_pw_len)) { DEBUG(3,("samr: failed to convert incoming password buffer to UTF16 charset\n")); + authsam_update_bad_pwd_count(sam_ctx, res[0], ldb_get_default_basedn(sam_ctx)); return NT_STATUS_WRONG_PASSWORD; } new_unicode_password.length = unicode_pw_len; @@ -148,6 +153,7 @@ NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, E_deshash(new_pass, new_lm_hash); E_old_pw_hash(new_lm_hash, lm_pwd->hash, lm_verifier.hash); if (memcmp(lm_verifier.hash, r->in.hash->hash, 16) != 0) { + authsam_update_bad_pwd_count(sam_ctx, res[0], ldb_get_default_basedn(sam_ctx)); return NT_STATUS_WRONG_PASSWORD; } @@ -204,13 +210,14 @@ NTSTATUS dcesrv_samr_ChangePasswordUser3(struct dcesrv_call_state *dce_call, NTSTATUS status; DATA_BLOB new_password; struct ldb_context *sam_ctx = NULL; - struct ldb_dn *user_dn; + struct ldb_dn *user_dn = NULL; int ret; struct ldb_message **res; const char * const attrs[] = { "unicodePwd", "dBCSPwd", "userAccountControl", "msDS-User-Account-Control-Computed", - NULL }; + "badPwdCount", "badPasswordTime", + "objectSid", NULL }; struct samr_Password *nt_pwd, *lm_pwd; DATA_BLOB nt_pwd_blob; struct samr_DomInfo1 *dominfo = NULL; @@ -351,6 +358,11 @@ NTSTATUS dcesrv_samr_ChangePasswordUser3(struct dcesrv_call_state *dce_call, return NT_STATUS_OK; failed: + /* Only update the badPwdCount if we found the user */ + if (user_dn != NULL && NT_STATUS_EQUAL(status, NT_STATUS_WRONG_PASSWORD)) { + authsam_update_bad_pwd_count(sam_ctx, res[0], ldb_get_default_basedn(sam_ctx)); + } + reject = talloc_zero(mem_ctx, struct userPwdChangeFailureInformation); if (reject != NULL) { reject->extendedFailureReason = reason; diff --git a/source4/rpc_server/wscript_build b/source4/rpc_server/wscript_build index aaee8208eb2..28662578eba 100755 --- a/source4/rpc_server/wscript_build +++ b/source4/rpc_server/wscript_build @@ -80,7 +80,7 @@ bld.SAMBA_MODULE('dcesrv_samr', autoproto='samr/proto.h', subsystem='dcerpc_server', init_function='dcerpc_server_samr_init', - deps='samdb DCERPC_COMMON ndr-standard' + deps='samdb DCERPC_COMMON ndr-standard auth4_sam' ) -- 2.34.1