From 0f581e4af943a7e5dfd71d1c308ac668f287aed3 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Mon, 10 May 2004 11:23:50 +0000 Subject: r623: setUserInfo level 24 (password set) now works in the SAMR server. This includes all of the password complexity, password history and other password restrictions. (This used to be commit cb070b9084d95cf5178edbef951b75eab62b7220) --- source4/rpc_server/samr/dcesrv_samr.c | 73 +++++++-- source4/rpc_server/samr/samdb.c | 270 ++++++++++++++++++++++++++++++++++ 2 files changed, 332 insertions(+), 11 deletions(-) (limited to 'source4/rpc_server/samr') diff --git a/source4/rpc_server/samr/dcesrv_samr.c b/source4/rpc_server/samr/dcesrv_samr.c index 6412192127..1d64fad5ba 100644 --- a/source4/rpc_server/samr/dcesrv_samr.c +++ b/source4/rpc_server/samr/dcesrv_samr.c @@ -1628,6 +1628,35 @@ static NTSTATUS samr_QueryUserInfo(struct dcesrv_call_state *dce_call, TALLOC_CT } +/* + set password via a samr_CryptPassword buffer + this will in the 'msg' with modify operations that will update the user + password when applied +*/ +static NTSTATUS samr_set_password(struct dcesrv_call_state *dce_call, + struct samr_account_state *state, TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + struct samr_CryptPassword *pwbuf) +{ + char new_pass[512]; + uint32 new_pass_len; + DATA_BLOB session_key = dce_call->conn->session_key; + + SamOEMhashBlob(pwbuf->data, 516, &session_key); + + if (!decode_pw_buffer(pwbuf->data, new_pass, sizeof(new_pass), + &new_pass_len, STR_UNICODE)) { + DEBUG(3,("samr: failed to decode password buffer\n")); + return NT_STATUS_WRONG_PASSWORD; + } + + /* set the password - samdb needs to know both the domain and user DNs, + so the domain password policy can be used */ + return samdb_set_password(state->sam_ctx, mem_ctx, + state->basedn, state->domain_state->basedn, + msg, new_pass); +} + /* samr_SetUserInfo */ @@ -1638,6 +1667,7 @@ static NTSTATUS samr_SetUserInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX struct samr_account_state *state; struct ldb_message mod, *msg = &mod; int i, ret; + NTSTATUS status = NT_STATUS_OK; DCESRV_PULL_HANDLE(h, r->in.handle, SAMR_HANDLE_USER); @@ -1703,30 +1733,51 @@ static NTSTATUS samr_SetUserInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX case 21: #define IFSET(bit) if (bit & r->in.info->info21.fields_present) - IFSET(SAMR_FIELD_NAME) SET_STRING(msg, info21.full_name.name, "displayName"); - IFSET(SAMR_FIELD_DESCRIPTION) SET_STRING(msg, info21.description.name, "description"); - IFSET(SAMR_FIELD_COMMENT) SET_STRING(msg, info21.comment.name, "comment"); - IFSET(SAMR_FIELD_LOGON_SCRIPT) SET_STRING(msg, info21.logon_script.name, "scriptPath"); - IFSET(SAMR_FIELD_PROFILE) SET_STRING(msg, info21.profile.name, "profilePath"); - IFSET(SAMR_FIELD_WORKSTATION) SET_STRING(msg, info21.workstations.name, "userWorkstations"); - IFSET(SAMR_FIELD_LOGON_HOURS) SET_LHOURS(msg, info21.logon_hours, "logonHours"); - IFSET(SAMR_FIELD_CALLBACK) SET_STRING(msg, info21.callback.name, "userParameters"); - IFSET(SAMR_FIELD_COUNTRY_CODE) SET_UINT(msg, info21.country_code, "countryCode"); - IFSET(SAMR_FIELD_CODE_PAGE) SET_UINT(msg, info21.code_page, "codePage"); + IFSET(SAMR_FIELD_NAME) + SET_STRING(msg, info21.full_name.name, "displayName"); + IFSET(SAMR_FIELD_DESCRIPTION) + SET_STRING(msg, info21.description.name, "description"); + IFSET(SAMR_FIELD_COMMENT) + SET_STRING(msg, info21.comment.name, "comment"); + IFSET(SAMR_FIELD_LOGON_SCRIPT) + SET_STRING(msg, info21.logon_script.name, "scriptPath"); + IFSET(SAMR_FIELD_PROFILE) + SET_STRING(msg, info21.profile.name, "profilePath"); + IFSET(SAMR_FIELD_WORKSTATION) + SET_STRING(msg, info21.workstations.name, "userWorkstations"); + IFSET(SAMR_FIELD_LOGON_HOURS) + SET_LHOURS(msg, info21.logon_hours, "logonHours"); + IFSET(SAMR_FIELD_CALLBACK) + SET_STRING(msg, info21.callback.name, "userParameters"); + IFSET(SAMR_FIELD_COUNTRY_CODE) + SET_UINT (msg, info21.country_code, "countryCode"); + IFSET(SAMR_FIELD_CODE_PAGE) + SET_UINT (msg, info21.code_page, "codePage"); break; + /* the set password levels are handled separately */ + case 24: + status = samr_set_password(dce_call, state, mem_ctx, msg, + &r->in.info->info24.password); + break; + + default: /* many info classes are not valid for SetUserInfo */ return NT_STATUS_INVALID_INFO_CLASS; } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + /* mark all the message elements as LDB_FLAG_MOD_REPLACE */ for (i=0;isam_ctx, mem_ctx, &mod); + ret = samdb_modify(state->sam_ctx, mem_ctx, msg); if (ret != 0) { /* we really need samdb.c to return NTSTATUS */ return NT_STATUS_UNSUCCESSFUL; diff --git a/source4/rpc_server/samr/samdb.c b/source4/rpc_server/samr/samdb.c index c6a85176da..5f59ce1883 100644 --- a/source4/rpc_server/samr/samdb.c +++ b/source4/rpc_server/samr/samdb.c @@ -371,6 +371,52 @@ NTTIME samdb_result_force_pwd_change(void *ctx, TALLOC_CTX *mem_ctx, return nttime_from_double_nt(attr_time); } +/* + pull a samr_Hash structutre from a result set. +*/ +struct samr_Hash samdb_result_hash(struct ldb_message *msg, const char *attr) +{ + struct samr_Hash hash; + const struct ldb_val *val = ldb_msg_find_ldb_val(msg, attr); + ZERO_STRUCT(hash); + if (val) { + memcpy(hash.hash, val->data, MIN(val->length, 16)); + } + return hash; +} + +/* + pull an array of samr_Hash structutres from a result set. +*/ +uint_t samdb_result_hashes(TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr, struct samr_Hash **hashes) +{ + uint_t count = 0; + const struct ldb_val *val = ldb_msg_find_ldb_val(msg, attr); + int i; + + *hashes = NULL; + if (!val) { + return 0; + } + count = val->length / 16; + if (count == 0) { + return 0; + } + + *hashes = talloc_array_p(mem_ctx, struct samr_Hash, count); + if (! *hashes) { + return 0; + } + + for (i=0;idata, 16); + } + + return count; +} + + /* pull a samr_LogonHours structutre from a result set. */ @@ -590,6 +636,55 @@ int samdb_msg_add_uint(void *ctx, TALLOC_CTX *mem_ctx, struct ldb_message *msg, return samdb_msg_add_string(ctx, mem_ctx, msg, attr_name, s); } +/* + add a double element to a message (actually a large integer) +*/ +int samdb_msg_add_double(void *ctx, TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr_name, double v) +{ + const char *s = talloc_asprintf(mem_ctx, "%.0f", v); + return samdb_msg_add_string(ctx, mem_ctx, msg, attr_name, s); +} + +/* + add a samr_Hash element to a message +*/ +int samdb_msg_add_hash(void *ctx, TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr_name, struct samr_Hash hash) +{ + struct samdb_context *sam_ctx = ctx; + struct ldb_val val; + val.data = talloc(mem_ctx, 16); + val.length = 16; + if (!val.data) { + return -1; + } + memcpy(val.data, hash.hash, 16); + ldb_set_alloc(sam_ctx->ldb, samdb_alloc, mem_ctx); + return ldb_msg_add_value(sam_ctx->ldb, msg, attr_name, &val); +} + +/* + add a samr_Hash array to a message +*/ +int samdb_msg_add_hashes(void *ctx, TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr_name, struct samr_Hash *hashes, uint_t count) +{ + struct samdb_context *sam_ctx = ctx; + struct ldb_val val; + int i; + val.data = talloc(mem_ctx, count*16); + val.length = count*16; + if (!val.data) { + return -1; + } + for (i=0;ildb, samdb_alloc, mem_ctx); + return ldb_msg_add_value(sam_ctx->ldb, msg, attr_name, &val); +} + /* add a acct_flags element to a message */ @@ -682,3 +777,178 @@ int samdb_modify(void *ctx, TALLOC_CTX *mem_ctx, struct ldb_message *msg) ldb_set_alloc(sam_ctx->ldb, samdb_alloc, mem_ctx); return ldb_modify(sam_ctx->ldb, msg); } + +/* + check that a password is sufficiently complex +*/ +static BOOL samdb_password_complexity_ok(const char *pass) +{ + return check_password_quality(pass); +} + +/* + set the user password using plaintext, obeying any user or domain + password restrictions +*/ +NTSTATUS samdb_set_password(void *ctx, TALLOC_CTX *mem_ctx, + const char *user_dn, const char *domain_dn, + struct ldb_message *mod, const char *new_pass) +{ + const char * const user_attrs[] = { "userAccountControl", "lmPwdHistory", + "ntPwdHistory", "unicodePwd", + "lmPwdHash", "ntPwdHash", "badPwdCount", + NULL }; + const char * const domain_attrs[] = { "pwdProperties", "pwdHistoryLength", + "maxPwdAge", "minPwdAge", + "minPwdLength", "pwdLastSet", NULL }; + const char *unicodePwd; + double minPwdAge, pwdLastSet; + uint_t minPwdLength, pwdProperties, pwdHistoryLength; + uint_t userAccountControl, badPwdCount; + struct samr_Hash *lmPwdHistory, *ntPwdHistory, lmPwdHash, ntPwdHash; + struct samr_Hash *new_lmPwdHistory, *new_ntPwdHistory; + struct samr_Hash lmNewHash, ntNewHash; + uint_t lmPwdHistory_len, ntPwdHistory_len; + struct ldb_message **res; + int count; + time_t now = time(NULL); + NTTIME now_nt; + double now_double; + int i; + + /* we need to know the time to compute password age */ + unix_to_nt_time(&now_nt, now); + now_double = nttime_to_double_nt(now_nt); + + /* pull all the user parameters */ + count = samdb_search(ctx, mem_ctx, NULL, &res, user_attrs, "dn=%s", user_dn); + if (count != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + unicodePwd = samdb_result_string(res[0], "unicodePwd", NULL); + userAccountControl = samdb_result_uint(res[0], "userAccountControl", 0); + badPwdCount = samdb_result_uint(res[0], "badPwdCount", 0); + lmPwdHistory_len = samdb_result_hashes(mem_ctx, res[0], + "lmPwdHistory", &lmPwdHistory); + ntPwdHistory_len = samdb_result_hashes(mem_ctx, res[0], + "ntPwdHistory", &ntPwdHistory); + lmPwdHash = samdb_result_hash(res[0], "lmPwdHash"); + ntPwdHash = samdb_result_hash(res[0], "ntPwdHash"); + pwdLastSet = samdb_result_double(res[0], "pwdLastSet", 0); + + /* pull the domain parameters */ + count = samdb_search(ctx, mem_ctx, NULL, &res, domain_attrs, "dn=%s", domain_dn); + if (count != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + pwdProperties = samdb_result_uint(res[0], "pwdProperties", 0); + pwdHistoryLength = samdb_result_uint(res[0], "pwdHistoryLength", 0); + minPwdLength = samdb_result_uint(res[0], "minPwdLength", 0); + minPwdAge = samdb_result_double(res[0], "minPwdAge", 0); + + /* are all password changes disallowed? */ + if (pwdProperties & DOMAIN_REFUSE_PASSWORD_CHANGE) { + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* can this user change password? */ + if (userAccountControl & UF_PASSWD_CANT_CHANGE) { + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* check the various password restrictions */ + if (minPwdLength > str_charnum(new_pass)) { + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* yes, this is a minus. The ages are in negative 100nsec units! */ + if (pwdLastSet - minPwdAge > now_double) { + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* possibly check password complexity */ + if (pwdProperties & DOMAIN_PASSWORD_COMPLEX && + !samdb_password_complexity_ok(new_pass)) { + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* compute the new nt and lm hashes */ + E_deshash(new_pass, lmNewHash.hash); + E_md4hash(new_pass, ntNewHash.hash); + + /* check the immediately past password */ + if (pwdHistoryLength > 0 && + (memcmp(lmNewHash.hash, lmPwdHash.hash, 16) == 0 || + memcmp(ntNewHash.hash, ntPwdHash.hash, 16) == 0)) { + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* check the password history */ + lmPwdHistory_len = MIN(lmPwdHistory_len, pwdHistoryLength); + ntPwdHistory_len = MIN(ntPwdHistory_len, pwdHistoryLength); + + if (pwdHistoryLength > 0) { + if (strcmp(unicodePwd, new_pass) == 0 || + memcmp(lmNewHash.hash, lmPwdHash.hash, 16) == 0 || + memcmp(ntNewHash.hash, ntPwdHash.hash, 16) == 0) { + return NT_STATUS_PASSWORD_RESTRICTION; + } + } + + for (i=0;i 0) { + new_lmPwdHistory = talloc_array_p(mem_ctx, struct samr_Hash, + pwdHistoryLength); + if (!new_lmPwdHistory) { + return NT_STATUS_NO_MEMORY; + } + new_ntPwdHistory = talloc_array_p(mem_ctx, struct samr_Hash, + pwdHistoryLength); + if (!new_ntPwdHistory) { + return NT_STATUS_NO_MEMORY; + } + for (i=0;i