/* Unix SMB/CIFS implementation. samr server password set/change handling Copyright (C) Andrew Tridgell 2004 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 2 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, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "includes.h" #include "rpc_server/common/common.h" #include "rpc_server/samr/dcesrv_samr.h" /* samr_ChangePasswordUser */ NTSTATUS samr_ChangePasswordUser(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct samr_ChangePasswordUser *r) { struct dcesrv_handle *h; struct samr_account_state *a_state; struct ldb_message **res, mod, *msg; int ret; struct samr_Hash *lmPwdHash=NULL, *ntPwdHash=NULL; struct samr_Hash new_lmPwdHash, new_ntPwdHash, checkHash; NTSTATUS status = NT_STATUS_OK; const char * const attrs[] = { "lmPwdHash", "ntPwdHash" , NULL }; DCESRV_PULL_HANDLE(h, r->in.handle, SAMR_HANDLE_USER); a_state = h->data; /* fetch the old hashes */ ret = samdb_search(a_state->sam_ctx, mem_ctx, NULL, &res, attrs, "dn=%s", a_state->account_dn); if (ret != 1) { return NT_STATUS_INTERNAL_DB_CORRUPTION; } msg = res[0]; /* basic sanity checking on parameters */ if (!r->in.lm_present || !r->in.nt_present || !r->in.old_lm_crypted || !r->in.new_lm_crypted || !r->in.old_nt_crypted || !r->in.new_nt_crypted) { /* we should really handle a change with lm not present */ return NT_STATUS_INVALID_PARAMETER_MIX; } if (!r->in.cross1_present || !r->in.nt_cross) { return NT_STATUS_NT_CROSS_ENCRYPTION_REQUIRED; } if (!r->in.cross2_present || !r->in.lm_cross) { return NT_STATUS_LM_CROSS_ENCRYPTION_REQUIRED; } ret = samdb_result_hashes(mem_ctx, msg, "lmPwdHash", &lmPwdHash); if (ret != 1) { return NT_STATUS_WRONG_PASSWORD; } ret = samdb_result_hashes(mem_ctx, msg, "ntPwdHash", &ntPwdHash); if (ret != 1) { return NT_STATUS_WRONG_PASSWORD; } /* decrypt and check the new lm hash */ D_P16(lmPwdHash->hash, r->in.new_lm_crypted->hash, new_lmPwdHash.hash); D_P16(new_lmPwdHash.hash, r->in.old_lm_crypted->hash, checkHash.hash); if (memcmp(checkHash.hash, lmPwdHash->hash, 16) != 0) { return NT_STATUS_WRONG_PASSWORD; } /* decrypt and check the new nt hash */ D_P16(ntPwdHash->hash, r->in.new_nt_crypted->hash, new_ntPwdHash.hash); D_P16(new_ntPwdHash.hash, r->in.old_nt_crypted->hash, checkHash.hash); if (memcmp(checkHash.hash, ntPwdHash->hash, 16) != 0) { return NT_STATUS_WRONG_PASSWORD; } /* check the nt cross hash */ D_P16(lmPwdHash->hash, r->in.nt_cross->hash, checkHash.hash); if (memcmp(checkHash.hash, new_ntPwdHash.hash, 16) != 0) { return NT_STATUS_WRONG_PASSWORD; } /* check the lm cross hash */ D_P16(ntPwdHash->hash, r->in.lm_cross->hash, checkHash.hash); if (memcmp(checkHash.hash, new_lmPwdHash.hash, 16) != 0) { return NT_STATUS_WRONG_PASSWORD; } ZERO_STRUCT(mod); mod.dn = talloc_strdup(mem_ctx, a_state->account_dn); if (!mod.dn) { return NT_STATUS_NO_MEMORY; } status = samdb_set_password(a_state->sam_ctx, mem_ctx, a_state->account_dn, a_state->domain_state->domain_dn, &mod, NULL, &new_lmPwdHash, &new_ntPwdHash, True); if (!NT_STATUS_IS_OK(status)) { return status; } /* modify the samdb record */ ret = samdb_replace(a_state->sam_ctx, mem_ctx, &mod); if (ret != 0) { return NT_STATUS_UNSUCCESSFUL; } return NT_STATUS_OK; } /* samr_OemChangePasswordUser2 */ NTSTATUS samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct samr_OemChangePasswordUser2 *r) { NTSTATUS status; char new_pass[512]; uint32 new_pass_len; struct samr_CryptPassword *pwbuf = r->in.password; void *sam_ctx; const char *user_dn, *domain_dn; int ret; struct ldb_message **res, mod; const char * const attrs[] = { "objectSid", "lmPwdHash", NULL }; const char *domain_sid; struct samr_Hash *lmPwdHash; if (pwbuf == NULL) { return NT_STATUS_WRONG_PASSWORD; } /* this call doesn't take a policy handle, so we need to open the sam db from scratch */ sam_ctx = samdb_connect(); if (sam_ctx == NULL) { 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 = samdb_search(sam_ctx, mem_ctx, NULL, &res, attrs, "(&(sAMAccountName=%s)(objectclass=user))", r->in.account->name); if (ret != 1) { samdb_close(sam_ctx); return NT_STATUS_NO_SUCH_USER; } user_dn = res[0]->dn; ret = samdb_result_hashes(mem_ctx, res[0], "lmPwdHash", &lmPwdHash); if (ret != 1) { samdb_close(sam_ctx); return NT_STATUS_WRONG_PASSWORD; } /* decrypt the password we have been given */ SamOEMhash(pwbuf->data, lmPwdHash->hash, 516); if (!decode_pw_buffer(pwbuf->data, new_pass, sizeof(new_pass), &new_pass_len, STR_ASCII)) { DEBUG(3,("samr: failed to decode password buffer\n")); samdb_close(sam_ctx); return NT_STATUS_WRONG_PASSWORD; } /* work out the domain dn */ domain_sid = samdb_result_sid_prefix(mem_ctx, res[0], "objectSid"); if (domain_sid == NULL) { samdb_close(sam_ctx); return NT_STATUS_NO_SUCH_USER; } domain_dn = samdb_search_string(sam_ctx, mem_ctx, NULL, "dn", "(objectSid=%s)", domain_sid); if (!domain_dn) { samdb_close(sam_ctx); return NT_STATUS_INTERNAL_DB_CORRUPTION; } ZERO_STRUCT(mod); mod.dn = talloc_strdup(mem_ctx, user_dn); if (!mod.dn) { samdb_close(sam_ctx); return NT_STATUS_NO_MEMORY; } /* set the password - samdb needs to know both the domain and user DNs, so the domain password policy can be used */ status = samdb_set_password(sam_ctx, mem_ctx, user_dn, domain_dn, &mod, new_pass, NULL, NULL, True); if (!NT_STATUS_IS_OK(status)) { samdb_close(sam_ctx); return status; } /* modify the samdb record */ ret = samdb_replace(sam_ctx, mem_ctx, &mod); if (ret != 0) { samdb_close(sam_ctx); return NT_STATUS_UNSUCCESSFUL; } samdb_close(sam_ctx); return NT_STATUS_OK; } /* samr_ChangePasswordUser2 */ NTSTATUS samr_ChangePasswordUser2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct samr_ChangePasswordUser2 *r) { DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); } /* samr_ChangePasswordUser3 */ NTSTATUS samr_ChangePasswordUser3(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct samr_ChangePasswordUser3 *r) { DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); } /* check that a password is sufficiently complex */ 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, struct samr_Hash *lmNewHash, struct samr_Hash *ntNewHash, BOOL user_change) { 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 local_lmNewHash, local_ntNewHash; int 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); if (new_pass) { /* check the various password restrictions */ if (minPwdLength > str_charnum(new_pass)) { 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 */ if (E_deshash(new_pass, local_lmNewHash.hash)) { lmNewHash = &local_lmNewHash; } E_md4hash(new_pass, local_ntNewHash.hash); ntNewHash = &local_ntNewHash; } if (user_change) { /* 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; } /* yes, this is a minus. The ages are in negative 100nsec units! */ if (pwdLastSet - minPwdAge > now_double) { return NT_STATUS_PASSWORD_RESTRICTION; } /* check the immediately past password */ if (pwdHistoryLength > 0) { if (lmNewHash && memcmp(lmNewHash->hash, lmPwdHash.hash, 16) == 0) { return NT_STATUS_PASSWORD_RESTRICTION; } if (ntNewHash && 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 (unicodePwd && new_pass && strcmp(unicodePwd, new_pass) == 0) { return NT_STATUS_PASSWORD_RESTRICTION; } if (lmNewHash && memcmp(lmNewHash->hash, lmPwdHash.hash, 16) == 0) { return NT_STATUS_PASSWORD_RESTRICTION; } if (ntNewHash && memcmp(ntNewHash->hash, ntPwdHash.hash, 16) == 0) { return NT_STATUS_PASSWORD_RESTRICTION; } } for (i=0; lmNewHash && ihash, lmPwdHistory[i].hash, 16) == 0) { return NT_STATUS_PASSWORD_RESTRICTION; } } for (i=0; ntNewHash && ihash, ntPwdHistory[i].hash, 16) == 0) { return NT_STATUS_PASSWORD_RESTRICTION; } } } #define CHECK_RET(x) do { if (x != 0) return NT_STATUS_NO_MEMORY; } while(0) /* the password is acceptable. Start forming the new fields */ if (lmNewHash) { CHECK_RET(samdb_msg_add_hash(ctx, mem_ctx, mod, "lmPwdHash", *lmNewHash)); } else { CHECK_RET(samdb_msg_add_delete(ctx, mem_ctx, mod, "lmPwdHash")); } if (ntNewHash) { CHECK_RET(samdb_msg_add_hash(ctx, mem_ctx, mod, "ntPwdHash", *ntNewHash)); } else { CHECK_RET(samdb_msg_add_delete(ctx, mem_ctx, mod, "ntPwdHash")); } if (new_pass && (pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT) && (userAccountControl & UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED)) { CHECK_RET(samdb_msg_add_string(ctx, mem_ctx, mod, "unicodePwd", new_pass)); } else { CHECK_RET(samdb_msg_add_delete(ctx, mem_ctx, mod, "unicodePwd")); } CHECK_RET(samdb_msg_add_double(ctx, mem_ctx, mod, "pwdLastSet", now_double)); if (pwdHistoryLength == 0) { CHECK_RET(samdb_msg_add_delete(ctx, mem_ctx, mod, "lmPwdHistory")); CHECK_RET(samdb_msg_add_delete(ctx, mem_ctx, mod, "ntPwdHistory")); return NT_STATUS_OK; } /* store the password history */ 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;iconn->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(sam_ctx, mem_ctx, account_dn, domain_dn, msg, new_pass, NULL, NULL, False /* This is a password set, not change */); }