X-Git-Url: http://git.samba.org/?a=blobdiff_plain;f=source4%2Fdsdb%2Fcommon%2Futil.c;h=63870278ceb3f75029723458d6dac6c0bc71c085;hb=5159dbec812b45a037ba5df88e7b04ae25c877bd;hp=e395ea540b446e30be64701958e881427ac5c23f;hpb=73513fb7e7145e9abe1a155acd47c27bc09c5092;p=metze%2Fsamba%2Fwip.git diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c index e395ea540b44..63870278ceb3 100644 --- a/source4/dsdb/common/util.c +++ b/source4/dsdb/common/util.c @@ -40,6 +40,8 @@ #include "system/locale.h" #include "lib/util/tsort.h" #include "dsdb/common/util.h" +#include "lib/socket/socket.h" +#include "dsdb/samdb/ldb_modules/util.h" /* search the sam for the specified attributes in a specific domain, filter on @@ -628,18 +630,25 @@ NTSTATUS samdb_result_passwords(TALLOC_CTX *mem_ctx, struct loadparm_context *lp struct samr_LogonHours samdb_result_logon_hours(TALLOC_CTX *mem_ctx, struct ldb_message *msg, const char *attr) { struct samr_LogonHours hours; - const int units_per_week = 168; + size_t units_per_week = 168; const struct ldb_val *val = ldb_msg_find_ldb_val(msg, attr); + ZERO_STRUCT(hours); - hours.bits = talloc_array(mem_ctx, uint8_t, units_per_week); + + if (val) { + units_per_week = val->length * 8; + } + + hours.bits = talloc_array(mem_ctx, uint8_t, units_per_week/8); if (!hours.bits) { return hours; } hours.units_per_week = units_per_week; - memset(hours.bits, 0xFF, units_per_week); + memset(hours.bits, 0xFF, units_per_week/8); if (val) { - memcpy(hours.bits, val->data, MIN(val->length, units_per_week)); + memcpy(hours.bits, val->data, val->length); } + return hours; } @@ -720,6 +729,47 @@ struct ldb_message_element *samdb_find_attribute(struct ldb_context *ldb, return NULL; } +/* + * This is intended for use by the "password hash" module since there + * password changes can be specified through one message element with the + * new password (to set) and another one with the old password (to unset). + * + * The first which sets a password (new value) can have flags + * (LDB_FLAG_MOD_ADD, LDB_FLAG_MOD_REPLACE) but also none (on "add" operations + * for entries). The latter (old value) has always specified + * LDB_FLAG_MOD_DELETE. + * + * Returns LDB_ERR_NO_SUCH_ATTRIBUTE if the attribute which should be deleted + * doesn't contain only one value (this is the Windows Server behaviour) + * otherwise LDB_SUCCESS. + */ +int samdb_msg_find_old_and_new_ldb_val(const struct ldb_message *msg, + const char *name, + const struct ldb_val **new_val, + const struct ldb_val **old_val) +{ + unsigned int i; + + *new_val = NULL; + *old_val = NULL; + + if (msg == NULL) { + return LDB_SUCCESS; + } + + for (i = 0; i < msg->num_elements; i++) { + if (ldb_attr_cmp(msg->elements[i].name, name) == 0) { + if (msg->elements[i].flags == LDB_FLAG_MOD_DELETE) { + *old_val = &msg->elements[i].values[0]; + } else { + *new_val = &msg->elements[i].values[0]; + } + } + } + + return LDB_SUCCESS; +} + int samdb_find_or_add_value(struct ldb_context *ldb, struct ldb_message *msg, const char *name, const char *set_value) { if (samdb_find_attribute(ldb, msg, name, set_value) == NULL) { @@ -1592,6 +1642,107 @@ const char *samdb_server_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx) return (const char *) val->data; } +/* + * Finds the client site by using the client's IP address. + * The "subnet_name" returns the name of the subnet if parameter != NULL + */ +const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, + const char *ip_address, char **subnet_name) +{ + const char *attrs[] = { "cn", "siteObject", NULL }; + struct ldb_dn *sites_container_dn, *subnets_dn, *sites_dn; + struct ldb_result *res; + const struct ldb_val *val; + const char *site_name = NULL, *l_subnet_name = NULL; + const char *allow_list[2] = { NULL, NULL }; + unsigned int i, count; + int cnt, ret; + + /* + * if we don't have a client ip e.g. ncalrpc + * the server site is the client site + */ + if (ip_address == NULL) { + return samdb_server_site_name(ldb, mem_ctx); + } + + sites_container_dn = samdb_sites_dn(ldb, mem_ctx); + if (sites_container_dn == NULL) { + return NULL; + } + + subnets_dn = ldb_dn_copy(mem_ctx, sites_container_dn); + if ( ! ldb_dn_add_child_fmt(subnets_dn, "CN=Subnets")) { + talloc_free(sites_container_dn); + talloc_free(subnets_dn); + return NULL; + } + + ret = ldb_search(ldb, mem_ctx, &res, subnets_dn, LDB_SCOPE_ONELEVEL, + attrs, NULL); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + count = 0; + } else if (ret != LDB_SUCCESS) { + talloc_free(sites_container_dn); + talloc_free(subnets_dn); + return NULL; + } else { + count = res->count; + } + + for (i = 0; i < count; i++) { + l_subnet_name = ldb_msg_find_attr_as_string(res->msgs[i], "cn", + NULL); + + allow_list[0] = l_subnet_name; + + if (allow_access(mem_ctx, NULL, allow_list, "", ip_address)) { + sites_dn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, + res->msgs[i], + "siteObject"); + if (sites_dn == NULL) { + /* No reference, maybe another subnet matches */ + continue; + } + + /* "val" cannot be NULL here since "sites_dn" != NULL */ + val = ldb_dn_get_rdn_val(sites_dn); + site_name = talloc_strdup(mem_ctx, + (const char *) val->data); + + talloc_free(sites_dn); + + break; + } + } + + if (site_name == NULL) { + /* This is the Windows Server fallback rule: when no subnet + * exists and we have only one site available then use it (it + * is for sure the same as our server site). If more sites do + * exist then we don't know which one to use and set the site + * name to "". */ + cnt = samdb_search_count(ldb, sites_container_dn, + "(objectClass=site)"); + if (cnt == 1) { + site_name = samdb_server_site_name(ldb, mem_ctx); + } else { + site_name = talloc_strdup(mem_ctx, ""); + } + l_subnet_name = NULL; + } + + if (subnet_name != NULL) { + *subnet_name = talloc_strdup(mem_ctx, l_subnet_name); + } + + talloc_free(sites_container_dn); + talloc_free(subnets_dn); + talloc_free(res); + + return site_name; +} + /* work out if we are the PDC for the domain of the current open ldb */ @@ -1747,277 +1898,195 @@ enum samr_ValidationStatus samdb_check_password(const DATA_BLOB *password, return SAMR_VALIDATION_STATUS_SUCCESS; } +/* + * Callback for "samdb_set_password" password change + */ +int samdb_set_password_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + int ret; + + if (!ares) { + return ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + } + + if (ares->error != LDB_SUCCESS) { + ret = ares->error; + req->context = talloc_steal(req, + ldb_reply_get_control(ares, DSDB_CONTROL_PASSWORD_CHANGE_STATUS_OID)); + talloc_free(ares); + return ldb_request_done(req, ret); + } + + if (ares->type != LDB_REPLY_DONE) { + talloc_free(ares); + return ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + } + + req->context = talloc_steal(req, + ldb_reply_get_control(ares, DSDB_CONTROL_PASSWORD_CHANGE_STATUS_OID)); + talloc_free(ares); + return ldb_request_done(req, LDB_SUCCESS); +} + /* * Sets the user password using plaintext UTF16 (attribute "new_password") or * LM (attribute "lmNewHash") or NT (attribute "ntNewHash") hash. Also pass * as parameter if it's a user change or not ("userChange"). The "rejectReason" * gives some more informations if the changed failed. * - * The caller should have a LDB transaction wrapping this. - * * Results: NT_STATUS_OK, NT_STATUS_INTERNAL_DB_CORRUPTION, * NT_STATUS_INVALID_PARAMETER, NT_STATUS_UNSUCCESSFUL, - * NT_STATUS_PASSWORD_RESTRICTION + * NT_STATUS_WRONG_PASSWORD, NT_STATUS_PASSWORD_RESTRICTION */ -NTSTATUS samdb_set_password(struct ldb_context *ctx, TALLOC_CTX *mem_ctx, +NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, struct ldb_dn *user_dn, struct ldb_dn *domain_dn, - struct ldb_message *mod, const DATA_BLOB *new_password, - struct samr_Password *param_lmNewHash, - struct samr_Password *param_ntNewHash, + struct samr_Password *lmNewHash, + struct samr_Password *ntNewHash, bool user_change, enum samPwdChangeReason *reject_reason, struct samr_DomInfo1 **_dominfo) { - const char * const user_attrs[] = { "userAccountControl", - "lmPwdHistory", - "ntPwdHistory", - "dBCSPwd", "unicodePwd", - "objectSid", - "pwdLastSet", NULL }; - const char * const domain_attrs[] = { "minPwdLength", "pwdProperties", - "pwdHistoryLength", - "maxPwdAge", "minPwdAge", NULL }; - NTTIME pwdLastSet; - uint32_t minPwdLength, pwdProperties, pwdHistoryLength; - int64_t maxPwdAge, minPwdAge; - uint32_t userAccountControl; - struct samr_Password *sambaLMPwdHistory, *sambaNTPwdHistory, - *lmPwdHash, *ntPwdHash, *lmNewHash, *ntNewHash; - struct samr_Password local_lmNewHash, local_ntNewHash; - int sambaLMPwdHistory_len, sambaNTPwdHistory_len; - struct dom_sid *domain_sid; - struct ldb_message **res; - bool restrictions; - int count; - time_t now = time(NULL); - NTTIME now_nt; - unsigned int i; + struct ldb_message *msg; + struct ldb_message_element *el; + struct ldb_request *req; + struct dsdb_control_password_change_status *pwd_stat = NULL; + int ret; + NTSTATUS status; - /* we need to know the time to compute password age */ - unix_to_nt_time(&now_nt, now); +#define CHECK_RET(x) \ + if (x != LDB_SUCCESS) { \ + talloc_free(msg); \ + return NT_STATUS_NO_MEMORY; \ + } - /* pull all the user parameters */ - count = gendb_search_dn(ctx, mem_ctx, user_dn, &res, user_attrs); - if (count != 1) { - return NT_STATUS_INTERNAL_DB_CORRUPTION; + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; } - userAccountControl = samdb_result_uint(res[0], "userAccountControl", 0); - sambaLMPwdHistory_len = samdb_result_hashes(mem_ctx, res[0], - "lmPwdHistory", &sambaLMPwdHistory); - sambaNTPwdHistory_len = samdb_result_hashes(mem_ctx, res[0], - "ntPwdHistory", &sambaNTPwdHistory); - lmPwdHash = samdb_result_hash(mem_ctx, res[0], "dBCSPwd"); - ntPwdHash = samdb_result_hash(mem_ctx, res[0], "unicodePwd"); - pwdLastSet = samdb_result_uint64(res[0], "pwdLastSet", 0); - - /* Copy parameters */ - lmNewHash = param_lmNewHash; - ntNewHash = param_ntNewHash; - - /* Only non-trust accounts have restrictions (possibly this - * test is the wrong way around, but I like to be restrictive - * if possible */ - restrictions = !(userAccountControl & (UF_INTERDOMAIN_TRUST_ACCOUNT - |UF_WORKSTATION_TRUST_ACCOUNT - |UF_SERVER_TRUST_ACCOUNT)); - - if (domain_dn != NULL) { - /* pull the domain parameters */ - count = gendb_search_dn(ctx, mem_ctx, domain_dn, &res, - domain_attrs); - if (count != 1) { - DEBUG(2, ("samdb_set_password: Domain DN %s is invalid, for user %s\n", - ldb_dn_get_linearized(domain_dn), - ldb_dn_get_linearized(user_dn))); - return NT_STATUS_NO_SUCH_DOMAIN; + msg->dn = user_dn; + if ((new_password != NULL) + && ((lmNewHash == NULL) && (ntNewHash == NULL))) { + /* we have the password as plaintext UTF16 */ + CHECK_RET(samdb_msg_add_value(ldb, mem_ctx, msg, + "clearTextPassword", new_password)); + el = ldb_msg_find_element(msg, "clearTextPassword"); + el->flags = LDB_FLAG_MOD_REPLACE; + } else if ((new_password == NULL) + && ((lmNewHash != NULL) || (ntNewHash != NULL))) { + /* we have a password as LM and/or NT hash */ + if (lmNewHash != NULL) { + CHECK_RET(samdb_msg_add_hash(ldb, mem_ctx, msg, + "dBCSPwd", lmNewHash)); + el = ldb_msg_find_element(msg, "dBCSPwd"); + el->flags = LDB_FLAG_MOD_REPLACE; } - } else { - /* work out the domain sid, and pull the domain from there */ - domain_sid = samdb_result_sid_prefix(mem_ctx, res[0], - "objectSid"); - if (domain_sid == NULL) { - return NT_STATUS_INTERNAL_DB_CORRUPTION; + if (ntNewHash != NULL) { + CHECK_RET(samdb_msg_add_hash(ldb, mem_ctx, msg, + "unicodePwd", ntNewHash)); + el = ldb_msg_find_element(msg, "unicodePwd"); + el->flags = LDB_FLAG_MOD_REPLACE; } + } else { + /* the password wasn't specified correctly */ + talloc_free(msg); + return NT_STATUS_INVALID_PARAMETER; + } - count = gendb_search(ctx, mem_ctx, NULL, &res, domain_attrs, - "(objectSid=%s)", - ldap_encode_ndr_dom_sid(mem_ctx, domain_sid)); - if (count != 1) { - DEBUG(2, ("samdb_set_password: Could not find domain to match SID: %s, for user %s\n", - dom_sid_string(mem_ctx, domain_sid), - ldb_dn_get_linearized(user_dn))); - return NT_STATUS_NO_SUCH_DOMAIN; + /* build modify request */ + ret = ldb_build_mod_req(&req, ldb, mem_ctx, msg, NULL, NULL, + samdb_set_password_callback, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + + if (user_change) { + /* a user password change and we've checked already the old + * password somewhere else (callers responsability) */ + ret = ldb_request_add_control(req, + DSDB_CONTROL_PASSWORD_CHANGE_OLD_PW_CHECKED_OID, + true, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(req); + talloc_free(msg); + return NT_STATUS_NO_MEMORY; } } + ret = ldb_request_add_control(req, + DSDB_CONTROL_PASSWORD_HASH_VALUES_OID, + true, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(req); + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + ret = ldb_request_add_control(req, + DSDB_CONTROL_PASSWORD_CHANGE_STATUS_OID, + true, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(req); + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } - minPwdLength = samdb_result_uint(res[0], "minPwdLength", 0); - pwdProperties = samdb_result_uint(res[0], "pwdProperties", 0); - pwdHistoryLength = samdb_result_uint(res[0], "pwdHistoryLength", 0); - maxPwdAge = samdb_result_int64(res[0], "maxPwdAge", 0); - minPwdAge = samdb_result_int64(res[0], "minPwdAge", 0); + ret = dsdb_autotransaction_request(ldb, req); - if ((userAccountControl & UF_PASSWD_NOTREQD) != 0) { - /* see [MS-ADTS] 2.2.15 */ - minPwdLength = 0; + if (req->context != NULL) { + pwd_stat = talloc_steal(mem_ctx, + ((struct ldb_control *)req->context)->data); } + talloc_free(req); + talloc_free(msg); + + /* Sets the domain info (if requested) */ if (_dominfo != NULL) { struct samr_DomInfo1 *dominfo; - /* on failure we need to fill in the reject reasons */ - dominfo = talloc(mem_ctx, struct samr_DomInfo1); + + dominfo = talloc_zero(mem_ctx, struct samr_DomInfo1); if (dominfo == NULL) { return NT_STATUS_NO_MEMORY; } - dominfo->min_password_length = minPwdLength; - dominfo->password_properties = pwdProperties; - dominfo->password_history_length = pwdHistoryLength; - dominfo->max_password_age = maxPwdAge; - dominfo->min_password_age = minPwdAge; - *_dominfo = dominfo; - } - - if ((restrictions != 0) && (new_password != 0)) { - char *new_pass; - /* checks if the "minPwdLength" property is satisfied */ - if ((restrictions != 0) - && (minPwdLength > utf16_len_n( - new_password->data, new_password->length)/2)) { - if (reject_reason) { - *reject_reason = SAM_PWD_CHANGE_PASSWORD_TOO_SHORT; - } - return NT_STATUS_PASSWORD_RESTRICTION; + if (pwd_stat != NULL) { + dominfo->min_password_length = pwd_stat->domain_data.minPwdLength; + dominfo->password_properties = pwd_stat->domain_data.pwdProperties; + dominfo->password_history_length = pwd_stat->domain_data.pwdHistoryLength; + dominfo->max_password_age = pwd_stat->domain_data.maxPwdAge; + dominfo->min_password_age = pwd_stat->domain_data.minPwdAge; } - /* Create the NT hash */ - mdfour(local_ntNewHash.hash, new_password->data, - new_password->length); - - ntNewHash = &local_ntNewHash; - - /* Only check complexity if we can convert it at all. Assuming unconvertable passwords are 'strong' */ - if (convert_string_talloc_convenience(mem_ctx, - lp_iconv_convenience(ldb_get_opaque(ctx, "loadparm")), - CH_UTF16, CH_UNIX, - new_password->data, new_password->length, - (void **)&new_pass, NULL, false)) { - - /* checks the password complexity */ - if ((restrictions != 0) - && ((pwdProperties - & DOMAIN_PASSWORD_COMPLEX) != 0) - && (!check_password_quality(new_pass))) { - if (reject_reason) { - *reject_reason = SAM_PWD_CHANGE_NOT_COMPLEX; - } - return NT_STATUS_PASSWORD_RESTRICTION; - } - - /* compute the new lm hashes (for checking history - case insenitivly!) */ - if (E_deshash(new_pass, local_lmNewHash.hash)) { - lmNewHash = &local_lmNewHash; - } - } + *_dominfo = dominfo; } - if ((restrictions != 0) && user_change) { - /* are all password changes disallowed? */ - if ((pwdProperties & DOMAIN_REFUSE_PASSWORD_CHANGE) != 0) { - if (reject_reason) { - *reject_reason = SAM_PWD_CHANGE_NO_ERROR; - } - return NT_STATUS_PASSWORD_RESTRICTION; - } - - /* can this user change the password? */ - if ((userAccountControl & UF_PASSWD_CANT_CHANGE) != 0) { - if (reject_reason) { - *reject_reason = SAM_PWD_CHANGE_NO_ERROR; - } - return NT_STATUS_PASSWORD_RESTRICTION; - } - - /* Password minimum age: yes, this is a minus. The ages are in negative 100nsec units! */ - if (pwdLastSet - minPwdAge > now_nt) { - if (reject_reason) { - *reject_reason = SAM_PWD_CHANGE_NO_ERROR; - } - return NT_STATUS_PASSWORD_RESTRICTION; - } - - /* check the immediately past password */ - if (pwdHistoryLength > 0) { - if (lmNewHash && lmPwdHash && memcmp(lmNewHash->hash, - lmPwdHash->hash, 16) == 0) { - if (reject_reason) { - *reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY; - } - return NT_STATUS_PASSWORD_RESTRICTION; - } - if (ntNewHash && ntPwdHash && memcmp(ntNewHash->hash, - ntPwdHash->hash, 16) == 0) { - if (reject_reason) { - *reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY; - } - return NT_STATUS_PASSWORD_RESTRICTION; - } - } - - /* check the password history */ - sambaLMPwdHistory_len = MIN(sambaLMPwdHistory_len, - pwdHistoryLength); - sambaNTPwdHistory_len = MIN(sambaNTPwdHistory_len, - pwdHistoryLength); - - for (i=0; lmNewHash && ihash, sambaLMPwdHistory[i].hash, - 16) == 0) { - if (reject_reason) { - *reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY; - } - return NT_STATUS_PASSWORD_RESTRICTION; - } - } - for (i=0; ntNewHash && ihash, sambaNTPwdHistory[i].hash, - 16) == 0) { - if (reject_reason) { - *reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY; - } - return NT_STATUS_PASSWORD_RESTRICTION; - } + if (reject_reason != NULL) { + if (pwd_stat != NULL) { + *reject_reason = pwd_stat->reject_reason; + } else { + *reject_reason = SAM_PWD_CHANGE_NO_ERROR; } } -#define CHECK_RET(x) do { if (x != 0) return NT_STATUS_NO_MEMORY; } while(0) + if (pwd_stat != NULL) { + talloc_free(pwd_stat); + } - /* the password is acceptable. Start forming the new fields */ - if (new_password != NULL) { - /* if we know the cleartext UTF16 password, then set it. - * Modules in ldb will set all the appropriate - * hashes */ - CHECK_RET(ldb_msg_add_value(mod, "clearTextPassword", new_password, NULL)); + /* TODO: Error results taken from "password_hash" module. Are they + correct? */ + if (ret == LDB_ERR_UNWILLING_TO_PERFORM) { + status = NT_STATUS_WRONG_PASSWORD; + } else if (ret == LDB_ERR_CONSTRAINT_VIOLATION) { + status = NT_STATUS_PASSWORD_RESTRICTION; + } else if (ret != LDB_SUCCESS) { + status = NT_STATUS_UNSUCCESSFUL; } else { - /* we don't have the cleartext, so set what we have of the - * hashes */ - - if (lmNewHash) { - CHECK_RET(samdb_msg_add_hash(ctx, mem_ctx, mod, "dBCSPwd", lmNewHash)); - } - - if (ntNewHash) { - CHECK_RET(samdb_msg_add_hash(ctx, mem_ctx, mod, "unicodePwd", ntNewHash)); - } + status = NT_STATUS_OK; } - if (reject_reason) { - *reject_reason = SAM_PWD_CHANGE_NO_ERROR; - } - return NT_STATUS_OK; + return status; } - /* * Sets the user password using plaintext UTF16 (attribute "new_password") or * LM (attribute "lmNewHash") or NT (attribute "ntNewHash") hash. Also pass @@ -2032,7 +2101,7 @@ NTSTATUS samdb_set_password(struct ldb_context *ctx, TALLOC_CTX *mem_ctx, * * Results: NT_STATUS_OK, NT_STATUS_INTERNAL_DB_CORRUPTION, * NT_STATUS_INVALID_PARAMETER, NT_STATUS_UNSUCCESSFUL, - * NT_STATUS_PASSWORD_RESTRICTION + * NT_STATUS_WRONG_PASSWORD, NT_STATUS_PASSWORD_RESTRICTION */ NTSTATUS samdb_set_password_sid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, const struct dom_sid *user_sid, @@ -2045,7 +2114,6 @@ NTSTATUS samdb_set_password_sid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, { NTSTATUS nt_status; struct ldb_dn *user_dn; - struct ldb_message *msg; int ret; ret = ldb_transaction_start(ldb); @@ -2064,45 +2132,18 @@ NTSTATUS samdb_set_password_sid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, return NT_STATUS_NO_SUCH_USER; } - msg = ldb_msg_new(mem_ctx); - if (msg == NULL) { - ldb_transaction_cancel(ldb); - talloc_free(user_dn); - return NT_STATUS_NO_MEMORY; - } - - msg->dn = ldb_dn_copy(msg, user_dn); - if (!msg->dn) { - ldb_transaction_cancel(ldb); - talloc_free(user_dn); - talloc_free(msg); - return NT_STATUS_NO_MEMORY; - } - nt_status = samdb_set_password(ldb, mem_ctx, user_dn, NULL, - msg, new_password, + new_password, lmNewHash, ntNewHash, user_change, reject_reason, _dominfo); if (!NT_STATUS_IS_OK(nt_status)) { ldb_transaction_cancel(ldb); talloc_free(user_dn); - talloc_free(msg); return nt_status; } - /* modify the samdb record */ - ret = dsdb_replace(ldb, msg, 0); - if (ret != LDB_SUCCESS) { - ldb_transaction_cancel(ldb); - talloc_free(user_dn); - talloc_free(msg); - return NT_STATUS_ACCESS_DENIED; - } - - talloc_free(msg); - ret = ldb_transaction_commit(ldb); if (ret != LDB_SUCCESS) { DEBUG(0,("Failed to commit transaction to change password on %s: %s\n", @@ -2413,35 +2454,6 @@ int dsdb_find_sid_by_dn(struct ldb_context *ldb, } -int dsdb_validate_client_flags(struct ldb_context *ldb, - const struct repsFromTo1 *client_rf) -{ - int ret; - TALLOC_CTX *tmp_ctx = talloc_new(ldb); - - if (client_rf->replica_flags & DRSUAPI_DRS_WRIT_REP) { - bool is_rodc; - ret = samdb_is_rodc(ldb, &client_rf->source_dsa_invocation_id, &is_rodc); - if (ret != LDB_SUCCESS) { - talloc_free(tmp_ctx); - return ret; - } - if (is_rodc) { - DEBUG(0,("Client %s claimed to be WRIT_REP, but is RODC\n", - GUID_string(tmp_ctx, &client_rf->source_dsa_invocation_id))); - talloc_free(tmp_ctx); - return LDB_ERR_UNWILLING_TO_PERFORM; - } - } - - /* TODO: we may need to validate more client flags here, if they - are security sensitive */ - - talloc_free(tmp_ctx); - return LDB_SUCCESS; -} - - /* load a repsFromTo blob list for a given partition GUID attr must be "repsFrom" or "repsTo" @@ -2650,7 +2662,7 @@ int drsuapi_DsReplicaCursor_compare(const struct drsuapi_DsReplicaCursor *c1, /* see if a computer identified by its invocationId is a RODC */ -int samdb_is_rodc(struct ldb_context *sam_ctx, const struct GUID *invocationId, bool *is_rodc) +int samdb_is_rodc(struct ldb_context *sam_ctx, const struct GUID *objectGUID, bool *is_rodc) { /* 1) find the DN for this servers NTDSDSA object 2) search for the msDS-isRODC attribute @@ -2670,8 +2682,18 @@ int samdb_is_rodc(struct ldb_context *sam_ctx, const struct GUID *invocationId, } ret = dsdb_search(sam_ctx, tmp_ctx, &res, config_dn, LDB_SCOPE_SUBTREE, attrs, - DSDB_SEARCH_ONE_ONLY, "invocationID=%s", GUID_string(tmp_ctx, invocationId)); + DSDB_SEARCH_ONE_ONLY, "objectGUID=%s", GUID_string(tmp_ctx, objectGUID)); + + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + *is_rodc = false; + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + if (ret != LDB_SUCCESS) { + DEBUG(1,(("Failed to find our own NTDS Settings object by objectGUID=%s!\n"), + GUID_string(tmp_ctx, objectGUID))); + *is_rodc = false; talloc_free(tmp_ctx); return ret; } @@ -2689,12 +2711,40 @@ int samdb_is_rodc(struct ldb_context *sam_ctx, const struct GUID *invocationId, */ int samdb_rodc(struct ldb_context *sam_ctx, bool *am_rodc) { - const struct GUID *invocationId; - invocationId = samdb_ntds_invocation_id(sam_ctx); - if (!invocationId) { + const struct GUID *objectGUID; + int ret; + bool *cached; + + /* see if we have a cached copy */ + cached = (bool *)ldb_get_opaque(sam_ctx, "cache.am_rodc"); + if (cached) { + *am_rodc = *cached; + return LDB_SUCCESS; + } + + objectGUID = samdb_ntds_objectGUID(sam_ctx); + if (!objectGUID) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = samdb_is_rodc(sam_ctx, objectGUID, am_rodc); + if (ret != LDB_SUCCESS) { + return ret; + } + + cached = talloc(sam_ctx, bool); + if (cached == NULL) { return LDB_ERR_OPERATIONS_ERROR; } - return samdb_is_rodc(sam_ctx, invocationId, am_rodc); + *cached = *am_rodc; + + ret = ldb_set_opaque(sam_ctx, "cache.am_rodc", cached); + if (ret != LDB_SUCCESS) { + talloc_free(cached); + return LDB_ERR_OPERATIONS_ERROR; + } + + return LDB_SUCCESS; } @@ -2886,6 +2936,35 @@ NTSTATUS dsdb_get_extended_dn_uint32(struct ldb_dn *dn, uint32_t *val, const cha return NT_STATUS_OK; } +/* + return a dom_sid from a extended DN structure + */ +NTSTATUS dsdb_get_extended_dn_sid(struct ldb_dn *dn, struct dom_sid *sid, const char *component_name) +{ + const struct ldb_val *sid_blob; + struct TALLOC_CTX *tmp_ctx; + enum ndr_err_code ndr_err; + + sid_blob = ldb_dn_get_extended_component(dn, "SID"); + if (!sid_blob) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + tmp_ctx = talloc_new(NULL); + + ndr_err = ndr_pull_struct_blob_all(sid_blob, tmp_ctx, NULL, sid, + (ndr_pull_flags_fn_t)ndr_pull_dom_sid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + talloc_free(tmp_ctx); + return status; + } + + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} + + /* return RMD_FLAGS directly from a ldb_dn returns 0 if not found @@ -3568,3 +3647,116 @@ const char *samdb_forest_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx) return forest_name; } + +/* + validate that an DSA GUID belongs to the specified user sid. + The user SID must be a domain controller account (either RODC or + RWDC) + */ +int dsdb_validate_dsa_guid(struct ldb_context *ldb, + const struct GUID *dsa_guid, + const struct dom_sid *sid) +{ + /* strategy: + - find DN of record with the DSA GUID in the + configuration partition (objectGUID) + - remove "NTDS Settings" component from DN + - do a base search on that DN for serverReference with + extended-dn enabled + - extract objectSID from resulting serverReference + attribute + - check this sid matches the sid argument + */ + struct ldb_dn *config_dn; + TALLOC_CTX *tmp_ctx = talloc_new(ldb); + struct ldb_message *msg; + const char *attrs1[] = { NULL }; + const char *attrs2[] = { "serverReference", NULL }; + int ret; + struct ldb_dn *dn, *account_dn; + struct dom_sid sid2; + NTSTATUS status; + + config_dn = ldb_get_config_basedn(ldb); + + ret = dsdb_search_one(ldb, tmp_ctx, &msg, config_dn, LDB_SCOPE_SUBTREE, + attrs1, 0, "(&(objectGUID=%s)(objectClass=nTDSDSA))", GUID_string(tmp_ctx, dsa_guid)); + if (ret != LDB_SUCCESS) { + DEBUG(1,(__location__ ": Failed to find DSA objectGUID %s for sid %s\n", + GUID_string(tmp_ctx, dsa_guid), dom_sid_string(tmp_ctx, sid))); + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + dn = msg->dn; + + if (!ldb_dn_remove_child_components(dn, 1)) { + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = dsdb_search_one(ldb, tmp_ctx, &msg, dn, LDB_SCOPE_BASE, + attrs2, DSDB_SEARCH_SHOW_EXTENDED_DN, + "(objectClass=server)"); + if (ret != LDB_SUCCESS) { + DEBUG(1,(__location__ ": Failed to find server record for DSA with objectGUID %s, sid %s\n", + GUID_string(tmp_ctx, dsa_guid), dom_sid_string(tmp_ctx, sid))); + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + + account_dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, msg, "serverReference"); + if (account_dn == NULL) { + DEBUG(1,(__location__ ": Failed to find account_dn for DSA with objectGUID %s, sid %s\n", + GUID_string(tmp_ctx, dsa_guid), dom_sid_string(tmp_ctx, sid))); + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + + status = dsdb_get_extended_dn_sid(account_dn, &sid2, "SID"); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,(__location__ ": Failed to find SID for DSA with objectGUID %s, sid %s\n", + GUID_string(tmp_ctx, dsa_guid), dom_sid_string(tmp_ctx, sid))); + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + + if (!dom_sid_equal(sid, &sid2)) { + /* someone is trying to spoof another account */ + DEBUG(0,(__location__ ": Bad DSA objectGUID %s for sid %s - expected sid %s\n", + GUID_string(tmp_ctx, dsa_guid), + dom_sid_string(tmp_ctx, sid), + dom_sid_string(tmp_ctx, &sid2))); + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + + talloc_free(tmp_ctx); + return LDB_SUCCESS; +} + +const char *rodc_fas_list[] = {"ms-PKI-DPAPIMasterKeys", + "ms-PKI-AccountCredentials", + "ms-PKI-RoamingTimeStamp", + "ms-FVE-KeyPackage", + "ms-FVE-RecoveryGuid", + "ms-FVE-RecoveryInformation", + "ms-FVE-RecoveryPassword", + "ms-FVE-VolumeGuid", + "ms-TPM-OwnerInformation", + NULL}; +/* + check if the attribute belongs to the RODC filtered attribute set +*/ +bool dsdb_attr_in_rodc_fas(uint32_t replica_flags, const struct dsdb_attribute *sa) +{ + int rodc_filtered_flags = SEARCH_FLAG_RODC_ATTRIBUTE | SEARCH_FLAG_CONFIDENTIAL; + bool drs_write_replica = ((replica_flags & DRSUAPI_DRS_WRIT_REP) == 0); + + if (drs_write_replica && (sa->searchFlags & rodc_filtered_flags)) { + return true; + } + if (drs_write_replica && is_attr_in_list(rodc_fas_list, sa->cn)) { + return true; + } + return false; +}