From bd21bc9ce4076fa41991abd6f8bf02a71d88938d Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Tue, 30 Mar 2021 10:51:26 +1300 Subject: [PATCH 01/11] s4-rpc_server: Use authsam_search_account() to find the user This helps the bad password and audit log handling code as it allows assumptions to be made about the attributes found in the variable "msg", such as that DSDB_SEARCH_SHOW_EXTENDED_DN was used. This ensures we can re-search on the DN via the embedded GUID, which in in turn rename-proof. Signed-off-by: Andrew Bartlett --- source4/rpc_server/samr/samr_password.c | 85 +++++++++++-------------- 1 file changed, 38 insertions(+), 47 deletions(-) diff --git a/source4/rpc_server/samr/samr_password.c b/source4/rpc_server/samr/samr_password.c index 83b104fbd0e..caa9fc14090 100644 --- a/source4/rpc_server/samr/samr_password.c +++ b/source4/rpc_server/samr/samr_password.c @@ -114,14 +114,7 @@ NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, struct ldb_context *sam_ctx; struct ldb_dn *user_dn; int ret; - struct ldb_message **res; - const char * const attrs[] = { "objectSid", "dBCSPwd", - "userAccountControl", - "msDS-ResultantPSO", - "msDS-User-Account-Control-Computed", - "badPwdCount", "badPasswordTime", - "samAccountName", - NULL }; + struct ldb_message *msg; struct samr_Password *lm_pwd; uint8_t new_lm_hash[16]; struct samr_Password lm_verifier; @@ -166,25 +159,27 @@ NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, return NT_STATUS_INVALID_SYSTEM_SERVICE; } - /* we need the users dn and the domain dn (derived from the - user SID). We also need the current lm password hash in - order to decrypt the incoming password */ - ret = gendb_search(sam_ctx, - mem_ctx, NULL, &res, attrs, - "(&(sAMAccountName=%s)(objectclass=user))", - ldb_binary_encode_string(mem_ctx, r->in.account->string)); - if (ret != 1) { - status = NT_STATUS_NO_SUCH_USER; /* Converted to WRONG_PASSWORD below */ + /* + * We use authsam_search_account() to be consistant with the + * other callers in the bad password and audit log handling + * systems. It ensures we get DSDB_SEARCH_SHOW_EXTENDED_DN. + */ + status = authsam_search_account(mem_ctx, + sam_ctx, + r->in.account->string, + ldb_get_default_basedn(sam_ctx), + &msg); + if (!NT_STATUS_IS_OK(status)) { goto failed; } - user_dn = res[0]->dn; + user_dn = msg->dn; - user_samAccountName = ldb_msg_find_attr_as_string(res[0], "samAccountName", NULL); - user_objectSid = samdb_result_dom_sid(res, res[0], "objectSid"); + user_samAccountName = ldb_msg_find_attr_as_string(msg, "samAccountName", NULL); + user_objectSid = samdb_result_dom_sid(msg, msg, "objectSid"); status = samdb_result_passwords(mem_ctx, dce_call->conn->dce_ctx->lp_ctx, - res[0], &lm_pwd, NULL); + msg, &lm_pwd, NULL); if (!NT_STATUS_IS_OK(status)) { goto failed; } else if (!lm_pwd) { @@ -221,7 +216,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)); + authsam_update_bad_pwd_count(sam_ctx, msg, ldb_get_default_basedn(sam_ctx)); status = NT_STATUS_WRONG_PASSWORD; goto failed; } @@ -232,7 +227,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)); + authsam_update_bad_pwd_count(sam_ctx, msg, ldb_get_default_basedn(sam_ctx)); status = NT_STATUS_WRONG_PASSWORD; goto failed; } @@ -243,7 +238,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)); + authsam_update_bad_pwd_count(sam_ctx, msg, ldb_get_default_basedn(sam_ctx)); status = NT_STATUS_WRONG_PASSWORD; goto failed; } @@ -256,7 +251,7 @@ NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, goto failed; } 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)); + authsam_update_bad_pwd_count(sam_ctx, msg, ldb_get_default_basedn(sam_ctx)); status = NT_STATUS_WRONG_PASSWORD; goto failed; } @@ -323,7 +318,7 @@ failed: } /* Only update the badPwdCount if we found the user */ if (NT_STATUS_EQUAL(status, NT_STATUS_WRONG_PASSWORD)) { - authsam_update_bad_pwd_count(sam_ctx, res[0], ldb_get_default_basedn(sam_ctx)); + authsam_update_bad_pwd_count(sam_ctx, msg, ldb_get_default_basedn(sam_ctx)); } else if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { /* Don't give the game away: (don't allow anonymous users to prove the existence of usernames) */ status = NT_STATUS_WRONG_PASSWORD; @@ -349,13 +344,7 @@ NTSTATUS dcesrv_samr_ChangePasswordUser3(struct dcesrv_call_state *dce_call, struct ldb_context *sam_ctx = NULL; struct ldb_dn *user_dn = NULL; int ret; - struct ldb_message **res; - const char * const attrs[] = { "unicodePwd", "dBCSPwd", - "userAccountControl", - "msDS-ResultantPSO", - "msDS-User-Account-Control-Computed", - "badPwdCount", "badPasswordTime", - "objectSid", NULL }; + struct ldb_message *msg; struct samr_Password *nt_pwd, *lm_pwd; struct samr_DomInfo1 *dominfo = NULL; struct userPwdChangeFailureInformation *reject = NULL; @@ -397,24 +386,26 @@ NTSTATUS dcesrv_samr_ChangePasswordUser3(struct dcesrv_call_state *dce_call, return NT_STATUS_INVALID_SYSTEM_SERVICE; } - /* we need the users dn and the domain dn (derived from the - user SID). We also need the current lm and nt password hashes - in order to decrypt the incoming passwords */ - ret = gendb_search(sam_ctx, - mem_ctx, NULL, &res, attrs, - "(&(sAMAccountName=%s)(objectclass=user))", - ldb_binary_encode_string(mem_ctx, r->in.account->string)); - if (ret != 1) { - status = NT_STATUS_NO_SUCH_USER; /* Converted to WRONG_PASSWORD below */ + /* + * We use authsam_search_account() to be consistant with the + * other callers in the bad password and audit log handling + * systems. It ensures we get DSDB_SEARCH_SHOW_EXTENDED_DN. + */ + status = authsam_search_account(mem_ctx, + sam_ctx, + r->in.account->string, + ldb_get_default_basedn(sam_ctx), + &msg); + if (!NT_STATUS_IS_OK(status)) { goto failed; } - user_dn = res[0]->dn; - user_samAccountName = ldb_msg_find_attr_as_string(res[0], "samAccountName", NULL); - user_objectSid = samdb_result_dom_sid(res, res[0], "objectSid"); + user_dn = msg->dn; + user_samAccountName = ldb_msg_find_attr_as_string(msg, "samAccountName", NULL); + user_objectSid = samdb_result_dom_sid(mem_ctx, msg, "objectSid"); status = samdb_result_passwords(mem_ctx, dce_call->conn->dce_ctx->lp_ctx, - res[0], &lm_pwd, &nt_pwd); + msg, &lm_pwd, &nt_pwd); if (!NT_STATUS_IS_OK(status) ) { goto failed; } @@ -560,7 +551,7 @@ failed: /* Only update the badPwdCount if we found the user */ if (NT_STATUS_EQUAL(status, NT_STATUS_WRONG_PASSWORD)) { - authsam_update_bad_pwd_count(sam_ctx, res[0], ldb_get_default_basedn(sam_ctx)); + authsam_update_bad_pwd_count(sam_ctx, msg, ldb_get_default_basedn(sam_ctx)); } else if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { /* Don't give the game away: (don't allow anonymous users to prove the existence of usernames) */ status = NT_STATUS_WRONG_PASSWORD; -- 2.25.1 From d800461f696b1259f9b0af32d29e7fd2d7631a15 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Tue, 16 Mar 2021 10:52:58 +1300 Subject: [PATCH 02/11] auth4: split samdb_result_msds_LockoutObservationWindow() out samdb_result_msds_LockoutObservationWindow() is split out of samdb_result_effective_badPwdCount() Signed-off-by: Gary Lockyer --- source4/dsdb/common/util.c | 50 ++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c index 769e589e265..26305212cd1 100644 --- a/source4/dsdb/common/util.c +++ b/source4/dsdb/common/util.c @@ -5298,9 +5298,9 @@ int dsdb_create_partial_replica_NC(struct ldb_context *ldb, struct ldb_dn *dn) * This also requires that the domain_msg have (if present): * - lockOutObservationWindow */ -static int dsdb_effective_badPwdCount(const struct ldb_message *user_msg, - int64_t lockOutObservationWindow, - NTTIME now) +int dsdb_effective_badPwdCount(const struct ldb_message *user_msg, + int64_t lockOutObservationWindow, + NTTIME now) { int64_t badPasswordTime; badPasswordTime = ldb_msg_find_attr_as_int64(user_msg, "badPasswordTime", 0); @@ -5347,27 +5347,25 @@ static struct ldb_result *lookup_user_pso(struct ldb_context *sam_ldb, } /* - * Return the effective badPwdCount + * Return the msDS-LockoutObservationWindow for a user message * * This requires that the user_msg have (if present): - * - badPasswordTime - * - badPwdCount * - msDS-ResultantPSO */ -int samdb_result_effective_badPwdCount(struct ldb_context *sam_ldb, - TALLOC_CTX *mem_ctx, - struct ldb_dn *domain_dn, - const struct ldb_message *user_msg) +int64_t samdb_result_msds_LockoutObservationWindow( + struct ldb_context *sam_ldb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *domain_dn, + const struct ldb_message *user_msg) { - struct timeval tv_now = timeval_current(); - NTTIME now = timeval_to_nttime(&tv_now); int64_t lockOutObservationWindow; struct ldb_result *res = NULL; const char *attrs[] = { "msDS-LockoutObservationWindow", NULL }; - + if (!domain_dn) { + smb_panic("domain dn is NULL"); + } res = lookup_user_pso(sam_ldb, mem_ctx, user_msg, attrs); - if (res != NULL) { lockOutObservationWindow = ldb_msg_find_attr_as_int64(res->msgs[0], @@ -5375,14 +5373,34 @@ int samdb_result_effective_badPwdCount(struct ldb_context *sam_ldb, DEFAULT_OBSERVATION_WINDOW); talloc_free(res); } else { - /* no PSO was found, lookup the default domain setting */ lockOutObservationWindow = samdb_search_int64(sam_ldb, mem_ctx, 0, domain_dn, "lockOutObservationWindow", NULL); } + return lockOutObservationWindow; +} - return dsdb_effective_badPwdCount(user_msg, lockOutObservationWindow, now); +/* + * Return the effective badPwdCount + * + * This requires that the user_msg have (if present): + * - badPasswordTime + * - badPwdCount + * - msDS-ResultantPSO + */ +int samdb_result_effective_badPwdCount(struct ldb_context *sam_ldb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *domain_dn, + const struct ldb_message *user_msg) +{ + struct timeval tv_now = timeval_current(); + NTTIME now = timeval_to_nttime(&tv_now); + int64_t lockOutObservationWindow = + samdb_result_msds_LockoutObservationWindow( + sam_ldb, mem_ctx, domain_dn, user_msg); + return dsdb_effective_badPwdCount( + user_msg, lockOutObservationWindow, now); } /* -- 2.25.1 From 989b6775238dfb8dbef13b657422898d1157d908 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Wed, 27 Jan 2021 14:24:58 +1300 Subject: [PATCH 03/11] s4 auth: Prepare to make bad password count increment atomic To ensure that the bad password count is incremented atomically, and that the successful logon accounting data is updated atomically, without always opening a transaction, we will need to make a note of all bad and successful passwords in a side-DB outside the transaction lock. This provides the functions needed for that and hooks them in (future commits will handle errors and use the results). Based on patches by Gary Lockyer BUG: https://bugzilla.samba.org/show_bug.cgi?id=14611 Signed-off-by: Andrew Bartlett --- source4/auth/sam.c | 182 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/source4/auth/sam.c b/source4/auth/sam.c index 39e48c26b52..ca7b8509f40 100644 --- a/source4/auth/sam.c +++ b/source4/auth/sam.c @@ -31,6 +31,8 @@ #include "libcli/ldap/ldap_ndr.h" #include "param/param.h" #include "librpc/gen_ndr/ndr_winbind_c.h" +#include "lib/dbwrap/dbwrap.h" +#include "cluster/cluster.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_AUTH @@ -813,6 +815,172 @@ static int authsam_get_user_pso(struct ldb_context *sam_ctx, return LDB_SUCCESS; } +static struct db_context *authsam_get_bad_password_db( + TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx) +{ + struct loadparm_context *lp_ctx = NULL; + const char *db_name = "bad_password"; + struct db_context *db_ctx = NULL; + + lp_ctx = ldb_get_opaque(sam_ctx, "loadparm"); + if (lp_ctx == NULL) { + DBG_ERR("Unable to get loadparm_context\n"); + return NULL; + } + + db_ctx = cluster_db_tmp_open(mem_ctx, lp_ctx, db_name, 0); + if (db_ctx == NULL) { + DBG_ERR("Unable to open bad password attempts database\n"); + return NULL; + } + return db_ctx; +} + +static NTSTATUS get_object_sid_as_tdb_data( + TALLOC_CTX *mem_ctx, + const struct ldb_message *msg, + TDB_DATA *key) +{ + struct dom_sid *objectsid; + struct dom_sid_buf sid_buf; + + /* + * Convert the objectSID to a human readable form to + * make debugging easier + */ + objectsid = samdb_result_dom_sid(mem_ctx, msg, "objectSID"); + if (objectsid == NULL) { + DBG_ERR("Unable to extract objectSID\n"); + return NT_STATUS_INTERNAL_ERROR; + } + dom_sid_str_buf(objectsid, &sid_buf); + key->dptr = (unsigned char*) &sid_buf.buf; + key->dsize = strlen(sid_buf.buf); + + return NT_STATUS_OK; +} + +/* + * Add the users objectSID to the bad password attempt database + * to indicate that last authentication failed due to a bad password + */ +static NTSTATUS authsam_set_bad_password_indicator( + struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + const struct ldb_message *msg) +{ + NTSTATUS status = NT_STATUS_OK; + TDB_DATA key = {0}; + TDB_DATA value = {0}; + struct db_context *db = NULL; + + TALLOC_CTX *ctx = talloc_new(mem_ctx); + if (ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + db = authsam_get_bad_password_db(mem_ctx, sam_ctx); + if (db == NULL) { + status = NT_STATUS_INTERNAL_ERROR; + goto exit; + } + + status = get_object_sid_as_tdb_data(ctx, msg, &key); + if (!NT_STATUS_IS_OK(status)) { + goto exit; + } + + status = dbwrap_store(db, key, value, 0); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Unable to store bad password indicator\n"); + } +exit: + talloc_free(ctx); + return status; +} + +/* + * see if the users objectSID is in the bad password attempt database + */ +static NTSTATUS authsam_check_bad_password_indicator( + struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + bool *exists, + const struct ldb_message *msg) +{ + NTSTATUS status = NT_STATUS_OK; + TDB_DATA key = {0}; + struct db_context *db = NULL; + + TALLOC_CTX *ctx = talloc_new(mem_ctx); + if (ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + db = authsam_get_bad_password_db(mem_ctx, sam_ctx); + if (db == NULL) { + status = NT_STATUS_INTERNAL_ERROR; + goto exit; + } + + status = get_object_sid_as_tdb_data(ctx, msg, &key); + if (!NT_STATUS_IS_OK(status)) { + goto exit; + } + + *exists = dbwrap_exists(db, key); +exit: + talloc_free(ctx); + return status; +} + +/* + * Remove the users objectSID to the bad password attempt database + * to indicate that last authentication succeeded. + */ +static NTSTATUS authsam_clear_bad_password_indicator( + struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + const struct ldb_message *msg) +{ + NTSTATUS status = NT_STATUS_OK; + TDB_DATA key = {0}; + struct db_context *db = NULL; + + TALLOC_CTX *ctx = talloc_new(mem_ctx); + if (ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + db = authsam_get_bad_password_db(mem_ctx, sam_ctx); + if (db == NULL) { + status = NT_STATUS_INTERNAL_ERROR; + goto exit; + } + + status = get_object_sid_as_tdb_data(ctx, msg, &key); + if (!NT_STATUS_IS_OK(status)) { + goto exit; + } + + status = dbwrap_delete(db, key); + if (NT_STATUS_EQUAL(NT_STATUS_NOT_FOUND, status)) { + /* + * Ok there was no bad password indicator this is expected + */ + status = NT_STATUS_OK; + } + if (NT_STATUS_IS_ERR(status)) { + DBG_ERR("Unable to delete bad password indicator, %s %s\n", + nt_errstr(status), + get_friendly_nt_error_msg(status)); + } +exit: + talloc_free(ctx); + return status; +} + NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx, struct ldb_message *msg, struct ldb_dn *domain_dn) @@ -882,6 +1050,10 @@ NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx, ret = dsdb_autotransaction_request(sam_ctx, req); talloc_free(req); + + status = authsam_set_bad_password_indicator( + sam_ctx, mem_ctx, msg); + /* Failure is ignored for now */ } done: @@ -1045,12 +1217,19 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, NTTIME now; NTTIME lastLogonTimestamp; bool am_rodc = false; + bool need_db_reread; mem_ctx = talloc_new(msg); if (mem_ctx == NULL) { return NT_STATUS_NO_MEMORY; } + status = authsam_check_bad_password_indicator( + sam_ctx, mem_ctx, &need_db_reread, msg); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0); dbBadPwdCount = ldb_msg_find_attr_as_int(msg, "badPwdCount", 0); if (interactive_or_kerberos) { @@ -1185,6 +1364,9 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, talloc_free(req); } + status = authsam_clear_bad_password_indicator(sam_ctx, mem_ctx, msg); + /* Failure is ignored for now */ + done: if (ret != LDB_SUCCESS) { DEBUG(0, ("Failed to set badPwdCount and lockoutTime " -- 2.25.1 From 8ab96fe24ac81522d72b267e11f53322a5214da0 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Tue, 30 Mar 2021 17:57:10 +1300 Subject: [PATCH 04/11] auth4: Reread the user record if a bad password is noticed. As is, this is pointless, as we need a transaction to make this any less of a race, but this provides the steps towards that goal. Signed-off-by: Andrew Bartlett --- source4/auth/sam.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/source4/auth/sam.c b/source4/auth/sam.c index ca7b8509f40..46b825e597d 100644 --- a/source4/auth/sam.c +++ b/source4/auth/sam.c @@ -815,6 +815,68 @@ static int authsam_get_user_pso(struct ldb_context *sam_ctx, return LDB_SUCCESS; } +/* + * Re-read the bad password and successful logon data for a user. + * + * the passed user record, must contain the "objectGUID", as this is + * used as the re-read key + */ +static NTSTATUS authsam_reread_user_logon_data( + struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + const struct ldb_message *user_msg, + struct ldb_message **current) +{ + const struct ldb_val *v = NULL; + struct ldb_result *res = NULL; + uint16_t acct_flags = 0; + const char *attr_name = "msDS-User-Account-Control-Computed"; + + int ret; + + /* + * Re-read the account details, using the GUID in case the DN + * is being changed (this is automatic in LDB because the + * original search also used DSDB_SEARCH_SHOW_EXTENDED_DN) + * + * We re read all the attributes in user_attrs, rather than using a + * subset to ensure that we can reuse existing validation code. + */ + ret = dsdb_search_dn(sam_ctx, + mem_ctx, + &res, + user_msg->dn, + user_attrs, + DSDB_SEARCH_SHOW_EXTENDED_DN); + if (ret != LDB_SUCCESS) { + DBG_ERR("Unable to re-read account control data for %s\n", + ldb_dn_get_linearized(user_msg->dn)); + return NT_STATUS_INTERNAL_ERROR; + } + + /* + * Ensure the account has not been locked out by another request + */ + v = ldb_msg_find_ldb_val(res->msgs[0], attr_name); + if (v == NULL || v->data == NULL) { + DBG_ERR("No %s attribute for %s\n", + attr_name, + ldb_dn_get_linearized(user_msg->dn)); + TALLOC_FREE(res); + return NT_STATUS_INTERNAL_ERROR; + } + acct_flags = samdb_result_acct_flags(res->msgs[0], attr_name); + if (acct_flags & ACB_AUTOLOCK) { + DBG_WARNING( + "Account for user %s was locked out.\n", + ldb_dn_get_linearized(user_msg->dn)); + TALLOC_FREE(res); + return NT_STATUS_ACCOUNT_LOCKED_OUT; + } + *current = res->msgs[0]; + return NT_STATUS_OK; +} + static struct db_context *authsam_get_bad_password_db( TALLOC_CTX *mem_ctx, struct ldb_context *sam_ctx) @@ -1217,6 +1279,7 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, NTTIME now; NTTIME lastLogonTimestamp; bool am_rodc = false; + struct ldb_message *current = NULL; bool need_db_reread; mem_ctx = talloc_new(msg); @@ -1230,6 +1293,25 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, return status; } + if (need_db_reread) { + + /* + * Re-read the account details, using the GUID + * embedded in DN so this is safe against a race where + * it is being renamed. + */ + status = authsam_reread_user_logon_data( + sam_ctx, mem_ctx, msg, ¤t); + if (!NT_STATUS_IS_OK(status)) { + /* + * The re-read can return account locked out, as well + * as an internal error + */ + return status; + } + msg = current; + } + lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0); dbBadPwdCount = ldb_msg_find_attr_as_int(msg, "badPwdCount", 0); if (interactive_or_kerberos) { -- 2.25.1 From d1511d9ec70a87e059086398c4e9fc81989cb5c3 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Tue, 30 Mar 2021 18:01:39 +1300 Subject: [PATCH 05/11] s4 auth: make bad password count increment atomic Ensure that the bad password count is incremented atomically, and that the successful logon accounting data is updated atomically. Use bad password indicator (in a distinct TDB) to determine if to open a transaction We open a transaction when we have seen the hint that this user has recorded a bad password. This allows us to avoid always needing one, while not missing a possible lockout. We also go back and get a transation if we did not take out one out but we chose to do a write (eg for lastLogonTimestamp) Based on patches by Gary Lockyer BUG: https://bugzilla.samba.org/show_bug.cgi?id=14611 Signed-off-by: Andrew Bartlett --- source4/auth/sam.c | 300 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 247 insertions(+), 53 deletions(-) diff --git a/source4/auth/sam.c b/source4/auth/sam.c index 46b825e597d..58c2393a735 100644 --- a/source4/auth/sam.c +++ b/source4/auth/sam.c @@ -1056,7 +1056,9 @@ NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx, NTSTATUS status; struct ldb_result *domain_res; struct ldb_message *msg_mod = NULL; + struct ldb_message *current = NULL; struct ldb_message *pso_msg = NULL; + bool txn_active = false; TALLOC_CTX *mem_ctx; mem_ctx = talloc_new(msg); @@ -1081,14 +1083,65 @@ NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx, ret, ldb_dn_get_linearized(msg->dn)); } - status = dsdb_update_bad_pwd_count(mem_ctx, sam_ctx, - msg, domain_res->msgs[0], pso_msg, - &msg_mod); + /* + * To ensure that the bad password count is updated atomically, + * we need to: + * begin a transaction + * re-read the account details, + * using the msgs[0], + pso_msg, + &msg_mod); + if (!NT_STATUS_IS_OK(status)) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; } + /* + * Write the data back to disk if required. + */ if (msg_mod != NULL) { struct ldb_request *req; @@ -1099,7 +1152,9 @@ NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx, ldb_op_default_callback, NULL); if (ret != LDB_SUCCESS) { - goto done; + TALLOC_FREE(msg_mod); + status = NT_STATUS_INTERNAL_ERROR; + goto error; } ret = ldb_request_add_control(req, @@ -1107,31 +1162,72 @@ NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx, false, NULL); if (ret != LDB_SUCCESS) { talloc_free(req); - goto done; + status = NT_STATUS_INTERNAL_ERROR; + goto error; } - ret = dsdb_autotransaction_request(sam_ctx, req); + /* + * As we're in a transaction, make the ldb request directly + * to avoid the nested transaction that would result if we + * called dsdb_autotransaction_request + */ + ret = ldb_request(sam_ctx, req); + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } talloc_free(req); - + if (ret != LDB_SUCCESS) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } status = authsam_set_bad_password_indicator( sam_ctx, mem_ctx, msg); - /* Failure is ignored for now */ + if (!NT_STATUS_IS_OK(status)) { + goto error; + } } - -done: + /* + * Note that we may not have updated the user record, but + * committing the transaction in that case is still the correct + * thing to do. + * If the transaction was cancelled, this would be logged by + * the dsdb audit log as a failure. When in fact it is expected + * behaviour. + */ +exit: + TALLOC_FREE(mem_ctx); + ret = ldb_transaction_commit(sam_ctx); if (ret != LDB_SUCCESS) { - DBG_ERR("Failed to update badPwdCount, badPasswordTime or " - "set lockoutTime on %s: %s\n", - ldb_dn_get_linearized(msg->dn), - ldb_errstring(sam_ctx)); - TALLOC_FREE(mem_ctx); + DBG_ERR("Error (%d) %s, committing transaction," + " while updating bad password count" + " for (%s)\n", + ret, + ldb_errstring(sam_ctx), + ldb_dn_get_linearized(msg->dn)); return NT_STATUS_INTERNAL_ERROR; } + return status; +error: + DBG_ERR("Failed to update badPwdCount, badPasswordTime or " + "set lockoutTime on %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ctx) != NULL ? + ldb_errstring(sam_ctx) :nt_errstr(status)); + if (txn_active) { + ret = ldb_transaction_cancel(sam_ctx); + if (ret != LDB_SUCCESS) { + DBG_ERR("Error rolling back transaction," + " while updating bad password count" + " on %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ctx)); + } + } TALLOC_FREE(mem_ctx); - return NT_STATUS_OK; -} + return status; +} static NTSTATUS authsam_update_lastlogon_timestamp(struct ldb_context *sam_ctx, struct ldb_message *msg_mod, @@ -1279,6 +1375,7 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, NTTIME now; NTTIME lastLogonTimestamp; bool am_rodc = false; + bool txn_active = false; struct ldb_message *current = NULL; bool need_db_reread; @@ -1287,14 +1384,40 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, return NT_STATUS_NO_MEMORY; } + /* + * Any update of the last logon data, needs to be done inside a + * transaction. + * And the user data needs to be re-read, and the account re-checked + * for lockout. + * + * Howevver we have long-running transactions like replication + * that could otherwise grind the system to a halt so we first + * determine if *this* account has seen a bad password, + * otherwise we only start a transaction if there was a need + * (because a change was to be made). + */ + status = authsam_check_bad_password_indicator( sam_ctx, mem_ctx, &need_db_reread, msg); if (!NT_STATUS_IS_OK(status)) { return status; } +get_transaction: + if (need_db_reread) { + /* + * Start a new transaction + */ + ret = ldb_transaction_start(sam_ctx); + if (ret != LDB_SUCCESS) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } + + txn_active = true; + /* * Re-read the account details, using the GUID * embedded in DN so this is safe against a race where @@ -1307,7 +1430,15 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, * The re-read can return account locked out, as well * as an internal error */ - return status; + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT)) { + /* + * For NT_STATUS_ACCOUNT_LOCKED_OUT we want to commit + * the transaction. Again to avoid cluttering the + * audit logs with spurious errors + */ + goto exit; + } + goto error; } msg = current; } @@ -1328,9 +1459,15 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, msg_mod = ldb_msg_new(mem_ctx); if (msg_mod == NULL) { - TALLOC_FREE(mem_ctx); - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto error; } + + /* + * By using the DN from msg->dn directly, we allow LDB to + * prefer the embedded GUID form, so this is actually quite + * safe even in the case where DN has been changed + */ msg_mod->dn = msg->dn; if (lockoutTime != 0) { @@ -1339,14 +1476,14 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, */ ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, "lockoutTime", 0); if (ret != LDB_SUCCESS) { - TALLOC_FREE(mem_ctx); - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto error; } } else if (badPwdCount != 0) { ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, "badPwdCount", 0); if (ret != LDB_SUCCESS) { - TALLOC_FREE(mem_ctx); - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto error; } } @@ -1358,8 +1495,8 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, ret = samdb_msg_add_int64(sam_ctx, msg_mod, msg_mod, "lastLogon", now); if (ret != LDB_SUCCESS) { - TALLOC_FREE(mem_ctx); - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto error; } } @@ -1373,8 +1510,8 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, "logonCount", logonCount); if (ret != LDB_SUCCESS) { - TALLOC_FREE(mem_ctx); - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto error; } } else { /* Set an unset logonCount to 0 on first successful login */ @@ -1390,16 +1527,16 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, ret = samdb_rodc(sam_ctx, &am_rodc); if (ret != LDB_SUCCESS) { - TALLOC_FREE(mem_ctx); - return NT_STATUS_INTERNAL_ERROR; + status = NT_STATUS_INTERNAL_ERROR; + goto error; } if (!am_rodc) { status = authsam_update_lastlogon_timestamp(sam_ctx, msg_mod, domain_dn, lastLogonTimestamp, now); if (!NT_STATUS_IS_OK(status)) { - TALLOC_FREE(mem_ctx); - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto error; } } else { /* Perform the (async) SendToSAM calls for MS-SAMS */ @@ -1419,6 +1556,16 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, unsigned int i; struct ldb_request *req; + /* + * If it turns out we are going to update the DB, go + * back to the start, get a transaction and the + * current DB state and try again + */ + if (txn_active == false) { + need_db_reread = true; + goto get_transaction; + } + /* mark all the message elements as LDB_FLAG_MOD_REPLACE */ for (i=0;inum_elements;i++) { msg_mod->elements[i].flags = LDB_FLAG_MOD_REPLACE; @@ -1431,35 +1578,82 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, ldb_op_default_callback, NULL); if (ret != LDB_SUCCESS) { - goto done; + status = NT_STATUS_INTERNAL_ERROR; + goto error; } - ret = ldb_request_add_control(req, - DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE, - false, NULL); + ret = ldb_request_add_control( + req, DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE, false, NULL); if (ret != LDB_SUCCESS) { - talloc_free(req); - goto done; + TALLOC_FREE(req); + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } + /* + * As we're in a transaction, make the ldb request directly + * to avoid the nested transaction that would result if we + * called dsdb_autotransaction_request */ + ret = ldb_request(sam_ctx, req); + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } + TALLOC_FREE(req); + if (ret != LDB_SUCCESS) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; } - - ret = dsdb_autotransaction_request(sam_ctx, req); - talloc_free(req); } - status = authsam_clear_bad_password_indicator(sam_ctx, mem_ctx, msg); - /* Failure is ignored for now */ + if (!NT_STATUS_IS_OK(status)) { + goto error; + } + + /* + * Note that we may not have updated the user record, but + * committing the transaction in that case is still the correct + * thing to do. + * If the transaction was cancelled, this would be logged by + * the dsdb audit log as a failure. When in fact it is expected + * behaviour. + * + * Thankfully both TDB and LMDB seem to optimise for the empty + * transaction case + */ +exit: + TALLOC_FREE(mem_ctx); + + if (txn_active == false) { + return NT_STATUS_OK; + } -done: + ret = ldb_transaction_commit(sam_ctx); if (ret != LDB_SUCCESS) { - DEBUG(0, ("Failed to set badPwdCount and lockoutTime " - "to 0 and/or lastlogon to now (%lld) " - "%s: %s\n", (long long int)now, - ldb_dn_get_linearized(msg_mod->dn), - ldb_errstring(sam_ctx))); - TALLOC_FREE(mem_ctx); + DBG_ERR("Error (%d) %s, committing transaction," + " while updating successful logon accounting" + " for (%s)\n", + ret, + ldb_errstring(sam_ctx), + ldb_dn_get_linearized(msg->dn)); return NT_STATUS_INTERNAL_ERROR; } + return NT_STATUS_OK; +error: + DBG_ERR("Failed to update badPwdCount, badPasswordTime or " + "set lockoutTime on %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ctx) != NULL ? + ldb_errstring(sam_ctx) :nt_errstr(status)); + if (txn_active) { + ret = ldb_transaction_cancel(sam_ctx); + if (ret != LDB_SUCCESS) { + DBG_ERR("Error rolling back transaction," + " while updating bad password count" + " on %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ctx)); + } + } TALLOC_FREE(mem_ctx); - return NT_STATUS_OK; + return status; } -- 2.25.1 From 4106b3baa9e151d65546b86baa6e2705c2ad49fa Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Tue, 30 Mar 2021 16:35:44 +1300 Subject: [PATCH 06/11] auth4: Add missing newline to debug message on PSO read failure Signed-off-by: Andrew Bartlett --- source4/auth/sam.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source4/auth/sam.c b/source4/auth/sam.c index 58c2393a735..5cbbf2f27c1 100644 --- a/source4/auth/sam.c +++ b/source4/auth/sam.c @@ -1079,7 +1079,7 @@ NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx, * fallback to using the domain defaults so that we still * record the bad password attempt */ - DBG_ERR("Error (%d) checking PSO for %s", + DBG_ERR("Error (%d) checking PSO for %s\n", ret, ldb_dn_get_linearized(msg->dn)); } -- 2.25.1 From 8ce0eea4f81e3f636515253f0d128d1d12966bef Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Thu, 25 Mar 2021 11:30:59 +1300 Subject: [PATCH 07/11] auth4: Return only the result message and free the surrounding result Signed-off-by: Andrew Bartlett --- source4/auth/sam.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source4/auth/sam.c b/source4/auth/sam.c index 5cbbf2f27c1..26e81b7aa6a 100644 --- a/source4/auth/sam.c +++ b/source4/auth/sam.c @@ -873,7 +873,8 @@ static NTSTATUS authsam_reread_user_logon_data( TALLOC_FREE(res); return NT_STATUS_ACCOUNT_LOCKED_OUT; } - *current = res->msgs[0]; + *current = talloc_steal(mem_ctx, res->msgs[0]); + TALLOC_FREE(res); return NT_STATUS_OK; } -- 2.25.1 From e5ad1dda5ec0d48bfdb54773c2791490155e8cff Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 25 Mar 2021 14:42:39 +1300 Subject: [PATCH 08/11] auth4: Split authsam_calculate_lastlogon_sync_interval() out authsam_calculate_lastlogon_sync_interval() is split out of authsam_update_lastlogon_timestamp() Based on work by Gary Lockyer Signed-off-by: Andrew Bartlett --- source4/auth/sam.c | 118 ++++++++++++++++++++++++++++----------------- 1 file changed, 75 insertions(+), 43 deletions(-) diff --git a/source4/auth/sam.c b/source4/auth/sam.c index 26e81b7aa6a..fb43e0325e3 100644 --- a/source4/auth/sam.c +++ b/source4/auth/sam.c @@ -1230,40 +1230,35 @@ error: } -static NTSTATUS authsam_update_lastlogon_timestamp(struct ldb_context *sam_ctx, - struct ldb_message *msg_mod, - struct ldb_dn *domain_dn, - NTTIME old_timestamp, - NTTIME now) +/* + * msDS-LogonTimeSyncInterval is an int32_t number of days. + * + * The docs say: "the initial update, after the domain functional + * level is raised to DS_BEHAVIOR_WIN2003 or higher, is calculated as + * 14 days minus a random percentage of 5 days", but we aren't doing + * that. The blogosphere seems to think that this randomised update + * happens everytime, but [MS-ADA1] doesn't agree. + * + * Dochelp referred us to the following blog post: + * http://blogs.technet.com/b/askds/archive/2009/04/15/the-lastlogontimestamp-attribute-what-it-was-designed-for-and-how-it-works.aspx + * + * when msDS-LogonTimeSyncInterval is zero, the lastLogonTimestamp is + * not changed. + */ + +static NTSTATUS authsam_calculate_lastlogon_sync_interval( + struct ldb_context *sam_ctx, + TALLOC_CTX *ctx, + struct ldb_dn *domain_dn, + NTTIME *sync_interval_nt) { - /* - * We only set lastLogonTimestamp if the current value is older than - * now - msDS-LogonTimeSyncInterval days. - * - * msDS-LogonTimeSyncInterval is an int32_t number of days, while - * lastLogonTimestamp is in the 64 bit 100ns NTTIME format. - * - * The docs say: "the initial update, after the domain functional - * level is raised to DS_BEHAVIOR_WIN2003 or higher, is calculated as - * 14 days minus a random percentage of 5 days", but we aren't doing - * that. The blogosphere seems to think that this randomised update - * happens everytime, but [MS-ADA1] doesn't agree. - * - * Dochelp referred us to the following blog post: - * http://blogs.technet.com/b/askds/archive/2009/04/15/the-lastlogontimestamp-attribute-what-it-was-designed-for-and-how-it-works.aspx - * - * en msDS-LogonTimeSyncInterval is zero, the lastLogonTimestamp is - * not changed. - */ static const char *attrs[] = { "msDS-LogonTimeSyncInterval", NULL }; int ret; struct ldb_result *domain_res = NULL; - TALLOC_CTX *mem_ctx = NULL; - int32_t sync_interval; - NTTIME sync_interval_nt; + uint32_t sync_interval; - mem_ctx = talloc_new(msg_mod); + TALLOC_CTX *mem_ctx = talloc_new(ctx); if (mem_ctx == NULL) { return NT_STATUS_NO_MEMORY; } @@ -1279,15 +1274,7 @@ static NTSTATUS authsam_update_lastlogon_timestamp(struct ldb_context *sam_ctx, "msDS-LogonTimeSyncInterval", 14); DEBUG(5, ("sync interval is %d\n", sync_interval)); - if (sync_interval == 0){ - /* - * Setting msDS-LogonTimeSyncInterval to zero is how you ask - * that nothing happens here. - */ - TALLOC_FREE(mem_ctx); - return NT_STATUS_OK; - } - else if (sync_interval >= 5){ + if (sync_interval >= 5){ /* * Subtract "a random percentage of 5" days. Presumably this * percentage is between 0 and 100, and modulus is accurate @@ -1295,17 +1282,49 @@ static NTSTATUS authsam_update_lastlogon_timestamp(struct ldb_context *sam_ctx, */ uint32_t r = generate_random() % 6; sync_interval -= r; - DEBUG(5, ("randomised sync interval is %d (-%d)\n", sync_interval, r)); + DBG_INFO("randomised sync interval is %d (-%d)\n", + sync_interval, + r); } /* In the case where sync_interval < 5 there is no randomisation */ - sync_interval_nt = sync_interval * 24LL * 3600LL * 10000000LL; + /* + * msDS-LogonTimeSyncInterval is an int32_t number of days, + * while lastLogonTimestamp (to be updated) is in the 64 bit + * 100ns NTTIME format so we must convert. + */ + *sync_interval_nt = sync_interval * 24LL * 3600LL * 10000000LL; + TALLOC_FREE(mem_ctx); + return NT_STATUS_OK; +} + +/* + * We only set lastLogonTimestamp if the current value is older than + * now - msDS-LogonTimeSyncInterval days. + * + * lastLogonTimestamp is in the 64 bit 100ns NTTIME format + */ +static NTSTATUS authsam_update_lastlogon_timestamp(struct ldb_context *sam_ctx, + struct ldb_message *msg_mod, + struct ldb_dn *domain_dn, + NTTIME old_timestamp, + NTTIME now, + NTTIME sync_interval_nt) +{ + int ret; DEBUG(5, ("old timestamp is %lld, threshold %lld, diff %lld\n", (long long int)old_timestamp, (long long int)(now - sync_interval_nt), (long long int)(old_timestamp - now + sync_interval_nt))); + if (sync_interval_nt == 0){ + /* + * Setting msDS-LogonTimeSyncInterval to zero is how you ask + * that nothing happens here. + */ + return NT_STATUS_OK; + } if (old_timestamp > now){ DEBUG(0, ("lastLogonTimestamp is in the future! (%lld > %lld)\n", (long long int)old_timestamp, (long long int)now)); @@ -1320,11 +1339,9 @@ static NTSTATUS authsam_update_lastlogon_timestamp(struct ldb_context *sam_ctx, "lastLogonTimestamp", now); if (ret != LDB_SUCCESS) { - TALLOC_FREE(mem_ctx); return NT_STATUS_NO_MEMORY; } } - TALLOC_FREE(mem_ctx); return NT_STATUS_OK; } @@ -1533,8 +1550,23 @@ get_transaction: } if (!am_rodc) { - status = authsam_update_lastlogon_timestamp(sam_ctx, msg_mod, domain_dn, - lastLogonTimestamp, now); + NTTIME sync_interval_nt; + + status = authsam_calculate_lastlogon_sync_interval( + sam_ctx, mem_ctx, domain_dn, &sync_interval_nt); + + if (!NT_STATUS_IS_OK(status)) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } + + status = authsam_update_lastlogon_timestamp( + sam_ctx, + msg_mod, + domain_dn, + lastLogonTimestamp, + now, + sync_interval_nt); if (!NT_STATUS_IS_OK(status)) { status = NT_STATUS_NO_MEMORY; goto error; -- 2.25.1 From d5722dd319208edd27a8668bfbebaeff58c83d07 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 25 Mar 2021 15:33:08 +1300 Subject: [PATCH 09/11] auth4: Inline samdb_result_effective_badPwdCount() in authsam_logon_success_accounting() By bringing this fucntion inline it can then be spit out in a subsequent commit. Based on work by Gary Lockyer Signed-off-by: Andrew Bartlett --- source4/auth/sam.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/source4/auth/sam.c b/source4/auth/sam.c index fb43e0325e3..341331a2905 100644 --- a/source4/auth/sam.c +++ b/source4/auth/sam.c @@ -1463,11 +1463,17 @@ get_transaction: lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0); dbBadPwdCount = ldb_msg_find_attr_as_int(msg, "badPwdCount", 0); + tv_now = timeval_current(); + now = timeval_to_nttime(&tv_now); + if (interactive_or_kerberos) { badPwdCount = dbBadPwdCount; } else { - badPwdCount = samdb_result_effective_badPwdCount(sam_ctx, mem_ctx, - domain_dn, msg); + int64_t lockOutObservationWindow = + samdb_result_msds_LockoutObservationWindow( + sam_ctx, mem_ctx, domain_dn, msg); + badPwdCount = dsdb_effective_badPwdCount( + msg, lockOutObservationWindow, now); } lastLogonTimestamp = ldb_msg_find_attr_as_int64(msg, "lastLogonTimestamp", 0); @@ -1505,9 +1511,6 @@ get_transaction: } } - tv_now = timeval_current(); - now = timeval_to_nttime(&tv_now); - if (interactive_or_kerberos || (badPwdCount != 0 && lockoutTime == 0)) { ret = samdb_msg_add_int64(sam_ctx, msg_mod, msg_mod, -- 2.25.1 From c91aaad5cb6653df4565553e473f0532bfd4bc96 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Tue, 30 Mar 2021 16:48:31 +1300 Subject: [PATCH 10/11] auth4: Avoid reading the database twice by precaculating some variables These variables are not important to protect against a race with and a double-read can easily be avoided by moving them up the file a little. Signed-off-by: Andrew Bartlett --- source4/auth/sam.c | 55 ++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/source4/auth/sam.c b/source4/auth/sam.c index 341331a2905..bbfc4678d61 100644 --- a/source4/auth/sam.c +++ b/source4/auth/sam.c @@ -1392,6 +1392,8 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, struct timeval tv_now; NTTIME now; NTTIME lastLogonTimestamp; + int64_t lockOutObservationWindow; + NTTIME sync_interval_nt = 0; bool am_rodc = false; bool txn_active = false; struct ldb_message *current = NULL; @@ -1421,6 +1423,36 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, return status; } + if (interactive_or_kerberos == false) { + /* + * Avoid calculating this twice, it reads the PSO. A + * race on this is unimportant. + */ + lockOutObservationWindow + = samdb_result_msds_LockoutObservationWindow( + sam_ctx, mem_ctx, domain_dn, msg); + } + + ret = samdb_rodc(sam_ctx, &am_rodc); + if (ret != LDB_SUCCESS) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } + + if (!am_rodc) { + /* + * Avoid reading the main domain DN twice. A race on + * this is unimportant. + */ + status = authsam_calculate_lastlogon_sync_interval( + sam_ctx, mem_ctx, domain_dn, &sync_interval_nt); + + if (!NT_STATUS_IS_OK(status)) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } + } + get_transaction: if (need_db_reread) { @@ -1469,9 +1501,10 @@ get_transaction: if (interactive_or_kerberos) { badPwdCount = dbBadPwdCount; } else { - int64_t lockOutObservationWindow = - samdb_result_msds_LockoutObservationWindow( - sam_ctx, mem_ctx, domain_dn, msg); + /* + * We get lockOutObservationWindow above, before the + * transaction + */ badPwdCount = dsdb_effective_badPwdCount( msg, lockOutObservationWindow, now); } @@ -1546,23 +1579,7 @@ get_transaction: } } - ret = samdb_rodc(sam_ctx, &am_rodc); - if (ret != LDB_SUCCESS) { - status = NT_STATUS_INTERNAL_ERROR; - goto error; - } - if (!am_rodc) { - NTTIME sync_interval_nt; - - status = authsam_calculate_lastlogon_sync_interval( - sam_ctx, mem_ctx, domain_dn, &sync_interval_nt); - - if (!NT_STATUS_IS_OK(status)) { - status = NT_STATUS_INTERNAL_ERROR; - goto error; - } - status = authsam_update_lastlogon_timestamp( sam_ctx, msg_mod, -- 2.25.1 From baf21c328b1b0c305bfc1a8ef376fff1f9d7799e Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Tue, 9 Feb 2021 11:59:05 +1300 Subject: [PATCH 11/11] s4 auth test: Unit tests for source4/auth/sam.c cmocka unit tests for the authsam_reread_user_logon_data in source4/auth/sam.c BUG: https://bugzilla.samba.org/show_bug.cgi?id=14611 Signed-off-by: Gary Lockyer --- selftest/tests.py | 2 + source4/auth/tests/sam.c | 2688 ++++++++++++++++++++++++++++++++++++ source4/auth/wscript_build | 10 + 3 files changed, 2700 insertions(+) create mode 100644 source4/auth/tests/sam.c diff --git a/selftest/tests.py b/selftest/tests.py index 6bf46ae5621..41b7d7905db 100644 --- a/selftest/tests.py +++ b/selftest/tests.py @@ -419,6 +419,8 @@ plantestsuite("samba.unittests.test_registry_regfio", "none", [os.path.join(bindir(), "default/source3/test_registry_regfio")]) plantestsuite("samba.unittests.test_oLschema2ldif", "none", [os.path.join(bindir(), "default/source4/utils/oLschema2ldif/test_oLschema2ldif")]) +plantestsuite("samba.unittests.auth.sam", "none", + [os.path.join(bindir(), "test_auth_sam")]) if with_elasticsearch_backend: plantestsuite("samba.unittests.mdsparser_es", "none", [os.path.join(bindir(), "default/source3/test_mdsparser_es")] + [configuration]) diff --git a/source4/auth/tests/sam.c b/source4/auth/tests/sam.c new file mode 100644 index 00000000000..69835cbce75 --- /dev/null +++ b/source4/auth/tests/sam.c @@ -0,0 +1,2688 @@ +/* + * Unit tests for source4/auth/sam.c + * + * Copyright (C) Catalyst.NET Ltd 2021 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "includes.h" +#include "auth/sam.c" +#include "ldb.h" +#include "ntstatus.h" +#include "librpc/gen_ndr/ndr_security.h" + +/***************************************************************************** + * wrapped functions + * + *****************************************************************************/ +int __wrap_samdb_msg_add_int64( + struct ldb_context *sam_ldb, + TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + const char *attr_name, + int64_t v); +int __real_samdb_msg_add_int64( + struct ldb_context *sam_ldb, + TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + const char *attr_name, + int64_t v); +int __wrap_samdb_msg_add_int64( + struct ldb_context *sam_ldb, + TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + const char *attr_name, + int64_t v) +{ + + int ret; + ret = (int)mock(); + if (ret != LDB_SUCCESS) { + return ret; + } + return __real_samdb_msg_add_int64(sam_ldb, mem_ctx, msg, attr_name, v); +} +/***************************************************************************** + * Mock implementations + *****************************************************************************/ +int dsdb_search_by_dn_guid_ret = LDB_SUCCESS; +struct ldb_result *dsdb_search_by_dn_guid_res = NULL; +int dsdb_search_by_dn_guid(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_result **_result, + const struct GUID *guid, + const char * const *attrs, + uint32_t dsdb_flags) +{ + *_result = talloc_steal(mem_ctx, dsdb_search_by_dn_guid_res); + return dsdb_search_by_dn_guid_ret; +} + +int *dsdb_search_dn_ret = NULL; +struct ldb_result **dsdb_search_dn_res = NULL; +unsigned dsdb_search_dn_ctr = 0; +int dsdb_search_dn(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_result **_result, + struct ldb_dn *basedn, + const char * const *attrs, + uint32_t dsdb_flags) +{ + int ret = 0; + ret = dsdb_search_dn_ret[dsdb_search_dn_ctr]; + if (ret == LDB_SUCCESS) { + *_result = talloc_steal( + mem_ctx, dsdb_search_dn_res[dsdb_search_dn_ctr]); + } + dsdb_search_dn_ctr++; + return ret; +} + +int ldb_transaction_start_ret = LDB_SUCCESS; +bool in_transaction = false; +int ldb_transaction_start(struct ldb_context *ldb) { + if (ldb_transaction_start_ret == LDB_SUCCESS) { + in_transaction = true; + } + return ldb_transaction_start_ret; +} + +int ldb_transaction_cancel_ret = LDB_SUCCESS; +bool transaction_cancelled = false; +int ldb_transaction_cancel(struct ldb_context *ldb) { + assert_true(in_transaction); + if (ldb_transaction_cancel_ret == LDB_SUCCESS) { + in_transaction = false; + transaction_cancelled = true; + } + return ldb_transaction_cancel_ret; +} + +int ldb_transaction_commit_ret = LDB_SUCCESS; +bool transaction_committed = false; +int ldb_transaction_commit(struct ldb_context *ldb) { + assert_true(in_transaction); + if (ldb_transaction_commit_ret == LDB_SUCCESS) { + in_transaction = false; + transaction_committed = true; + } + return ldb_transaction_commit_ret; +} + +NTSTATUS dsdb_update_bad_pwd_count_ret = NT_STATUS_OK; +struct ldb_message *dsdb_update_bad_pwd_count_res = NULL; +NTSTATUS dsdb_update_bad_pwd_count(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct ldb_message *user_msg, + struct ldb_message *domain_msg, + struct ldb_message *pso_msg, + struct ldb_message **_mod_msg) { + + *_mod_msg = talloc_steal(mem_ctx, dsdb_update_bad_pwd_count_res); + return dsdb_update_bad_pwd_count_ret; +} + +int ldb_build_mod_req_ret = LDB_SUCCESS; +struct ldb_request *ldb_build_mod_req_res = NULL; +int ldb_build_mod_req(struct ldb_request **ret_req, + struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const struct ldb_message *message, + struct ldb_control **controls, + void *context, + ldb_request_callback_t callback, + struct ldb_request *parent) +{ + *ret_req = talloc_steal(mem_ctx, ldb_build_mod_req_res); + return ldb_build_mod_req_ret; +} + +int ldb_request_add_control_ret = LDB_SUCCESS; +int ldb_request_add_control(struct ldb_request *req, + const char *oid, + bool critical, + void *data) +{ + return ldb_request_add_control_ret; +} + +int ldb_request_ret = LDB_SUCCESS; +int ldb_request(struct ldb_context *ldb, + struct ldb_request *req) +{ + return ldb_request_ret; +} + +int ldb_wait_ret = LDB_SUCCESS; +int ldb_wait(struct ldb_handle *handle, + enum ldb_wait_type type) +{ + return ldb_wait_ret; +} +bool ldb_msg_new_fail = false; +struct ldb_message *ldb_msg_new(TALLOC_CTX *mem_ctx) +{ + if (ldb_msg_new_fail) { + return NULL; + } else { + return talloc_zero(mem_ctx, struct ldb_message); + } +} + +int samdb_rodc_ret = LDB_SUCCESS; +bool samdb_rodc_res = false; + +int samdb_rodc( + struct ldb_context *sam_ctx, + bool *am_rodc) +{ + + *am_rodc = samdb_rodc_res; + return samdb_rodc_ret; +} + +struct loadparm_context *ldb_get_opaque_ret = NULL; +void *ldb_get_opaque(struct ldb_context *ldb, const char *name) +{ + return ldb_get_opaque_ret; +} + +struct db_context {}; +struct db_context *cluster_db_tmp_open_ret = NULL; +struct db_context *cluster_db_tmp_open( + TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const char *dbbase, + int flags) +{ + return cluster_db_tmp_open_ret; +} + +NTSTATUS dbwrap_store_ret = NT_STATUS_OK; +NTSTATUS dbwrap_store(struct db_context *db, TDB_DATA key, + TDB_DATA data, int flags) +{ + return dbwrap_store_ret; +} +bool dbwrap_exists_ret = true; + +bool dbwrap_exists(struct db_context *db, TDB_DATA key) +{ + return dbwrap_exists_ret; +} + +NTSTATUS dbwrap_delete_ret = NT_STATUS_OK; +NTSTATUS dbwrap_delete(struct db_context *db, TDB_DATA key) +{ + return dbwrap_delete_ret; +} + +/* + * Set the globals used by the mocked functions to a known and consistent state + * + */ +static void init_mock_results(TALLOC_CTX *mem_ctx) +{ + dsdb_search_dn_ret = NULL; + dsdb_search_dn_res = NULL; + dsdb_search_dn_ctr = 0; + + ldb_transaction_start_ret = LDB_SUCCESS; + in_transaction = false; + + ldb_transaction_cancel_ret = LDB_SUCCESS; + transaction_cancelled = false; + + ldb_transaction_commit_ret = LDB_SUCCESS; + transaction_committed = false; + + dsdb_update_bad_pwd_count_ret = NT_STATUS_OK; + dsdb_update_bad_pwd_count_res = NULL; + + ldb_build_mod_req_ret = LDB_SUCCESS; + ldb_build_mod_req_res = NULL; + + ldb_request_add_control_ret = LDB_SUCCESS; + ldb_request_ret = LDB_SUCCESS; + ldb_wait_ret = LDB_SUCCESS; + + dsdb_search_by_dn_guid_ret = LDB_SUCCESS; + dsdb_search_by_dn_guid_res = NULL; + + ldb_msg_new_fail = false; + + samdb_rodc_ret = LDB_SUCCESS; + samdb_rodc_res = false; + + ldb_get_opaque_ret = loadparm_init(mem_ctx); + + cluster_db_tmp_open_ret = talloc_zero(mem_ctx, struct db_context); + + dbwrap_store_ret = NT_STATUS_OK; + + dbwrap_exists_ret = true; + + dbwrap_delete_ret = NT_STATUS_OK; + +} + +/***************************************************************************** + * Unit test set up and tear down + *****************************************************************************/ +struct context { +}; + +static int setup(void **state) { + struct context *ctx = talloc_zero(NULL, struct context); + init_mock_results(ctx); + + *state = ctx; + return 0; +} + +static int teardown(void **state) { + struct context *ctx = *state; + TALLOC_FREE(ctx); + return 0; +} + +/****************************************************************************** + * Helper functions + ******************************************************************************/ + +/* + * Build the "Original" user details record, i.e. the user being + * authenticated + */ +static struct ldb_message* create_message(TALLOC_CTX *ctx) +{ + + int ret; + struct timeval tv_now = timeval_current(); + NTTIME now = timeval_to_nttime(&tv_now); + + struct ldb_message* msg = ldb_msg_new(ctx); + + assert_non_null(msg); + ret = samdb_msg_add_int(ctx, msg, msg, "badPwdCount", 10); + assert_int_equal(LDB_SUCCESS, ret); + ret = __real_samdb_msg_add_int64(ctx, msg, msg, "badPasswordTime", now); + assert_int_equal(LDB_SUCCESS, ret); + ret = __real_samdb_msg_add_int64(ctx, msg, msg, "lockoutTime", now); + assert_int_equal(LDB_SUCCESS, ret); + return msg; +} + +/* + * Add the "objectGUID element to a message + */ +static void add_object_guid(struct ldb_message *msg, + struct GUID *guid) +{ + + NTSTATUS status; + int ret; + DATA_BLOB guid_blob; + struct ldb_val *new_val; + + status = GUID_to_ndr_blob(guid, msg, &guid_blob); + assert_true(NT_STATUS_IS_OK(status)); + new_val = talloc(msg, struct ldb_val); + assert_non_null(new_val); + new_val->data = talloc_steal(new_val, guid_blob.data); + new_val->length = guid_blob.length; + ret = ldb_msg_add_value(msg, "objectGUID", new_val, NULL); + assert_int_equal(LDB_SUCCESS, ret); +} + +/* + * Add an objectSID in string form to the supplied message + * + * + */ +static void add_sid( + struct ldb_message *msg, + const char *sid_str) +{ + struct ldb_val v; + enum ndr_err_code ndr_err; + struct dom_sid *sid = NULL; + + sid = talloc_zero(msg, struct dom_sid); + assert_non_null(sid); + assert_true(string_to_sid(sid, sid_str)); + ndr_err = ndr_push_struct_blob( + &v, msg, sid, (ndr_push_flags_fn_t)ndr_push_dom_sid); + assert_true(NDR_ERR_CODE_IS_SUCCESS(ndr_err)); + assert_int_equal(0, ldb_msg_add_value(msg, "objectSID", &v, NULL)); +} + +/* + * Build an ldb_result, for the re-reading of a user record + * + * if account_control < 0 then the msDS-User-Account-Control-Computed + * element is not included + * otherwise it is set to the value passed in account_control. + * + */ +static struct ldb_result *build_reread_result( + struct ldb_context *ldb, + TALLOC_CTX *ctx, + int account_control) +{ + struct ldb_message *msg = NULL; + int ret; + + struct ldb_result *res = talloc_zero(ctx, struct ldb_result); + + assert_non_null(res); + res->count = 1; + res->msgs = talloc_array(res, struct ldb_message *, 1); + + msg = create_message(res); + if (account_control >= 0) { + ret = samdb_msg_add_int( + ldb, + msg, + msg, + "msDS-User-Account-Control-Computed", + account_control); + assert_int_equal(LDB_SUCCESS, ret); + } + + res->msgs[0] = msg; + return res; +} + +/* + * Build a mock domain pso ldb_result + */ +static struct ldb_result *build_domain_pso_result( + struct ldb_context *ldb, + TALLOC_CTX *ctx) +{ + struct ldb_message *msg = NULL; + struct ldb_result *res = talloc_zero(ctx, struct ldb_result); + + assert_non_null(res); + res->count = 1; + res->msgs = talloc_array(res, struct ldb_message *, 1); + assert_non_null(res->msgs); + msg = talloc_zero(res, struct ldb_message); + assert_non_null(msg); + res->msgs[0] = msg; + return res; +} + +/***************************************************************************** + * authsam_reread_user_logon_data unit tests + *****************************************************************************/ +/* + * Pass an empty user message to authsam_reread_user_logon_data + * + */ +static void test_reread_empty_msg(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_message *cur = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = ldb_msg_new(ldb); + assert_non_null(msg); + + before = talloc_total_size(ctx); + + status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * Pass a user message to authsam_reread_user_logon_data that does not have + * a objectGUID element + * + */ +static void test_reread_no_objectGUID(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_message *cur = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + + before = talloc_total_size(ctx); + + status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * Pass a user message to authsam_reread_user_logon_data that has an invalid + * objectGUID element + * + */ +static void test_reread_invalid_objectGUID(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_message *cur = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + int ret; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + ret = ldb_msg_add_string(msg, "objectGUID", "wibble"); + assert_int_equal(LDB_SUCCESS, ret); + + before = talloc_total_size(ctx); + + status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_reread_user_logon_data unable to re-read the user record. + * + */ +static void test_reread_read_failure(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_message *cur = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_ret = LDB_ERR_NO_SUCH_OBJECT; + + status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_reread_user_logon_data account control flags missing from + * re-read data + * + */ +static void test_reread_missing_account_control(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_message *cur = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, -1); + dsdb_search_by_dn_guid_ret = LDB_SUCCESS; + + status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_reread_user_logon_data account locked + * re-read data + * + */ +static void test_reread_account_locked(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_message *cur = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, UF_LOCKOUT); + + status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_reread_user_logon_data account is not locked + * re-read data + * + */ +static void test_reread_account_not_locked(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_message *cur = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + size_t result_size = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + /* + * authsam_reread_user_logon_data returns the ldb_message portion + * of the ldb_result created by build_reread_result. + * So the tests for memory leaks will need to adjust for that + */ + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + result_size = talloc_total_size(dsdb_search_by_dn_guid_res) - + talloc_total_size(dsdb_search_by_dn_guid_res->msgs[0]); + before = talloc_total_size(ctx); + before -= result_size; + + status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur); + assert_true(NT_STATUS_IS_OK(status)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + + +/***************************************************************************** + * authsam_update_bad_pwd_count unit tests + *****************************************************************************/ + +/* + * authsam_update_bad_pwd_account + * + * Unable to read the domain_dn record + * + */ +static void test_update_bad_domain_dn_search_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = talloc_zero(ctx, struct ldb_message); + assert_non_null(msg); + + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_ERR_NO_SUCH_OBJECT; + + before = talloc_total_size(ctx); + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_DB_CORRUPTION)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * authsam_get_user_pso failure + * + */ +static void test_update_bad_get_pso_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = talloc_zero(ctx, struct ldb_message); + assert_non_null(msg); + ldb_msg_add_string(msg, "msDS-ResultantPSO", ""); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 2); + dsdb_search_dn_ret = talloc_array(ctx, int, 2); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + dsdb_search_dn_ret[1] = LDB_ERR_NO_SUCH_OBJECT; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + fprintf(stderr, "==================== %s\n", nt_errstr(status)); + assert_true(NT_STATUS_IS_OK(status)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + + +/* + * authsam_update_bad_pwd_account + * + * start_transaction failure + * + */ +static void test_update_bad_start_txn_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = talloc_zero(ctx, struct ldb_message); + assert_non_null(msg); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + ldb_transaction_start_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * User details re-read failed + * + */ +static void test_update_bad_reread_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = talloc_zero(ctx, struct ldb_message); + assert_non_null(msg); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_ret = LDB_ERR_NO_SUCH_OBJECT; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * User details re-read reported locked out. + * + */ +static void test_update_bad_reread_locked_out(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + size_t result_size = 0; + NTSTATUS status; + struct GUID guid; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + assert_non_null(msg); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, UF_LOCKOUT); + before -= result_size; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT)); + assert_false(transaction_cancelled); + assert_true(transaction_committed); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} +/* + * authsam_update_bad_pwd_account + * + * Transaction cancel failure + */ +static void test_update_bad_txn_cancel_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = talloc_zero(ctx, struct ldb_message); + assert_non_null(msg); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_ret = LDB_ERR_NO_SUCH_OBJECT; + ldb_transaction_cancel_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_false(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * dsdb_update_bad_pwd_count failure + * + */ +static void test_update_bad_update_count_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + + dsdb_update_bad_pwd_count_ret = NT_STATUS_INTERNAL_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * No need to update the bad password stats + * + */ +static void test_update_bad_no_update_required(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_IS_OK(status)); + assert_true(transaction_committed); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * Transaction commit failure + * + */ +static void test_update_bad_commit_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + + ldb_transaction_commit_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_false(transaction_committed); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * ldb_build_mod_req failed building the user update details + * + */ +static void test_update_bad_build_mod_request_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + + dsdb_update_bad_pwd_count_res = + talloc_zero(ctx, struct ldb_message); + ldb_build_mod_req_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * ldb_request_add_control failed to add DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE + * to the user update record. + * + */ +static void test_update_bad_add_control_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + + dsdb_update_bad_pwd_count_res = + talloc_zero(dsdb_search_dn_res, struct ldb_message); + ldb_build_mod_req_res = + talloc_zero(dsdb_search_dn_res, struct ldb_request); + ldb_request_add_control_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * call to ldb_request failed + * + */ +static void test_update_bad_ldb_request_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + + dsdb_update_bad_pwd_count_res = + talloc_zero(dsdb_search_dn_res, struct ldb_message); + ldb_build_mod_req_res = + talloc_zero(dsdb_search_dn_res, struct ldb_request); + ldb_request_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * call to ldb_wait failed + * + */ +static void test_update_bad_ldb_wait_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + + dsdb_update_bad_pwd_count_res = + talloc_zero(dsdb_search_dn_res, struct ldb_message); + ldb_build_mod_req_res = + talloc_zero(dsdb_search_dn_res, struct ldb_request); + ldb_wait_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/***************************************************************************** + * authsam_logon_success_accounting unit tests + *****************************************************************************/ +/* + * authsam_logon_success_accounting + * + * start_transaction failure + * + */ +static void test_success_accounting_start_txn_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + ldb_transaction_start_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * User details re-read failed + * + */ +static void test_success_accounting_reread_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_ret = LDB_ERR_NO_SUCH_OBJECT; + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * ldb_msg_new failed + * + */ +static void test_success_accounting_ldb_msg_new_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + ldb_msg_new_fail = true; + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * samdb_rodc failed + * + */ +static void test_success_accounting_samdb_rodc_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + samdb_rodc_ret = LDB_ERR_OPERATIONS_ERROR; + + will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS); + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * authsam_update_lastlogon_timestamp failed + * + */ +static void test_success_accounting_update_lastlogon_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + + ldb_build_mod_req_res = + talloc_zero(dsdb_search_dn_res, struct ldb_request); + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + + will_return(__wrap_samdb_msg_add_int64, LDB_ERR_OPERATIONS_ERROR); + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * ldb_build_mod_req failed + * + */ +static void test_success_accounting_build_mod_req_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + + ldb_build_mod_req_ret = LDB_ERR_OPERATIONS_ERROR; + + will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS); + will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS); + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * ldb_request_add_control failed + * + */ +static void test_success_accounting_add_control_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + + ldb_build_mod_req_res = + talloc_zero(ldb, struct ldb_request); + ldb_request_add_control_ret = LDB_ERR_OPERATIONS_ERROR; + + will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS); + will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS); + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * ldb_request failed + * + */ +static void test_success_accounting_ldb_request_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + + ldb_build_mod_req_res = + talloc_zero(ldb, struct ldb_request); + ldb_request_ret = LDB_ERR_OPERATIONS_ERROR; + + will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS); + will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS); + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * ldb_wait failed + * + */ +static void test_success_accounting_ldb_wait_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + + ldb_build_mod_req_res = + talloc_zero(ldb, struct ldb_request); + ldb_wait_ret = LDB_ERR_OPERATIONS_ERROR; + + will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS); + will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS); + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * ldb_transaction_commit failed + * + */ +static void test_success_accounting_commit_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + + ldb_build_mod_req_res = + talloc_zero(ldb, struct ldb_request); + ldb_transaction_commit_ret = LDB_ERR_OPERATIONS_ERROR; + + will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS); + will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS); + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_false(transaction_cancelled); + assert_false(transaction_committed); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * ldb_wait failed and then ldb_transaction_cancel failed + * + */ +static void test_success_accounting_rollback_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000"); + + dsdb_search_dn_res = talloc_array(ctx, struct ldb_result *, 1); + dsdb_search_dn_ret = talloc_array(ctx, int, 1); + dsdb_search_dn_ret[0] = LDB_SUCCESS; + + before = talloc_total_size(ctx); + dsdb_search_dn_res[0] = build_domain_pso_result(ldb, ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + + ldb_build_mod_req_res = + talloc_zero(ldb, struct ldb_request); + ldb_wait_ret = LDB_ERR_OPERATIONS_ERROR; + ldb_transaction_cancel_ret = LDB_ERR_OPERATIONS_ERROR; + + will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS); + will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS); + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_false(transaction_cancelled); + assert_false(transaction_committed); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * get_bad_password_db + * + * ldb_get_opaque failure. + */ +static void test_get_bad_password_get_opaque_failed(void **state) { + struct ldb_context *ldb = NULL; + TALLOC_CTX *ctx = NULL; + struct db_context *db = NULL; + size_t before = 0; + size_t after = 0; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + /* + * clear the mock ldb_get_opaque return value, so that we get a null + * response. + */ + TALLOC_FREE(ldb_get_opaque_ret); + + before = talloc_total_size(ctx); + + db = authsam_get_bad_password_db(ctx, ldb); + assert_null(db); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * get_bad_password_db + * + * cluster_db_tmp_open failure. + */ +static void test_get_bad_password_db_open_failed(void **state) { + struct ldb_context *ldb = NULL; + TALLOC_CTX *ctx = NULL; + struct db_context *db = NULL; + size_t before = 0; + size_t after = 0; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + /* + * Clear the mock cluster_db_tmp_open return value so that + * it returns NULL + */ + TALLOC_FREE(cluster_db_tmp_open_ret); + before = talloc_total_size(ctx); + + db = authsam_get_bad_password_db(ctx, ldb); + assert_null(db); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * set_bad_password_indicator + * + * set_bad_password_indicator failure. + */ +static void test_set_bad_password_indicator_get_db_failed(void **state) { + struct ldb_context *ldb = NULL; + TALLOC_CTX *ctx = NULL; + NTSTATUS status; + size_t before = 0; + size_t after = 0; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + /* + * Clear the mock cluster_db_tmp_open return value so that + * it returns NULL + */ + TALLOC_FREE(cluster_db_tmp_open_ret); + before = talloc_total_size(ctx); + + status = authsam_set_bad_password_indicator(ldb, ctx, NULL); + assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_ERROR, status)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * set_bad_password_indicator + * + * get_object_sid_as_tdb_data failure. + */ +static void test_set_bad_password_indicator_get_object_sid_failed( + void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + TALLOC_CTX *ctx = NULL; + NTSTATUS status; + size_t before = 0; + size_t after = 0; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + /* + * The created message does not contain a GUID, so + * get_object_sid_as_tdb_data will fail. + */ + msg = create_message(ctx); + + before = talloc_total_size(ctx); + + status = authsam_set_bad_password_indicator(ldb, ctx, msg); + assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_ERROR, status)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * set_bad_password_indicator + * + * dbwrap_store failure. + */ +static void test_set_bad_password_indicator_dbwrap_store_failed( + void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + TALLOC_CTX *ctx = NULL; + NTSTATUS status; + size_t before = 0; + size_t after = 0; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1010"); + + dbwrap_store_ret = NT_STATUS_INTERNAL_DB_CORRUPTION; + + before = talloc_total_size(ctx); + + status = authsam_set_bad_password_indicator(ldb, ctx, msg); + assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_DB_CORRUPTION, status)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * check_bad_password_indicator + * + * set_bad_password_indicator failure. + */ +static void test_check_bad_password_indicator_get_db_failed(void **state) { + struct ldb_context *ldb = NULL; + TALLOC_CTX *ctx = NULL; + NTSTATUS status; + size_t before = 0; + size_t after = 0; + bool exists = false; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + /* + * Clear the mock cluster_db_tmp_open return value so that + * it returns NULL + */ + TALLOC_FREE(cluster_db_tmp_open_ret); + before = talloc_total_size(ctx); + + status = authsam_check_bad_password_indicator(ldb, ctx, &exists, NULL); + assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_ERROR, status)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * check_bad_password_indicator + * + * get_object_sid_as_tdb_data failure. + */ +static void test_check_bad_password_indicator_get_object_sid_failed( + void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + TALLOC_CTX *ctx = NULL; + NTSTATUS status; + size_t before = 0; + size_t after = 0; + bool exists = false; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + /* + * The created message does not contain a ObjectSID, so + * get_object_sid_as_tdb_data will fail. + */ + msg = create_message(ctx); + + before = talloc_total_size(ctx); + + status = authsam_check_bad_password_indicator(ldb, ctx, &exists, msg); + assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_ERROR, status)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * clear_bad_password_indicator + * + * set_bad_password_indicator failure. + */ +static void test_clear_bad_password_indicator_get_db_failed(void **state) { + struct ldb_context *ldb = NULL; + TALLOC_CTX *ctx = NULL; + NTSTATUS status; + size_t before = 0; + size_t after = 0; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + /* + * Clear the mock cluster_db_tmp_open return value so that + * it returns NULL + */ + TALLOC_FREE(cluster_db_tmp_open_ret); + before = talloc_total_size(ctx); + + status = authsam_clear_bad_password_indicator(ldb, ctx, NULL); + assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_ERROR, status)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * clear_bad_password_indicator + * + * get_object_sid_as_tdb_data failure. + */ +static void test_clear_bad_password_indicator_get_object_sid_failed( + void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + TALLOC_CTX *ctx = NULL; + NTSTATUS status; + size_t before = 0; + size_t after = 0; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + /* + * The created message does not contain a objectSID, so + * get_object_sid_as_tdb_data will fail. + */ + msg = create_message(ctx); + + before = talloc_total_size(ctx); + + status = authsam_clear_bad_password_indicator(ldb, ctx, msg); + assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_ERROR, status)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * clear_bad_password_indicator + * + * dbwrap_delete failure. + */ +static void test_clear_bad_password_indicator_dbwrap_store_failed( + void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + TALLOC_CTX *ctx = NULL; + NTSTATUS status; + size_t before = 0; + size_t after = 0; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1010"); + + dbwrap_delete_ret = NT_STATUS_INTERNAL_DB_CORRUPTION; + + before = talloc_total_size(ctx); + + status = authsam_clear_bad_password_indicator(ldb, ctx, msg); + assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_DB_CORRUPTION, status)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * clear_bad_password_indicator + * + * dbwrap_delete returns NT_STATUS_NOT_FOUND. + */ +static void test_clear_bad_pwd_indicator_dbwrap_store_not_found( + void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + TALLOC_CTX *ctx = NULL; + NTSTATUS status; + size_t before = 0; + size_t after = 0; + + ctx = talloc_new(*state); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1010"); + + dbwrap_delete_ret = NT_STATUS_NOT_FOUND; + + before = talloc_total_size(ctx); + + status = authsam_clear_bad_password_indicator(ldb, ctx, msg); + assert_true(NT_STATUS_IS_OK(status)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_reread_empty_msg, setup, teardown), + cmocka_unit_test_setup_teardown( + test_reread_no_objectGUID, setup, teardown), + cmocka_unit_test_setup_teardown( + test_reread_invalid_objectGUID, setup, teardown), + cmocka_unit_test_setup_teardown( + test_reread_read_failure, setup, teardown), + cmocka_unit_test_setup_teardown( + test_reread_missing_account_control, setup, teardown), + cmocka_unit_test_setup_teardown( + test_reread_account_locked, setup, teardown), + cmocka_unit_test_setup_teardown( + test_reread_account_not_locked, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_domain_dn_search_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_get_pso_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_start_txn_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_reread_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_reread_locked_out, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_update_count_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_no_update_required, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_build_mod_request_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_add_control_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_ldb_request_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_ldb_wait_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_txn_cancel_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_commit_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_start_txn_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_reread_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_ldb_msg_new_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_samdb_rodc_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_update_lastlogon_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_build_mod_req_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_add_control_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_ldb_request_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_ldb_wait_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_commit_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_rollback_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_get_bad_password_get_opaque_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_get_bad_password_db_open_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_set_bad_password_indicator_get_db_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_set_bad_password_indicator_get_object_sid_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_set_bad_password_indicator_dbwrap_store_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_check_bad_password_indicator_get_db_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_check_bad_password_indicator_get_object_sid_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_clear_bad_password_indicator_get_db_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_clear_bad_password_indicator_get_object_sid_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_clear_bad_password_indicator_dbwrap_store_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_clear_bad_pwd_indicator_dbwrap_store_not_found, + setup, + teardown), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/source4/auth/wscript_build b/source4/auth/wscript_build index 381a7b19bf0..eb0a26cceae 100644 --- a/source4/auth/wscript_build +++ b/source4/auth/wscript_build @@ -49,6 +49,16 @@ bld.SAMBA_BINARY('test_kerberos', for_selftest=True ) +bld.SAMBA_BINARY('test_auth_sam', + source='tests/sam.c', + deps='cmocka samdb samba-security ldb tevent', + local_include=False, + for_selftest=True, + ldflags=''' + -Wl,--wrap,samdb_msg_add_int64 + ''' + ) + pytalloc_util = bld.pyembed_libname('pytalloc-util') pyparam_util = bld.pyembed_libname('pyparam_util') pyldb_util = bld.pyembed_libname('pyldb-util') -- 2.25.1