X-Git-Url: http://git.samba.org/?a=blobdiff_plain;f=source4%2Fdsdb%2Fcommon%2Futil.c;h=411d1dd22b05d2d55fe674b5f9837b7d7b69cf45;hb=e18610a197aab80a32cae8c1e09b96496679bbad;hp=086f2a5a1cec616cb3b2989b86e47ad0af151143;hpb=4d7dad13158fe6d998d7f63ed0f4ac7935a29bf8;p=samba.git diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c index 086f2a5a1ce..411d1dd22b0 100644 --- a/source4/dsdb/common/util.c +++ b/source4/dsdb/common/util.c @@ -39,11 +39,24 @@ #include "libcli/auth/libcli_auth.h" #include "librpc/gen_ndr/ndr_drsblobs.h" #include "system/locale.h" +#include "system/filesys.h" #include "lib/util/tsort.h" #include "dsdb/common/util.h" #include "lib/socket/socket.h" #include "librpc/gen_ndr/irpc.h" #include "libds/common/flag_mapping.h" +#include "lib/util/access.h" +#include "lib/util/util_str_hex.h" +#include "libcli/util/ntstatus.h" + +/* + * This included to allow us to handle DSDB_FLAG_REPLICATED_UPDATE in + * dsdb_request_add_controls() + */ +#include "dsdb/samdb/ldb_modules/util.h" + +/* default is 30 minutes: -1e7 * 30 * 60 */ +#define DEFAULT_OBSERVATION_WINDOW -18000000000 /* search the sam for the specified attributes in a specific domain, filter on @@ -189,26 +202,6 @@ struct dom_sid *samdb_search_dom_sid(struct ldb_context *sam_ldb, return sid; } -/* - return the count of the number of records in the sam matching the query -*/ -int samdb_search_count(struct ldb_context *sam_ldb, - TALLOC_CTX *mem_ctx, - struct ldb_dn *basedn, - const char *format, ...) _PRINTF_ATTRIBUTE(4,5) -{ - va_list ap; - const char *attrs[] = { NULL }; - int ret; - - va_start(ap, format); - ret = gendb_search_v(sam_ldb, mem_ctx, basedn, NULL, attrs, format, ap); - va_end(ap); - - return ret; -} - - /* search the sam for a single integer attribute in exactly 1 record */ @@ -349,7 +342,7 @@ uint32_t samdb_result_rid_from_sid(TALLOC_CTX *mem_ctx, const struct ldb_message struct dom_sid *samdb_result_dom_sid(TALLOC_CTX *mem_ctx, const struct ldb_message *msg, const char *attr) { - bool ok; + struct sid_parse_ret ret; const struct ldb_val *v; struct dom_sid *sid; v = ldb_msg_find_ldb_val(msg, attr); @@ -360,8 +353,8 @@ struct dom_sid *samdb_result_dom_sid(TALLOC_CTX *mem_ctx, const struct ldb_messa if (sid == NULL) { return NULL; } - ok = sid_blob_parse(*v, sid); - if (!ok) { + ret = sid_parse(v->data, v->length, sid); + if (ret.len == -1) { talloc_free(sid); return NULL; } @@ -475,45 +468,6 @@ NTTIME samdb_result_allow_password_change(struct ldb_context *sam_ldb, return attr_time; } -/* - construct the force_password_change field from the PwdLastSet - attribute, the userAccountControl and the domain password settings -*/ -NTTIME samdb_result_force_password_change(struct ldb_context *sam_ldb, - TALLOC_CTX *mem_ctx, - struct ldb_dn *domain_dn, - struct ldb_message *msg) -{ - int64_t attr_time = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0); - uint32_t userAccountControl = ldb_msg_find_attr_as_uint(msg, - "userAccountControl", - 0); - int64_t maxPwdAge; - - /* Machine accounts don't expire, and there is a flag for 'no expiry' */ - if (!(userAccountControl & UF_NORMAL_ACCOUNT) - || (userAccountControl & UF_DONT_EXPIRE_PASSWD)) { - return 0x7FFFFFFFFFFFFFFFULL; - } - - if (attr_time == 0) { - return 0; - } - if (attr_time == -1) { - return 0x7FFFFFFFFFFFFFFFULL; - } - - maxPwdAge = samdb_search_int64(sam_ldb, mem_ctx, 0, domain_dn, - "maxPwdAge", NULL); - if (maxPwdAge == 0 || maxPwdAge == -0x8000000000000000ULL) { - return 0x7FFFFFFFFFFFFFFFULL; - } else { - attr_time -= maxPwdAge; - } - - return attr_time; -} - /* pull a samr_Password structutre from a result set. */ @@ -558,10 +512,51 @@ unsigned int samdb_result_hashes(TALLOC_CTX *mem_ctx, const struct ldb_message * return count; } -NTSTATUS samdb_result_passwords(TALLOC_CTX *mem_ctx, struct loadparm_context *lp_ctx, struct ldb_message *msg, - struct samr_Password **lm_pwd, struct samr_Password **nt_pwd) +NTSTATUS samdb_result_passwords_from_history(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct ldb_message *msg, + unsigned int idx, + struct samr_Password **lm_pwd, + struct samr_Password **nt_pwd) +{ + struct samr_Password *lmPwdHash, *ntPwdHash; + + if (nt_pwd) { + unsigned int num_nt; + num_nt = samdb_result_hashes(mem_ctx, msg, "ntPwdHistory", &ntPwdHash); + if (num_nt <= idx) { + *nt_pwd = NULL; + } else { + *nt_pwd = &ntPwdHash[idx]; + } + } + if (lm_pwd) { + /* Ensure that if we have turned off LM + * authentication, that we never use the LM hash, even + * if we store it */ + if (lpcfg_lanman_auth(lp_ctx)) { + unsigned int num_lm; + num_lm = samdb_result_hashes(mem_ctx, msg, "lmPwdHistory", &lmPwdHash); + if (num_lm <= idx) { + *lm_pwd = NULL; + } else { + *lm_pwd = &lmPwdHash[idx]; + } + } else { + *lm_pwd = NULL; + } + } + return NT_STATUS_OK; +} + +NTSTATUS samdb_result_passwords_no_lockout(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const struct ldb_message *msg, + struct samr_Password **lm_pwd, + struct samr_Password **nt_pwd) { struct samr_Password *lmPwdHash, *ntPwdHash; + if (nt_pwd) { unsigned int num_nt; num_nt = samdb_result_hashes(mem_ctx, msg, "unicodePwd", &ntPwdHash); @@ -594,6 +589,27 @@ NTSTATUS samdb_result_passwords(TALLOC_CTX *mem_ctx, struct loadparm_context *lp return NT_STATUS_OK; } +NTSTATUS samdb_result_passwords(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const struct ldb_message *msg, + struct samr_Password **lm_pwd, + struct samr_Password **nt_pwd) +{ + uint16_t acct_flags; + + acct_flags = samdb_result_acct_flags(msg, + "msDS-User-Account-Control-Computed"); + /* Quit if the account was locked out. */ + if (acct_flags & ACB_AUTOLOCK) { + DEBUG(3,("samdb_result_passwords: Account for user %s was locked out.\n", + ldb_dn_get_linearized(msg->dn))); + return NT_STATUS_ACCOUNT_LOCKED_OUT; + } + + return samdb_result_passwords_no_lockout(mem_ctx, lp_ctx, msg, + lm_pwd, nt_pwd); +} + /* pull a samr_LogonHours structutre from a result set. */ @@ -625,52 +641,63 @@ struct samr_LogonHours samdb_result_logon_hours(TALLOC_CTX *mem_ctx, struct ldb_ /* pull a set of account_flags from a result set. - This requires that the attributes: - pwdLastSet - userAccountControl - be included in 'msg' + Naturally, this requires that userAccountControl and + (if not null) the attributes 'attr' be already + included in msg */ -uint32_t samdb_result_acct_flags(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, - struct ldb_message *msg, struct ldb_dn *domain_dn) +uint32_t samdb_result_acct_flags(const struct ldb_message *msg, const char *attr) { uint32_t userAccountControl = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0); + uint32_t attr_flags = 0; uint32_t acct_flags = ds_uf2acb(userAccountControl); - NTTIME must_change_time; - NTTIME now; - - must_change_time = samdb_result_force_password_change(sam_ctx, mem_ctx, - domain_dn, msg); - - /* Test account expire time */ - unix_to_nt_time(&now, time(NULL)); - /* check for expired password */ - if (must_change_time < now) { - acct_flags |= ACB_PW_EXPIRED; + if (attr) { + attr_flags = ldb_msg_find_attr_as_uint(msg, attr, UF_ACCOUNTDISABLE); + if (attr_flags == UF_ACCOUNTDISABLE) { + DEBUG(0, ("Attribute %s not found, disabling account %s!\n", attr, + ldb_dn_get_linearized(msg->dn))); + } + acct_flags |= ds_uf2acb(attr_flags); } + return acct_flags; } -struct lsa_BinaryString samdb_result_parameters(TALLOC_CTX *mem_ctx, - struct ldb_message *msg, - const char *attr) +NTSTATUS samdb_result_parameters(TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + const char *attr, + struct lsa_BinaryString *s) { - struct lsa_BinaryString s; + int i; const struct ldb_val *val = ldb_msg_find_ldb_val(msg, attr); - ZERO_STRUCT(s); + ZERO_STRUCTP(s); if (!val) { - return s; + return NT_STATUS_OK; + } + + if ((val->length % 2) != 0) { + /* + * If the on-disk data is not even in length, we know + * it is corrupt, and can not be safely pushed. We + * would either truncate, send either a un-initilaised + * byte or send a forced zero byte + */ + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + s->array = talloc_array(mem_ctx, uint16_t, val->length/2); + if (!s->array) { + return NT_STATUS_NO_MEMORY; } + s->length = s->size = val->length; - s.array = talloc_array(mem_ctx, uint16_t, val->length/2); - if (!s.array) { - return s; + /* The on-disk format is the 'network' format, being UTF16LE (sort of) */ + for (i = 0; i < s->length / 2; i++) { + s->array[i] = SVAL(val->data, i * 2); } - s.length = s.size = val->length; - memcpy(s.array, val->data, val->length); - return s; + return NT_STATUS_OK; } /* Find an attribute, with a particular value */ @@ -699,16 +726,50 @@ struct ldb_message_element *samdb_find_attribute(struct ldb_context *ldb, return NULL; } -int samdb_find_or_add_attribute(struct ldb_context *ldb, struct ldb_message *msg, const char *name, const char *set_value) +static int samdb_find_or_add_attribute_ex(struct ldb_context *ldb, + struct ldb_message *msg, + const char *name, + const char *set_value, + unsigned attr_flags, + bool *added) { + int ret; struct ldb_message_element *el; + SMB_ASSERT(attr_flags != 0); + el = ldb_msg_find_element(msg, name); if (el) { + if (added != NULL) { + *added = false; + } + return LDB_SUCCESS; } - return ldb_msg_add_string(msg, name, set_value); + ret = ldb_msg_add_empty(msg, name, + attr_flags, + &el); + if (ret != LDB_SUCCESS) { + return ret; + } + + if (set_value != NULL) { + ret = ldb_msg_add_string(msg, name, set_value); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (added != NULL) { + *added = true; + } + return LDB_SUCCESS; +} + +int samdb_find_or_add_attribute(struct ldb_context *ldb, struct ldb_message *msg, const char *name, const char *set_value) +{ + return samdb_find_or_add_attribute_ex(ldb, msg, name, set_value, LDB_FLAG_MOD_ADD, NULL); } /* @@ -978,10 +1039,26 @@ int samdb_msg_add_logon_hours(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, int samdb_msg_add_parameters(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg, const char *attr_name, struct lsa_BinaryString *parameters) { + int i; struct ldb_val val; + if ((parameters->length % 2) != 0) { + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + + val.data = talloc_array(mem_ctx, uint8_t, parameters->length); + if (val.data == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } val.length = parameters->length; - val.data = (uint8_t *)parameters->array; - return ldb_msg_add_value(msg, attr_name, &val, NULL); + for (i = 0; i < parameters->length / 2; i++) { + /* + * The on-disk format needs to be in the 'network' + * format, parmeters->array is a uint16_t array of + * length parameters->length / 2 + */ + SSVAL(val.data, i * 2, parameters->array[i]); + } + return ldb_msg_add_steal_value(msg, attr_name, &val); } /* @@ -1012,8 +1089,8 @@ int samdb_msg_set_uint(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, /* * Handle ldb_request in transaction */ -static int dsdb_autotransaction_request(struct ldb_context *sam_ldb, - struct ldb_request *req) +int dsdb_autotransaction_request(struct ldb_context *sam_ldb, + struct ldb_request *req) { int ret; @@ -1201,6 +1278,61 @@ failed: return false; } +/* + work out the domain guid for the current open ldb +*/ +const struct GUID *samdb_domain_guid(struct ldb_context *ldb) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct GUID *domain_guid = NULL; + const char *attrs[] = { + "objectGUID", + NULL + }; + struct ldb_result *res = NULL; + int ret; + + /* see if we have a cached copy */ + domain_guid = (struct GUID *)ldb_get_opaque(ldb, "cache.domain_guid"); + if (domain_guid) { + return domain_guid; + } + + tmp_ctx = talloc_new(ldb); + if (tmp_ctx == NULL) { + goto failed; + } + + ret = ldb_search(ldb, tmp_ctx, &res, ldb_get_default_basedn(ldb), LDB_SCOPE_BASE, attrs, "objectGUID=*"); + if (ret != LDB_SUCCESS) { + goto failed; + } + + if (res->count != 1) { + goto failed; + } + + domain_guid = talloc(tmp_ctx, struct GUID); + if (domain_guid == NULL) { + goto failed; + } + *domain_guid = samdb_result_guid(res->msgs[0], "objectGUID"); + + /* cache the domain_sid in the ldb */ + if (ldb_set_opaque(ldb, "cache.domain_guid", domain_guid) != LDB_SUCCESS) { + goto failed; + } + + talloc_steal(ldb, domain_guid); + talloc_free(tmp_ctx); + + return domain_guid; + +failed: + talloc_free(tmp_ctx); + return NULL; +} + bool samdb_set_ntds_settings_dn(struct ldb_context *ldb, struct ldb_dn *ntds_settings_dn_in) { TALLOC_CTX *tmp_ctx; @@ -1289,20 +1421,24 @@ failed: } /* - work out the ntds settings invocationId for the current open ldb + work out the ntds settings invocationID/objectGUID for the current open ldb */ -const struct GUID *samdb_ntds_invocation_id(struct ldb_context *ldb) +static const struct GUID *samdb_ntds_GUID(struct ldb_context *ldb, + const char *attribute, + const char *cache_name) { TALLOC_CTX *tmp_ctx; - const char *attrs[] = { "invocationId", NULL }; + const char *attrs[] = { attribute, NULL }; int ret; struct ldb_result *res; - struct GUID *invocation_id; + struct GUID *ntds_guid; + struct ldb_dn *ntds_settings_dn = NULL; + const char *errstr = NULL; /* see if we have a cached copy */ - invocation_id = (struct GUID *)ldb_get_opaque(ldb, "cache.invocation_id"); - if (invocation_id) { - return invocation_id; + ntds_guid = (struct GUID *)ldb_get_opaque(ldb, cache_name); + if (ntds_guid != NULL) { + return ntds_guid; } tmp_ctx = talloc_new(ldb); @@ -1310,75 +1446,56 @@ const struct GUID *samdb_ntds_invocation_id(struct ldb_context *ldb) goto failed; } - ret = ldb_search(ldb, tmp_ctx, &res, samdb_ntds_settings_dn(ldb, tmp_ctx), LDB_SCOPE_BASE, attrs, NULL); - if (ret) { + ntds_settings_dn = samdb_ntds_settings_dn(ldb, tmp_ctx); + if (ntds_settings_dn == NULL) { + errstr = "samdb_ntds_settings_dn() returned NULL"; goto failed; } - if (res->count != 1) { + ret = ldb_search(ldb, tmp_ctx, &res, ntds_settings_dn, + LDB_SCOPE_BASE, attrs, NULL); + if (ret) { + errstr = ldb_errstring(ldb); goto failed; } - invocation_id = talloc(tmp_ctx, struct GUID); - if (!invocation_id) { + if (res->count != 1) { + errstr = "incorrect number of results from base search"; goto failed; } - *invocation_id = samdb_result_guid(res->msgs[0], "invocationId"); - - /* cache the domain_sid in the ldb */ - if (ldb_set_opaque(ldb, "cache.invocation_id", invocation_id) != LDB_SUCCESS) { + ntds_guid = talloc(tmp_ctx, struct GUID); + if (ntds_guid == NULL) { goto failed; } - talloc_steal(ldb, invocation_id); - talloc_free(tmp_ctx); - - return invocation_id; - -failed: - DEBUG(1,("Failed to find our own NTDS Settings invocationId in the ldb!\n")); - talloc_free(tmp_ctx); - return NULL; -} - -bool samdb_set_ntds_invocation_id(struct ldb_context *ldb, const struct GUID *invocation_id_in) -{ - TALLOC_CTX *tmp_ctx; - struct GUID *invocation_id_new; - struct GUID *invocation_id_old; - - /* see if we have a cached copy */ - invocation_id_old = (struct GUID *)ldb_get_opaque(ldb, - "cache.invocation_id"); - - tmp_ctx = talloc_new(ldb); - if (tmp_ctx == NULL) { - goto failed; - } + *ntds_guid = samdb_result_guid(res->msgs[0], attribute); - invocation_id_new = talloc(tmp_ctx, struct GUID); - if (!invocation_id_new) { + if (GUID_all_zero(ntds_guid)) { + if (ldb_msg_find_ldb_val(res->msgs[0], attribute)) { + errstr = "failed to find the GUID attribute"; + } else { + errstr = "failed to parse the GUID"; + } goto failed; } - *invocation_id_new = *invocation_id_in; - /* cache the domain_sid in the ldb */ - if (ldb_set_opaque(ldb, "cache.invocation_id", invocation_id_new) != LDB_SUCCESS) { + if (ldb_set_opaque(ldb, cache_name, ntds_guid) != LDB_SUCCESS) { + errstr = "ldb_set_opaque() failed"; goto failed; } - talloc_steal(ldb, invocation_id_new); + talloc_steal(ldb, ntds_guid); talloc_free(tmp_ctx); - talloc_free(invocation_id_old); - return true; + return ntds_guid; failed: - DEBUG(1,("Failed to set our own cached invocationId in the ldb!\n")); + DBG_WARNING("Failed to find our own NTDS Settings %s in the ldb: %s!\n", + attribute, errstr); talloc_free(tmp_ctx); - return false; + return NULL; } /* @@ -1386,63 +1503,28 @@ failed: */ const struct GUID *samdb_ntds_objectGUID(struct ldb_context *ldb) { - TALLOC_CTX *tmp_ctx; - const char *attrs[] = { "objectGUID", NULL }; - int ret; - struct ldb_result *res; - struct GUID *ntds_guid; - - /* see if we have a cached copy */ - ntds_guid = (struct GUID *)ldb_get_opaque(ldb, "cache.ntds_guid"); - if (ntds_guid) { - return ntds_guid; - } - - tmp_ctx = talloc_new(ldb); - if (tmp_ctx == NULL) { - goto failed; - } - - ret = ldb_search(ldb, tmp_ctx, &res, samdb_ntds_settings_dn(ldb, tmp_ctx), LDB_SCOPE_BASE, attrs, NULL); - if (ret) { - goto failed; - } - - if (res->count != 1) { - goto failed; - } - - ntds_guid = talloc(tmp_ctx, struct GUID); - if (!ntds_guid) { - goto failed; - } - - *ntds_guid = samdb_result_guid(res->msgs[0], "objectGUID"); - - /* cache the domain_sid in the ldb */ - if (ldb_set_opaque(ldb, "cache.ntds_guid", ntds_guid) != LDB_SUCCESS) { - goto failed; - } - - talloc_steal(ldb, ntds_guid); - talloc_free(tmp_ctx); - - return ntds_guid; + return samdb_ntds_GUID(ldb, "objectGUID", "cache.ntds_guid"); +} -failed: - DEBUG(1,("Failed to find our own NTDS Settings objectGUID in the ldb!\n")); - talloc_free(tmp_ctx); - return NULL; +/* + work out the ntds settings invocationId for the current open ldb +*/ +const struct GUID *samdb_ntds_invocation_id(struct ldb_context *ldb) +{ + return samdb_ntds_GUID(ldb, "invocationId", "cache.invocation_id"); } -bool samdb_set_ntds_objectGUID(struct ldb_context *ldb, const struct GUID *ntds_guid_in) +static bool samdb_set_ntds_GUID(struct ldb_context *ldb, + const struct GUID *ntds_guid_in, + const char *attribute, + const char *cache_name) { TALLOC_CTX *tmp_ctx; struct GUID *ntds_guid_new; struct GUID *ntds_guid_old; /* see if we have a cached copy */ - ntds_guid_old = (struct GUID *)ldb_get_opaque(ldb, "cache.ntds_guid"); + ntds_guid_old = (struct GUID *)ldb_get_opaque(ldb, cache_name); tmp_ctx = talloc_new(ldb); if (tmp_ctx == NULL) { @@ -1457,7 +1539,7 @@ bool samdb_set_ntds_objectGUID(struct ldb_context *ldb, const struct GUID *ntds_ *ntds_guid_new = *ntds_guid_in; /* cache the domain_sid in the ldb */ - if (ldb_set_opaque(ldb, "cache.ntds_guid", ntds_guid_new) != LDB_SUCCESS) { + if (ldb_set_opaque(ldb, cache_name, ntds_guid_new) != LDB_SUCCESS) { goto failed; } @@ -1468,11 +1550,28 @@ bool samdb_set_ntds_objectGUID(struct ldb_context *ldb, const struct GUID *ntds_ return true; failed: - DEBUG(1,("Failed to set our own cached invocationId in the ldb!\n")); + DBG_WARNING("Failed to set our own cached %s in the ldb!\n", + attribute); talloc_free(tmp_ctx); return false; } +bool samdb_set_ntds_objectGUID(struct ldb_context *ldb, const struct GUID *ntds_guid_in) +{ + return samdb_set_ntds_GUID(ldb, + ntds_guid_in, + "objectGUID", + "cache.ntds_guid"); +} + +bool samdb_set_ntds_invocation_id(struct ldb_context *ldb, const struct GUID *invocation_id_in) +{ + return samdb_set_ntds_GUID(ldb, + invocation_id_in, + "invocationId", + "cache.invocation_id"); +} + /* work out the server dn for the current open ldb */ @@ -1670,7 +1769,7 @@ int samdb_server_reference_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, stru server_dn = samdb_server_dn(ldb, mem_ctx); if (server_dn == NULL) { - return LDB_ERR_NO_SUCH_OBJECT; + return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__); } ret = samdb_reference_dn(ldb, mem_ctx, server_dn, "serverReference", dn); @@ -1722,18 +1821,25 @@ const char *samdb_server_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx) /* * Finds the client site by using the client's IP address. * The "subnet_name" returns the name of the subnet if parameter != NULL + * + * Has a Windows-based fallback to provide the only site available, or an empty + * string if there are multiple sites. */ const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, - const char *ip_address, char **subnet_name) + const char *ip_address, char **subnet_name, + bool fallback) { 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; + struct ldb_dn *sites_container_dn = NULL; + struct ldb_dn *subnets_dn = NULL; + struct ldb_dn *sites_dn = NULL; + struct ldb_result *res = NULL; + const struct ldb_val *val = NULL; + const char *site_name = NULL; + const char *l_subnet_name = NULL; const char *allow_list[2] = { NULL, NULL }; unsigned int i, count; - int cnt, ret; + int ret; /* * if we don't have a client ip e.g. ncalrpc @@ -1745,14 +1851,12 @@ const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, sites_container_dn = samdb_sites_dn(ldb, mem_ctx); if (sites_container_dn == NULL) { - return NULL; + goto exit; } 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; + goto exit; } ret = ldb_search(ldb, mem_ctx, &res, subnets_dn, LDB_SCOPE_ONELEVEL, @@ -1760,9 +1864,7 @@ const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, 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; + goto exit; } else { count = res->count; } @@ -1773,7 +1875,7 @@ const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, allow_list[0] = l_subnet_name; - if (socket_allow_access(mem_ctx, NULL, allow_list, "", ip_address)) { + if (allow_access_nolog(NULL, allow_list, "", ip_address)) { sites_dn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, res->msgs[i], "siteObject"); @@ -1787,20 +1889,29 @@ const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, site_name = talloc_strdup(mem_ctx, (const char *) val->data); - talloc_free(sites_dn); + TALLOC_FREE(sites_dn); break; } } - if (site_name == NULL) { + if (site_name == NULL && fallback) { /* 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, mem_ctx, sites_container_dn, - "(objectClass=site)"); + size_t cnt = 0; + ret = dsdb_domain_count( + ldb, + &cnt, + sites_container_dn, + NULL, + LDB_SCOPE_SUBTREE, + "(objectClass=site)"); + if (ret != LDB_SUCCESS) { + goto exit; + } if (cnt == 1) { site_name = samdb_server_site_name(ldb, mem_ctx); } else { @@ -1813,9 +1924,10 @@ const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, *subnet_name = talloc_strdup(mem_ctx, l_subnet_name); } - talloc_free(sites_container_dn); - talloc_free(subnets_dn); - talloc_free(res); +exit: + TALLOC_FREE(sites_container_dn); + TALLOC_FREE(subnets_dn); + TALLOC_FREE(res); return site_name; } @@ -1898,6 +2010,15 @@ int samdb_search_for_parent_domain(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, return ret; } +static void pwd_timeout_debug(struct tevent_context *unused1, + struct tevent_timer *unused2, + struct timeval unused3, + void *unused4) +{ + DEBUG(0, ("WARNING: check_password_complexity: password script " + "took more than 1 second to run\n")); +} + /* * Performs checks on a user password (plaintext UNIX format - attribute @@ -1906,19 +2027,149 @@ int samdb_search_for_parent_domain(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, * * Result codes from "enum samr_ValidationStatus" (consider "samr.idl") */ -enum samr_ValidationStatus samdb_check_password(const DATA_BLOB *password, +enum samr_ValidationStatus samdb_check_password(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const char *account_name, + const char *user_principal_name, + const char *full_name, + const DATA_BLOB *utf8_blob, const uint32_t pwdProperties, const uint32_t minPwdLength) { + const char *utf8_pw = (const char *)utf8_blob->data; + size_t utf8_len = strlen_m(utf8_pw); + char *password_script = NULL; + /* checks if the "minPwdLength" property is satisfied */ - if (minPwdLength > password->length) + if (minPwdLength > utf8_len) { return SAMR_VALIDATION_STATUS_PWD_TOO_SHORT; + } /* checks the password complexity */ - if (((pwdProperties & DOMAIN_PASSWORD_COMPLEX) != 0) - && (password->data != NULL) - && (!check_password_quality((const char *) password->data))) + if (!(pwdProperties & DOMAIN_PASSWORD_COMPLEX)) { + return SAMR_VALIDATION_STATUS_SUCCESS; + } + + if (utf8_len == 0) { + return SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH; + } + + password_script = lpcfg_check_password_script(lp_ctx, mem_ctx); + if (password_script != NULL && *password_script != '\0') { + int check_ret = 0; + int error = 0; + struct tevent_context *event_ctx = NULL; + struct tevent_req *req = NULL; + int cps_stdin = -1; + const char * const cmd[4] = { + "/bin/sh", "-c", + password_script, + NULL + }; + + event_ctx = tevent_context_init(mem_ctx); + if (event_ctx == NULL) { + TALLOC_FREE(password_script); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + + /* Gives a warning after 1 second, terminates after 10 */ + tevent_add_timer(event_ctx, event_ctx, + tevent_timeval_current_ofs(1, 0), + pwd_timeout_debug, NULL); + + check_ret = setenv("SAMBA_CPS_ACCOUNT_NAME", account_name, 1); + if (check_ret != 0) { + TALLOC_FREE(password_script); + TALLOC_FREE(event_ctx); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + if (user_principal_name != NULL) { + check_ret = setenv("SAMBA_CPS_USER_PRINCIPAL_NAME", + user_principal_name, 1); + } else { + unsetenv("SAMBA_CPS_USER_PRINCIPAL_NAME"); + } + if (check_ret != 0) { + TALLOC_FREE(password_script); + TALLOC_FREE(event_ctx); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + if (full_name != NULL) { + check_ret = setenv("SAMBA_CPS_FULL_NAME", full_name, 1); + } else { + unsetenv("SAMBA_CPS_FULL_NAME"); + } + if (check_ret != 0) { + TALLOC_FREE(password_script); + TALLOC_FREE(event_ctx); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + + req = samba_runcmd_send(event_ctx, event_ctx, + tevent_timeval_current_ofs(10, 0), + 100, 100, cmd, NULL); + unsetenv("SAMBA_CPS_ACCOUNT_NAME"); + unsetenv("SAMBA_CPS_USER_PRINCIPAL_NAME"); + unsetenv("SAMBA_CPS_FULL_NAME"); + if (req == NULL) { + TALLOC_FREE(password_script); + TALLOC_FREE(event_ctx); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + + cps_stdin = samba_runcmd_export_stdin(req); + + if (write(cps_stdin, utf8_pw, utf8_len) != utf8_len) { + close(cps_stdin); + cps_stdin = -1; + TALLOC_FREE(password_script); + TALLOC_FREE(event_ctx); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + + close(cps_stdin); + cps_stdin = -1; + + if (!tevent_req_poll(req, event_ctx)) { + TALLOC_FREE(password_script); + TALLOC_FREE(event_ctx); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + + check_ret = samba_runcmd_recv(req, &error); + TALLOC_FREE(event_ctx); + + if (error == ETIMEDOUT) { + DEBUG(0, ("check_password_complexity: check password script took too long!\n")); + TALLOC_FREE(password_script); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + DEBUG(5,("check_password_complexity: check password script (%s) " + "returned [%d]\n", password_script, check_ret)); + + if (check_ret != 0) { + DEBUG(1,("check_password_complexity: " + "check password script said new password is not good " + "enough!\n")); + TALLOC_FREE(password_script); + return SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH; + } + + TALLOC_FREE(password_script); + return SAMR_VALIDATION_STATUS_SUCCESS; + } + + TALLOC_FREE(password_script); + + /* + * Here are the standard AD password quality rules, which we + * run after the script. + */ + + if (!check_password_quality(utf8_pw)) { return SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH; + } return SAMR_VALIDATION_STATUS_SUCCESS; } @@ -1963,7 +2214,7 @@ int samdb_set_password_callback(struct ldb_request *req, struct ldb_reply *ares) * Results: NT_STATUS_OK, NT_STATUS_INVALID_PARAMETER, NT_STATUS_UNSUCCESSFUL, * NT_STATUS_WRONG_PASSWORD, NT_STATUS_PASSWORD_RESTRICTION */ -NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, +static NTSTATUS samdb_set_password_internal(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, struct ldb_dn *user_dn, struct ldb_dn *domain_dn, const DATA_BLOB *new_password, const struct samr_Password *lmNewHash, @@ -1971,13 +2222,15 @@ NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, const struct samr_Password *lmOldHash, const struct samr_Password *ntOldHash, enum samPwdChangeReason *reject_reason, - struct samr_DomInfo1 **_dominfo) + struct samr_DomInfo1 **_dominfo, + bool permit_interdomain_trust) { struct ldb_message *msg; struct ldb_message_element *el; struct ldb_request *req; struct dsdb_control_password_change_status *pwd_stat = NULL; int ret; + bool hash_values = false; NTSTATUS status = NT_STATUS_OK; #define CHECK_RET(x) \ @@ -2013,6 +2266,7 @@ NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, el = ldb_msg_find_element(msg, "unicodePwd"); el->flags = LDB_FLAG_MOD_REPLACE; } + hash_values = true; } else { /* the password wasn't specified correctly */ talloc_free(msg); @@ -2050,13 +2304,25 @@ NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, 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; + if (hash_values) { + 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; + } + } + if (permit_interdomain_trust) { + ret = ldb_request_add_control(req, + DSDB_CONTROL_PERMIT_INTERDOMAIN_TRUST_UAC_OID, + false, 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, @@ -2070,8 +2336,11 @@ NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, ret = dsdb_autotransaction_request(ldb, req); if (req->context != NULL) { - pwd_stat = talloc_steal(mem_ctx, - ((struct ldb_control *)req->context)->data); + struct ldb_control *control = talloc_get_type_abort(req->context, + struct ldb_control); + pwd_stat = talloc_get_type_abort(control->data, + struct dsdb_control_password_change_status); + talloc_steal(mem_ctx, pwd_stat); } talloc_free(req); @@ -2112,10 +2381,11 @@ NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, if (ret == LDB_ERR_CONSTRAINT_VIOLATION) { const char *errmsg = ldb_errstring(ldb); char *endptr = NULL; - WERROR werr = WERR_GENERAL_FAILURE; + WERROR werr = WERR_GEN_FAILURE; status = NT_STATUS_UNSUCCESSFUL; if (errmsg != NULL) { werr = W_ERROR(strtol(errmsg, &endptr, 16)); + DBG_WARNING("%s\n", errmsg); } if (endptr != errmsg) { if (W_ERROR_EQUAL(werr, WERR_INVALID_PASSWORD)) { @@ -2128,13 +2398,37 @@ NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, } else if (ret == LDB_ERR_NO_SUCH_OBJECT) { /* don't let the caller know if an account doesn't exist */ status = NT_STATUS_WRONG_PASSWORD; + } else if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { + status = NT_STATUS_ACCESS_DENIED; } else if (ret != LDB_SUCCESS) { + DEBUG(1, ("Failed to set password on %s: %s\n", + ldb_dn_get_linearized(user_dn), + ldb_errstring(ldb))); status = NT_STATUS_UNSUCCESSFUL; } return status; } +NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, + struct ldb_dn *user_dn, struct ldb_dn *domain_dn, + const DATA_BLOB *new_password, + const struct samr_Password *lmNewHash, + const struct samr_Password *ntNewHash, + const struct samr_Password *lmOldHash, + const struct samr_Password *ntOldHash, + enum samPwdChangeReason *reject_reason, + struct samr_DomInfo1 **_dominfo) +{ + return samdb_set_password_internal(ldb, mem_ctx, + user_dn, domain_dn, + new_password, + lmNewHash, ntNewHash, + lmOldHash, ntOldHash, + reject_reason, _dominfo, + false); /* reject trusts */ +} + /* * Sets the user password using plaintext UTF16 (attribute "new_password") or * LM (attribute "lmNewHash") or NT (attribute "ntNewHash") hash. Also pass @@ -2155,6 +2449,7 @@ NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, */ NTSTATUS samdb_set_password_sid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, const struct dom_sid *user_sid, + const uint32_t *new_version, /* optional for trusts */ const DATA_BLOB *new_password, const struct samr_Password *lmNewHash, const struct samr_Password *ntNewHash, @@ -2163,48 +2458,387 @@ NTSTATUS samdb_set_password_sid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, enum samPwdChangeReason *reject_reason, struct samr_DomInfo1 **_dominfo) { + TALLOC_CTX *frame = talloc_stackframe(); NTSTATUS nt_status; - struct ldb_dn *user_dn; + const char * const user_attrs[] = { + "userAccountControl", + "sAMAccountName", + NULL + }; + struct ldb_message *user_msg = NULL; int ret; + uint32_t uac = 0; ret = ldb_transaction_start(ldb); if (ret != LDB_SUCCESS) { DEBUG(1, ("Failed to start transaction: %s\n", ldb_errstring(ldb))); + TALLOC_FREE(frame); return NT_STATUS_TRANSACTION_ABORTED; } - user_dn = samdb_search_dn(ldb, mem_ctx, NULL, - "(&(objectSid=%s)(objectClass=user))", - ldap_encode_ndr_dom_sid(mem_ctx, user_sid)); - if (!user_dn) { + ret = dsdb_search_one(ldb, frame, &user_msg, ldb_get_default_basedn(ldb), + LDB_SCOPE_SUBTREE, user_attrs, 0, + "(&(objectSid=%s)(objectClass=user))", + ldap_encode_ndr_dom_sid(frame, user_sid)); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(ldb); + DEBUG(3, ("samdb_set_password_sid: SID[%s] not found in samdb %s - %s, " + "returning NO_SUCH_USER\n", + dom_sid_string(frame, user_sid), + ldb_strerror(ret), ldb_errstring(ldb))); + TALLOC_FREE(frame); + return NT_STATUS_NO_SUCH_USER; + } + + uac = ldb_msg_find_attr_as_uint(user_msg, "userAccountControl", 0); + if (!(uac & UF_ACCOUNT_TYPE_MASK)) { ldb_transaction_cancel(ldb); - DEBUG(3, ("samdb_set_password_sid: SID %s not found in samdb, returning NO_SUCH_USER\n", - dom_sid_string(mem_ctx, user_sid))); + DEBUG(1, ("samdb_set_password_sid: invalid " + "userAccountControl[0x%08X] for SID[%s] DN[%s], " + "returning NO_SUCH_USER\n", + (unsigned)uac, dom_sid_string(frame, user_sid), + ldb_dn_get_linearized(user_msg->dn))); + TALLOC_FREE(frame); return NT_STATUS_NO_SUCH_USER; } - nt_status = samdb_set_password(ldb, mem_ctx, - user_dn, NULL, - new_password, - lmNewHash, ntNewHash, - lmOldHash, ntOldHash, - reject_reason, _dominfo); + if (uac & UF_INTERDOMAIN_TRUST_ACCOUNT) { + const char * const tdo_attrs[] = { + "trustAuthIncoming", + "trustDirection", + NULL + }; + struct ldb_message *tdo_msg = NULL; + const char *account_name = NULL; + uint32_t trust_direction; + uint32_t i; + const struct ldb_val *old_val = NULL; + struct trustAuthInOutBlob old_blob = { + .count = 0, + }; + uint32_t old_version = 0; + struct AuthenticationInformation *old_version_a = NULL; + uint32_t _new_version = 0; + struct trustAuthInOutBlob new_blob = { + .count = 0, + }; + struct ldb_val new_val = { + .length = 0, + }; + struct timeval tv = timeval_current(); + NTTIME now = timeval_to_nttime(&tv); + enum ndr_err_code ndr_err; + + if (new_password == NULL && ntNewHash == NULL) { + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: " + "no new password provided " + "sAMAccountName for SID[%s] DN[%s], " + "returning INVALID_PARAMETER\n", + dom_sid_string(frame, user_sid), + ldb_dn_get_linearized(user_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + if (new_password != NULL && ntNewHash != NULL) { + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: " + "two new passwords provided " + "sAMAccountName for SID[%s] DN[%s], " + "returning INVALID_PARAMETER\n", + dom_sid_string(frame, user_sid), + ldb_dn_get_linearized(user_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + if (new_password != NULL && (new_password->length % 2)) { + ldb_transaction_cancel(ldb); + DEBUG(2, ("samdb_set_password_sid: " + "invalid utf16 length (%zu) " + "sAMAccountName for SID[%s] DN[%s], " + "returning WRONG_PASSWORD\n", + new_password->length, + dom_sid_string(frame, user_sid), + ldb_dn_get_linearized(user_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_WRONG_PASSWORD; + } + + if (new_password != NULL && new_password->length >= 500) { + ldb_transaction_cancel(ldb); + DEBUG(2, ("samdb_set_password_sid: " + "utf16 password too long (%zu) " + "sAMAccountName for SID[%s] DN[%s], " + "returning WRONG_PASSWORD\n", + new_password->length, + dom_sid_string(frame, user_sid), + ldb_dn_get_linearized(user_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_WRONG_PASSWORD; + } + + account_name = ldb_msg_find_attr_as_string(user_msg, + "sAMAccountName", NULL); + if (account_name == NULL) { + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: missing " + "sAMAccountName for SID[%s] DN[%s], " + "returning NO_SUCH_USER\n", + dom_sid_string(frame, user_sid), + ldb_dn_get_linearized(user_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_NO_SUCH_USER; + } + + nt_status = dsdb_trust_search_tdo_by_type(ldb, + SEC_CHAN_DOMAIN, + account_name, + tdo_attrs, + frame, &tdo_msg); + if (!NT_STATUS_IS_OK(nt_status)) { + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: dsdb_trust_search_tdo " + "failed(%s) for sAMAccountName[%s] SID[%s] DN[%s], " + "returning INTERNAL_DB_CORRUPTION\n", + nt_errstr(nt_status), account_name, + dom_sid_string(frame, user_sid), + ldb_dn_get_linearized(user_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + trust_direction = ldb_msg_find_attr_as_int(tdo_msg, + "trustDirection", 0); + if (!(trust_direction & LSA_TRUST_DIRECTION_INBOUND)) { + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: direction[0x%08X] is " + "not inbound for sAMAccountName[%s] " + "DN[%s] TDO[%s], " + "returning INTERNAL_DB_CORRUPTION\n", + (unsigned)trust_direction, + account_name, + ldb_dn_get_linearized(user_msg->dn), + ldb_dn_get_linearized(tdo_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + old_val = ldb_msg_find_ldb_val(tdo_msg, "trustAuthIncoming"); + if (old_val != NULL) { + ndr_err = ndr_pull_struct_blob(old_val, frame, &old_blob, + (ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: " + "failed(%s) to parse " + "trustAuthOutgoing sAMAccountName[%s] " + "DN[%s] TDO[%s], " + "returning INTERNAL_DB_CORRUPTION\n", + ndr_map_error2string(ndr_err), + account_name, + ldb_dn_get_linearized(user_msg->dn), + ldb_dn_get_linearized(tdo_msg->dn))); + + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } + + for (i = old_blob.current.count; i > 0; i--) { + struct AuthenticationInformation *a = + &old_blob.current.array[i - 1]; + + switch (a->AuthType) { + case TRUST_AUTH_TYPE_NONE: + if (i == old_blob.current.count) { + /* + * remove TRUST_AUTH_TYPE_NONE at the + * end + */ + old_blob.current.count--; + } + break; + + case TRUST_AUTH_TYPE_VERSION: + old_version_a = a; + old_version = a->AuthInfo.version.version; + break; + + case TRUST_AUTH_TYPE_CLEAR: + break; + + case TRUST_AUTH_TYPE_NT4OWF: + break; + } + } + + if (new_version == NULL) { + _new_version = 0; + new_version = &_new_version; + } + + if (old_version_a != NULL && *new_version != (old_version + 1)) { + old_version_a->LastUpdateTime = now; + old_version_a->AuthType = TRUST_AUTH_TYPE_NONE; + } + + new_blob.count = MAX(old_blob.current.count, 2); + new_blob.current.array = talloc_zero_array(frame, + struct AuthenticationInformation, + new_blob.count); + if (new_blob.current.array == NULL) { + ldb_transaction_cancel(ldb); + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + new_blob.previous.array = talloc_zero_array(frame, + struct AuthenticationInformation, + new_blob.count); + if (new_blob.current.array == NULL) { + ldb_transaction_cancel(ldb); + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < old_blob.current.count; i++) { + struct AuthenticationInformation *o = + &old_blob.current.array[i]; + struct AuthenticationInformation *p = + &new_blob.previous.array[i]; + + *p = *o; + new_blob.previous.count++; + } + for (; i < new_blob.count; i++) { + struct AuthenticationInformation *pi = + &new_blob.previous.array[i]; + + if (i == 0) { + /* + * new_blob.previous is still empty so + * we'll do new_blob.previous = new_blob.current + * below. + */ + break; + } + + pi->LastUpdateTime = now; + pi->AuthType = TRUST_AUTH_TYPE_NONE; + new_blob.previous.count++; + } + + for (i = 0; i < new_blob.count; i++) { + struct AuthenticationInformation *ci = + &new_blob.current.array[i]; + + ci->LastUpdateTime = now; + switch (i) { + case 0: + if (ntNewHash != NULL) { + ci->AuthType = TRUST_AUTH_TYPE_NT4OWF; + ci->AuthInfo.nt4owf.password = *ntNewHash; + break; + } + + ci->AuthType = TRUST_AUTH_TYPE_CLEAR; + ci->AuthInfo.clear.size = new_password->length; + ci->AuthInfo.clear.password = new_password->data; + break; + case 1: + ci->AuthType = TRUST_AUTH_TYPE_VERSION; + ci->AuthInfo.version.version = *new_version; + break; + default: + ci->AuthType = TRUST_AUTH_TYPE_NONE; + break; + } + + new_blob.current.count++; + } + + if (new_blob.previous.count == 0) { + TALLOC_FREE(new_blob.previous.array); + new_blob.previous = new_blob.current; + } + + ndr_err = ndr_push_struct_blob(&new_val, frame, &new_blob, + (ndr_push_flags_fn_t)ndr_push_trustAuthInOutBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: " + "failed(%s) to generate " + "trustAuthOutgoing sAMAccountName[%s] " + "DN[%s] TDO[%s], " + "returning UNSUCCESSFUL\n", + ndr_map_error2string(ndr_err), + account_name, + ldb_dn_get_linearized(user_msg->dn), + ldb_dn_get_linearized(tdo_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_UNSUCCESSFUL; + } + + tdo_msg->num_elements = 0; + TALLOC_FREE(tdo_msg->elements); + + ret = ldb_msg_add_empty(tdo_msg, "trustAuthIncoming", + LDB_FLAG_MOD_REPLACE, NULL); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(ldb); + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + ret = ldb_msg_add_value(tdo_msg, "trustAuthIncoming", + &new_val, NULL); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(ldb); + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_modify(ldb, tdo_msg); + if (ret != LDB_SUCCESS) { + nt_status = dsdb_ldb_err_to_ntstatus(ret); + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: " + "failed to replace " + "trustAuthOutgoing sAMAccountName[%s] " + "DN[%s] TDO[%s], " + "%s - %s\n", + account_name, + ldb_dn_get_linearized(user_msg->dn), + ldb_dn_get_linearized(tdo_msg->dn), + nt_errstr(nt_status), ldb_errstring(ldb))); + TALLOC_FREE(frame); + return nt_status; + } + } + + nt_status = samdb_set_password_internal(ldb, mem_ctx, + user_msg->dn, NULL, + new_password, + lmNewHash, ntNewHash, + lmOldHash, ntOldHash, + reject_reason, _dominfo, + true); /* permit trusts */ if (!NT_STATUS_IS_OK(nt_status)) { ldb_transaction_cancel(ldb); - talloc_free(user_dn); + TALLOC_FREE(frame); return nt_status; } ret = ldb_transaction_commit(ldb); if (ret != LDB_SUCCESS) { DEBUG(0,("Failed to commit transaction to change password on %s: %s\n", - ldb_dn_get_linearized(user_dn), + ldb_dn_get_linearized(user_msg->dn), ldb_errstring(ldb))); - talloc_free(user_dn); + TALLOC_FREE(frame); return NT_STATUS_TRANSACTION_ABORTED; } - talloc_free(user_dn); + TALLOC_FREE(frame); return NT_STATUS_OK; } @@ -2443,7 +3077,9 @@ struct ldb_dn *samdb_domain_to_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, */ int dsdb_find_dn_by_guid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, - const struct GUID *guid, struct ldb_dn **dn) + const struct GUID *guid, + uint32_t dsdb_flags, + struct ldb_dn **dn) { int ret; struct ldb_result *res; @@ -2457,7 +3093,7 @@ int dsdb_find_dn_by_guid(struct ldb_context *ldb, ret = dsdb_search(ldb, mem_ctx, &res, NULL, LDB_SCOPE_SUBTREE, attrs, DSDB_SEARCH_SEARCH_ALL_PARTITIONS | DSDB_SEARCH_SHOW_EXTENDED_DN | - DSDB_SEARCH_ONE_ONLY, + DSDB_SEARCH_ONE_ONLY | dsdb_flags, "objectGUID=%s", guid_str); talloc_free(guid_str); if (ret != LDB_SUCCESS) { @@ -2494,7 +3130,7 @@ int dsdb_find_guid_attr_by_dn(struct ldb_context *ldb, } if (res->count < 1) { talloc_free(tmp_ctx); - return LDB_ERR_NO_SUCH_OBJECT; + return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__); } *guid = samdb_result_guid(res->msgs[0], attribute); talloc_free(tmp_ctx); @@ -2570,12 +3206,12 @@ int dsdb_find_sid_by_dn(struct ldb_context *ldb, } if (res->count < 1) { talloc_free(tmp_ctx); - return LDB_ERR_NO_SUCH_OBJECT; + return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__); } s = samdb_result_dom_sid(tmp_ctx, res->msgs[0], "objectSid"); if (s == NULL) { talloc_free(tmp_ctx); - return LDB_ERR_NO_SUCH_OBJECT; + return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__); } *sid = *s; talloc_free(tmp_ctx); @@ -2911,6 +3547,53 @@ int samdb_rodc(struct ldb_context *sam_ctx, bool *am_rodc) return LDB_SUCCESS; } +int samdb_dns_host_name(struct ldb_context *sam_ctx, const char **host_name) +{ + const char *_host_name = NULL; + const char *attrs[] = { "dnsHostName", NULL }; + TALLOC_CTX *tmp_ctx = NULL; + int ret; + struct ldb_result *res = NULL; + + _host_name = (const char *)ldb_get_opaque(sam_ctx, "cache.dns_host_name"); + if (_host_name != NULL) { + *host_name = _host_name; + return LDB_SUCCESS; + } + + tmp_ctx = talloc_new(sam_ctx); + + ret = dsdb_search_dn(sam_ctx, tmp_ctx, &res, NULL, attrs, 0); + + if (res->count != 1 || ret != LDB_SUCCESS) { + DEBUG(0, ("Failed to get rootDSE for dnsHostName: %s", + ldb_errstring(sam_ctx))); + TALLOC_FREE(tmp_ctx); + return ret; + } + + + _host_name = ldb_msg_find_attr_as_string(res->msgs[0], + "dnsHostName", + NULL); + if (_host_name == NULL) { + DEBUG(0, ("Failed to get dnsHostName from rootDSE")); + TALLOC_FREE(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + ret = ldb_set_opaque(sam_ctx, "cache.dns_host_name", + discard_const_p(char *, _host_name)); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(tmp_ctx); + return ldb_operr(sam_ctx); + } + + *host_name = talloc_steal(sam_ctx, _host_name); + + TALLOC_FREE(tmp_ctx); + return LDB_SUCCESS; +} + bool samdb_set_am_rodc(struct ldb_context *ldb, bool am_rodc) { TALLOC_CTX *tmp_ctx; @@ -2987,7 +3670,7 @@ int samdb_ntds_site_settings_options(struct ldb_context *ldb_ctx, failed: DEBUG(1,("Failed to find our NTDS Site Settings options in ldb!\n")); talloc_free(tmp_ctx); - return LDB_ERR_NO_SUCH_OBJECT; + return ldb_error(ldb_ctx, LDB_ERR_NO_SUCH_OBJECT, __func__); } /* @@ -3025,7 +3708,7 @@ int samdb_ntds_options(struct ldb_context *ldb, uint32_t *options) failed: DEBUG(1,("Failed to find our own NTDS Settings options in the ldb!\n")); talloc_free(tmp_ctx); - return LDB_ERR_NO_SUCH_OBJECT; + return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__); } const char* samdb_ntds_object_category(TALLOC_CTX *tmp_ctx, struct ldb_context *ldb) @@ -3060,16 +3743,17 @@ const char *samdb_cn_to_lDAPDisplayName(TALLOC_CTX *mem_ctx, const char *cn) size_t i; tokens = str_list_make(mem_ctx, cn, " -_"); - if (tokens == NULL) + if (tokens == NULL || tokens[0] == NULL) { return NULL; + } /* "tolower()" and "toupper()" should also work properly on 0x00 */ tokens[0][0] = tolower(tokens[0][0]); - for (i = 1; i < str_list_length((const char * const *)tokens); i++) + for (i = 1; tokens[i] != NULL; i++) tokens[i][0] = toupper(tokens[i][0]); ret = talloc_strdup(mem_ctx, tokens[0]); - for (i = 1; i < str_list_length((const char * const *)tokens); i++) + for (i = 1; tokens[i] != NULL; i++) ret = talloc_asprintf_append_buffer(ret, "%s", tokens[i]); talloc_free(tokens); @@ -3146,18 +3830,26 @@ NTSTATUS dsdb_get_extended_dn_guid(struct ldb_dn *dn, struct GUID *guid, const c NTSTATUS dsdb_get_extended_dn_uint64(struct ldb_dn *dn, uint64_t *val, const char *component_name) { const struct ldb_val *v; - char *s; + int error = 0; v = ldb_dn_get_extended_component(dn, component_name); if (v == NULL) { return NT_STATUS_OBJECT_NAME_NOT_FOUND; } - s = talloc_strndup(dn, (const char *)v->data, v->length); - NT_STATUS_HAVE_NO_MEMORY(s); - *val = strtoull(s, NULL, 0); + /* Just check we don't allow the caller to fill our stack */ + if (v->length >= 64) { + return NT_STATUS_INVALID_PARAMETER; + } else { + char s[v->length+1]; + memcpy(s, v->data, v->length); + s[v->length] = 0; - talloc_free(s); + *val = strtoull_err(s, NULL, 0, &error); + if (error != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + } return NT_STATUS_OK; } @@ -3175,19 +3867,26 @@ NTSTATUS dsdb_get_extended_dn_nttime(struct ldb_dn *dn, NTTIME *nttime, const ch NTSTATUS dsdb_get_extended_dn_uint32(struct ldb_dn *dn, uint32_t *val, const char *component_name) { const struct ldb_val *v; - char *s; + int error = 0; v = ldb_dn_get_extended_component(dn, component_name); if (v == NULL) { return NT_STATUS_OBJECT_NAME_NOT_FOUND; } - s = talloc_strndup(dn, (const char *)v->data, v->length); - NT_STATUS_HAVE_NO_MEMORY(s); - - *val = strtoul(s, NULL, 0); + /* Just check we don't allow the caller to fill our stack */ + if (v->length >= 32) { + return NT_STATUS_INVALID_PARAMETER; + } else { + char s[v->length + 1]; + memcpy(s, v->data, v->length); + s[v->length] = 0; + *val = strtoul_err(s, NULL, 0, &error); + if (error != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + } - talloc_free(s); return NT_STATUS_OK; } @@ -3197,7 +3896,6 @@ NTSTATUS dsdb_get_extended_dn_uint32(struct ldb_dn *dn, uint32_t *val, const cha 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, component_name); @@ -3205,17 +3903,13 @@ NTSTATUS dsdb_get_extended_dn_sid(struct ldb_dn *dn, struct dom_sid *sid, const return NT_STATUS_OBJECT_NAME_NOT_FOUND; } - tmp_ctx = talloc_new(NULL); - - ndr_err = ndr_pull_struct_blob_all(sid_blob, tmp_ctx, sid, - (ndr_pull_flags_fn_t)ndr_pull_dom_sid); + ndr_err = ndr_pull_struct_blob_all_noalloc(sid_blob, 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; } @@ -3226,13 +3920,13 @@ NTSTATUS dsdb_get_extended_dn_sid(struct ldb_dn *dn, struct dom_sid *sid, const */ uint32_t dsdb_dn_rmd_flags(struct ldb_dn *dn) { - const struct ldb_val *v; - char buf[32]; - v = ldb_dn_get_extended_component(dn, "RMD_FLAGS"); - if (!v || v->length > sizeof(buf)-1) return 0; - strncpy(buf, (const char *)v->data, v->length); - buf[v->length] = 0; - return strtoul(buf, NULL, 10); + uint32_t rmd_flags = 0; + NTSTATUS status = dsdb_get_extended_dn_uint32(dn, &rmd_flags, + "RMD_FLAGS"); + if (NT_STATUS_IS_OK(status)) { + return rmd_flags; + } + return 0; } /* @@ -3244,6 +3938,7 @@ uint32_t dsdb_dn_val_rmd_flags(const struct ldb_val *val) const char *p; uint32_t flags; char *end; + int error = 0; if (val->length < 13) { return 0; @@ -3252,8 +3947,8 @@ uint32_t dsdb_dn_val_rmd_flags(const struct ldb_val *val) if (!p) { return 0; } - flags = strtoul(p+11, &end, 10); - if (!end || *end != '>') { + flags = strtoul_err(p+11, &end, 10, &error); + if (!end || *end != '>' || error != 0) { /* it must end in a > */ return 0; } @@ -3272,7 +3967,7 @@ bool dsdb_dn_is_deleted_val(const struct ldb_val *val) return true if a ldb_val containing a DN in storage form is in the upgraded w2k3 linked attribute format */ -bool dsdb_dn_is_upgraded_link_val(struct ldb_val *val) +bool dsdb_dn_is_upgraded_link_val(const struct ldb_val *val) { return memmem(val->data, val->length, "count == 0) { DEBUG(1,("Searching for namingContexts in rootDSE failed: %s\n", ldb_errstring(samdb))); talloc_free(tmp_ctx); return ret; @@ -3348,7 +4043,7 @@ int dsdb_find_nc_root(struct ldb_context *samdb, TALLOC_CTX *mem_ctx, struct ldb if ((el == NULL) || (el->num_values < 3)) { struct ldb_message *tmp_msg; - DEBUG(5,("dsdb_find_nc_root: Finding a valid 'namingContexts' element in the RootDSE failed. Using a temporary list.")); + DEBUG(5,("dsdb_find_nc_root: Finding a valid 'namingContexts' element in the RootDSE failed. Using a temporary list.\n")); /* This generates a temporary list of NCs in order to let the * provisioning work. */ @@ -3403,7 +4098,7 @@ int dsdb_find_nc_root(struct ldb_context *samdb, TALLOC_CTX *mem_ctx, struct ldb } talloc_free(tmp_ctx); - return LDB_ERR_NO_SUCH_OBJECT; + return ldb_error(samdb, LDB_ERR_NO_SUCH_OBJECT, __func__); } @@ -3436,7 +4131,7 @@ int dsdb_tombstone_lifetime(struct ldb_context *ldb, uint32_t *lifetime) struct ldb_dn *dn; dn = ldb_get_config_basedn(ldb); if (!dn) { - return LDB_ERR_NO_SUCH_OBJECT; + return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__); } dn = ldb_dn_copy(ldb, dn); if (!dn) { @@ -3483,11 +4178,12 @@ int dsdb_load_udv_v2(struct ldb_context *samdb, struct ldb_dn *dn, TALLOC_CTX *m const struct ldb_val *ouv_value; unsigned int i; int ret; - uint64_t highest_usn; + uint64_t highest_usn = 0; const struct GUID *our_invocation_id; - struct timeval now = timeval_current(); + static const struct timeval tv1970; + NTTIME nt1970 = timeval_to_nttime(&tv1970); - ret = ldb_search(samdb, mem_ctx, &r, dn, LDB_SCOPE_BASE, attrs, NULL); + ret = dsdb_search_dn(samdb, mem_ctx, &r, dn, attrs, DSDB_SEARCH_SHOW_RECYCLED|DSDB_SEARCH_SHOW_DELETED); if (ret != LDB_SUCCESS) { return ret; } @@ -3526,7 +4222,7 @@ int dsdb_load_udv_v2(struct ldb_context *samdb, struct ldb_dn *dn, TALLOC_CTX *m return ldb_operr(samdb); } - ret = dsdb_load_partition_usn(samdb, dn, &highest_usn, NULL); + ret = ldb_sequence_number(samdb, LDB_SEQ_HIGHEST_SEQ, &highest_usn); if (ret != LDB_SUCCESS) { /* nothing to add - this can happen after a vampire */ TYPESAFE_QSORT(*cursors, *count, drsuapi_DsReplicaCursor2_compare); @@ -3536,7 +4232,7 @@ int dsdb_load_udv_v2(struct ldb_context *samdb, struct ldb_dn *dn, TALLOC_CTX *m for (i=0; i<*count; i++) { if (GUID_equal(our_invocation_id, &(*cursors)[i].source_dsa_invocation_id)) { (*cursors)[i].highest_usn = highest_usn; - (*cursors)[i].last_sync_success = timeval_to_nttime(&now); + (*cursors)[i].last_sync_success = nt1970; TYPESAFE_QSORT(*cursors, *count, drsuapi_DsReplicaCursor2_compare); return LDB_SUCCESS; } @@ -3549,7 +4245,7 @@ int dsdb_load_udv_v2(struct ldb_context *samdb, struct ldb_dn *dn, TALLOC_CTX *m (*cursors)[*count].source_dsa_invocation_id = *our_invocation_id; (*cursors)[*count].highest_usn = highest_usn; - (*cursors)[*count].last_sync_success = timeval_to_nttime(&now); + (*cursors)[*count].last_sync_success = nt1970; (*count)++; TYPESAFE_QSORT(*cursors, *count, drsuapi_DsReplicaCursor2_compare); @@ -3723,6 +4419,13 @@ int dsdb_request_add_controls(struct ldb_request *req, uint32_t dsdb_flags) } } + if (dsdb_flags & DSDB_REPLMD_VANISH_LINKS) { + ret = ldb_request_add_control(req, DSDB_CONTROL_REPLMD_VANISH_LINKS, true, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + if (dsdb_flags & DSDB_MODIFY_PARTIAL_REPLICA) { ret = ldb_request_add_control(req, DSDB_CONTROL_PARTIAL_REPLICA, false, NULL); if (ret != LDB_SUCCESS) { @@ -3730,9 +4433,24 @@ int dsdb_request_add_controls(struct ldb_request *req, uint32_t dsdb_flags) } } + if (dsdb_flags & DSDB_FLAG_REPLICATED_UPDATE) { + ret = ldb_request_add_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + return LDB_SUCCESS; } +/* + returns true if a control with the specified "oid" exists +*/ +bool dsdb_request_has_control(struct ldb_request *req, const char *oid) +{ + return (ldb_request_get_control(req, oid) != NULL); +} + /* an add with a set of controls */ @@ -3845,7 +4563,7 @@ int dsdb_replace(struct ldb_context *ldb, struct ldb_message *msg, uint32_t dsdb */ int dsdb_search_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, - struct ldb_result **_res, + struct ldb_result **_result, struct ldb_dn *basedn, const char * const *attrs, uint32_t dsdb_flags) @@ -3890,7 +4608,7 @@ int dsdb_search_dn(struct ldb_context *ldb, return ret; } - *_res = res; + *_result = res; return LDB_SUCCESS; } @@ -3900,7 +4618,7 @@ int dsdb_search_dn(struct ldb_context *ldb, */ int dsdb_search_by_dn_guid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, - struct ldb_result **_res, + struct ldb_result **_result, const struct GUID *guid, const char * const *attrs, uint32_t dsdb_flags) @@ -3915,7 +4633,7 @@ int dsdb_search_by_dn_guid(struct ldb_context *ldb, return ldb_oom(ldb); } - ret = dsdb_search_dn(ldb, mem_ctx, _res, dn, attrs, dsdb_flags); + ret = dsdb_search_dn(ldb, mem_ctx, _result, dn, attrs, dsdb_flags); talloc_free(tmp_ctx); return ret; } @@ -3925,7 +4643,7 @@ int dsdb_search_by_dn_guid(struct ldb_context *ldb, */ int dsdb_search(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, - struct ldb_result **_res, + struct ldb_result **_result, struct ldb_dn *basedn, enum ldb_scope scope, const char * const *attrs, @@ -3994,7 +4712,7 @@ int dsdb_search(struct ldb_context *ldb, if (res->count == 0) { talloc_free(tmp_ctx); ldb_reset_err_string(ldb); - return LDB_ERR_NO_SUCH_OBJECT; + return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__); } if (res->count != 1) { talloc_free(tmp_ctx); @@ -4003,7 +4721,7 @@ int dsdb_search(struct ldb_context *ldb, } } - *_res = talloc_steal(mem_ctx, res); + *_result = talloc_steal(mem_ctx, res); talloc_free(tmp_ctx); return LDB_SUCCESS; @@ -4160,8 +4878,12 @@ int dsdb_validate_dsa_guid(struct ldb_context *ldb, 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))); + DEBUG(1,(__location__ ": Failed to find account dn " + "(serverReference) for %s, parent of DSA with " + "objectGUID %s, sid %s\n", + ldb_dn_get_linearized(msg->dn), + GUID_string(tmp_ctx, dsa_guid), + dom_sid_string(tmp_ctx, sid))); talloc_free(tmp_ctx); return ldb_operr(ldb); } @@ -4368,6 +5090,17 @@ bool is_attr_in_list(const char * const * attrs, const char *attr) return false; } +int dsdb_werror_at(struct ldb_context *ldb, int ldb_ecode, WERROR werr, + const char *location, const char *func, + const char *reason) +{ + if (reason == NULL) { + reason = win_errstr(werr); + } + ldb_asprintf_errstring(ldb, "%08X: %s at %s:%s", + W_ERROR_V(werr), reason, location, func); + return ldb_ecode; +} /* map an ldb error code to an approximate NTSTATUS code @@ -4523,12 +5256,12 @@ _PUBLIC_ NTSTATUS NS_GUID_from_string(const char *s, struct GUID *guid) return NT_STATUS_INVALID_PARAMETER; } - if (11 == sscanf(s, "%08x-%04x%04x-%02x%02x%02x%02x-%02x%02x%02x%02x", - &time_low, &time_mid, &time_hi_and_version, - &clock_seq[0], &clock_seq[1], - &node[0], &node[1], &node[2], &node[3], &node[4], &node[5])) { - status = NT_STATUS_OK; - } + status = parse_guid_string(s, + &time_low, + &time_mid, + &time_hi_and_version, + clock_seq, + node); if (!NT_STATUS_IS_OK(status)) { return status; @@ -4558,3 +5291,651 @@ _PUBLIC_ char *NS_GUID_string(TALLOC_CTX *mem_ctx, const struct GUID *guid) guid->node[2], guid->node[3], guid->node[4], guid->node[5]); } + +/* + * Return the effective badPwdCount + * + * This requires that the user_msg have (if present): + * - badPasswordTime + * - badPwdCount + * + * 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) +{ + int64_t badPasswordTime; + badPasswordTime = ldb_msg_find_attr_as_int64(user_msg, "badPasswordTime", 0); + + if (badPasswordTime - lockOutObservationWindow >= now) { + return ldb_msg_find_attr_as_int(user_msg, "badPwdCount", 0); + } else { + return 0; + } +} + +/* + * Returns a user's PSO, or NULL if none was found + */ +static struct ldb_result *lookup_user_pso(struct ldb_context *sam_ldb, + TALLOC_CTX *mem_ctx, + const struct ldb_message *user_msg, + const char * const *attrs) +{ + struct ldb_result *res = NULL; + struct ldb_dn *pso_dn = NULL; + int ret; + + /* if the user has a PSO that applies, then use the PSO's setting */ + pso_dn = ldb_msg_find_attr_as_dn(sam_ldb, mem_ctx, user_msg, + "msDS-ResultantPSO"); + + if (pso_dn != NULL) { + + ret = dsdb_search_dn(sam_ldb, mem_ctx, &res, pso_dn, attrs, 0); + if (ret != LDB_SUCCESS) { + + /* + * log the error. The caller should fallback to using + * the default domain password settings + */ + DBG_ERR("Error retrieving msDS-ResultantPSO %s for %s", + ldb_dn_get_linearized(pso_dn), + ldb_dn_get_linearized(user_msg->dn)); + } + talloc_free(pso_dn); + } + return res; +} + +/* + * 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; + struct ldb_result *res = NULL; + const char *attrs[] = { "msDS-LockoutObservationWindow", + 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], + "msDS-LockoutObservationWindow", + 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 dsdb_effective_badPwdCount(user_msg, lockOutObservationWindow, now); +} + +/* + * Returns the lockoutThreshold that applies. If a PSO is specified, then that + * setting is used over the domain defaults + */ +static int64_t get_lockout_threshold(struct ldb_message *domain_msg, + struct ldb_message *pso_msg) +{ + if (pso_msg != NULL) { + return ldb_msg_find_attr_as_int(pso_msg, + "msDS-LockoutThreshold", 0); + } else { + return ldb_msg_find_attr_as_int(domain_msg, + "lockoutThreshold", 0); + } +} + +/* + * Returns the lockOutObservationWindow that applies. If a PSO is specified, + * then that setting is used over the domain defaults + */ +static int64_t get_lockout_observation_window(struct ldb_message *domain_msg, + struct ldb_message *pso_msg) +{ + if (pso_msg != NULL) { + return ldb_msg_find_attr_as_int64(pso_msg, + "msDS-LockoutObservationWindow", + DEFAULT_OBSERVATION_WINDOW); + } else { + return ldb_msg_find_attr_as_int64(domain_msg, + "lockOutObservationWindow", + DEFAULT_OBSERVATION_WINDOW); + } +} + +/* + * Prepare an update to the badPwdCount and associated attributes. + * + * This requires that the user_msg have (if present): + * - objectSid + * - badPasswordTime + * - badPwdCount + * + * This also requires that the domain_msg have (if present): + * - pwdProperties + * - lockoutThreshold + * - lockOutObservationWindow + * + * This also requires that the pso_msg have (if present): + * - msDS-LockoutThreshold + * - msDS-LockoutObservationWindow + */ +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) +{ + int i, ret, badPwdCount; + int64_t lockoutThreshold, lockOutObservationWindow; + struct dom_sid *sid; + struct timeval tv_now = timeval_current(); + NTTIME now = timeval_to_nttime(&tv_now); + NTSTATUS status; + uint32_t pwdProperties, rid = 0; + struct ldb_message *mod_msg; + + sid = samdb_result_dom_sid(mem_ctx, user_msg, "objectSid"); + + pwdProperties = ldb_msg_find_attr_as_uint(domain_msg, + "pwdProperties", -1); + if (sid && !(pwdProperties & DOMAIN_PASSWORD_LOCKOUT_ADMINS)) { + status = dom_sid_split_rid(NULL, sid, NULL, &rid); + if (!NT_STATUS_IS_OK(status)) { + /* + * This can't happen anyway, but always try + * and update the badPwdCount on failure + */ + rid = 0; + } + } + TALLOC_FREE(sid); + + /* + * Work out if we are doing password lockout on the domain. + * Also, the built in administrator account is exempt: + * http://msdn.microsoft.com/en-us/library/windows/desktop/aa375371%28v=vs.85%29.aspx + */ + lockoutThreshold = get_lockout_threshold(domain_msg, pso_msg); + if (lockoutThreshold == 0 || (rid == DOMAIN_RID_ADMINISTRATOR)) { + DEBUG(5, ("Not updating badPwdCount on %s after wrong password\n", + ldb_dn_get_linearized(user_msg->dn))); + return NT_STATUS_OK; + } + + mod_msg = ldb_msg_new(mem_ctx); + if (mod_msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + mod_msg->dn = ldb_dn_copy(mod_msg, user_msg->dn); + if (mod_msg->dn == NULL) { + TALLOC_FREE(mod_msg); + return NT_STATUS_NO_MEMORY; + } + + lockOutObservationWindow = get_lockout_observation_window(domain_msg, + pso_msg); + + badPwdCount = dsdb_effective_badPwdCount(user_msg, lockOutObservationWindow, now); + + badPwdCount++; + + ret = samdb_msg_add_int(sam_ctx, mod_msg, mod_msg, "badPwdCount", badPwdCount); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(mod_msg); + return NT_STATUS_NO_MEMORY; + } + ret = samdb_msg_add_int64(sam_ctx, mod_msg, mod_msg, "badPasswordTime", now); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(mod_msg); + return NT_STATUS_NO_MEMORY; + } + + if (badPwdCount >= lockoutThreshold) { + ret = samdb_msg_add_int64(sam_ctx, mod_msg, mod_msg, "lockoutTime", now); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(mod_msg); + return NT_STATUS_NO_MEMORY; + } + DEBUGC( DBGC_AUTH, 1, ("Locked out user %s after %d wrong passwords\n", + ldb_dn_get_linearized(user_msg->dn), badPwdCount)); + } else { + DEBUGC( DBGC_AUTH, 5, ("Updated badPwdCount on %s after %d wrong passwords\n", + ldb_dn_get_linearized(user_msg->dn), badPwdCount)); + } + + /* mark all the message elements as LDB_FLAG_MOD_REPLACE */ + for (i=0; i< mod_msg->num_elements; i++) { + mod_msg->elements[i].flags = LDB_FLAG_MOD_REPLACE; + } + + *_mod_msg = mod_msg; + return NT_STATUS_OK; +} + +/** + * Sets defaults for a User object + * List of default attributes set: + * accountExpires, badPasswordTime, badPwdCount, + * codePage, countryCode, lastLogoff, lastLogon + * logonCount, pwdLastSet + */ +int dsdb_user_obj_set_defaults(struct ldb_context *ldb, + struct ldb_message *usr_obj, + struct ldb_request *req) +{ + int i, ret; + const struct attribute_values { + const char *name; + const char *value; + const char *add_value; + const char *mod_value; + const char *control; + unsigned add_flags; + unsigned mod_flags; + } map[] = { + { + .name = "accountExpires", + .add_value = "9223372036854775807", + .mod_value = "0", + }, + { + .name = "badPasswordTime", + .value = "0" + }, + { + .name = "badPwdCount", + .value = "0" + }, + { + .name = "codePage", + .value = "0" + }, + { + .name = "countryCode", + .value = "0" + }, + { + .name = "lastLogoff", + .value = "0" + }, + { + .name = "lastLogon", + .value = "0" + }, + { + .name = "logonCount", + .value = "0" + }, + { + .name = "logonHours", + .add_flags = DSDB_FLAG_INTERNAL_FORCE_META_DATA, + }, + { + .name = "pwdLastSet", + .value = "0", + .control = DSDB_CONTROL_PASSWORD_DEFAULT_LAST_SET_OID, + }, + { + .name = "adminCount", + .mod_value = "0", + }, + { + .name = "operatorCount", + .mod_value = "0", + }, + }; + + for (i = 0; i < ARRAY_SIZE(map); i++) { + bool added = false; + const char *value = NULL; + unsigned flags = 0; + + if (req != NULL && req->operation == LDB_ADD) { + value = map[i].add_value; + flags = map[i].add_flags; + } else { + value = map[i].mod_value; + flags = map[i].mod_flags; + } + + if (value == NULL) { + value = map[i].value; + } + + if (value != NULL) { + flags |= LDB_FLAG_MOD_ADD; + } + + if (flags == 0) { + continue; + } + + ret = samdb_find_or_add_attribute_ex(ldb, usr_obj, + map[i].name, + value, flags, + &added); + if (ret != LDB_SUCCESS) { + return ret; + } + + if (req != NULL && added && map[i].control != NULL) { + ret = ldb_request_add_control(req, + map[i].control, + false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + } + + return LDB_SUCCESS; +} + +/** + * Sets 'sAMAccountType on user object based on userAccountControl + * @param ldb Current ldb_context + * @param usr_obj ldb_message representing User object + * @param user_account_control Value for userAccountControl flags + * @param account_type_p Optional pointer to account_type to return + * @return LDB_SUCCESS or LDB_ERR* code on failure + */ +int dsdb_user_obj_set_account_type(struct ldb_context *ldb, struct ldb_message *usr_obj, + uint32_t user_account_control, uint32_t *account_type_p) +{ + int ret; + uint32_t account_type; + struct ldb_message_element *el; + + account_type = ds_uf2atype(user_account_control); + if (account_type == 0) { + ldb_set_errstring(ldb, "dsdb: Unrecognized account type!"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + ret = samdb_msg_add_uint(ldb, usr_obj, usr_obj, + "sAMAccountType", + account_type); + if (ret != LDB_SUCCESS) { + return ret; + } + el = ldb_msg_find_element(usr_obj, "sAMAccountType"); + el->flags = LDB_FLAG_MOD_REPLACE; + + if (account_type_p) { + *account_type_p = account_type; + } + + return LDB_SUCCESS; +} + +/** + * Determine and set primaryGroupID based on userAccountControl value + * @param ldb Current ldb_context + * @param usr_obj ldb_message representing User object + * @param user_account_control Value for userAccountControl flags + * @param group_rid_p Optional pointer to group RID to return + * @return LDB_SUCCESS or LDB_ERR* code on failure + */ +int dsdb_user_obj_set_primary_group_id(struct ldb_context *ldb, struct ldb_message *usr_obj, + uint32_t user_account_control, uint32_t *group_rid_p) +{ + int ret; + uint32_t rid; + struct ldb_message_element *el; + + rid = ds_uf2prim_group_rid(user_account_control); + + ret = samdb_msg_add_uint(ldb, usr_obj, usr_obj, + "primaryGroupID", rid); + if (ret != LDB_SUCCESS) { + return ret; + } + el = ldb_msg_find_element(usr_obj, "primaryGroupID"); + el->flags = LDB_FLAG_MOD_REPLACE; + + if (group_rid_p) { + *group_rid_p = rid; + } + + return LDB_SUCCESS; +} + +/** + * Returns True if the source and target DNs both have the same naming context, + * i.e. they're both in the same partition. + */ +bool dsdb_objects_have_same_nc(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *source_dn, + struct ldb_dn *target_dn) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *source_nc; + struct ldb_dn *target_nc; + int ret; + bool same_nc = true; + + tmp_ctx = talloc_new(mem_ctx); + + ret = dsdb_find_nc_root(ldb, tmp_ctx, source_dn, &source_nc); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to find base DN for source %s\n", + ldb_dn_get_linearized(source_dn)); + talloc_free(tmp_ctx); + return true; + } + + ret = dsdb_find_nc_root(ldb, tmp_ctx, target_dn, &target_nc); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to find base DN for target %s\n", + ldb_dn_get_linearized(target_dn)); + talloc_free(tmp_ctx); + return true; + } + + same_nc = (ldb_dn_compare(source_nc, target_nc) == 0); + + talloc_free(tmp_ctx); + + return same_nc; +} +/* + * Context for dsdb_count_domain_callback + */ +struct dsdb_count_domain_context { + /* + * Number of matching records + */ + size_t count; + /* + * sid of the domain that the records must belong to. + * if NULL records can belong to any domain. + */ + struct dom_sid *dom_sid; +}; + +/* + * @brief ldb aysnc callback for dsdb_domain_count. + * + * count the number of records in the database matching an LDAP query, + * optionally filtering for domain membership. + * + * @param [in,out] req the ldb request being processed + * req->context contains: + * count The number of matching records + * dom_sid The domain sid, if present records must belong + * to the domain to be counted. + *@param [in,out] ares The query result. + * + * @return an LDB error code + * + */ +static int dsdb_count_domain_callback( + struct ldb_request *req, + struct ldb_reply *ares) +{ + + if (ares == NULL) { + return ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + int error = ares->error; + TALLOC_FREE(ares); + return ldb_request_done(req, error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + { + struct dsdb_count_domain_context *context = NULL; + struct sid_parse_ret ret; + bool in_domain; + struct dom_sid sid; + const struct ldb_val *v; + + context = req->context; + if (context->dom_sid == NULL) { + context->count++; + break; + } + + v = ldb_msg_find_ldb_val(ares->message, "objectSid"); + if (v == NULL) { + break; + } + + ret = sid_parse(v->data, v->length, &sid); + if (ret.len == -1) { + break; + } + + in_domain = dom_sid_in_domain(context->dom_sid, &sid); + if (!in_domain) { + break; + } + + context->count++; + break; + } + case LDB_REPLY_REFERRAL: + break; + + case LDB_REPLY_DONE: + TALLOC_FREE(ares); + return ldb_request_done(req, LDB_SUCCESS); + } + + TALLOC_FREE(ares); + + return LDB_SUCCESS; +} + +/* + * @brief Count the number of records matching a query. + * + * Count the number of entries in the database matching the supplied query, + * optionally filtering only those entries belonging to the supplied domain. + * + * @param ldb [in] Current ldb context + * @param count [out] Pointer to the count + * @param base [in] The base dn for the quey + * @param dom_sid [in] The domain sid, if non NULL records that are not a member + * of the domain are ignored. + * @param scope [in] Search scope. + * @param exp_fmt [in] format string for the query. + * + * @return LDB_STATUS code. + */ +int dsdb_domain_count( + struct ldb_context *ldb, + size_t *count, + struct ldb_dn *base, + struct dom_sid *dom_sid, + enum ldb_scope scope, + const char *exp_fmt, ...) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_request *req = NULL; + struct dsdb_count_domain_context *context = NULL; + char *expression = NULL; + const char *object_sid[] = {"objectSid", NULL}; + const char *none[] = {NULL}; + va_list ap; + int ret; + + *count = 0; + tmp_ctx = talloc_new(ldb); + + context = talloc_zero(tmp_ctx, struct dsdb_count_domain_context); + if (context == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + context->dom_sid = dom_sid; + + if (exp_fmt) { + va_start(ap, exp_fmt); + expression = talloc_vasprintf(tmp_ctx, exp_fmt, ap); + va_end(ap); + + if (expression == NULL) { + TALLOC_FREE(context); + TALLOC_FREE(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + } + + ret = ldb_build_search_req( + &req, + ldb, + tmp_ctx, + base, + scope, + expression, + (dom_sid == NULL) ? none : object_sid, + NULL, + context, + dsdb_count_domain_callback, + NULL); + ldb_req_set_location(req, "dsdb_domain_count"); + + if (ret != LDB_SUCCESS) goto done; + + ret = ldb_request(ldb, req); + + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + if (ret == LDB_SUCCESS) { + *count = context->count; + } + } + + +done: + TALLOC_FREE(expression); + TALLOC_FREE(req); + TALLOC_FREE(context); + TALLOC_FREE(tmp_ctx); + + return ret; +}