From 00364e4e0d3d091e1b8cc9dd2b1b30eb84f2f085 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 11 Aug 2011 15:47:01 +1000 Subject: s3-passdb Add support for pdb_add_sam_account() and password hashes to pdb_samba4 This will help when using this as part of the Samba3 passdb -> Samba4 ldb database upgrade script. Andrew Bartlett --- source3/passdb/pdb_samba4.c | 291 +++++++++++++++++++++++++++++++++----------- 1 file changed, 222 insertions(+), 69 deletions(-) (limited to 'source3/passdb') diff --git a/source3/passdb/pdb_samba4.c b/source3/passdb/pdb_samba4.c index aa3352a658..35316d71d4 100644 --- a/source3/passdb/pdb_samba4.c +++ b/source3/passdb/pdb_samba4.c @@ -328,115 +328,224 @@ static bool pdb_samba4_add_time(struct ldb_message *msg, return ldb_msg_add_fmt(msg, attrib, "%llu", (unsigned long long) nt_time); } -/* Like in pdb_ldap(), this will need to be a function pointer when we - * start to support 'adds' for migrations from samba3 passdb backends - * to samba4 */ -static bool update_required(struct samu *sam, enum pdb_elements element) -{ - return (IS_SAM_CHANGED(sam, element)); -} - -static bool pdb_samba4_init_samba4_from_sam(struct pdb_samba4_state *state, - struct ldb_message *existing, - TALLOC_CTX *mem_ctx, - struct ldb_message **pmods, - struct samu *sam) +static int pdb_samba4_replace_by_sam(struct pdb_samba4_state *state, + bool (*need_update)(const struct samu *, + enum pdb_elements), + struct ldb_dn *dn, + struct samu *sam) { int ret = LDB_SUCCESS; const char *pw; struct ldb_message *msg; - + struct ldb_request *req; + unsigned int j; /* TODO: All fields :-) */ - msg = ldb_msg_new(mem_ctx); + msg = ldb_msg_new(talloc_tos()); if (!msg) { return false; } - msg->dn = existing->dn; + msg->dn = dn; + + /* build modify request */ + ret = ldb_build_mod_req(&req, state->ldb, talloc_tos(), msg, NULL, NULL, + NULL, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return ret; + } pw = pdb_get_plaintext_passwd(sam); - if (update_required(sam, PDB_PLAINTEXT_PW)) { + if (need_update(sam, PDB_PLAINTEXT_PW)) { if (pw == NULL) { - ret = LDB_ERR_OPERATIONS_ERROR; - goto fail; + return LDB_ERR_OPERATIONS_ERROR; } ret |= ldb_msg_add_string(msg, "clearTextPassword", pw); + } else { + bool changed_lm_pw = false; + bool changed_nt_pw = false; + bool changed_history = false; + if (need_update(sam, PDB_LMPASSWD)) { + struct ldb_val val; + val.data = pdb_get_lanman_passwd(sam); + if (!val.data) { + samdb_msg_add_delete(state->ldb, msg, msg, + "dBCSPwd"); + } else { + val.length = LM_HASH_LEN; + ret |= ldb_msg_add_value(msg, "dBCSPwd", &val, NULL); + } + changed_lm_pw = true; + } + if (need_update(sam, PDB_NTPASSWD)) { + struct ldb_val val; + val.data = pdb_get_lanman_passwd(sam); + if (!val.data) { + samdb_msg_add_delete(state->ldb, msg, msg, + "unicodePwd"); + } else { + val.length = NT_HASH_LEN; + ret |= ldb_msg_add_value(msg, "unicodePwd", &val, NULL); + } + changed_nt_pw = true; + } + + /* Try to ensure we don't get out of sync */ + if (changed_lm_pw && !changed_nt_pw) { + samdb_msg_add_delete(state->ldb, msg, msg, + "unicodePwd"); + } else if (changed_nt_pw && !changed_lm_pw) { + samdb_msg_add_delete(state->ldb, msg, msg, + "dBCSPwd"); + } + if (changed_lm_pw || changed_nt_pw) { + samdb_msg_add_delete(state->ldb, msg, msg, + "supplementalCredentials"); + + } + + /* If we set a plaintext password, the system will + * force the pwdLastSet to now(), and it isn't worth + * working around this for the real world use cases of + * pdb_samba4 */ + if (need_update(sam, PDB_PASSLASTSET)) { + ret |= pdb_samba4_add_time(msg, "pwdLastSet", + pdb_get_pass_last_set_time(sam)); + } + + if (need_update(sam, PDB_PWHISTORY)) { + uint32_t current_hist_len; + const uint8_t *history = pdb_get_pw_history(sam, ¤t_hist_len); + + bool invalid_history = false; + struct samr_Password *history_hashes = talloc_array(talloc_tos(), struct samr_Password, + current_hist_len); + if (!history) { + invalid_history = true; + } else { + unsigned int i; + static const uint8_t zeros[16]; + /* Parse the history into the correct format */ + for (i = 0; i < current_hist_len; i++) { + if (memcmp(&history[i*PW_HISTORY_ENTRY_LEN], zeros, 16) != 0) { + /* If the history is in the old format, with a salted hash, then we can't migrate it to AD format */ + invalid_history = true; + break; + } + /* Copy out the 2nd 16 bytes of the 32 byte password history, containing the NT hash */ + memcpy(history_hashes[i].hash, + &history[(i*PW_HISTORY_ENTRY_LEN) + PW_HISTORY_SALT_LEN], + sizeof(history_hashes[i].hash)); + } + } + if (invalid_history) { + ret |= samdb_msg_add_delete(state->ldb, msg, msg, + "ntPwdHistory"); + + ret |= samdb_msg_add_delete(state->ldb, msg, msg, + "lmPwdHistory"); + } else { + ret |= samdb_msg_add_hashes(state->ldb, msg, msg, + "ntPwdHistory", + history_hashes, + current_hist_len); + } + changed_history = true; + } + if (changed_lm_pw || changed_nt_pw || changed_history) { + ret |= ldb_request_add_control(req, + DSDB_CONTROL_BYPASS_PASSWORD_HASH_OID, + true, NULL); + } } - if (update_required(sam, PDB_FULLNAME)) { + /* PDB_USERSID is only allowed on ADD, handled in caller */ + if (need_update(sam, PDB_GROUPSID)) { + const struct dom_sid *sid = pdb_get_group_sid(sam); + uint32_t rid; + NTSTATUS status = dom_sid_split_rid(NULL, sid, NULL, &rid); + if (!NT_STATUS_IS_OK(status)) { + return LDB_ERR_OPERATIONS_ERROR; + } + if (!dom_sid_in_domain(samdb_domain_sid(state->ldb), sid)) { + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + ret |= samdb_msg_add_uint(state->ldb, msg, msg, "primaryGroupID", rid); + } + if (need_update(sam, PDB_FULLNAME)) { ret |= ldb_msg_add_string(msg, "displayName", pdb_get_fullname(sam)); } - if (update_required(sam, PDB_SMBHOME)) { + if (need_update(sam, PDB_SMBHOME)) { ret |= ldb_msg_add_string(msg, "homeDirectory", pdb_get_homedir(sam)); } - if (update_required(sam, PDB_PROFILE)) { + if (need_update(sam, PDB_PROFILE)) { ret |= ldb_msg_add_string(msg, "profilePath", pdb_get_profile_path(sam)); } - if (update_required(sam, PDB_DRIVE)) { + if (need_update(sam, PDB_DRIVE)) { ret |= ldb_msg_add_string(msg, "homeDrive", pdb_get_dir_drive(sam)); } - if (update_required(sam, PDB_LOGONSCRIPT)) { + if (need_update(sam, PDB_LOGONSCRIPT)) { ret |= ldb_msg_add_string(msg, "scriptPath", pdb_get_logon_script(sam)); } - if (update_required(sam, PDB_KICKOFFTIME)) { + if (need_update(sam, PDB_KICKOFFTIME)) { ret |= pdb_samba4_add_time(msg, "accountExpires", pdb_get_kickoff_time(sam)); } - if (update_required(sam, PDB_USERNAME)) { + if (need_update(sam, PDB_USERNAME)) { ret |= ldb_msg_add_string(msg, "samAccountName", pdb_get_username(sam)); } - if (update_required(sam, PDB_HOURSLEN) || update_required(sam, PDB_HOURS)) { + if (need_update(sam, PDB_HOURSLEN) || need_update(sam, PDB_HOURS)) { struct ldb_val hours = data_blob_const(pdb_get_hours(sam), pdb_get_hours_len(sam)); ret |= ldb_msg_add_value(msg, "logonHours", &hours, NULL); } - if (update_required(sam, PDB_ACCTCTRL)) { - ret |= ldb_msg_add_fmt(msg, "userAccountControl", - "%d", ds_acb2uf(pdb_get_acct_ctrl(sam))); + if (need_update(sam, PDB_ACCTCTRL)) { + ret |= samdb_msg_add_acct_flags(state->ldb, msg, msg, + "userAccountControl", pdb_get_acct_ctrl(sam)); } - if (update_required(sam, PDB_COMMENT)) { + if (need_update(sam, PDB_COMMENT)) { ret |= ldb_msg_add_string(msg, "comment", pdb_get_comment(sam)); } - if (update_required(sam, PDB_ACCTDESC)) { + if (need_update(sam, PDB_ACCTDESC)) { ret |= ldb_msg_add_string(msg, "description", pdb_get_acct_desc(sam)); } - if (update_required(sam, PDB_WORKSTATIONS)) { + if (need_update(sam, PDB_WORKSTATIONS)) { ret |= ldb_msg_add_string(msg, "userWorkstations", pdb_get_workstations(sam)); } /* This will need work, it is actually a UTF8 'string' with internal NULLs, to handle TS parameters */ - if (update_required(sam, PDB_MUNGEDDIAL)) { + if (need_update(sam, PDB_MUNGEDDIAL)) { ret |= ldb_msg_add_string(msg, "userParameters", pdb_get_munged_dial(sam)); } - if (update_required(sam, PDB_COUNTRY_CODE)) { + if (need_update(sam, PDB_COUNTRY_CODE)) { ret |= ldb_msg_add_fmt(msg, "countryCode", "%i", (int)pdb_get_country_code(sam)); } - if (update_required(sam, PDB_CODE_PAGE)) { + if (need_update(sam, PDB_CODE_PAGE)) { ret |= ldb_msg_add_fmt(msg, "codePage", "%i", (int)pdb_get_code_page(sam)); } @@ -445,28 +554,46 @@ static bool pdb_samba4_init_samba4_from_sam(struct pdb_samba4_state *state, PDB_LOGONTIME, PDB_LOGOFFTIME, PDB_BAD_PASSWORD_TIME, - PDB_CANCHANGETIME, - PDB_MUSTCHANGETIME, + PDB_CANCHANGETIME, - these are calculated per policy, not stored + PDB_MUSTCHANGETIME, - these are calculated per policy, not stored PDB_DOMAIN, - PDB_NTUSERNAME, + PDB_NTUSERNAME, - this makes no sense, and never really did PDB_LOGONDIVS, - PDB_USERSID, - PDB_GROUPSID, - PDB_PASSLASTSET, + PDB_USERSID, - Handled in pdb_samba4_add_sam_account() PDB_FIELDS_PRESENT, PDB_BAD_PASSWORD_COUNT, PDB_LOGON_COUNT, PDB_UNKNOWN6, - PDB_LMPASSWD, - PDB_NTPASSWD, PDB_PWHISTORY, PDB_BACKEND_PRIVATE_DATA, */ + if (ret != LDB_SUCCESS) { + return LDB_ERR_OPERATIONS_ERROR; + } - *pmods = msg; -fail: - return ret == LDB_SUCCESS; + if (msg->num_elements == 0) { + /* Nothing to do, just return success */ + return LDB_SUCCESS; + } + + /* mark everything here as a replace */ + for (j=0;jnum_elements;j++) { + msg->elements[j].flags = LDB_FLAG_MOD_REPLACE; + } + + ret = ldb_request(state->ldb, req); + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } + + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to modify account record %s to set user attributes: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(state->ldb))); + } + + return ret; } static NTSTATUS pdb_samba4_getsamupriv(struct pdb_samba4_state *state, @@ -626,7 +753,50 @@ static NTSTATUS pdb_samba4_delete_user(struct pdb_methods *m, static NTSTATUS pdb_samba4_add_sam_account(struct pdb_methods *m, struct samu *sampass) { - return NT_STATUS_NOT_IMPLEMENTED; + int ret; + NTSTATUS status; + struct ldb_dn *dn; + struct pdb_samba4_state *state = talloc_get_type_abort( + m->private_data, struct pdb_samba4_state); + uint32_t acb_flags = pdb_get_acct_ctrl(sampass); + const char *username = pdb_get_username(sampass); + const struct dom_sid *user_sid = pdb_get_user_sid(sampass); + TALLOC_CTX *tframe = talloc_stackframe(); + + acb_flags &= (ACB_NORMAL|ACB_WSTRUST|ACB_SVRTRUST|ACB_DOMTRUST); + + ret = ldb_transaction_start(state->ldb); + if (ret != LDB_SUCCESS) { + talloc_free(tframe); + return NT_STATUS_LOCK_NOT_GRANTED; + } + + status = dsdb_add_user(state->ldb, talloc_tos(), username, + acb_flags, user_sid, NULL, &dn); + if (!NT_STATUS_IS_OK(status)) { + ldb_transaction_cancel(state->ldb); + talloc_free(tframe); + return status; + } + + ret = pdb_samba4_replace_by_sam(state, pdb_element_is_set_or_changed, + dn, sampass); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(state->ldb); + talloc_free(tframe); + return dsdb_ldb_err_to_ntstatus(ret); + } + + ret = ldb_transaction_commit(state->ldb); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to commit transaction to add and modify account record %s: %s\n", + ldb_dn_get_linearized(dn), + ldb_errstring(state->ldb))); + talloc_free(tframe); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + talloc_free(tframe); + return NT_STATUS_OK; } /* @@ -642,28 +812,11 @@ static NTSTATUS pdb_samba4_update_sam_account(struct pdb_methods *m, m->private_data, struct pdb_samba4_state); struct ldb_message *msg = pdb_samba4_get_samu_private( m, sam); - struct ldb_message *replace_msg; - int rc; - - if (!pdb_samba4_init_samba4_from_sam(state, msg, talloc_tos(), - &replace_msg, sam)) { - return NT_STATUS_NO_MEMORY; - } - - if (replace_msg->num_elements == 0) { - /* Nothing to do, just return success */ - return NT_STATUS_OK; - } - - rc = dsdb_replace(state->ldb, replace_msg, 0); - TALLOC_FREE(replace_msg); - if (rc != LDB_SUCCESS) { - DEBUG(10, ("dsdb_replace for %s failed: %s\n", ldb_dn_get_linearized(replace_msg->dn), - ldb_errstring(state->ldb))); - return NT_STATUS_LDAP(rc); - } + int ret; - return NT_STATUS_OK; + ret = pdb_samba4_replace_by_sam(state, pdb_element_is_changed, msg->dn, + sam); + return dsdb_ldb_err_to_ntstatus(ret); } static NTSTATUS pdb_samba4_delete_sam_account(struct pdb_methods *m, -- cgit