diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
commit | 4f5791ebd03eaec1c7da0865a383175b05102712 (patch) | |
tree | 8ce7b00f7a76baa386372422adebbe64510812d4 /source4/rpc_server/samr | |
parent | Initial commit. (diff) | |
download | samba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz samba-4f5791ebd03eaec1c7da0865a383175b05102712.zip |
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source4/rpc_server/samr')
-rw-r--r-- | source4/rpc_server/samr/dcesrv_samr.c | 5365 | ||||
-rw-r--r-- | source4/rpc_server/samr/dcesrv_samr.h | 88 | ||||
-rw-r--r-- | source4/rpc_server/samr/samr_password.c | 833 |
3 files changed, 6286 insertions, 0 deletions
diff --git a/source4/rpc_server/samr/dcesrv_samr.c b/source4/rpc_server/samr/dcesrv_samr.c new file mode 100644 index 0000000..b1342cb --- /dev/null +++ b/source4/rpc_server/samr/dcesrv_samr.c @@ -0,0 +1,5365 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the samr pipe + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Volker Lendecke 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + Copyright (C) Matthias Dieter Wallnöfer 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "librpc/gen_ndr/ndr_samr.h" +#include "rpc_server/dcerpc_server.h" +#include "rpc_server/common/common.h" +#include "rpc_server/samr/dcesrv_samr.h" +#include "system/time.h" +#include <ldb.h> +#include <ldb_errors.h> +#include "../libds/common/flags.h" +#include "dsdb/samdb/samdb.h" +#include "dsdb/common/util.h" +#include "libcli/ldap/ldap_ndr.h" +#include "libcli/security/security.h" +#include "rpc_server/samr/proto.h" +#include "../lib/util/util_ldb.h" +#include "param/param.h" +#include "lib/util/tsort.h" +#include "libds/common/flag_mapping.h" + +#undef strcasecmp + +#define DCESRV_INTERFACE_SAMR_BIND(context, iface) \ + dcesrv_interface_samr_bind(context, iface) +static NTSTATUS dcesrv_interface_samr_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + return dcesrv_interface_bind_reject_connect(context, iface); +} + +/* these query macros make samr_Query[User|Group|Alias]Info a bit easier to read */ + +#define QUERY_STRING(msg, field, attr) \ + info->field.string = ldb_msg_find_attr_as_string(msg, attr, ""); +#define QUERY_UINT(msg, field, attr) \ + info->field = ldb_msg_find_attr_as_uint(msg, attr, 0); +#define QUERY_RID(msg, field, attr) \ + info->field = samdb_result_rid_from_sid(mem_ctx, msg, attr, 0); +#define QUERY_UINT64(msg, field, attr) \ + info->field = ldb_msg_find_attr_as_uint64(msg, attr, 0); +#define QUERY_APASSC(msg, field, attr) \ + info->field = samdb_result_allow_password_change(sam_ctx, mem_ctx, \ + a_state->domain_state->domain_dn, msg, attr); +#define QUERY_BPWDCT(msg, field, attr) \ + info->field = samdb_result_effective_badPwdCount(sam_ctx, mem_ctx, \ + a_state->domain_state->domain_dn, msg); +#define QUERY_LHOURS(msg, field, attr) \ + info->field = samdb_result_logon_hours(mem_ctx, msg, attr); +#define QUERY_AFLAGS(msg, field, attr) \ + info->field = samdb_result_acct_flags(msg, attr); + + +/* these are used to make the Set[User|Group]Info code easier to follow */ + +#define SET_STRING(msg, field, attr) do { \ + struct ldb_message_element *set_el; \ + if (r->in.info->field.string == NULL) return NT_STATUS_INVALID_PARAMETER; \ + if (r->in.info->field.string[0] == '\0') { \ + if (ldb_msg_add_empty(msg, attr, LDB_FLAG_MOD_DELETE, NULL) != LDB_SUCCESS) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + } \ + if (ldb_msg_add_string(msg, attr, r->in.info->field.string) != LDB_SUCCESS) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + set_el = ldb_msg_find_element(msg, attr); \ + set_el->flags = LDB_FLAG_MOD_REPLACE; \ +} while (0) + +#define SET_UINT(msg, field, attr) do { \ + struct ldb_message_element *set_el; \ + if (samdb_msg_add_uint(sam_ctx, mem_ctx, msg, attr, r->in.info->field) != LDB_SUCCESS) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + set_el = ldb_msg_find_element(msg, attr); \ + set_el->flags = LDB_FLAG_MOD_REPLACE; \ +} while (0) + +#define SET_INT64(msg, field, attr) do { \ + struct ldb_message_element *set_el; \ + if (samdb_msg_add_int64(sam_ctx, mem_ctx, msg, attr, r->in.info->field) != LDB_SUCCESS) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + set_el = ldb_msg_find_element(msg, attr); \ + set_el->flags = LDB_FLAG_MOD_REPLACE; \ +} while (0) + +#define SET_UINT64(msg, field, attr) do { \ + struct ldb_message_element *set_el; \ + if (samdb_msg_add_uint64(sam_ctx, mem_ctx, msg, attr, r->in.info->field) != LDB_SUCCESS) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + set_el = ldb_msg_find_element(msg, attr); \ + set_el->flags = LDB_FLAG_MOD_REPLACE; \ +} while (0) + +/* Set account flags, discarding flags that cannot be set with SAMR */ +#define SET_AFLAGS(msg, field, attr) do { \ + struct ldb_message_element *set_el; \ + if (samdb_msg_add_acct_flags(sam_ctx, mem_ctx, msg, attr, r->in.info->field) != 0) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + set_el = ldb_msg_find_element(msg, attr); \ + set_el->flags = LDB_FLAG_MOD_REPLACE; \ +} while (0) + +#define SET_LHOURS(msg, field, attr) do { \ + struct ldb_message_element *set_el; \ + if (samdb_msg_add_logon_hours(sam_ctx, mem_ctx, msg, attr, &r->in.info->field) != LDB_SUCCESS) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + set_el = ldb_msg_find_element(msg, attr); \ + set_el->flags = LDB_FLAG_MOD_REPLACE; \ +} while (0) + +#define SET_PARAMETERS(msg, field, attr) do { \ + struct ldb_message_element *set_el; \ + if (r->in.info->field.length != 0) { \ + if (samdb_msg_add_parameters(sam_ctx, mem_ctx, msg, attr, &r->in.info->field) != LDB_SUCCESS) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + set_el = ldb_msg_find_element(msg, attr); \ + set_el->flags = LDB_FLAG_MOD_REPLACE; \ + } \ +} while (0) + +/* + * Clear a GUID cache + */ +static void clear_guid_cache(struct samr_guid_cache *cache) +{ + cache->handle = 0; + cache->size = 0; + TALLOC_FREE(cache->entries); +} + +/* + * initialize a GUID cache + */ +static void initialize_guid_cache(struct samr_guid_cache *cache) +{ + cache->handle = 0; + cache->size = 0; + cache->entries = NULL; +} + +static NTSTATUS load_guid_cache( + struct samr_guid_cache *cache, + struct samr_domain_state *d_state, + unsigned int ldb_cnt, + struct ldb_message **res) +{ + NTSTATUS status = NT_STATUS_OK; + unsigned int i; + TALLOC_CTX *frame = talloc_stackframe(); + + clear_guid_cache(cache); + + /* + * Store the GUID's in the cache. + */ + cache->handle = 0; + cache->size = ldb_cnt; + cache->entries = talloc_array(d_state, struct GUID, ldb_cnt); + if (cache->entries == NULL) { + clear_guid_cache(cache); + status = NT_STATUS_NO_MEMORY; + goto exit; + } + + /* + * Extract a list of the GUIDs for all the matching objects + * we cache just the GUIDS to reduce the memory overhead of + * the result cache. + */ + for (i = 0; i < ldb_cnt; i++) { + cache->entries[i] = samdb_result_guid(res[i], "objectGUID"); + } +exit: + TALLOC_FREE(frame); + return status; +} + +/* + samr_Connect + + create a connection to the SAM database +*/ +static NTSTATUS dcesrv_samr_Connect(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_Connect *r) +{ + struct samr_connect_state *c_state; + struct dcesrv_handle *handle; + + ZERO_STRUCTP(r->out.connect_handle); + + c_state = talloc(mem_ctx, struct samr_connect_state); + if (!c_state) { + return NT_STATUS_NO_MEMORY; + } + + /* make sure the sam database is accessible */ + c_state->sam_ctx = dcesrv_samdb_connect_as_user(c_state, dce_call); + if (c_state->sam_ctx == NULL) { + talloc_free(c_state); + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + handle = dcesrv_handle_create(dce_call, SAMR_HANDLE_CONNECT); + if (!handle) { + talloc_free(c_state); + return NT_STATUS_NO_MEMORY; + } + + handle->data = talloc_steal(handle, c_state); + + c_state->access_mask = r->in.access_mask; + *r->out.connect_handle = handle->wire_handle; + + return NT_STATUS_OK; +} + + +/* + samr_Close +*/ +static NTSTATUS dcesrv_samr_Close(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_Close *r) +{ + struct dcesrv_handle *h; + + *r->out.handle = *r->in.handle; + + DCESRV_PULL_HANDLE(h, r->in.handle, DCESRV_HANDLE_ANY); + + talloc_free(h); + + ZERO_STRUCTP(r->out.handle); + + return NT_STATUS_OK; +} + + +/* + samr_SetSecurity +*/ +static NTSTATUS dcesrv_samr_SetSecurity(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetSecurity *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_QuerySecurity +*/ +static NTSTATUS dcesrv_samr_QuerySecurity(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QuerySecurity *r) +{ + struct dcesrv_handle *h; + struct sec_desc_buf *sd; + + *r->out.sdbuf = NULL; + + DCESRV_PULL_HANDLE(h, r->in.handle, DCESRV_HANDLE_ANY); + + sd = talloc(mem_ctx, struct sec_desc_buf); + if (sd == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sd->sd = samdb_default_security_descriptor(mem_ctx); + + *r->out.sdbuf = sd; + + return NT_STATUS_OK; +} + + +/* + samr_Shutdown + + we refuse this operation completely. If a admin wants to shutdown samr + in Samba then they should use the samba admin tools to disable the samr pipe +*/ +static NTSTATUS dcesrv_samr_Shutdown(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_Shutdown *r) +{ + return NT_STATUS_ACCESS_DENIED; +} + + +/* + samr_LookupDomain + + this maps from a domain name to a SID +*/ +static NTSTATUS dcesrv_samr_LookupDomain(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_LookupDomain *r) +{ + struct samr_connect_state *c_state; + struct dcesrv_handle *h; + struct dom_sid *sid; + const char * const dom_attrs[] = { "objectSid", NULL}; + struct ldb_message **dom_msgs; + int ret; + + *r->out.sid = NULL; + + DCESRV_PULL_HANDLE(h, r->in.connect_handle, SAMR_HANDLE_CONNECT); + + c_state = h->data; + + if (r->in.domain_name->string == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (strcasecmp(r->in.domain_name->string, "BUILTIN") == 0) { + ret = gendb_search(c_state->sam_ctx, + mem_ctx, NULL, &dom_msgs, dom_attrs, + "(objectClass=builtinDomain)"); + } else if (strcasecmp_m(r->in.domain_name->string, lpcfg_sam_name(dce_call->conn->dce_ctx->lp_ctx)) == 0) { + ret = gendb_search_dn(c_state->sam_ctx, + mem_ctx, ldb_get_default_basedn(c_state->sam_ctx), + &dom_msgs, dom_attrs); + } else { + return NT_STATUS_NO_SUCH_DOMAIN; + } + if (ret != 1) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + sid = samdb_result_dom_sid(mem_ctx, dom_msgs[0], + "objectSid"); + + if (sid == NULL) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + *r->out.sid = sid; + + return NT_STATUS_OK; +} + + +/* + samr_EnumDomains + + list the domains in the SAM +*/ +static NTSTATUS dcesrv_samr_EnumDomains(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_EnumDomains *r) +{ + struct dcesrv_handle *h; + struct samr_SamArray *array; + uint32_t i, start_i; + + *r->out.resume_handle = 0; + *r->out.sam = NULL; + *r->out.num_entries = 0; + + DCESRV_PULL_HANDLE(h, r->in.connect_handle, SAMR_HANDLE_CONNECT); + + *r->out.resume_handle = 2; + + start_i = *r->in.resume_handle; + + if (start_i >= 2) { + /* search past end of list is not an error for this call */ + return NT_STATUS_OK; + } + + array = talloc(mem_ctx, struct samr_SamArray); + if (array == NULL) { + return NT_STATUS_NO_MEMORY; + } + + array->count = 0; + array->entries = NULL; + + array->entries = talloc_array(mem_ctx, struct samr_SamEntry, 2 - start_i); + if (array->entries == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0;i<2-start_i;i++) { + array->entries[i].idx = start_i + i; + if (i == 0) { + array->entries[i].name.string = lpcfg_sam_name(dce_call->conn->dce_ctx->lp_ctx); + } else { + array->entries[i].name.string = "BUILTIN"; + } + } + + *r->out.sam = array; + *r->out.num_entries = i; + array->count = *r->out.num_entries; + + return NT_STATUS_OK; +} + + +/* + samr_OpenDomain +*/ +static NTSTATUS dcesrv_samr_OpenDomain(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_OpenDomain *r) +{ + struct dcesrv_handle *h_conn, *h_domain; + struct samr_connect_state *c_state; + struct samr_domain_state *d_state; + const char * const dom_attrs[] = { "cn", NULL}; + struct ldb_message **dom_msgs; + int ret; + unsigned int i; + + ZERO_STRUCTP(r->out.domain_handle); + + DCESRV_PULL_HANDLE(h_conn, r->in.connect_handle, SAMR_HANDLE_CONNECT); + + c_state = h_conn->data; + + if (r->in.sid == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + d_state = talloc(mem_ctx, struct samr_domain_state); + if (!d_state) { + return NT_STATUS_NO_MEMORY; + } + + d_state->domain_sid = talloc_steal(d_state, r->in.sid); + + if (dom_sid_equal(d_state->domain_sid, dom_sid_parse_talloc(mem_ctx, SID_BUILTIN))) { + d_state->builtin = true; + d_state->domain_name = "BUILTIN"; + } else { + d_state->builtin = false; + d_state->domain_name = lpcfg_sam_name(dce_call->conn->dce_ctx->lp_ctx); + } + + ret = gendb_search(c_state->sam_ctx, + mem_ctx, ldb_get_default_basedn(c_state->sam_ctx), &dom_msgs, dom_attrs, + "(objectSid=%s)", + ldap_encode_ndr_dom_sid(mem_ctx, r->in.sid)); + + if (ret == 0) { + talloc_free(d_state); + return NT_STATUS_NO_SUCH_DOMAIN; + } else if (ret > 1) { + talloc_free(d_state); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (ret == -1) { + talloc_free(d_state); + DEBUG(1, ("Failed to open domain %s: %s\n", dom_sid_string(mem_ctx, r->in.sid), ldb_errstring(c_state->sam_ctx))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + d_state->domain_dn = talloc_steal(d_state, dom_msgs[0]->dn); + d_state->role = lpcfg_server_role(dce_call->conn->dce_ctx->lp_ctx); + d_state->connect_state = talloc_reference(d_state, c_state); + d_state->sam_ctx = c_state->sam_ctx; + d_state->access_mask = r->in.access_mask; + d_state->domain_users_cached = NULL; + + d_state->lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + + for (i = 0; i < SAMR_LAST_CACHE; i++) { + initialize_guid_cache(&d_state->guid_caches[i]); + } + + h_domain = dcesrv_handle_create(dce_call, SAMR_HANDLE_DOMAIN); + if (!h_domain) { + talloc_free(d_state); + return NT_STATUS_NO_MEMORY; + } + + h_domain->data = talloc_steal(h_domain, d_state); + + *r->out.domain_handle = h_domain->wire_handle; + + return NT_STATUS_OK; +} + +/* + return DomInfo1 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo1(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo1 *info) +{ + info->min_password_length = + ldb_msg_find_attr_as_uint(dom_msgs[0], "minPwdLength", 0); + info->password_history_length = + ldb_msg_find_attr_as_uint(dom_msgs[0], "pwdHistoryLength", 0); + info->password_properties = + ldb_msg_find_attr_as_uint(dom_msgs[0], "pwdProperties", 0); + info->max_password_age = + ldb_msg_find_attr_as_int64(dom_msgs[0], "maxPwdAge", 0); + info->min_password_age = + ldb_msg_find_attr_as_int64(dom_msgs[0], "minPwdAge", 0); + + return NT_STATUS_OK; +} + +/* + return DomInfo2 +*/ +static NTSTATUS dcesrv_samr_info_DomGeneralInformation(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomGeneralInformation *info) +{ + size_t count = 0; + const enum ldb_scope scope = LDB_SCOPE_SUBTREE; + int ret = 0; + + /* MS-SAMR 2.2.4.1 - ReplicaSourceNodeName: "domainReplica" attribute */ + info->primary.string = ldb_msg_find_attr_as_string(dom_msgs[0], + "domainReplica", + ""); + + info->force_logoff_time = ldb_msg_find_attr_as_uint64(dom_msgs[0], "forceLogoff", + 0x8000000000000000LL); + + info->oem_information.string = ldb_msg_find_attr_as_string(dom_msgs[0], + "oEMInformation", + ""); + info->domain_name.string = state->domain_name; + + info->sequence_num = ldb_msg_find_attr_as_uint64(dom_msgs[0], "modifiedCount", + 0); + switch (state->role) { + case ROLE_ACTIVE_DIRECTORY_DC: + /* This pulls the NetBIOS name from the + cn=NTDS Settings,cn=<NETBIOS name of PDC>,.... + string */ + if (samdb_is_pdc(state->sam_ctx)) { + info->role = SAMR_ROLE_DOMAIN_PDC; + } else { + info->role = SAMR_ROLE_DOMAIN_BDC; + } + break; + case ROLE_DOMAIN_PDC: + case ROLE_DOMAIN_BDC: + case ROLE_IPA_DC: + case ROLE_AUTO: + return NT_STATUS_INTERNAL_ERROR; + case ROLE_DOMAIN_MEMBER: + info->role = SAMR_ROLE_DOMAIN_MEMBER; + break; + case ROLE_STANDALONE: + info->role = SAMR_ROLE_STANDALONE; + break; + } + + /* + * Users are not meant to be in BUILTIN + * so to speed up the query we do not filter on domain_sid + */ + ret = dsdb_domain_count( + state->sam_ctx, + &count, + state->domain_dn, + NULL, + scope, + "(objectClass=user)"); + if (ret != LDB_SUCCESS || count > UINT32_MAX) { + goto error; + } + info->num_users = count; + + /* + * Groups are not meant to be in BUILTIN + * so to speed up the query we do not filter on domain_sid + */ + ret = dsdb_domain_count( + state->sam_ctx, + &count, + state->domain_dn, + NULL, + scope, + "(&(objectClass=group)(|(groupType=%d)(groupType=%d)))", + GTYPE_SECURITY_UNIVERSAL_GROUP, + GTYPE_SECURITY_GLOBAL_GROUP); + if (ret != LDB_SUCCESS || count > UINT32_MAX) { + goto error; + } + info->num_groups = count; + + ret = dsdb_domain_count( + state->sam_ctx, + &count, + state->domain_dn, + state->domain_sid, + scope, + "(&(objectClass=group)(|(groupType=%d)(groupType=%d)))", + GTYPE_SECURITY_BUILTIN_LOCAL_GROUP, + GTYPE_SECURITY_DOMAIN_LOCAL_GROUP); + if (ret != LDB_SUCCESS || count > UINT32_MAX) { + goto error; + } + info->num_aliases = count; + + return NT_STATUS_OK; + +error: + if (count > UINT32_MAX) { + return NT_STATUS_INTEGER_OVERFLOW; + } + return dsdb_ldb_err_to_ntstatus(ret); + +} + +/* + return DomInfo3 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo3(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo3 *info) +{ + info->force_logoff_time = ldb_msg_find_attr_as_uint64(dom_msgs[0], "forceLogoff", + 0x8000000000000000LL); + + return NT_STATUS_OK; +} + +/* + return DomInfo4 +*/ +static NTSTATUS dcesrv_samr_info_DomOEMInformation(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomOEMInformation *info) +{ + info->oem_information.string = ldb_msg_find_attr_as_string(dom_msgs[0], + "oEMInformation", + ""); + + return NT_STATUS_OK; +} + +/* + return DomInfo5 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo5(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo5 *info) +{ + info->domain_name.string = state->domain_name; + + return NT_STATUS_OK; +} + +/* + return DomInfo6 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo6(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo6 *info) +{ + /* MS-SAMR 2.2.4.1 - ReplicaSourceNodeName: "domainReplica" attribute */ + info->primary.string = ldb_msg_find_attr_as_string(dom_msgs[0], + "domainReplica", + ""); + + return NT_STATUS_OK; +} + +/* + return DomInfo7 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo7(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo7 *info) +{ + + switch (state->role) { + case ROLE_ACTIVE_DIRECTORY_DC: + /* This pulls the NetBIOS name from the + cn=NTDS Settings,cn=<NETBIOS name of PDC>,.... + string */ + if (samdb_is_pdc(state->sam_ctx)) { + info->role = SAMR_ROLE_DOMAIN_PDC; + } else { + info->role = SAMR_ROLE_DOMAIN_BDC; + } + break; + case ROLE_DOMAIN_PDC: + case ROLE_DOMAIN_BDC: + case ROLE_IPA_DC: + case ROLE_AUTO: + return NT_STATUS_INTERNAL_ERROR; + case ROLE_DOMAIN_MEMBER: + info->role = SAMR_ROLE_DOMAIN_MEMBER; + break; + case ROLE_STANDALONE: + info->role = SAMR_ROLE_STANDALONE; + break; + } + + return NT_STATUS_OK; +} + +/* + return DomInfo8 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo8(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo8 *info) +{ + info->sequence_num = ldb_msg_find_attr_as_uint64(dom_msgs[0], "modifiedCount", + time(NULL)); + + info->domain_create_time = ldb_msg_find_attr_as_uint(dom_msgs[0], "creationTime", + 0x0LL); + + return NT_STATUS_OK; +} + +/* + return DomInfo9 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo9(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo9 *info) +{ + info->domain_server_state = DOMAIN_SERVER_ENABLED; + + return NT_STATUS_OK; +} + +/* + return DomInfo11 +*/ +static NTSTATUS dcesrv_samr_info_DomGeneralInformation2(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomGeneralInformation2 *info) +{ + NTSTATUS status; + status = dcesrv_samr_info_DomGeneralInformation(state, mem_ctx, dom_msgs, &info->general); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + info->lockout_duration = ldb_msg_find_attr_as_int64(dom_msgs[0], "lockoutDuration", + -18000000000LL); + info->lockout_window = ldb_msg_find_attr_as_int64(dom_msgs[0], "lockOutObservationWindow", + -18000000000LL); + info->lockout_threshold = ldb_msg_find_attr_as_int64(dom_msgs[0], "lockoutThreshold", 0); + + return NT_STATUS_OK; +} + +/* + return DomInfo12 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo12(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo12 *info) +{ + info->lockout_duration = ldb_msg_find_attr_as_int64(dom_msgs[0], "lockoutDuration", + -18000000000LL); + info->lockout_window = ldb_msg_find_attr_as_int64(dom_msgs[0], "lockOutObservationWindow", + -18000000000LL); + info->lockout_threshold = ldb_msg_find_attr_as_int64(dom_msgs[0], "lockoutThreshold", 0); + + return NT_STATUS_OK; +} + +/* + return DomInfo13 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo13(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo13 *info) +{ + info->sequence_num = ldb_msg_find_attr_as_uint64(dom_msgs[0], "modifiedCount", + time(NULL)); + + info->domain_create_time = ldb_msg_find_attr_as_uint(dom_msgs[0], "creationTime", + 0x0LL); + + info->modified_count_at_last_promotion = 0; + + return NT_STATUS_OK; +} + +/* + samr_QueryDomainInfo +*/ +static NTSTATUS dcesrv_samr_QueryDomainInfo(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_QueryDomainInfo *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + union samr_DomainInfo *info; + + struct ldb_message **dom_msgs; + const char * const *attrs = NULL; + + *r->out.info = NULL; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + switch (r->in.level) { + case 1: + { + static const char * const attrs2[] = { "minPwdLength", + "pwdHistoryLength", + "pwdProperties", + "maxPwdAge", + "minPwdAge", + NULL }; + attrs = attrs2; + break; + } + case 2: + { + static const char * const attrs2[] = {"forceLogoff", + "oEMInformation", + "modifiedCount", + "domainReplica", + NULL}; + attrs = attrs2; + break; + } + case 3: + { + static const char * const attrs2[] = {"forceLogoff", + NULL}; + attrs = attrs2; + break; + } + case 4: + { + static const char * const attrs2[] = {"oEMInformation", + NULL}; + attrs = attrs2; + break; + } + case 5: + { + attrs = NULL; + break; + } + case 6: + { + static const char * const attrs2[] = { "domainReplica", + NULL }; + attrs = attrs2; + break; + } + case 7: + { + attrs = NULL; + break; + } + case 8: + { + static const char * const attrs2[] = { "modifiedCount", + "creationTime", + NULL }; + attrs = attrs2; + break; + } + case 9: + { + attrs = NULL; + break; + } + case 11: + { + static const char * const attrs2[] = { "oEMInformation", + "forceLogoff", + "modifiedCount", + "lockoutDuration", + "lockOutObservationWindow", + "lockoutThreshold", + NULL}; + attrs = attrs2; + break; + } + case 12: + { + static const char * const attrs2[] = { "lockoutDuration", + "lockOutObservationWindow", + "lockoutThreshold", + NULL}; + attrs = attrs2; + break; + } + case 13: + { + static const char * const attrs2[] = { "modifiedCount", + "creationTime", + NULL }; + attrs = attrs2; + break; + } + default: + { + return NT_STATUS_INVALID_INFO_CLASS; + } + } + + /* some levels don't need a search */ + if (attrs) { + int ret; + ret = gendb_search_dn(d_state->sam_ctx, mem_ctx, + d_state->domain_dn, &dom_msgs, attrs); + if (ret == 0) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + if (ret != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } + + /* allocate the info structure */ + info = talloc_zero(mem_ctx, union samr_DomainInfo); + if (info == NULL) { + return NT_STATUS_NO_MEMORY; + } + + *r->out.info = info; + + switch (r->in.level) { + case 1: + return dcesrv_samr_info_DomInfo1(d_state, mem_ctx, dom_msgs, + &info->info1); + case 2: + return dcesrv_samr_info_DomGeneralInformation(d_state, mem_ctx, dom_msgs, + &info->general); + case 3: + return dcesrv_samr_info_DomInfo3(d_state, mem_ctx, dom_msgs, + &info->info3); + case 4: + return dcesrv_samr_info_DomOEMInformation(d_state, mem_ctx, dom_msgs, + &info->oem); + case 5: + return dcesrv_samr_info_DomInfo5(d_state, mem_ctx, dom_msgs, + &info->info5); + case 6: + return dcesrv_samr_info_DomInfo6(d_state, mem_ctx, dom_msgs, + &info->info6); + case 7: + return dcesrv_samr_info_DomInfo7(d_state, mem_ctx, dom_msgs, + &info->info7); + case 8: + return dcesrv_samr_info_DomInfo8(d_state, mem_ctx, dom_msgs, + &info->info8); + case 9: + return dcesrv_samr_info_DomInfo9(d_state, mem_ctx, dom_msgs, + &info->info9); + case 11: + return dcesrv_samr_info_DomGeneralInformation2(d_state, mem_ctx, dom_msgs, + &info->general2); + case 12: + return dcesrv_samr_info_DomInfo12(d_state, mem_ctx, dom_msgs, + &info->info12); + case 13: + return dcesrv_samr_info_DomInfo13(d_state, mem_ctx, dom_msgs, + &info->info13); + default: + return NT_STATUS_INVALID_INFO_CLASS; + } +} + + +/* + samr_SetDomainInfo +*/ +static NTSTATUS dcesrv_samr_SetDomainInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetDomainInfo *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + struct ldb_message *msg; + int ret; + struct ldb_context *sam_ctx; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + sam_ctx = d_state->sam_ctx; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + msg->dn = talloc_reference(mem_ctx, d_state->domain_dn); + if (!msg->dn) { + return NT_STATUS_NO_MEMORY; + } + + switch (r->in.level) { + case 1: + SET_UINT (msg, info1.min_password_length, "minPwdLength"); + SET_UINT (msg, info1.password_history_length, "pwdHistoryLength"); + SET_UINT (msg, info1.password_properties, "pwdProperties"); + SET_INT64 (msg, info1.max_password_age, "maxPwdAge"); + SET_INT64 (msg, info1.min_password_age, "minPwdAge"); + break; + case 3: + SET_UINT64 (msg, info3.force_logoff_time, "forceLogoff"); + break; + case 4: + SET_STRING(msg, oem.oem_information, "oEMInformation"); + break; + + case 6: + case 7: + case 9: + /* No op, we don't know where to set these */ + return NT_STATUS_OK; + + case 12: + /* + * It is not possible to set lockout_duration < lockout_window. + * (The test is the other way around since the negative numbers + * are stored...) + * + * TODO: + * This check should be moved to the backend, i.e. to some + * ldb module under dsdb/samdb/ldb_modules/ . + * + * This constraint is documented here for the samr rpc service: + * MS-SAMR 3.1.1.6 Attribute Constraints for Originating Updates + * http://msdn.microsoft.com/en-us/library/cc245667%28PROT.10%29.aspx + * + * And here for the ldap backend: + * MS-ADTS 3.1.1.5.3.2 Constraints + * http://msdn.microsoft.com/en-us/library/cc223462(PROT.10).aspx + */ + if (r->in.info->info12.lockout_duration > + r->in.info->info12.lockout_window) + { + return NT_STATUS_INVALID_PARAMETER; + } + SET_INT64 (msg, info12.lockout_duration, "lockoutDuration"); + SET_INT64 (msg, info12.lockout_window, "lockOutObservationWindow"); + SET_INT64 (msg, info12.lockout_threshold, "lockoutThreshold"); + break; + + default: + /* many info classes are not valid for SetDomainInfo */ + return NT_STATUS_INVALID_INFO_CLASS; + } + + /* modify the samdb record */ + ret = ldb_modify(sam_ctx, msg); + if (ret != LDB_SUCCESS) { + DEBUG(1,("Failed to modify record %s: %s\n", + ldb_dn_get_linearized(d_state->domain_dn), + ldb_errstring(sam_ctx))); + return dsdb_ldb_err_to_ntstatus(ret); + } + + return NT_STATUS_OK; +} + +/* + samr_CreateDomainGroup +*/ +static NTSTATUS dcesrv_samr_CreateDomainGroup(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_CreateDomainGroup *r) +{ + NTSTATUS status; + struct samr_domain_state *d_state; + struct samr_account_state *a_state; + struct dcesrv_handle *h; + const char *groupname; + struct dom_sid *group_sid; + struct ldb_dn *group_dn; + struct dcesrv_handle *g_handle; + + ZERO_STRUCTP(r->out.group_handle); + *r->out.rid = 0; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + if (d_state->builtin) { + DEBUG(5, ("Cannot create a domain group in the BUILTIN domain")); + return NT_STATUS_ACCESS_DENIED; + } + + groupname = r->in.name->string; + + if (groupname == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = dsdb_add_domain_group(d_state->sam_ctx, mem_ctx, groupname, &group_sid, &group_dn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + a_state = talloc(mem_ctx, struct samr_account_state); + if (!a_state) { + return NT_STATUS_NO_MEMORY; + } + a_state->sam_ctx = d_state->sam_ctx; + a_state->access_mask = r->in.access_mask; + a_state->domain_state = talloc_reference(a_state, d_state); + a_state->account_dn = talloc_steal(a_state, group_dn); + + a_state->account_name = talloc_steal(a_state, groupname); + + /* create the policy handle */ + g_handle = dcesrv_handle_create(dce_call, SAMR_HANDLE_GROUP); + if (!g_handle) { + return NT_STATUS_NO_MEMORY; + } + + g_handle->data = talloc_steal(g_handle, a_state); + + *r->out.group_handle = g_handle->wire_handle; + *r->out.rid = group_sid->sub_auths[group_sid->num_auths-1]; + + return NT_STATUS_OK; +} + + +/* + comparison function for sorting SamEntry array +*/ +static int compare_SamEntry(struct samr_SamEntry *e1, struct samr_SamEntry *e2) +{ + return e1->idx - e2->idx; +} + +static int compare_msgRid(struct ldb_message **m1, struct ldb_message **m2) { + struct dom_sid *sid1 = NULL; + struct dom_sid *sid2 = NULL; + uint32_t rid1; + uint32_t rid2; + int res = 0; + NTSTATUS status; + TALLOC_CTX *frame = talloc_stackframe(); + + sid1 = samdb_result_dom_sid(frame, *m1, "objectSid"); + sid2 = samdb_result_dom_sid(frame, *m2, "objectSid"); + + /* + * If entries don't have a SID we want to sort them to the end of + * the list. + */ + if (sid1 == NULL && sid2 == NULL) { + res = 0; + goto exit; + } else if (sid2 == NULL) { + res = 1; + goto exit; + } else if (sid1 == NULL) { + res = -1; + goto exit; + } + + /* + * Get and compare the rids, if we fail to extract a rid treat it as a + * missing SID and sort to the end of the list + */ + status = dom_sid_split_rid(NULL, sid1, NULL, &rid1); + if (!NT_STATUS_IS_OK(status)) { + res = 1; + goto exit; + } + + status = dom_sid_split_rid(NULL, sid2, NULL, &rid2); + if (!NT_STATUS_IS_OK(status)) { + res = -1; + goto exit; + } + + if (rid1 == rid2) { + res = 0; + } + else if (rid1 > rid2) { + res = 1; + } + else { + res = -1; + } +exit: + TALLOC_FREE(frame); + return res; +} + +/* + samr_EnumDomainGroups +*/ +static NTSTATUS dcesrv_samr_EnumDomainGroups(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_EnumDomainGroups *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + struct ldb_message **res; + uint32_t i; + uint32_t count; + uint32_t results; + uint32_t max_entries; + uint32_t remaining_entries; + uint32_t resume_handle; + struct samr_SamEntry *entries; + const char * const attrs[] = { "objectSid", "sAMAccountName", NULL }; + const char * const cache_attrs[] = { "objectSid", "objectGUID", NULL }; + struct samr_SamArray *sam; + struct samr_guid_cache *cache = NULL; + + *r->out.resume_handle = 0; + *r->out.sam = NULL; + *r->out.num_entries = 0; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + cache = &d_state->guid_caches[SAMR_ENUM_DOMAIN_GROUPS_CACHE]; + + /* + * If the resume_handle is zero, query the database and cache the + * matching GUID's + */ + if (*r->in.resume_handle == 0) { + NTSTATUS status; + int ldb_cnt; + clear_guid_cache(cache); + /* + * search for all domain groups in this domain. + */ + ldb_cnt = samdb_search_domain( + d_state->sam_ctx, + mem_ctx, + d_state->domain_dn, + &res, + cache_attrs, + d_state->domain_sid, + "(&(|(groupType=%d)(groupType=%d))(objectClass=group))", + GTYPE_SECURITY_UNIVERSAL_GROUP, + GTYPE_SECURITY_GLOBAL_GROUP); + if (ldb_cnt < 0) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + /* + * Sort the results into RID order, while the spec states there + * is no order, Windows appears to sort the results by RID and + * so it is possible that there are clients that depend on + * this ordering + */ + TYPESAFE_QSORT(res, ldb_cnt, compare_msgRid); + + /* + * cache the sorted GUID's + */ + status = load_guid_cache(cache, d_state, ldb_cnt, res); + TALLOC_FREE(res); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + cache->handle = 0; + } + + + /* + * If the resume handle is out of range we return an empty response + * and invalidate the cache. + * + * From the specification: + * Servers SHOULD validate that EnumerationContext is an expected + * value for the server's implementation. Windows does NOT validate + * the input, though the result of malformed information merely results + * in inconsistent output to the client. + */ + if (*r->in.resume_handle >= cache->size) { + clear_guid_cache(cache); + sam = talloc(mem_ctx, struct samr_SamArray); + if (!sam) { + return NT_STATUS_NO_MEMORY; + } + sam->entries = NULL; + sam->count = 0; + + *r->out.sam = sam; + *r->out.resume_handle = 0; + return NT_STATUS_OK; + } + + + /* + * Calculate the number of entries to return limit by max_size. + * Note that we use the w2k3 element size value of 54 + */ + max_entries = 1 + (r->in.max_size/SAMR_ENUM_USERS_MULTIPLIER); + remaining_entries = cache->size - *r->in.resume_handle; + results = MIN(remaining_entries, max_entries); + + /* + * Process the list of result GUID's. + * Read the details of each object and populate the Entries + * for the current level. + */ + count = 0; + resume_handle = *r->in.resume_handle; + entries = talloc_array(mem_ctx, struct samr_SamEntry, results); + if (entries == NULL) { + clear_guid_cache(cache); + return NT_STATUS_NO_MEMORY; + } + for (i = 0; i < results; i++) { + struct dom_sid *objectsid; + uint32_t rid; + struct ldb_result *rec; + const uint32_t idx = *r->in.resume_handle + i; + int ret; + NTSTATUS status; + const char *name = NULL; + resume_handle++; + /* + * Read an object from disk using the GUID as the key + * + * If the object can not be read, or it does not have a SID + * it is ignored. + * + * As a consequence of this, if all the remaining GUID's + * have been deleted an empty result will be returned. + * i.e. even if the previous call returned a non zero + * resume_handle it is possible for no results to be returned. + * + */ + ret = dsdb_search_by_dn_guid(d_state->sam_ctx, + mem_ctx, + &rec, + &cache->entries[idx], + attrs, + 0); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + struct GUID_txt_buf guid_buf; + DBG_WARNING( + "GUID [%s] not found\n", + GUID_buf_string(&cache->entries[idx], &guid_buf)); + continue; + } else if (ret != LDB_SUCCESS) { + clear_guid_cache(cache); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + objectsid = samdb_result_dom_sid(mem_ctx, + rec->msgs[0], + "objectSID"); + if (objectsid == NULL) { + struct GUID_txt_buf guid_buf; + DBG_WARNING( + "objectSID for GUID [%s] not found\n", + GUID_buf_string(&cache->entries[idx], &guid_buf)); + continue; + } + status = dom_sid_split_rid(NULL, + objectsid, + NULL, + &rid); + if (!NT_STATUS_IS_OK(status)) { + struct dom_sid_buf sid_buf; + struct GUID_txt_buf guid_buf; + DBG_WARNING( + "objectSID [%s] for GUID [%s] invalid\n", + dom_sid_str_buf(objectsid, &sid_buf), + GUID_buf_string(&cache->entries[idx], &guid_buf)); + continue; + } + + entries[count].idx = rid; + name = ldb_msg_find_attr_as_string( + rec->msgs[0], "sAMAccountName", ""); + entries[count].name.string = talloc_strdup(entries, name); + count++; + } + + sam = talloc(mem_ctx, struct samr_SamArray); + if (!sam) { + clear_guid_cache(cache); + return NT_STATUS_NO_MEMORY; + } + + sam->entries = entries; + sam->count = count; + + *r->out.sam = sam; + *r->out.resume_handle = resume_handle; + *r->out.num_entries = count; + + /* + * Signal no more results by returning zero resume handle, + * the cache is also cleared at this point + */ + if (*r->out.resume_handle >= cache->size) { + *r->out.resume_handle = 0; + clear_guid_cache(cache); + return NT_STATUS_OK; + } + /* + * There are more results to be returned. + */ + return STATUS_MORE_ENTRIES; +} + + +/* + samr_CreateUser2 + + This call uses transactions to ensure we don't get a new conflicting + user while we are processing this, and to ensure the user either + completly exists, or does not. +*/ +static NTSTATUS dcesrv_samr_CreateUser2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_CreateUser2 *r) +{ + NTSTATUS status; + struct samr_domain_state *d_state; + struct samr_account_state *a_state; + struct dcesrv_handle *h; + struct ldb_dn *dn; + struct dom_sid *sid; + struct dcesrv_handle *u_handle; + const char *account_name; + + ZERO_STRUCTP(r->out.user_handle); + *r->out.access_granted = 0; + *r->out.rid = 0; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + if (d_state->builtin) { + DEBUG(5, ("Cannot create a user in the BUILTIN domain")); + return NT_STATUS_ACCESS_DENIED; + } else if (r->in.acct_flags == ACB_DOMTRUST) { + /* Domain trust accounts must be created by the LSA calls */ + return NT_STATUS_ACCESS_DENIED; + } + account_name = r->in.account_name->string; + + if (account_name == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = dsdb_add_user(d_state->sam_ctx, mem_ctx, account_name, r->in.acct_flags, NULL, + &sid, &dn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + a_state = talloc(mem_ctx, struct samr_account_state); + if (!a_state) { + return NT_STATUS_NO_MEMORY; + } + a_state->sam_ctx = d_state->sam_ctx; + a_state->access_mask = r->in.access_mask; + a_state->domain_state = talloc_reference(a_state, d_state); + a_state->account_dn = talloc_steal(a_state, dn); + + a_state->account_name = talloc_steal(a_state, account_name); + if (!a_state->account_name) { + return NT_STATUS_NO_MEMORY; + } + + /* create the policy handle */ + u_handle = dcesrv_handle_create(dce_call, SAMR_HANDLE_USER); + if (!u_handle) { + return NT_STATUS_NO_MEMORY; + } + + u_handle->data = talloc_steal(u_handle, a_state); + + *r->out.user_handle = u_handle->wire_handle; + *r->out.access_granted = 0xf07ff; /* TODO: fix access mask calculations */ + + *r->out.rid = sid->sub_auths[sid->num_auths-1]; + + return NT_STATUS_OK; +} + + +/* + samr_CreateUser +*/ +static NTSTATUS dcesrv_samr_CreateUser(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_CreateUser *r) +{ + struct samr_CreateUser2 r2; + uint32_t access_granted = 0; + + + /* a simple wrapper around samr_CreateUser2 works nicely */ + + r2 = (struct samr_CreateUser2) { + .in.domain_handle = r->in.domain_handle, + .in.account_name = r->in.account_name, + .in.acct_flags = ACB_NORMAL, + .in.access_mask = r->in.access_mask, + .out.user_handle = r->out.user_handle, + .out.access_granted = &access_granted, + .out.rid = r->out.rid + }; + + return dcesrv_samr_CreateUser2(dce_call, mem_ctx, &r2); +} + +struct enum_dom_users_ctx { + struct samr_SamEntry *entries; + uint32_t num_entries; + uint32_t acct_flags; + struct dom_sid *domain_sid; +}; + +static int user_iterate_callback(struct ldb_request *req, + struct ldb_reply *ares); + +/* + * Iterate users and add all those that match a domain SID and pass an acct + * flags check to an array of SamEntry objects. + */ +static int user_iterate_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct enum_dom_users_ctx *ac =\ + talloc_get_type(req->context, struct enum_dom_users_ctx); + int ret = LDB_ERR_OPERATIONS_ERROR; + + if (!ares) { + return ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_request_done(req, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + { + struct ldb_message *msg = ares->message; + const struct ldb_val *val; + struct samr_SamEntry *ent; + struct dom_sid objectsid; + uint32_t rid; + size_t entries_array_len = 0; + NTSTATUS status; + ssize_t sid_size; + + if (ac->acct_flags && ((samdb_result_acct_flags(msg, NULL) & + ac->acct_flags) == 0)) { + ret = LDB_SUCCESS; + break; + } + + val = ldb_msg_find_ldb_val(msg, "objectSID"); + if (val == NULL) { + DBG_WARNING("objectSID for DN %s not found\n", + ldb_dn_get_linearized(msg->dn)); + ret = ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + break; + } + + sid_size = sid_parse(val->data, val->length, &objectsid); + if (sid_size == -1) { + struct dom_sid_buf sid_buf; + DBG_WARNING("objectsid [%s] for DN [%s] invalid\n", + dom_sid_str_buf(&objectsid, &sid_buf), + ldb_dn_get_linearized(msg->dn)); + ret = ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + break; + } + + if (!dom_sid_in_domain(ac->domain_sid, &objectsid)) { + /* Ignore if user isn't in the domain */ + ret = LDB_SUCCESS; + break; + } + + status = dom_sid_split_rid(ares, &objectsid, NULL, &rid); + if (!NT_STATUS_IS_OK(status)) { + struct dom_sid_buf sid_buf; + DBG_WARNING("Couldn't split RID from " + "SID [%s] of DN [%s]\n", + dom_sid_str_buf(&objectsid, &sid_buf), + ldb_dn_get_linearized(msg->dn)); + ret = ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + break; + } + + entries_array_len = talloc_array_length(ac->entries); + if (ac->num_entries >= entries_array_len) { + if (entries_array_len * 2 < entries_array_len) { + ret = ldb_request_done(req, + LDB_ERR_OPERATIONS_ERROR); + break; + } + ac->entries = talloc_realloc(ac, + ac->entries, + struct samr_SamEntry, + entries_array_len * 2); + if (ac->entries == NULL) { + ret = ldb_request_done(req, + LDB_ERR_OPERATIONS_ERROR); + break; + } + } + + ent = &(ac->entries[ac->num_entries++]); + val = ldb_msg_find_ldb_val(msg, "samaccountname"); + if (val == NULL) { + DBG_WARNING("samaccountname attribute not found\n"); + ret = ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + break; + } + ent->name.string = talloc_steal(ac->entries, + (char *)val->data); + ent->idx = rid; + ret = LDB_SUCCESS; + break; + } + case LDB_REPLY_DONE: + { + if (ac->num_entries != 0 && + ac->num_entries != talloc_array_length(ac->entries)) { + ac->entries = talloc_realloc(ac, + ac->entries, + struct samr_SamEntry, + ac->num_entries); + if (ac->entries == NULL) { + ret = ldb_request_done(req, + LDB_ERR_OPERATIONS_ERROR); + break; + } + } + ret = ldb_request_done(req, LDB_SUCCESS); + break; + } + case LDB_REPLY_REFERRAL: + { + ret = LDB_SUCCESS; + break; + } + default: + /* Doesn't happen */ + ret = LDB_ERR_OPERATIONS_ERROR; + } + TALLOC_FREE(ares); + + return ret; +} + +/* + * samr_EnumDomainUsers + * The previous implementation did an initial search and stored a list of + * matching GUIDs on the connection handle's domain state, then did direct + * GUID lookups for each record in a page indexed by resume_handle. That + * approach was memory efficient, requiring only 16 bytes per record, but + * was too slow for winbind which needs this RPC call for getpwent. + * + * Now we use an iterate pattern to populate a cached list of the rids and + * names for each record. This improves runtime performance but requires + * about 200 bytes per record which will mean for a 100k database we use + * about 2MB, which is fine. The speedup achieved by this new approach is + * around 50%. + */ +static NTSTATUS dcesrv_samr_EnumDomainUsers(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_EnumDomainUsers *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + uint32_t results; + uint32_t max_entries; + uint32_t num_entries; + uint32_t remaining_entries; + struct samr_SamEntry *entries; + const char * const attrs[] = { "objectSid", "sAMAccountName", + "userAccountControl", NULL }; + struct samr_SamArray *sam; + struct ldb_request *req; + + *r->out.resume_handle = 0; + *r->out.sam = NULL; + *r->out.num_entries = 0; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + entries = d_state->domain_users_cached; + + /* + * If the resume_handle is zero, query the database and cache the + * matching entries. + */ + if (*r->in.resume_handle == 0) { + int ret; + struct enum_dom_users_ctx *ac; + if (entries != NULL) { + talloc_free(entries); + d_state->domain_users_cached = NULL; + } + + ac = talloc(mem_ctx, struct enum_dom_users_ctx); + ac->num_entries = 0; + ac->domain_sid = d_state->domain_sid; + ac->entries = talloc_array(ac, + struct samr_SamEntry, + 100); + if (ac->entries == NULL) { + talloc_free(ac); + return NT_STATUS_NO_MEMORY; + } + ac->acct_flags = r->in.acct_flags; + + ret = ldb_build_search_req(&req, + d_state->sam_ctx, + mem_ctx, + d_state->domain_dn, + LDB_SCOPE_SUBTREE, + "(objectClass=user)", + attrs, + NULL, + ac, + user_iterate_callback, + NULL); + if (ret != LDB_SUCCESS) { + talloc_free(ac); + return dsdb_ldb_err_to_ntstatus(ret); + } + + ret = ldb_request(d_state->sam_ctx, req); + if (ret != LDB_SUCCESS) { + talloc_free(ac); + return dsdb_ldb_err_to_ntstatus(ret); + } + + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + if (ac->num_entries == 0) { + DBG_WARNING("No users in domain %s", + ldb_dn_get_linearized(d_state->domain_dn)); + talloc_free(ac); + return NT_STATUS_OK; + } + + entries = talloc_steal(d_state, ac->entries); + d_state->domain_users_cached = entries; + num_entries = ac->num_entries; + talloc_free(ac); + + /* + * Sort the entries into RID order, while the spec states there + * is no order, Windows appears to sort the results by RID and + * so it is possible that there are clients that depend on + * this ordering + */ + TYPESAFE_QSORT(entries, num_entries, compare_SamEntry); + } else { + num_entries = talloc_array_length(entries); + } + + /* + * If the resume handle is out of range we return an empty response + * and invalidate the cache. + * + * From the specification: + * Servers SHOULD validate that EnumerationContext is an expected + * value for the server's implementation. Windows does NOT validate + * the input, though the result of malformed information merely results + * in inconsistent output to the client. + */ + if (*r->in.resume_handle >= num_entries) { + talloc_free(entries); + d_state->domain_users_cached = NULL; + sam = talloc(mem_ctx, struct samr_SamArray); + if (!sam) { + return NT_STATUS_NO_MEMORY; + } + sam->entries = NULL; + sam->count = 0; + + *r->out.sam = sam; + *r->out.resume_handle = 0; + return NT_STATUS_OK; + } + + /* + * Calculate the number of entries to return limit by max_size. + * Note that we use the w2k3 element size value of 54 + */ + max_entries = 1 + (r->in.max_size / SAMR_ENUM_USERS_MULTIPLIER); + remaining_entries = num_entries - *r->in.resume_handle; + results = MIN(remaining_entries, max_entries); + + sam = talloc(mem_ctx, struct samr_SamArray); + if (!sam) { + d_state->domain_users_cached = NULL; + return NT_STATUS_NO_MEMORY; + } + + sam->entries = entries + *r->in.resume_handle; + sam->count = results; + + *r->out.sam = sam; + *r->out.resume_handle = *r->in.resume_handle + results; + *r->out.num_entries = results; + + /* + * Signal no more results by returning zero resume handle, + * the cache is also cleared at this point + */ + if (*r->out.resume_handle >= num_entries) { + *r->out.resume_handle = 0; + return NT_STATUS_OK; + } + /* + * There are more results to be returned. + */ + return STATUS_MORE_ENTRIES; +} + + +/* + samr_CreateDomAlias +*/ +static NTSTATUS dcesrv_samr_CreateDomAlias(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_CreateDomAlias *r) +{ + struct samr_domain_state *d_state; + struct samr_account_state *a_state; + struct dcesrv_handle *h; + const char *alias_name; + struct dom_sid *sid; + struct dcesrv_handle *a_handle; + struct ldb_dn *dn; + NTSTATUS status; + + ZERO_STRUCTP(r->out.alias_handle); + *r->out.rid = 0; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + if (d_state->builtin) { + DEBUG(5, ("Cannot create a domain alias in the BUILTIN domain")); + return NT_STATUS_ACCESS_DENIED; + } + + alias_name = r->in.alias_name->string; + + if (alias_name == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = dsdb_add_domain_alias(d_state->sam_ctx, mem_ctx, alias_name, &sid, &dn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + a_state = talloc(mem_ctx, struct samr_account_state); + if (!a_state) { + return NT_STATUS_NO_MEMORY; + } + + a_state->sam_ctx = d_state->sam_ctx; + a_state->access_mask = r->in.access_mask; + a_state->domain_state = talloc_reference(a_state, d_state); + a_state->account_dn = talloc_steal(a_state, dn); + + a_state->account_name = talloc_steal(a_state, alias_name); + + /* create the policy handle */ + a_handle = dcesrv_handle_create(dce_call, SAMR_HANDLE_ALIAS); + if (a_handle == NULL) + return NT_STATUS_NO_MEMORY; + + a_handle->data = talloc_steal(a_handle, a_state); + + *r->out.alias_handle = a_handle->wire_handle; + + *r->out.rid = sid->sub_auths[sid->num_auths-1]; + + return NT_STATUS_OK; +} + + +/* + samr_EnumDomainAliases +*/ +static NTSTATUS dcesrv_samr_EnumDomainAliases(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_EnumDomainAliases *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + struct ldb_message **res; + int i, ldb_cnt; + uint32_t first, count; + struct samr_SamEntry *entries; + const char * const attrs[] = { "objectSid", "sAMAccountName", NULL }; + struct samr_SamArray *sam; + + *r->out.resume_handle = 0; + *r->out.sam = NULL; + *r->out.num_entries = 0; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + /* search for all domain aliases in this domain. This could possibly be + cached and resumed based on resume_key */ + ldb_cnt = samdb_search_domain(d_state->sam_ctx, mem_ctx, NULL, + &res, attrs, + d_state->domain_sid, + "(&(|(grouptype=%d)(grouptype=%d)))" + "(objectclass=group))", + GTYPE_SECURITY_BUILTIN_LOCAL_GROUP, + GTYPE_SECURITY_DOMAIN_LOCAL_GROUP); + if (ldb_cnt < 0) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* convert to SamEntry format */ + entries = talloc_array(mem_ctx, struct samr_SamEntry, ldb_cnt); + if (!entries) { + return NT_STATUS_NO_MEMORY; + } + + count = 0; + + for (i=0;i<ldb_cnt;i++) { + struct dom_sid *alias_sid; + + alias_sid = samdb_result_dom_sid(mem_ctx, res[i], + "objectSid"); + + if (alias_sid == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + entries[count].idx = + alias_sid->sub_auths[alias_sid->num_auths-1]; + entries[count].name.string = + ldb_msg_find_attr_as_string(res[i], "sAMAccountName", ""); + count += 1; + } + + /* sort the results by rid */ + TYPESAFE_QSORT(entries, count, compare_SamEntry); + + /* find the first entry to return */ + for (first=0; + first<count && entries[first].idx <= *r->in.resume_handle; + first++) ; + + /* return the rest, limit by max_size. Note that we + use the w2k3 element size value of 54 */ + *r->out.num_entries = count - first; + *r->out.num_entries = MIN(*r->out.num_entries, + 1+(r->in.max_size/SAMR_ENUM_USERS_MULTIPLIER)); + + sam = talloc(mem_ctx, struct samr_SamArray); + if (!sam) { + return NT_STATUS_NO_MEMORY; + } + + sam->entries = entries+first; + sam->count = *r->out.num_entries; + + *r->out.sam = sam; + + if (first == count) { + return NT_STATUS_OK; + } + + if (*r->out.num_entries < count - first) { + *r->out.resume_handle = + entries[first+*r->out.num_entries-1].idx; + return STATUS_MORE_ENTRIES; + } + + return NT_STATUS_OK; +} + + +/* + samr_GetAliasMembership +*/ +static NTSTATUS dcesrv_samr_GetAliasMembership(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetAliasMembership *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + char *filter; + const char * const attrs[] = { "objectSid", NULL }; + struct ldb_message **res; + uint32_t i; + int count = 0; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + filter = talloc_asprintf(mem_ctx, + "(&(|(grouptype=%d)(grouptype=%d))" + "(objectclass=group)(|", + GTYPE_SECURITY_BUILTIN_LOCAL_GROUP, + GTYPE_SECURITY_DOMAIN_LOCAL_GROUP); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<r->in.sids->num_sids; i++) { + struct dom_sid_buf buf; + + filter = talloc_asprintf_append( + filter, + "(member=<SID=%s>)", + dom_sid_str_buf(r->in.sids->sids[i].sid, &buf)); + + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + /* Find out if we had at least one valid member SID passed - otherwise + * just skip the search. */ + if (strstr(filter, "member") != NULL) { + count = samdb_search_domain(d_state->sam_ctx, mem_ctx, NULL, + &res, attrs, d_state->domain_sid, + "%s))", filter); + if (count < 0) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } + + r->out.rids->count = 0; + r->out.rids->ids = talloc_array(mem_ctx, uint32_t, count); + if (r->out.rids->ids == NULL) + return NT_STATUS_NO_MEMORY; + + for (i=0; i<count; i++) { + struct dom_sid *alias_sid; + + alias_sid = samdb_result_dom_sid(mem_ctx, res[i], "objectSid"); + if (alias_sid == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + r->out.rids->ids[r->out.rids->count] = + alias_sid->sub_auths[alias_sid->num_auths-1]; + r->out.rids->count += 1; + } + + return NT_STATUS_OK; +} + + +/* + samr_LookupNames +*/ +static NTSTATUS dcesrv_samr_LookupNames(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_LookupNames *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + uint32_t i, num_mapped; + NTSTATUS status = NT_STATUS_OK; + const char * const attrs[] = { "sAMAccountType", "objectSid", NULL }; + int count; + + ZERO_STRUCTP(r->out.rids); + ZERO_STRUCTP(r->out.types); + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + if (r->in.num_names == 0) { + return NT_STATUS_OK; + } + + r->out.rids->ids = talloc_array(mem_ctx, uint32_t, r->in.num_names); + r->out.types->ids = talloc_array(mem_ctx, uint32_t, r->in.num_names); + if (!r->out.rids->ids || !r->out.types->ids) { + return NT_STATUS_NO_MEMORY; + } + r->out.rids->count = r->in.num_names; + r->out.types->count = r->in.num_names; + + num_mapped = 0; + + for (i=0;i<r->in.num_names;i++) { + struct ldb_message **res; + struct dom_sid *sid; + uint32_t atype, rtype; + + r->out.rids->ids[i] = 0; + r->out.types->ids[i] = SID_NAME_UNKNOWN; + + count = gendb_search(d_state->sam_ctx, mem_ctx, d_state->domain_dn, &res, attrs, + "sAMAccountName=%s", + ldb_binary_encode_string(mem_ctx, r->in.names[i].string)); + if (count != 1) { + status = STATUS_SOME_UNMAPPED; + continue; + } + + sid = samdb_result_dom_sid(mem_ctx, res[0], "objectSid"); + if (sid == NULL) { + status = STATUS_SOME_UNMAPPED; + continue; + } + + atype = ldb_msg_find_attr_as_uint(res[0], "sAMAccountType", 0); + if (atype == 0) { + status = STATUS_SOME_UNMAPPED; + continue; + } + + rtype = ds_atype_map(atype); + + if (rtype == SID_NAME_UNKNOWN) { + status = STATUS_SOME_UNMAPPED; + continue; + } + + r->out.rids->ids[i] = sid->sub_auths[sid->num_auths-1]; + r->out.types->ids[i] = rtype; + num_mapped++; + } + + if (num_mapped == 0) { + return NT_STATUS_NONE_MAPPED; + } + return status; +} + + +/* + samr_LookupRids +*/ +static NTSTATUS dcesrv_samr_LookupRids(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_LookupRids *r) +{ + NTSTATUS status; + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + const char **names; + struct lsa_String *lsa_names; + enum lsa_SidType *ids; + + ZERO_STRUCTP(r->out.names); + ZERO_STRUCTP(r->out.types); + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + if (r->in.num_rids == 0) + return NT_STATUS_OK; + + lsa_names = talloc_zero_array(mem_ctx, struct lsa_String, r->in.num_rids); + names = talloc_zero_array(mem_ctx, const char *, r->in.num_rids); + ids = talloc_zero_array(mem_ctx, enum lsa_SidType, r->in.num_rids); + + if ((lsa_names == NULL) || (names == NULL) || (ids == NULL)) + return NT_STATUS_NO_MEMORY; + + r->out.names->names = lsa_names; + r->out.names->count = r->in.num_rids; + + r->out.types->ids = (uint32_t *) ids; + r->out.types->count = r->in.num_rids; + + status = dsdb_lookup_rids(d_state->sam_ctx, mem_ctx, d_state->domain_sid, + r->in.num_rids, r->in.rids, names, ids); + if (NT_STATUS_IS_OK(status) || NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED) || NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED)) { + uint32_t i; + for (i = 0; i < r->in.num_rids; i++) { + lsa_names[i].string = names[i]; + } + } + return status; +} + + +/* + samr_OpenGroup +*/ +static NTSTATUS dcesrv_samr_OpenGroup(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_OpenGroup *r) +{ + struct samr_domain_state *d_state; + struct samr_account_state *a_state; + struct dcesrv_handle *h; + const char *groupname; + struct dom_sid *sid; + struct ldb_message **msgs; + struct dcesrv_handle *g_handle; + const char * const attrs[2] = { "sAMAccountName", NULL }; + int ret; + + ZERO_STRUCTP(r->out.group_handle); + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + /* form the group SID */ + sid = dom_sid_add_rid(mem_ctx, d_state->domain_sid, r->in.rid); + if (!sid) { + return NT_STATUS_NO_MEMORY; + } + + /* search for the group record */ + if (d_state->builtin) { + ret = gendb_search(d_state->sam_ctx, + mem_ctx, d_state->domain_dn, &msgs, attrs, + "(&(objectSid=%s)(objectClass=group)" + "(groupType=%d))", + ldap_encode_ndr_dom_sid(mem_ctx, sid), + GTYPE_SECURITY_BUILTIN_LOCAL_GROUP); + } else { + ret = gendb_search(d_state->sam_ctx, + mem_ctx, d_state->domain_dn, &msgs, attrs, + "(&(objectSid=%s)(objectClass=group)" + "(|(groupType=%d)(groupType=%d)))", + ldap_encode_ndr_dom_sid(mem_ctx, sid), + GTYPE_SECURITY_UNIVERSAL_GROUP, + GTYPE_SECURITY_GLOBAL_GROUP); + } + if (ret == 0) { + return NT_STATUS_NO_SUCH_GROUP; + } + if (ret != 1) { + DEBUG(0,("Found %d records matching sid %s\n", + ret, dom_sid_string(mem_ctx, sid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + groupname = ldb_msg_find_attr_as_string(msgs[0], "sAMAccountName", NULL); + if (groupname == NULL) { + DEBUG(0,("sAMAccountName field missing for sid %s\n", + dom_sid_string(mem_ctx, sid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + a_state = talloc(mem_ctx, struct samr_account_state); + if (!a_state) { + return NT_STATUS_NO_MEMORY; + } + a_state->sam_ctx = d_state->sam_ctx; + a_state->access_mask = r->in.access_mask; + a_state->domain_state = talloc_reference(a_state, d_state); + a_state->account_dn = talloc_steal(a_state, msgs[0]->dn); + a_state->account_sid = talloc_steal(a_state, sid); + a_state->account_name = talloc_strdup(a_state, groupname); + if (!a_state->account_name) { + return NT_STATUS_NO_MEMORY; + } + + /* create the policy handle */ + g_handle = dcesrv_handle_create(dce_call, SAMR_HANDLE_GROUP); + if (!g_handle) { + return NT_STATUS_NO_MEMORY; + } + + g_handle->data = talloc_steal(g_handle, a_state); + + *r->out.group_handle = g_handle->wire_handle; + + return NT_STATUS_OK; +} + +/* + samr_QueryGroupInfo +*/ +static NTSTATUS dcesrv_samr_QueryGroupInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryGroupInfo *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct ldb_message *msg, **res; + const char * const attrs[4] = { "sAMAccountName", "description", + "numMembers", NULL }; + int ret; + union samr_GroupInfo *info; + + *r->out.info = NULL; + + DCESRV_PULL_HANDLE(h, r->in.group_handle, SAMR_HANDLE_GROUP); + + a_state = h->data; + + /* pull all the group attributes */ + ret = gendb_search_dn(a_state->sam_ctx, mem_ctx, + a_state->account_dn, &res, attrs); + if (ret == 0) { + return NT_STATUS_NO_SUCH_GROUP; + } + if (ret != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + msg = res[0]; + + /* allocate the info structure */ + info = talloc_zero(mem_ctx, union samr_GroupInfo); + if (info == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* Fill in the level */ + switch (r->in.level) { + case GROUPINFOALL: + QUERY_STRING(msg, all.name, "sAMAccountName"); + info->all.attributes = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; /* Do like w2k3 */ + QUERY_UINT (msg, all.num_members, "numMembers") + QUERY_STRING(msg, all.description, "description"); + break; + case GROUPINFONAME: + QUERY_STRING(msg, name, "sAMAccountName"); + break; + case GROUPINFOATTRIBUTES: + info->attributes.attributes = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; /* Do like w2k3 */ + break; + case GROUPINFODESCRIPTION: + QUERY_STRING(msg, description, "description"); + break; + case GROUPINFOALL2: + QUERY_STRING(msg, all2.name, "sAMAccountName"); + info->all.attributes = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; /* Do like w2k3 */ + QUERY_UINT (msg, all2.num_members, "numMembers") + QUERY_STRING(msg, all2.description, "description"); + break; + default: + talloc_free(info); + return NT_STATUS_INVALID_INFO_CLASS; + } + + *r->out.info = info; + + return NT_STATUS_OK; +} + + +/* + samr_SetGroupInfo +*/ +static NTSTATUS dcesrv_samr_SetGroupInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetGroupInfo *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *g_state; + struct ldb_message *msg; + int ret; + + DCESRV_PULL_HANDLE(h, r->in.group_handle, SAMR_HANDLE_GROUP); + + g_state = h->data; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + msg->dn = ldb_dn_copy(mem_ctx, g_state->account_dn); + if (!msg->dn) { + return NT_STATUS_NO_MEMORY; + } + + switch (r->in.level) { + case GROUPINFODESCRIPTION: + SET_STRING(msg, description, "description"); + break; + case GROUPINFONAME: + /* On W2k3 this does not change the name, it changes the + * sAMAccountName attribute */ + SET_STRING(msg, name, "sAMAccountName"); + break; + case GROUPINFOATTRIBUTES: + /* This does not do anything obviously visible in W2k3 LDAP */ + return NT_STATUS_OK; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + /* modify the samdb record */ + ret = ldb_modify(g_state->sam_ctx, msg); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + return NT_STATUS_OK; +} + + +/* + samr_AddGroupMember +*/ +static NTSTATUS dcesrv_samr_AddGroupMember(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_AddGroupMember *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct samr_domain_state *d_state; + struct ldb_message *mod; + struct dom_sid *membersid; + const char *memberdn; + struct ldb_result *res; + const char * const attrs[] = { NULL }; + int ret; + + DCESRV_PULL_HANDLE(h, r->in.group_handle, SAMR_HANDLE_GROUP); + + a_state = h->data; + d_state = a_state->domain_state; + + membersid = dom_sid_add_rid(mem_ctx, d_state->domain_sid, r->in.rid); + if (membersid == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* according to MS-SAMR 3.1.5.8.2 all type of accounts are accepted */ + ret = ldb_search(d_state->sam_ctx, mem_ctx, &res, + d_state->domain_dn, LDB_SCOPE_SUBTREE, attrs, + "(objectSid=%s)", + ldap_encode_ndr_dom_sid(mem_ctx, membersid)); + + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (res->count == 0) { + return NT_STATUS_NO_SUCH_USER; + } + + if (res->count > 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + memberdn = ldb_dn_alloc_linearized(mem_ctx, res->msgs[0]->dn); + + if (memberdn == NULL) + return NT_STATUS_NO_MEMORY; + + mod = ldb_msg_new(mem_ctx); + if (mod == NULL) { + return NT_STATUS_NO_MEMORY; + } + + mod->dn = talloc_reference(mem_ctx, a_state->account_dn); + + ret = samdb_msg_add_addval(d_state->sam_ctx, mem_ctx, mod, "member", + memberdn); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + ret = ldb_modify(a_state->sam_ctx, mod); + switch (ret) { + case LDB_SUCCESS: + return NT_STATUS_OK; + case LDB_ERR_ENTRY_ALREADY_EXISTS: + return NT_STATUS_MEMBER_IN_GROUP; + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + return NT_STATUS_ACCESS_DENIED; + default: + return dsdb_ldb_err_to_ntstatus(ret); + } +} + + +/* + samr_DeleteDomainGroup +*/ +static NTSTATUS dcesrv_samr_DeleteDomainGroup(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_DeleteDomainGroup *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + int ret; + + *r->out.group_handle = *r->in.group_handle; + + DCESRV_PULL_HANDLE(h, r->in.group_handle, SAMR_HANDLE_GROUP); + + a_state = h->data; + + ret = ldb_delete(a_state->sam_ctx, a_state->account_dn); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + talloc_free(h); + ZERO_STRUCTP(r->out.group_handle); + + return NT_STATUS_OK; +} + + +/* + samr_DeleteGroupMember +*/ +static NTSTATUS dcesrv_samr_DeleteGroupMember(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_DeleteGroupMember *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct samr_domain_state *d_state; + struct ldb_message *mod; + struct dom_sid *membersid; + const char *memberdn; + struct ldb_result *res; + const char * const attrs[] = { NULL }; + int ret; + + DCESRV_PULL_HANDLE(h, r->in.group_handle, SAMR_HANDLE_GROUP); + + a_state = h->data; + d_state = a_state->domain_state; + + membersid = dom_sid_add_rid(mem_ctx, d_state->domain_sid, r->in.rid); + if (membersid == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* according to MS-SAMR 3.1.5.8.2 all type of accounts are accepted */ + ret = ldb_search(d_state->sam_ctx, mem_ctx, &res, + d_state->domain_dn, LDB_SCOPE_SUBTREE, attrs, + "(objectSid=%s)", + ldap_encode_ndr_dom_sid(mem_ctx, membersid)); + + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (res->count == 0) { + return NT_STATUS_NO_SUCH_USER; + } + + if (res->count > 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + memberdn = ldb_dn_alloc_linearized(mem_ctx, res->msgs[0]->dn); + + if (memberdn == NULL) + return NT_STATUS_NO_MEMORY; + + mod = ldb_msg_new(mem_ctx); + if (mod == NULL) { + return NT_STATUS_NO_MEMORY; + } + + mod->dn = talloc_reference(mem_ctx, a_state->account_dn); + + ret = samdb_msg_add_delval(d_state->sam_ctx, mem_ctx, mod, "member", + memberdn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_modify(a_state->sam_ctx, mod); + switch (ret) { + case LDB_SUCCESS: + return NT_STATUS_OK; + case LDB_ERR_UNWILLING_TO_PERFORM: + return NT_STATUS_MEMBER_NOT_IN_GROUP; + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + return NT_STATUS_ACCESS_DENIED; + default: + return dsdb_ldb_err_to_ntstatus(ret); + } +} + + +/* + samr_QueryGroupMember +*/ +static NTSTATUS dcesrv_samr_QueryGroupMember(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryGroupMember *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct samr_domain_state *d_state; + struct samr_RidAttrArray *array; + unsigned int i, num_members; + struct dom_sid *members; + NTSTATUS status; + + DCESRV_PULL_HANDLE(h, r->in.group_handle, SAMR_HANDLE_GROUP); + + a_state = h->data; + d_state = a_state->domain_state; + + status = dsdb_enum_group_mem(d_state->sam_ctx, mem_ctx, + a_state->account_dn, &members, + &num_members); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + array = talloc_zero(mem_ctx, struct samr_RidAttrArray); + if (array == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (num_members == 0) { + *r->out.rids = array; + + return NT_STATUS_OK; + } + + array->rids = talloc_array(array, uint32_t, num_members); + if (array->rids == NULL) { + return NT_STATUS_NO_MEMORY; + } + + array->attributes = talloc_array(array, uint32_t, num_members); + if (array->attributes == NULL) { + return NT_STATUS_NO_MEMORY; + } + + array->count = 0; + for (i=0; i<num_members; i++) { + if (!dom_sid_in_domain(d_state->domain_sid, &members[i])) { + continue; + } + + status = dom_sid_split_rid(NULL, &members[i], NULL, + &array->rids[array->count]); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + array->attributes[array->count] = SE_GROUP_MANDATORY | + SE_GROUP_ENABLED_BY_DEFAULT | + SE_GROUP_ENABLED; + array->count++; + } + + *r->out.rids = array; + + return NT_STATUS_OK; +} + + +/* + samr_SetMemberAttributesOfGroup +*/ +static NTSTATUS dcesrv_samr_SetMemberAttributesOfGroup(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetMemberAttributesOfGroup *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_OpenAlias +*/ +static NTSTATUS dcesrv_samr_OpenAlias(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_OpenAlias *r) +{ + struct samr_domain_state *d_state; + struct samr_account_state *a_state; + struct dcesrv_handle *h; + const char *alias_name; + struct dom_sid *sid; + struct ldb_message **msgs; + struct dcesrv_handle *g_handle; + const char * const attrs[2] = { "sAMAccountName", NULL }; + int ret; + + ZERO_STRUCTP(r->out.alias_handle); + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + /* form the alias SID */ + sid = dom_sid_add_rid(mem_ctx, d_state->domain_sid, r->in.rid); + if (sid == NULL) + return NT_STATUS_NO_MEMORY; + + /* search for the group record */ + ret = gendb_search(d_state->sam_ctx, mem_ctx, NULL, &msgs, attrs, + "(&(objectSid=%s)(objectclass=group)" + "(|(grouptype=%d)(grouptype=%d)))", + ldap_encode_ndr_dom_sid(mem_ctx, sid), + GTYPE_SECURITY_BUILTIN_LOCAL_GROUP, + GTYPE_SECURITY_DOMAIN_LOCAL_GROUP); + if (ret == 0) { + return NT_STATUS_NO_SUCH_ALIAS; + } + if (ret != 1) { + DEBUG(0,("Found %d records matching sid %s\n", + ret, dom_sid_string(mem_ctx, sid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + alias_name = ldb_msg_find_attr_as_string(msgs[0], "sAMAccountName", NULL); + if (alias_name == NULL) { + DEBUG(0,("sAMAccountName field missing for sid %s\n", + dom_sid_string(mem_ctx, sid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + a_state = talloc(mem_ctx, struct samr_account_state); + if (!a_state) { + return NT_STATUS_NO_MEMORY; + } + a_state->sam_ctx = d_state->sam_ctx; + a_state->access_mask = r->in.access_mask; + a_state->domain_state = talloc_reference(a_state, d_state); + a_state->account_dn = talloc_steal(a_state, msgs[0]->dn); + a_state->account_sid = talloc_steal(a_state, sid); + a_state->account_name = talloc_strdup(a_state, alias_name); + if (!a_state->account_name) { + return NT_STATUS_NO_MEMORY; + } + + /* create the policy handle */ + g_handle = dcesrv_handle_create(dce_call, SAMR_HANDLE_ALIAS); + if (!g_handle) { + return NT_STATUS_NO_MEMORY; + } + + g_handle->data = talloc_steal(g_handle, a_state); + + *r->out.alias_handle = g_handle->wire_handle; + + return NT_STATUS_OK; +} + + +/* + samr_QueryAliasInfo +*/ +static NTSTATUS dcesrv_samr_QueryAliasInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryAliasInfo *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct ldb_message *msg, **res; + const char * const attrs[4] = { "sAMAccountName", "description", + "numMembers", NULL }; + int ret; + union samr_AliasInfo *info; + + *r->out.info = NULL; + + DCESRV_PULL_HANDLE(h, r->in.alias_handle, SAMR_HANDLE_ALIAS); + + a_state = h->data; + + /* pull all the alias attributes */ + ret = gendb_search_dn(a_state->sam_ctx, mem_ctx, + a_state->account_dn, &res, attrs); + if (ret == 0) { + return NT_STATUS_NO_SUCH_ALIAS; + } + if (ret != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + msg = res[0]; + + /* allocate the info structure */ + info = talloc_zero(mem_ctx, union samr_AliasInfo); + if (info == NULL) { + return NT_STATUS_NO_MEMORY; + } + + switch(r->in.level) { + case ALIASINFOALL: + QUERY_STRING(msg, all.name, "sAMAccountName"); + QUERY_UINT (msg, all.num_members, "numMembers"); + QUERY_STRING(msg, all.description, "description"); + break; + case ALIASINFONAME: + QUERY_STRING(msg, name, "sAMAccountName"); + break; + case ALIASINFODESCRIPTION: + QUERY_STRING(msg, description, "description"); + break; + default: + talloc_free(info); + return NT_STATUS_INVALID_INFO_CLASS; + } + + *r->out.info = info; + + return NT_STATUS_OK; +} + + +/* + samr_SetAliasInfo +*/ +static NTSTATUS dcesrv_samr_SetAliasInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetAliasInfo *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct ldb_message *msg; + int ret; + + DCESRV_PULL_HANDLE(h, r->in.alias_handle, SAMR_HANDLE_ALIAS); + + a_state = h->data; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + msg->dn = ldb_dn_copy(mem_ctx, a_state->account_dn); + if (!msg->dn) { + return NT_STATUS_NO_MEMORY; + } + + switch (r->in.level) { + case ALIASINFODESCRIPTION: + SET_STRING(msg, description, "description"); + break; + case ALIASINFONAME: + /* On W2k3 this does not change the name, it changes the + * sAMAccountName attribute */ + SET_STRING(msg, name, "sAMAccountName"); + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + /* modify the samdb record */ + ret = ldb_modify(a_state->sam_ctx, msg); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + return NT_STATUS_OK; +} + + +/* + samr_DeleteDomAlias +*/ +static NTSTATUS dcesrv_samr_DeleteDomAlias(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_DeleteDomAlias *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + int ret; + + *r->out.alias_handle = *r->in.alias_handle; + + DCESRV_PULL_HANDLE(h, r->in.alias_handle, SAMR_HANDLE_ALIAS); + + a_state = h->data; + + ret = ldb_delete(a_state->sam_ctx, a_state->account_dn); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + talloc_free(h); + ZERO_STRUCTP(r->out.alias_handle); + + return NT_STATUS_OK; +} + + +/* + samr_AddAliasMember +*/ +static NTSTATUS dcesrv_samr_AddAliasMember(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_AddAliasMember *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct samr_domain_state *d_state; + struct ldb_message *mod; + struct ldb_message **msgs; + const char * const attrs[] = { NULL }; + struct ldb_dn *memberdn = NULL; + int ret; + NTSTATUS status; + + DCESRV_PULL_HANDLE(h, r->in.alias_handle, SAMR_HANDLE_ALIAS); + + a_state = h->data; + d_state = a_state->domain_state; + + ret = gendb_search(d_state->sam_ctx, mem_ctx, NULL, + &msgs, attrs, "(objectsid=%s)", + ldap_encode_ndr_dom_sid(mem_ctx, r->in.sid)); + + if (ret == 1) { + memberdn = msgs[0]->dn; + } else if (ret == 0) { + status = samdb_create_foreign_security_principal( + d_state->sam_ctx, mem_ctx, r->in.sid, &memberdn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } else { + DEBUG(0,("Found %d records matching sid %s\n", + ret, dom_sid_string(mem_ctx, r->in.sid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (memberdn == NULL) { + DEBUG(0, ("Could not find memberdn\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + mod = ldb_msg_new(mem_ctx); + if (mod == NULL) { + return NT_STATUS_NO_MEMORY; + } + + mod->dn = talloc_reference(mem_ctx, a_state->account_dn); + + ret = samdb_msg_add_addval(d_state->sam_ctx, mem_ctx, mod, "member", + ldb_dn_alloc_linearized(mem_ctx, memberdn)); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + ret = ldb_modify(a_state->sam_ctx, mod); + switch (ret) { + case LDB_SUCCESS: + return NT_STATUS_OK; + case LDB_ERR_ENTRY_ALREADY_EXISTS: + return NT_STATUS_MEMBER_IN_GROUP; + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + return NT_STATUS_ACCESS_DENIED; + default: + return dsdb_ldb_err_to_ntstatus(ret); + } +} + + +/* + samr_DeleteAliasMember +*/ +static NTSTATUS dcesrv_samr_DeleteAliasMember(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_DeleteAliasMember *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct samr_domain_state *d_state; + struct ldb_message *mod; + const char *memberdn; + int ret; + + DCESRV_PULL_HANDLE(h, r->in.alias_handle, SAMR_HANDLE_ALIAS); + + a_state = h->data; + d_state = a_state->domain_state; + + memberdn = samdb_search_string(d_state->sam_ctx, mem_ctx, NULL, + "distinguishedName", "(objectSid=%s)", + ldap_encode_ndr_dom_sid(mem_ctx, r->in.sid)); + if (memberdn == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + mod = ldb_msg_new(mem_ctx); + if (mod == NULL) { + return NT_STATUS_NO_MEMORY; + } + + mod->dn = talloc_reference(mem_ctx, a_state->account_dn); + + ret = samdb_msg_add_delval(d_state->sam_ctx, mem_ctx, mod, "member", + memberdn); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + ret = ldb_modify(a_state->sam_ctx, mod); + switch (ret) { + case LDB_SUCCESS: + return NT_STATUS_OK; + case LDB_ERR_UNWILLING_TO_PERFORM: + return NT_STATUS_MEMBER_NOT_IN_GROUP; + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + return NT_STATUS_ACCESS_DENIED; + default: + return dsdb_ldb_err_to_ntstatus(ret); + } +} + + +/* + samr_GetMembersInAlias +*/ +static NTSTATUS dcesrv_samr_GetMembersInAlias(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetMembersInAlias *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct samr_domain_state *d_state; + struct lsa_SidPtr *array; + unsigned int i, num_members; + struct dom_sid *members; + NTSTATUS status; + + DCESRV_PULL_HANDLE(h, r->in.alias_handle, SAMR_HANDLE_ALIAS); + + a_state = h->data; + d_state = a_state->domain_state; + + status = dsdb_enum_group_mem(d_state->sam_ctx, mem_ctx, + a_state->account_dn, &members, + &num_members); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (num_members == 0) { + r->out.sids->sids = NULL; + + return NT_STATUS_OK; + } + + array = talloc_array(mem_ctx, struct lsa_SidPtr, num_members); + if (array == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<num_members; i++) { + array[i].sid = &members[i]; + } + + r->out.sids->num_sids = num_members; + r->out.sids->sids = array; + + return NT_STATUS_OK; +} + +/* + samr_OpenUser +*/ +static NTSTATUS dcesrv_samr_OpenUser(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_OpenUser *r) +{ + struct samr_domain_state *d_state; + struct samr_account_state *a_state; + struct dcesrv_handle *h; + const char *account_name; + struct dom_sid *sid; + struct ldb_message **msgs; + struct dcesrv_handle *u_handle; + const char * const attrs[2] = { "sAMAccountName", NULL }; + int ret; + + ZERO_STRUCTP(r->out.user_handle); + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + /* form the users SID */ + sid = dom_sid_add_rid(mem_ctx, d_state->domain_sid, r->in.rid); + if (!sid) { + return NT_STATUS_NO_MEMORY; + } + + /* search for the user record */ + ret = gendb_search(d_state->sam_ctx, + mem_ctx, d_state->domain_dn, &msgs, attrs, + "(&(objectSid=%s)(objectclass=user))", + ldap_encode_ndr_dom_sid(mem_ctx, sid)); + if (ret == 0) { + return NT_STATUS_NO_SUCH_USER; + } + if (ret != 1) { + DEBUG(0,("Found %d records matching sid %s\n", ret, + dom_sid_string(mem_ctx, sid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + account_name = ldb_msg_find_attr_as_string(msgs[0], "sAMAccountName", NULL); + if (account_name == NULL) { + DEBUG(0,("sAMAccountName field missing for sid %s\n", + dom_sid_string(mem_ctx, sid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + a_state = talloc(mem_ctx, struct samr_account_state); + if (!a_state) { + return NT_STATUS_NO_MEMORY; + } + a_state->sam_ctx = d_state->sam_ctx; + a_state->access_mask = r->in.access_mask; + a_state->domain_state = talloc_reference(a_state, d_state); + a_state->account_dn = talloc_steal(a_state, msgs[0]->dn); + a_state->account_sid = talloc_steal(a_state, sid); + a_state->account_name = talloc_strdup(a_state, account_name); + if (!a_state->account_name) { + return NT_STATUS_NO_MEMORY; + } + + /* create the policy handle */ + u_handle = dcesrv_handle_create(dce_call, SAMR_HANDLE_USER); + if (!u_handle) { + return NT_STATUS_NO_MEMORY; + } + + u_handle->data = talloc_steal(u_handle, a_state); + + *r->out.user_handle = u_handle->wire_handle; + + return NT_STATUS_OK; + +} + + +/* + samr_DeleteUser +*/ +static NTSTATUS dcesrv_samr_DeleteUser(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_DeleteUser *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + int ret; + + *r->out.user_handle = *r->in.user_handle; + + DCESRV_PULL_HANDLE(h, r->in.user_handle, SAMR_HANDLE_USER); + + a_state = h->data; + + ret = ldb_delete(a_state->sam_ctx, a_state->account_dn); + if (ret != LDB_SUCCESS) { + DEBUG(1, ("Failed to delete user: %s: %s\n", + ldb_dn_get_linearized(a_state->account_dn), + ldb_errstring(a_state->sam_ctx))); + return dsdb_ldb_err_to_ntstatus(ret); + } + + talloc_free(h); + ZERO_STRUCTP(r->out.user_handle); + + return NT_STATUS_OK; +} + + +/* + samr_QueryUserInfo +*/ +static NTSTATUS dcesrv_samr_QueryUserInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryUserInfo *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct ldb_message *msg, **res; + int ret; + struct ldb_context *sam_ctx; + + const char * const *attrs = NULL; + union samr_UserInfo *info; + + NTSTATUS status; + + *r->out.info = NULL; + + DCESRV_PULL_HANDLE(h, r->in.user_handle, SAMR_HANDLE_USER); + + a_state = h->data; + sam_ctx = a_state->sam_ctx; + + /* fill in the reply */ + switch (r->in.level) { + case 1: + { + static const char * const attrs2[] = {"sAMAccountName", + "displayName", + "primaryGroupID", + "description", + "comment", + NULL}; + attrs = attrs2; + break; + } + case 2: + { + static const char * const attrs2[] = {"comment", + "countryCode", + "codePage", + NULL}; + attrs = attrs2; + break; + } + case 3: + { + static const char * const attrs2[] = {"sAMAccountName", + "displayName", + "objectSid", + "primaryGroupID", + "homeDirectory", + "homeDrive", + "scriptPath", + "profilePath", + "userWorkstations", + "lastLogon", + "lastLogoff", + "pwdLastSet", + "msDS-UserPasswordExpiryTimeComputed", + "logonHours", + "badPwdCount", + "badPasswordTime", + "logonCount", + "userAccountControl", + "msDS-User-Account-Control-Computed", + NULL}; + attrs = attrs2; + break; + } + case 4: + { + static const char * const attrs2[] = {"logonHours", + NULL}; + attrs = attrs2; + break; + } + case 5: + { + static const char * const attrs2[] = {"sAMAccountName", + "displayName", + "objectSid", + "primaryGroupID", + "homeDirectory", + "homeDrive", + "scriptPath", + "profilePath", + "description", + "userWorkstations", + "lastLogon", + "lastLogoff", + "logonHours", + "badPwdCount", + "badPasswordTime", + "logonCount", + "pwdLastSet", + "msDS-ResultantPSO", + "msDS-UserPasswordExpiryTimeComputed", + "accountExpires", + "userAccountControl", + "msDS-User-Account-Control-Computed", + NULL}; + attrs = attrs2; + break; + } + case 6: + { + static const char * const attrs2[] = {"sAMAccountName", + "displayName", + NULL}; + attrs = attrs2; + break; + } + case 7: + { + static const char * const attrs2[] = {"sAMAccountName", + NULL}; + attrs = attrs2; + break; + } + case 8: + { + static const char * const attrs2[] = {"displayName", + NULL}; + attrs = attrs2; + break; + } + case 9: + { + static const char * const attrs2[] = {"primaryGroupID", + NULL}; + attrs = attrs2; + break; + } + case 10: + { + static const char * const attrs2[] = {"homeDirectory", + "homeDrive", + NULL}; + attrs = attrs2; + break; + } + case 11: + { + static const char * const attrs2[] = {"scriptPath", + NULL}; + attrs = attrs2; + break; + } + case 12: + { + static const char * const attrs2[] = {"profilePath", + NULL}; + attrs = attrs2; + break; + } + case 13: + { + static const char * const attrs2[] = {"description", + NULL}; + attrs = attrs2; + break; + } + case 14: + { + static const char * const attrs2[] = {"userWorkstations", + NULL}; + attrs = attrs2; + break; + } + case 16: + { + static const char * const attrs2[] = {"userAccountControl", + "msDS-User-Account-Control-Computed", + "pwdLastSet", + "msDS-UserPasswordExpiryTimeComputed", + NULL}; + attrs = attrs2; + break; + } + case 17: + { + static const char * const attrs2[] = {"accountExpires", + NULL}; + attrs = attrs2; + break; + } + case 18: + { + return NT_STATUS_NOT_SUPPORTED; + } + case 20: + { + static const char * const attrs2[] = {"userParameters", + NULL}; + attrs = attrs2; + break; + } + case 21: + { + static const char * const attrs2[] = {"lastLogon", + "lastLogoff", + "pwdLastSet", + "msDS-ResultantPSO", + "msDS-UserPasswordExpiryTimeComputed", + "accountExpires", + "sAMAccountName", + "displayName", + "homeDirectory", + "homeDrive", + "scriptPath", + "profilePath", + "description", + "userWorkstations", + "comment", + "userParameters", + "objectSid", + "primaryGroupID", + "userAccountControl", + "msDS-User-Account-Control-Computed", + "logonHours", + "badPwdCount", + "badPasswordTime", + "logonCount", + "countryCode", + "codePage", + NULL}; + attrs = attrs2; + break; + } + case 23: + case 24: + case 25: + case 26: + { + return NT_STATUS_NOT_SUPPORTED; + } + default: + { + return NT_STATUS_INVALID_INFO_CLASS; + } + } + + /* pull all the user attributes */ + ret = gendb_search_dn(a_state->sam_ctx, mem_ctx, + a_state->account_dn, &res, attrs); + if (ret == 0) { + return NT_STATUS_NO_SUCH_USER; + } + if (ret != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + msg = res[0]; + + /* allocate the info structure */ + info = talloc_zero(mem_ctx, union samr_UserInfo); + if (info == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* fill in the reply */ + switch (r->in.level) { + case 1: + QUERY_STRING(msg, info1.account_name, "sAMAccountName"); + QUERY_STRING(msg, info1.full_name, "displayName"); + QUERY_UINT (msg, info1.primary_gid, "primaryGroupID"); + QUERY_STRING(msg, info1.description, "description"); + QUERY_STRING(msg, info1.comment, "comment"); + break; + + case 2: + QUERY_STRING(msg, info2.comment, "comment"); + QUERY_UINT (msg, info2.country_code, "countryCode"); + QUERY_UINT (msg, info2.code_page, "codePage"); + break; + + case 3: + QUERY_STRING(msg, info3.account_name, "sAMAccountName"); + QUERY_STRING(msg, info3.full_name, "displayName"); + QUERY_RID (msg, info3.rid, "objectSid"); + QUERY_UINT (msg, info3.primary_gid, "primaryGroupID"); + QUERY_STRING(msg, info3.home_directory, "homeDirectory"); + QUERY_STRING(msg, info3.home_drive, "homeDrive"); + QUERY_STRING(msg, info3.logon_script, "scriptPath"); + QUERY_STRING(msg, info3.profile_path, "profilePath"); + QUERY_STRING(msg, info3.workstations, "userWorkstations"); + QUERY_UINT64(msg, info3.last_logon, "lastLogon"); + QUERY_UINT64(msg, info3.last_logoff, "lastLogoff"); + QUERY_UINT64(msg, info3.last_password_change, "pwdLastSet"); + QUERY_APASSC(msg, info3.allow_password_change, "pwdLastSet"); + QUERY_UINT64(msg, info3.force_password_change, "msDS-UserPasswordExpiryTimeComputed"); + QUERY_LHOURS(msg, info3.logon_hours, "logonHours"); + /* level 3 gives the raw badPwdCount value */ + QUERY_UINT (msg, info3.bad_password_count, "badPwdCount"); + QUERY_UINT (msg, info3.logon_count, "logonCount"); + QUERY_AFLAGS(msg, info3.acct_flags, "msDS-User-Account-Control-Computed"); + break; + + case 4: + QUERY_LHOURS(msg, info4.logon_hours, "logonHours"); + break; + + case 5: + QUERY_STRING(msg, info5.account_name, "sAMAccountName"); + QUERY_STRING(msg, info5.full_name, "displayName"); + QUERY_RID (msg, info5.rid, "objectSid"); + QUERY_UINT (msg, info5.primary_gid, "primaryGroupID"); + QUERY_STRING(msg, info5.home_directory, "homeDirectory"); + QUERY_STRING(msg, info5.home_drive, "homeDrive"); + QUERY_STRING(msg, info5.logon_script, "scriptPath"); + QUERY_STRING(msg, info5.profile_path, "profilePath"); + QUERY_STRING(msg, info5.description, "description"); + QUERY_STRING(msg, info5.workstations, "userWorkstations"); + QUERY_UINT64(msg, info5.last_logon, "lastLogon"); + QUERY_UINT64(msg, info5.last_logoff, "lastLogoff"); + QUERY_LHOURS(msg, info5.logon_hours, "logonHours"); + QUERY_BPWDCT(msg, info5.bad_password_count, "badPwdCount"); + QUERY_UINT (msg, info5.logon_count, "logonCount"); + QUERY_UINT64(msg, info5.last_password_change, "pwdLastSet"); + QUERY_UINT64(msg, info5.acct_expiry, "accountExpires"); + QUERY_AFLAGS(msg, info5.acct_flags, "msDS-User-Account-Control-Computed"); + break; + + case 6: + QUERY_STRING(msg, info6.account_name, "sAMAccountName"); + QUERY_STRING(msg, info6.full_name, "displayName"); + break; + + case 7: + QUERY_STRING(msg, info7.account_name, "sAMAccountName"); + break; + + case 8: + QUERY_STRING(msg, info8.full_name, "displayName"); + break; + + case 9: + QUERY_UINT (msg, info9.primary_gid, "primaryGroupID"); + break; + + case 10: + QUERY_STRING(msg, info10.home_directory,"homeDirectory"); + QUERY_STRING(msg, info10.home_drive, "homeDrive"); + break; + + case 11: + QUERY_STRING(msg, info11.logon_script, "scriptPath"); + break; + + case 12: + QUERY_STRING(msg, info12.profile_path, "profilePath"); + break; + + case 13: + QUERY_STRING(msg, info13.description, "description"); + break; + + case 14: + QUERY_STRING(msg, info14.workstations, "userWorkstations"); + break; + + case 16: + QUERY_AFLAGS(msg, info16.acct_flags, "msDS-User-Account-Control-Computed"); + break; + + case 17: + QUERY_UINT64(msg, info17.acct_expiry, "accountExpires"); + break; + + case 20: + status = samdb_result_parameters(mem_ctx, msg, "userParameters", &info->info20.parameters); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(info); + return status; + } + break; + + case 21: + QUERY_UINT64(msg, info21.last_logon, "lastLogon"); + QUERY_UINT64(msg, info21.last_logoff, "lastLogoff"); + QUERY_UINT64(msg, info21.last_password_change, "pwdLastSet"); + QUERY_UINT64(msg, info21.acct_expiry, "accountExpires"); + QUERY_APASSC(msg, info21.allow_password_change,"pwdLastSet"); + QUERY_UINT64(msg, info21.force_password_change, "msDS-UserPasswordExpiryTimeComputed"); + QUERY_STRING(msg, info21.account_name, "sAMAccountName"); + QUERY_STRING(msg, info21.full_name, "displayName"); + QUERY_STRING(msg, info21.home_directory, "homeDirectory"); + QUERY_STRING(msg, info21.home_drive, "homeDrive"); + QUERY_STRING(msg, info21.logon_script, "scriptPath"); + QUERY_STRING(msg, info21.profile_path, "profilePath"); + QUERY_STRING(msg, info21.description, "description"); + QUERY_STRING(msg, info21.workstations, "userWorkstations"); + QUERY_STRING(msg, info21.comment, "comment"); + status = samdb_result_parameters(mem_ctx, msg, "userParameters", &info->info21.parameters); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(info); + return status; + } + + QUERY_RID (msg, info21.rid, "objectSid"); + QUERY_UINT (msg, info21.primary_gid, "primaryGroupID"); + QUERY_AFLAGS(msg, info21.acct_flags, "msDS-User-Account-Control-Computed"); + info->info21.fields_present = 0x08FFFFFF; + QUERY_LHOURS(msg, info21.logon_hours, "logonHours"); + QUERY_BPWDCT(msg, info21.bad_password_count, "badPwdCount"); + QUERY_UINT (msg, info21.logon_count, "logonCount"); + if ((info->info21.acct_flags & ACB_PW_EXPIRED) != 0) { + info->info21.password_expired = PASS_MUST_CHANGE_AT_NEXT_LOGON; + } else { + info->info21.password_expired = PASS_DONT_CHANGE_AT_NEXT_LOGON; + } + QUERY_UINT (msg, info21.country_code, "countryCode"); + QUERY_UINT (msg, info21.code_page, "codePage"); + break; + + + default: + talloc_free(info); + return NT_STATUS_INVALID_INFO_CLASS; + } + + *r->out.info = info; + + return NT_STATUS_OK; +} + + +/* + samr_SetUserInfo +*/ +static NTSTATUS dcesrv_samr_SetUserInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetUserInfo *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct ldb_message *msg; + int ret; + NTSTATUS status = NT_STATUS_OK; + struct ldb_context *sam_ctx; + DATA_BLOB session_key = data_blob_null; + + DCESRV_PULL_HANDLE(h, r->in.user_handle, SAMR_HANDLE_USER); + + a_state = h->data; + sam_ctx = a_state->sam_ctx; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + msg->dn = talloc_reference(mem_ctx, a_state->account_dn); + if (!msg->dn) { + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_transaction_start(sam_ctx); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to start a transaction: %s\n", + ldb_errstring(sam_ctx)); + return NT_STATUS_LOCK_NOT_GRANTED; + } + + switch (r->in.level) { + case 2: + SET_STRING(msg, info2.comment, "comment"); + SET_UINT (msg, info2.country_code, "countryCode"); + SET_UINT (msg, info2.code_page, "codePage"); + break; + + case 4: + SET_LHOURS(msg, info4.logon_hours, "logonHours"); + break; + + case 6: + SET_STRING(msg, info6.account_name, "samAccountName"); + SET_STRING(msg, info6.full_name, "displayName"); + break; + + case 7: + SET_STRING(msg, info7.account_name, "samAccountName"); + break; + + case 8: + SET_STRING(msg, info8.full_name, "displayName"); + break; + + case 9: + SET_UINT(msg, info9.primary_gid, "primaryGroupID"); + break; + + case 10: + SET_STRING(msg, info10.home_directory, "homeDirectory"); + SET_STRING(msg, info10.home_drive, "homeDrive"); + break; + + case 11: + SET_STRING(msg, info11.logon_script, "scriptPath"); + break; + + case 12: + SET_STRING(msg, info12.profile_path, "profilePath"); + break; + + case 13: + SET_STRING(msg, info13.description, "description"); + break; + + case 14: + SET_STRING(msg, info14.workstations, "userWorkstations"); + break; + + case 16: + SET_AFLAGS(msg, info16.acct_flags, "userAccountControl"); + break; + + case 17: + SET_UINT64(msg, info17.acct_expiry, "accountExpires"); + break; + + case 18: + status = samr_set_password_buffers(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + r->in.info->info18.lm_pwd_active ? r->in.info->info18.lm_pwd.hash : NULL, + r->in.info->info18.nt_pwd_active ? r->in.info->info18.nt_pwd.hash : NULL); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (r->in.info->info18.password_expired > 0) { + struct ldb_message_element *set_el; + if (samdb_msg_add_uint64(sam_ctx, mem_ctx, msg, "pwdLastSet", 0) != LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } + break; + + case 20: + SET_PARAMETERS(msg, info20.parameters, "userParameters"); + break; + + case 21: + if (r->in.info->info21.fields_present == 0) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + +#define IFSET(bit) if (bit & r->in.info->info21.fields_present) + IFSET(SAMR_FIELD_LAST_LOGON) + SET_UINT64(msg, info21.last_logon, "lastLogon"); + IFSET(SAMR_FIELD_LAST_LOGOFF) + SET_UINT64(msg, info21.last_logoff, "lastLogoff"); + IFSET(SAMR_FIELD_ACCT_EXPIRY) + SET_UINT64(msg, info21.acct_expiry, "accountExpires"); + IFSET(SAMR_FIELD_ACCOUNT_NAME) + SET_STRING(msg, info21.account_name, "samAccountName"); + IFSET(SAMR_FIELD_FULL_NAME) + SET_STRING(msg, info21.full_name, "displayName"); + IFSET(SAMR_FIELD_HOME_DIRECTORY) + SET_STRING(msg, info21.home_directory, "homeDirectory"); + IFSET(SAMR_FIELD_HOME_DRIVE) + SET_STRING(msg, info21.home_drive, "homeDrive"); + IFSET(SAMR_FIELD_LOGON_SCRIPT) + SET_STRING(msg, info21.logon_script, "scriptPath"); + IFSET(SAMR_FIELD_PROFILE_PATH) + SET_STRING(msg, info21.profile_path, "profilePath"); + IFSET(SAMR_FIELD_DESCRIPTION) + SET_STRING(msg, info21.description, "description"); + IFSET(SAMR_FIELD_WORKSTATIONS) + SET_STRING(msg, info21.workstations, "userWorkstations"); + IFSET(SAMR_FIELD_COMMENT) + SET_STRING(msg, info21.comment, "comment"); + IFSET(SAMR_FIELD_PARAMETERS) + SET_PARAMETERS(msg, info21.parameters, "userParameters"); + IFSET(SAMR_FIELD_PRIMARY_GID) + SET_UINT(msg, info21.primary_gid, "primaryGroupID"); + IFSET(SAMR_FIELD_ACCT_FLAGS) + SET_AFLAGS(msg, info21.acct_flags, "userAccountControl"); + IFSET(SAMR_FIELD_LOGON_HOURS) + SET_LHOURS(msg, info21.logon_hours, "logonHours"); + IFSET(SAMR_FIELD_BAD_PWD_COUNT) + SET_UINT (msg, info21.bad_password_count, "badPwdCount"); + IFSET(SAMR_FIELD_NUM_LOGONS) + SET_UINT (msg, info21.logon_count, "logonCount"); + IFSET(SAMR_FIELD_COUNTRY_CODE) + SET_UINT (msg, info21.country_code, "countryCode"); + IFSET(SAMR_FIELD_CODE_PAGE) + SET_UINT (msg, info21.code_page, "codePage"); + + /* password change fields */ + IFSET(SAMR_FIELD_LAST_PWD_CHANGE) { + status = NT_STATUS_ACCESS_DENIED; + goto done; + } + + IFSET((SAMR_FIELD_LM_PASSWORD_PRESENT + | SAMR_FIELD_NT_PASSWORD_PRESENT)) { + uint8_t *lm_pwd_hash = NULL, *nt_pwd_hash = NULL; + + if (r->in.info->info21.lm_password_set) { + if ((r->in.info->info21.lm_owf_password.length != 16) + || (r->in.info->info21.lm_owf_password.size != 16)) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + lm_pwd_hash = (uint8_t *) r->in.info->info21.lm_owf_password.array; + } + if (r->in.info->info21.nt_password_set) { + if ((r->in.info->info21.nt_owf_password.length != 16) + || (r->in.info->info21.nt_owf_password.size != 16)) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + nt_pwd_hash = (uint8_t *) r->in.info->info21.nt_owf_password.array; + } + status = samr_set_password_buffers(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + lm_pwd_hash, + nt_pwd_hash); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + } + + + IFSET(SAMR_FIELD_EXPIRED_FLAG) { + const char *t = "0"; + struct ldb_message_element *set_el; + if (r->in.info->info21.password_expired + == PASS_DONT_CHANGE_AT_NEXT_LOGON) { + t = "-1"; + } + if (ldb_msg_add_string(msg, "pwdLastSet", t) != LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } +#undef IFSET + break; + + case 23: + if (r->in.info->info23.info.fields_present == 0) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + +#define IFSET(bit) if (bit & r->in.info->info23.info.fields_present) + IFSET(SAMR_FIELD_LAST_LOGON) + SET_UINT64(msg, info23.info.last_logon, "lastLogon"); + IFSET(SAMR_FIELD_LAST_LOGOFF) + SET_UINT64(msg, info23.info.last_logoff, "lastLogoff"); + IFSET(SAMR_FIELD_ACCT_EXPIRY) + SET_UINT64(msg, info23.info.acct_expiry, "accountExpires"); + IFSET(SAMR_FIELD_ACCOUNT_NAME) + SET_STRING(msg, info23.info.account_name, "samAccountName"); + IFSET(SAMR_FIELD_FULL_NAME) + SET_STRING(msg, info23.info.full_name, "displayName"); + IFSET(SAMR_FIELD_HOME_DIRECTORY) + SET_STRING(msg, info23.info.home_directory, "homeDirectory"); + IFSET(SAMR_FIELD_HOME_DRIVE) + SET_STRING(msg, info23.info.home_drive, "homeDrive"); + IFSET(SAMR_FIELD_LOGON_SCRIPT) + SET_STRING(msg, info23.info.logon_script, "scriptPath"); + IFSET(SAMR_FIELD_PROFILE_PATH) + SET_STRING(msg, info23.info.profile_path, "profilePath"); + IFSET(SAMR_FIELD_DESCRIPTION) + SET_STRING(msg, info23.info.description, "description"); + IFSET(SAMR_FIELD_WORKSTATIONS) + SET_STRING(msg, info23.info.workstations, "userWorkstations"); + IFSET(SAMR_FIELD_COMMENT) + SET_STRING(msg, info23.info.comment, "comment"); + IFSET(SAMR_FIELD_PARAMETERS) + SET_PARAMETERS(msg, info23.info.parameters, "userParameters"); + IFSET(SAMR_FIELD_PRIMARY_GID) + SET_UINT(msg, info23.info.primary_gid, "primaryGroupID"); + IFSET(SAMR_FIELD_ACCT_FLAGS) + SET_AFLAGS(msg, info23.info.acct_flags, "userAccountControl"); + IFSET(SAMR_FIELD_LOGON_HOURS) + SET_LHOURS(msg, info23.info.logon_hours, "logonHours"); + IFSET(SAMR_FIELD_BAD_PWD_COUNT) + SET_UINT (msg, info23.info.bad_password_count, "badPwdCount"); + IFSET(SAMR_FIELD_NUM_LOGONS) + SET_UINT (msg, info23.info.logon_count, "logonCount"); + + IFSET(SAMR_FIELD_COUNTRY_CODE) + SET_UINT (msg, info23.info.country_code, "countryCode"); + IFSET(SAMR_FIELD_CODE_PAGE) + SET_UINT (msg, info23.info.code_page, "codePage"); + + /* password change fields */ + IFSET(SAMR_FIELD_LAST_PWD_CHANGE) { + status = NT_STATUS_ACCESS_DENIED; + goto done; + } + + IFSET(SAMR_FIELD_NT_PASSWORD_PRESENT) { + status = samr_set_password(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + &r->in.info->info23.password); + } else IFSET(SAMR_FIELD_LM_PASSWORD_PRESENT) { + status = samr_set_password(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + &r->in.info->info23.password); + } + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + IFSET(SAMR_FIELD_EXPIRED_FLAG) { + const char *t = "0"; + struct ldb_message_element *set_el; + if (r->in.info->info23.info.password_expired + == PASS_DONT_CHANGE_AT_NEXT_LOGON) { + t = "-1"; + } + if (ldb_msg_add_string(msg, "pwdLastSet", t) != LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } +#undef IFSET + break; + + /* the set password levels are handled separately */ + case 24: + status = samr_set_password(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + &r->in.info->info24.password); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (r->in.info->info24.password_expired > 0) { + struct ldb_message_element *set_el; + if (samdb_msg_add_uint64(sam_ctx, mem_ctx, msg, "pwdLastSet", 0) != LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } + break; + + case 25: + if (r->in.info->info25.info.fields_present == 0) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + +#define IFSET(bit) if (bit & r->in.info->info25.info.fields_present) + IFSET(SAMR_FIELD_LAST_LOGON) + SET_UINT64(msg, info25.info.last_logon, "lastLogon"); + IFSET(SAMR_FIELD_LAST_LOGOFF) + SET_UINT64(msg, info25.info.last_logoff, "lastLogoff"); + IFSET(SAMR_FIELD_ACCT_EXPIRY) + SET_UINT64(msg, info25.info.acct_expiry, "accountExpires"); + IFSET(SAMR_FIELD_ACCOUNT_NAME) + SET_STRING(msg, info25.info.account_name, "samAccountName"); + IFSET(SAMR_FIELD_FULL_NAME) + SET_STRING(msg, info25.info.full_name, "displayName"); + IFSET(SAMR_FIELD_HOME_DIRECTORY) + SET_STRING(msg, info25.info.home_directory, "homeDirectory"); + IFSET(SAMR_FIELD_HOME_DRIVE) + SET_STRING(msg, info25.info.home_drive, "homeDrive"); + IFSET(SAMR_FIELD_LOGON_SCRIPT) + SET_STRING(msg, info25.info.logon_script, "scriptPath"); + IFSET(SAMR_FIELD_PROFILE_PATH) + SET_STRING(msg, info25.info.profile_path, "profilePath"); + IFSET(SAMR_FIELD_DESCRIPTION) + SET_STRING(msg, info25.info.description, "description"); + IFSET(SAMR_FIELD_WORKSTATIONS) + SET_STRING(msg, info25.info.workstations, "userWorkstations"); + IFSET(SAMR_FIELD_COMMENT) + SET_STRING(msg, info25.info.comment, "comment"); + IFSET(SAMR_FIELD_PARAMETERS) + SET_PARAMETERS(msg, info25.info.parameters, "userParameters"); + IFSET(SAMR_FIELD_PRIMARY_GID) + SET_UINT(msg, info25.info.primary_gid, "primaryGroupID"); + IFSET(SAMR_FIELD_ACCT_FLAGS) + SET_AFLAGS(msg, info25.info.acct_flags, "userAccountControl"); + IFSET(SAMR_FIELD_LOGON_HOURS) + SET_LHOURS(msg, info25.info.logon_hours, "logonHours"); + IFSET(SAMR_FIELD_BAD_PWD_COUNT) + SET_UINT (msg, info25.info.bad_password_count, "badPwdCount"); + IFSET(SAMR_FIELD_NUM_LOGONS) + SET_UINT (msg, info25.info.logon_count, "logonCount"); + IFSET(SAMR_FIELD_COUNTRY_CODE) + SET_UINT (msg, info25.info.country_code, "countryCode"); + IFSET(SAMR_FIELD_CODE_PAGE) + SET_UINT (msg, info25.info.code_page, "codePage"); + + /* password change fields */ + IFSET(SAMR_FIELD_LAST_PWD_CHANGE) { + status = NT_STATUS_ACCESS_DENIED; + goto done; + } + + IFSET(SAMR_FIELD_NT_PASSWORD_PRESENT) { + status = samr_set_password_ex(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + &r->in.info->info25.password); + } else IFSET(SAMR_FIELD_LM_PASSWORD_PRESENT) { + status = samr_set_password_ex(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + &r->in.info->info25.password); + } + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + IFSET(SAMR_FIELD_EXPIRED_FLAG) { + const char *t = "0"; + struct ldb_message_element *set_el; + if (r->in.info->info25.info.password_expired + == PASS_DONT_CHANGE_AT_NEXT_LOGON) { + t = "-1"; + } + if (ldb_msg_add_string(msg, "pwdLastSet", t) != LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } +#undef IFSET + break; + + /* the set password levels are handled separately */ + case 26: + status = samr_set_password_ex(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + &r->in.info->info26.password); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (r->in.info->info26.password_expired > 0) { + const char *t = "0"; + struct ldb_message_element *set_el; + if (r->in.info->info26.password_expired + == PASS_DONT_CHANGE_AT_NEXT_LOGON) { + t = "-1"; + } + if (ldb_msg_add_string(msg, "pwdLastSet", t) != LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } + break; + + case 31: + status = dcesrv_transport_session_key(dce_call, &session_key); + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("samr: failed to get session key: %s\n", + nt_errstr(status)); + goto done; + } + + status = samr_set_password_aes(dce_call, + mem_ctx, + &session_key, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + &r->in.info->info31.password, + DSDB_PASSWORD_RESET); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (r->in.info->info31.password_expired > 0) { + const char *t = "0"; + struct ldb_message_element *set_el = NULL; + + if (r->in.info->info31.password_expired == + PASS_DONT_CHANGE_AT_NEXT_LOGON) { + t = "-1"; + } + + ret = ldb_msg_add_string(msg, "pwdLastSet", t); + if (ret != LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } + + break; + case 32: + status = dcesrv_transport_session_key(dce_call, &session_key); + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("samr: failed to get session key: %s\n", + nt_errstr(status)); + goto done; + } + + if (r->in.info->info32.info.fields_present == 0) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + +#define IFSET(bit) if (bit & r->in.info->info32.info.fields_present) + IFSET(SAMR_FIELD_LAST_LOGON) + { + SET_UINT64(msg, info32.info.last_logon, "lastLogon"); + } + IFSET(SAMR_FIELD_LAST_LOGOFF) + { + SET_UINT64(msg, info32.info.last_logoff, "lastLogoff"); + } + IFSET(SAMR_FIELD_ACCT_EXPIRY) + { + SET_UINT64(msg, + info32.info.acct_expiry, + "accountExpires"); + } + IFSET(SAMR_FIELD_ACCOUNT_NAME) + { + SET_STRING(msg, + info32.info.account_name, + "samAccountName"); + } + IFSET(SAMR_FIELD_FULL_NAME) + { + SET_STRING(msg, info32.info.full_name, "displayName"); + } + IFSET(SAMR_FIELD_HOME_DIRECTORY) + { + SET_STRING(msg, + info32.info.home_directory, + "homeDirectory"); + } + IFSET(SAMR_FIELD_HOME_DRIVE) + { + SET_STRING(msg, info32.info.home_drive, "homeDrive"); + } + IFSET(SAMR_FIELD_LOGON_SCRIPT) + { + SET_STRING(msg, info32.info.logon_script, "scriptPath"); + } + IFSET(SAMR_FIELD_PROFILE_PATH) + { + SET_STRING(msg, + info32.info.profile_path, + "profilePath"); + } + IFSET(SAMR_FIELD_DESCRIPTION) + { + SET_STRING(msg, info32.info.description, "description"); + } + IFSET(SAMR_FIELD_WORKSTATIONS) + { + SET_STRING(msg, + info32.info.workstations, + "userWorkstations"); + } + IFSET(SAMR_FIELD_COMMENT) + { + SET_STRING(msg, info32.info.comment, "comment"); + } + IFSET(SAMR_FIELD_PARAMETERS) + { + SET_PARAMETERS(msg, + info32.info.parameters, + "userParameters"); + } + IFSET(SAMR_FIELD_PRIMARY_GID) + { + SET_UINT(msg, + info32.info.primary_gid, + "primaryGroupID"); + } + IFSET(SAMR_FIELD_ACCT_FLAGS) + { + SET_AFLAGS(msg, + info32.info.acct_flags, + "userAccountControl"); + } + IFSET(SAMR_FIELD_LOGON_HOURS) + { + SET_LHOURS(msg, info32.info.logon_hours, "logonHours"); + } + IFSET(SAMR_FIELD_BAD_PWD_COUNT) + { + SET_UINT(msg, + info32.info.bad_password_count, + "badPwdCount"); + } + IFSET(SAMR_FIELD_NUM_LOGONS) + { + SET_UINT(msg, info32.info.logon_count, "logonCount"); + } + IFSET(SAMR_FIELD_COUNTRY_CODE) + { + SET_UINT(msg, info32.info.country_code, "countryCode"); + } + IFSET(SAMR_FIELD_CODE_PAGE) + { + SET_UINT(msg, info32.info.code_page, "codePage"); + } + + /* password change fields */ + IFSET(SAMR_FIELD_LAST_PWD_CHANGE) + { + status = NT_STATUS_ACCESS_DENIED; + goto done; + } + + IFSET(SAMR_FIELD_NT_PASSWORD_PRESENT) + { + status = samr_set_password_aes( + dce_call, + mem_ctx, + &session_key, + a_state->sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + &r->in.info->info32.password, + DSDB_PASSWORD_RESET); + } + else IFSET(SAMR_FIELD_LM_PASSWORD_PRESENT) + { + status = samr_set_password_aes( + dce_call, + mem_ctx, + &session_key, + a_state->sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + &r->in.info->info32.password, + DSDB_PASSWORD_RESET); + } + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + IFSET(SAMR_FIELD_EXPIRED_FLAG) + { + const char *t = "0"; + struct ldb_message_element *set_el; + if (r->in.info->info32.info.password_expired == + PASS_DONT_CHANGE_AT_NEXT_LOGON) { + t = "-1"; + } + if (ldb_msg_add_string(msg, "pwdLastSet", t) != + LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } +#undef IFSET + + break; + default: + /* many info classes are not valid for SetUserInfo */ + status = NT_STATUS_INVALID_INFO_CLASS; + goto done; + } + + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + /* modify the samdb record */ + if (msg->num_elements > 0) { + ret = ldb_modify(sam_ctx, msg); + if (ret != LDB_SUCCESS) { + DEBUG(1,("Failed to modify record %s: %s\n", + ldb_dn_get_linearized(a_state->account_dn), + ldb_errstring(sam_ctx))); + + status = dsdb_ldb_err_to_ntstatus(ret); + goto done; + } + } + + ret = ldb_transaction_commit(sam_ctx); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to commit transaction modifying account record " + "%s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ctx)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = NT_STATUS_OK; +done: + if (!NT_STATUS_IS_OK(status)) { + ldb_transaction_cancel(sam_ctx); + } + + return status; +} + + +/* + samr_GetGroupsForUser +*/ +static NTSTATUS dcesrv_samr_GetGroupsForUser(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetGroupsForUser *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct samr_domain_state *d_state; + struct ldb_result *res, *res_memberof; + const char * const attrs[] = { "primaryGroupID", + "memberOf", + NULL }; + const char * const group_attrs[] = { "objectSid", + NULL }; + + struct samr_RidWithAttributeArray *array; + struct ldb_message_element *memberof_el; + int i, ret, count = 0; + uint32_t primary_group_id; + char *filter; + + DCESRV_PULL_HANDLE(h, r->in.user_handle, SAMR_HANDLE_USER); + + a_state = h->data; + d_state = a_state->domain_state; + + ret = dsdb_search_dn(a_state->sam_ctx, mem_ctx, + &res, + a_state->account_dn, + attrs, DSDB_SEARCH_SHOW_EXTENDED_DN); + + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + return NT_STATUS_NO_SUCH_USER; + } else if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (res->count != 1) { + return NT_STATUS_NO_SUCH_USER; + } + + primary_group_id = ldb_msg_find_attr_as_uint(res->msgs[0], "primaryGroupID", + 0); + + filter = talloc_asprintf(mem_ctx, + "(&(|(grouptype=%d)(grouptype=%d))" + "(objectclass=group)(|", + GTYPE_SECURITY_UNIVERSAL_GROUP, + GTYPE_SECURITY_GLOBAL_GROUP); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + memberof_el = ldb_msg_find_element(res->msgs[0], "memberOf"); + if (memberof_el != NULL) { + for (i = 0; i < memberof_el->num_values; i++) { + const struct ldb_val *memberof_sid_binary; + char *memberof_sid_escaped; + struct ldb_dn *memberof_dn + = ldb_dn_from_ldb_val(mem_ctx, + a_state->sam_ctx, + &memberof_el->values[i]); + if (memberof_dn == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + memberof_sid_binary + = ldb_dn_get_extended_component(memberof_dn, + "SID"); + if (memberof_sid_binary == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + memberof_sid_escaped = ldb_binary_encode(mem_ctx, + *memberof_sid_binary); + if (memberof_sid_escaped == NULL) { + return NT_STATUS_NO_MEMORY; + } + filter = talloc_asprintf_append(filter, "(objectSID=%s)", + memberof_sid_escaped); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + ret = dsdb_search(a_state->sam_ctx, mem_ctx, + &res_memberof, + d_state->domain_dn, + LDB_SCOPE_SUBTREE, + group_attrs, 0, + "%s))", filter); + + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + count = res_memberof->count; + } + + array = talloc(mem_ctx, struct samr_RidWithAttributeArray); + if (array == NULL) + return NT_STATUS_NO_MEMORY; + + array->count = 0; + array->rids = NULL; + + array->rids = talloc_array(mem_ctx, struct samr_RidWithAttribute, + count + 1); + if (array->rids == NULL) + return NT_STATUS_NO_MEMORY; + + /* Adds the primary group */ + + array->rids[0].rid = primary_group_id; + array->rids[0].attributes = SE_GROUP_MANDATORY + | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; + array->count += 1; + + /* Adds the additional groups */ + for (i = 0; i < count; i++) { + struct dom_sid *group_sid; + + group_sid = samdb_result_dom_sid(mem_ctx, + res_memberof->msgs[i], + "objectSid"); + if (group_sid == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + array->rids[i + 1].rid = + group_sid->sub_auths[group_sid->num_auths-1]; + array->rids[i + 1].attributes = SE_GROUP_MANDATORY + | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; + array->count += 1; + } + + *r->out.rids = array; + + return NT_STATUS_OK; +} + +/* + * samr_QueryDisplayInfo + * + * A cache of the GUID's matching the last query is maintained + * in the SAMR_QUERY_DISPLAY_INFO_CACHE guid_cache maintained o + * n the dcesrv_handle. + */ +static NTSTATUS dcesrv_samr_QueryDisplayInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryDisplayInfo *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + struct ldb_result *res; + uint32_t i; + uint32_t results = 0; + uint32_t count = 0; + const char *const cache_attrs[] = {"objectGUID", NULL}; + const char *const attrs[] = { + "objectSID", "sAMAccountName", "displayName", "description", NULL}; + struct samr_DispEntryFull *entriesFull = NULL; + struct samr_DispEntryFullGroup *entriesFullGroup = NULL; + struct samr_DispEntryAscii *entriesAscii = NULL; + struct samr_DispEntryGeneral *entriesGeneral = NULL; + const char *filter; + int ret; + NTSTATUS status; + struct samr_guid_cache *cache = NULL; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + cache = &d_state->guid_caches[SAMR_QUERY_DISPLAY_INFO_CACHE]; + /* + * Can the cached results be used? + * The cache is discarded if the start index is zero, or the requested + * level is different from that in the cache. + */ + if ((r->in.start_idx == 0) || (r->in.level != cache->handle)) { + /* + * The cached results can not be used, so will need to query + * the database. + */ + + /* + * Get the search filter for the current level + */ + switch (r->in.level) { + case 1: + case 4: + filter = talloc_asprintf(mem_ctx, + "(&(objectclass=user)" + "(sAMAccountType=%d))", + ATYPE_NORMAL_ACCOUNT); + break; + case 2: + filter = talloc_asprintf(mem_ctx, + "(&(objectclass=user)" + "(sAMAccountType=%d))", + ATYPE_WORKSTATION_TRUST); + break; + case 3: + case 5: + filter = + talloc_asprintf(mem_ctx, + "(&(|(groupType=%d)(groupType=%d))" + "(objectClass=group))", + GTYPE_SECURITY_UNIVERSAL_GROUP, + GTYPE_SECURITY_GLOBAL_GROUP); + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + clear_guid_cache(cache); + + /* + * search for all requested objects in all domains. + */ + ret = dsdb_search(d_state->sam_ctx, + mem_ctx, + &res, + ldb_get_default_basedn(d_state->sam_ctx), + LDB_SCOPE_SUBTREE, + cache_attrs, + 0, + "%s", + filter); + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + if ((res->count == 0) || (r->in.max_entries == 0)) { + return NT_STATUS_OK; + } + + status = load_guid_cache(cache, d_state, res->count, res->msgs); + TALLOC_FREE(res); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + cache->handle = r->in.level; + } + *r->out.total_size = cache->size; + + /* + * if there are no entries or the requested start index is greater + * than the number of entries, we return an empty response. + */ + if (r->in.start_idx >= cache->size) { + *r->out.returned_size = 0; + switch(r->in.level) { + case 1: + r->out.info->info1.count = *r->out.returned_size; + r->out.info->info1.entries = NULL; + break; + case 2: + r->out.info->info2.count = *r->out.returned_size; + r->out.info->info2.entries = NULL; + break; + case 3: + r->out.info->info3.count = *r->out.returned_size; + r->out.info->info3.entries = NULL; + break; + case 4: + r->out.info->info4.count = *r->out.returned_size; + r->out.info->info4.entries = NULL; + break; + case 5: + r->out.info->info5.count = *r->out.returned_size; + r->out.info->info5.entries = NULL; + break; + } + return NT_STATUS_OK; + } + + /* + * Allocate an array of the appropriate result structures for the + * current query level. + * + * r->in.start_idx is always < cache->size due to the check above + */ + results = MIN((cache->size - r->in.start_idx), r->in.max_entries); + switch (r->in.level) { + case 1: + entriesGeneral = talloc_array( + mem_ctx, struct samr_DispEntryGeneral, results); + break; + case 2: + entriesFull = + talloc_array(mem_ctx, struct samr_DispEntryFull, results); + break; + case 3: + entriesFullGroup = talloc_array( + mem_ctx, struct samr_DispEntryFullGroup, results); + break; + case 4: + case 5: + entriesAscii = + talloc_array(mem_ctx, struct samr_DispEntryAscii, results); + break; + } + + if ((entriesGeneral == NULL) && (entriesFull == NULL) && + (entriesAscii == NULL) && (entriesFullGroup == NULL)) + return NT_STATUS_NO_MEMORY; + + /* + * Process the list of result GUID's. + * Read the details of each object and populate the result structure + * for the current level. + */ + count = 0; + for (i = 0; i < results; i++) { + struct dom_sid *objectsid; + struct ldb_result *rec; + const uint32_t idx = r->in.start_idx + i; + uint32_t rid; + + /* + * Read an object from disk using the GUID as the key + * + * If the object can not be read, or it does not have a SID + * it is ignored. In this case the number of entries returned + * will be less than the requested size, there will also be + * a gap in the idx numbers in the returned elements e.g. if + * there are 3 GUIDs a, b, c in the cache and b is deleted from + * disk then details for a, and c will be returned with + * idx values of 1 and 3 respectively. + * + */ + ret = dsdb_search_by_dn_guid(d_state->sam_ctx, + mem_ctx, + &rec, + &cache->entries[idx], + attrs, + 0); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + struct GUID_txt_buf guid_buf; + char *guid_str = + GUID_buf_string(&cache->entries[idx], + &guid_buf); + DBG_WARNING("GUID [%s] not found\n", guid_str); + continue; + } else if (ret != LDB_SUCCESS) { + clear_guid_cache(cache); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + objectsid = samdb_result_dom_sid(mem_ctx, + rec->msgs[0], + "objectSID"); + if (objectsid == NULL) { + struct GUID_txt_buf guid_buf; + DBG_WARNING( + "objectSID for GUID [%s] not found\n", + GUID_buf_string(&cache->entries[idx], &guid_buf)); + continue; + } + status = dom_sid_split_rid(NULL, + objectsid, + NULL, + &rid); + if (!NT_STATUS_IS_OK(status)) { + struct dom_sid_buf sid_buf; + struct GUID_txt_buf guid_buf; + DBG_WARNING( + "objectSID [%s] for GUID [%s] invalid\n", + dom_sid_str_buf(objectsid, &sid_buf), + GUID_buf_string(&cache->entries[idx], &guid_buf)); + continue; + } + + /* + * Populate the result structure for the current object + */ + switch(r->in.level) { + case 1: + + entriesGeneral[count].idx = idx + 1; + entriesGeneral[count].rid = rid; + + entriesGeneral[count].acct_flags = + samdb_result_acct_flags(rec->msgs[0], NULL); + entriesGeneral[count].account_name.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "sAMAccountName", ""); + entriesGeneral[count].full_name.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "displayName", ""); + entriesGeneral[count].description.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "description", ""); + break; + case 2: + entriesFull[count].idx = idx + 1; + entriesFull[count].rid = rid; + + /* + * No idea why we need to or in ACB_NORMAL here, + * but this is what Win2k3 seems to do... + */ + entriesFull[count].acct_flags = + samdb_result_acct_flags(rec->msgs[0], NULL) | + ACB_NORMAL; + entriesFull[count].account_name.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "sAMAccountName", ""); + entriesFull[count].description.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "description", ""); + break; + case 3: + entriesFullGroup[count].idx = idx + 1; + entriesFullGroup[count].rid = rid; + + /* + * We get a "7" here for groups + */ + entriesFullGroup[count].acct_flags = + SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | + SE_GROUP_ENABLED; + entriesFullGroup[count].account_name.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "sAMAccountName", ""); + entriesFullGroup[count].description.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "description", ""); + break; + case 4: + case 5: + entriesAscii[count].idx = idx + 1; + entriesAscii[count].account_name.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "sAMAccountName", ""); + break; + } + count++; + } + + /* + * Build the response based on the request level. + */ + *r->out.returned_size = count; + switch(r->in.level) { + case 1: + r->out.info->info1.count = count; + r->out.info->info1.entries = entriesGeneral; + break; + case 2: + r->out.info->info2.count = count; + r->out.info->info2.entries = entriesFull; + break; + case 3: + r->out.info->info3.count = count; + r->out.info->info3.entries = entriesFullGroup; + break; + case 4: + r->out.info->info4.count = count; + r->out.info->info4.entries = entriesAscii; + break; + case 5: + r->out.info->info5.count = count; + r->out.info->info5.entries = entriesAscii; + break; + } + + return ((r->in.start_idx + results) < cache->size) + ? STATUS_MORE_ENTRIES + : NT_STATUS_OK; +} + + +/* + samr_GetDisplayEnumerationIndex +*/ +static NTSTATUS dcesrv_samr_GetDisplayEnumerationIndex(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetDisplayEnumerationIndex *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_TestPrivateFunctionsDomain +*/ +static NTSTATUS dcesrv_samr_TestPrivateFunctionsDomain(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_TestPrivateFunctionsDomain *r) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + + +/* + samr_TestPrivateFunctionsUser +*/ +static NTSTATUS dcesrv_samr_TestPrivateFunctionsUser(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_TestPrivateFunctionsUser *r) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + + +/* + samr_GetUserPwInfo +*/ +static NTSTATUS dcesrv_samr_GetUserPwInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetUserPwInfo *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + + ZERO_STRUCTP(r->out.info); + + DCESRV_PULL_HANDLE(h, r->in.user_handle, SAMR_HANDLE_USER); + + a_state = h->data; + + r->out.info->min_password_length = samdb_search_uint(a_state->sam_ctx, + mem_ctx, 0, a_state->domain_state->domain_dn, "minPwdLength", + NULL); + r->out.info->password_properties = samdb_search_uint(a_state->sam_ctx, + mem_ctx, 0, a_state->account_dn, "pwdProperties", NULL); + + return NT_STATUS_OK; +} + + +/* + samr_RemoveMemberFromForeignDomain +*/ +static NTSTATUS dcesrv_samr_RemoveMemberFromForeignDomain(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_RemoveMemberFromForeignDomain *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + const char *memberdn; + struct ldb_message **res; + const char *no_attrs[] = { NULL }; + int i, count; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + memberdn = samdb_search_string(d_state->sam_ctx, mem_ctx, NULL, + "distinguishedName", "(objectSid=%s)", + ldap_encode_ndr_dom_sid(mem_ctx, r->in.sid)); + /* Nothing to do */ + if (memberdn == NULL) { + return NT_STATUS_OK; + } + + count = samdb_search_domain(d_state->sam_ctx, mem_ctx, + d_state->domain_dn, &res, no_attrs, + d_state->domain_sid, + "(&(member=%s)(objectClass=group)" + "(|(groupType=%d)(groupType=%d)))", + memberdn, + GTYPE_SECURITY_BUILTIN_LOCAL_GROUP, + GTYPE_SECURITY_DOMAIN_LOCAL_GROUP); + + if (count < 0) + return NT_STATUS_INTERNAL_DB_CORRUPTION; + + for (i=0; i<count; i++) { + struct ldb_message *mod; + int ret; + + mod = ldb_msg_new(mem_ctx); + if (mod == NULL) { + return NT_STATUS_NO_MEMORY; + } + + mod->dn = res[i]->dn; + + if (samdb_msg_add_delval(d_state->sam_ctx, mem_ctx, mod, + "member", memberdn) != LDB_SUCCESS) + return NT_STATUS_NO_MEMORY; + + ret = ldb_modify(d_state->sam_ctx, mod); + talloc_free(mod); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + } + + return NT_STATUS_OK; +} + + +/* + samr_QueryDomainInfo2 + + just an alias for samr_QueryDomainInfo +*/ +static NTSTATUS dcesrv_samr_QueryDomainInfo2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryDomainInfo2 *r) +{ + struct samr_QueryDomainInfo r1; + NTSTATUS status; + + r1 = (struct samr_QueryDomainInfo) { + .in.domain_handle = r->in.domain_handle, + .in.level = r->in.level, + .out.info = r->out.info, + }; + + status = dcesrv_samr_QueryDomainInfo(dce_call, mem_ctx, &r1); + + return status; +} + + +/* + samr_QueryUserInfo2 + + just an alias for samr_QueryUserInfo +*/ +static NTSTATUS dcesrv_samr_QueryUserInfo2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryUserInfo2 *r) +{ + struct samr_QueryUserInfo r1; + NTSTATUS status; + + r1 = (struct samr_QueryUserInfo) { + .in.user_handle = r->in.user_handle, + .in.level = r->in.level, + .out.info = r->out.info + }; + + status = dcesrv_samr_QueryUserInfo(dce_call, mem_ctx, &r1); + + return status; +} + + +/* + samr_QueryDisplayInfo2 +*/ +static NTSTATUS dcesrv_samr_QueryDisplayInfo2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryDisplayInfo2 *r) +{ + struct samr_QueryDisplayInfo q; + NTSTATUS result; + + q = (struct samr_QueryDisplayInfo) { + .in.domain_handle = r->in.domain_handle, + .in.level = r->in.level, + .in.start_idx = r->in.start_idx, + .in.max_entries = r->in.max_entries, + .in.buf_size = r->in.buf_size, + .out.total_size = r->out.total_size, + .out.returned_size = r->out.returned_size, + .out.info = r->out.info, + }; + + result = dcesrv_samr_QueryDisplayInfo(dce_call, mem_ctx, &q); + + return result; +} + + +/* + samr_GetDisplayEnumerationIndex2 +*/ +static NTSTATUS dcesrv_samr_GetDisplayEnumerationIndex2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetDisplayEnumerationIndex2 *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_QueryDisplayInfo3 +*/ +static NTSTATUS dcesrv_samr_QueryDisplayInfo3(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryDisplayInfo3 *r) +{ + struct samr_QueryDisplayInfo q; + NTSTATUS result; + + q = (struct samr_QueryDisplayInfo) { + .in.domain_handle = r->in.domain_handle, + .in.level = r->in.level, + .in.start_idx = r->in.start_idx, + .in.max_entries = r->in.max_entries, + .in.buf_size = r->in.buf_size, + .out.total_size = r->out.total_size, + .out.returned_size = r->out.returned_size, + .out.info = r->out.info, + }; + + result = dcesrv_samr_QueryDisplayInfo(dce_call, mem_ctx, &q); + + return result; +} + + +/* + samr_AddMultipleMembersToAlias +*/ +static NTSTATUS dcesrv_samr_AddMultipleMembersToAlias(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_AddMultipleMembersToAlias *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_RemoveMultipleMembersFromAlias +*/ +static NTSTATUS dcesrv_samr_RemoveMultipleMembersFromAlias(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_RemoveMultipleMembersFromAlias *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_GetDomPwInfo + + this fetches the default password properties for a domain + + note that w2k3 completely ignores the domain name in this call, and + always returns the information for the servers primary domain +*/ +static NTSTATUS dcesrv_samr_GetDomPwInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetDomPwInfo *r) +{ + struct ldb_message **msgs; + int ret; + const char * const attrs[] = {"minPwdLength", "pwdProperties", NULL }; + struct ldb_context *sam_ctx; + + ZERO_STRUCTP(r->out.info); + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + /* The domain name in this call is ignored */ + ret = gendb_search_dn(sam_ctx, + mem_ctx, NULL, &msgs, attrs); + if (ret <= 0) { + talloc_free(sam_ctx); + + return NT_STATUS_NO_SUCH_DOMAIN; + } + if (ret > 1) { + talloc_free(msgs); + talloc_free(sam_ctx); + + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + r->out.info->min_password_length = ldb_msg_find_attr_as_uint(msgs[0], + "minPwdLength", 0); + r->out.info->password_properties = ldb_msg_find_attr_as_uint(msgs[0], + "pwdProperties", 1); + + talloc_free(msgs); + talloc_unlink(mem_ctx, sam_ctx); + + return NT_STATUS_OK; +} + + +/* + samr_Connect2 +*/ +static NTSTATUS dcesrv_samr_Connect2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_Connect2 *r) +{ + struct samr_Connect c; + + c = (struct samr_Connect) { + .in.system_name = NULL, + .in.access_mask = r->in.access_mask, + .out.connect_handle = r->out.connect_handle, + }; + + return dcesrv_samr_Connect(dce_call, mem_ctx, &c); +} + + +/* + samr_SetUserInfo2 + + just an alias for samr_SetUserInfo +*/ +static NTSTATUS dcesrv_samr_SetUserInfo2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetUserInfo2 *r) +{ + struct samr_SetUserInfo r2; + + r2 = (struct samr_SetUserInfo) { + .in.user_handle = r->in.user_handle, + .in.level = r->in.level, + .in.info = r->in.info, + }; + + return dcesrv_samr_SetUserInfo(dce_call, mem_ctx, &r2); +} + + +/* + samr_SetBootKeyInformation +*/ +static NTSTATUS dcesrv_samr_SetBootKeyInformation(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetBootKeyInformation *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_GetBootKeyInformation +*/ +static NTSTATUS dcesrv_samr_GetBootKeyInformation(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetBootKeyInformation *r) +{ + /* Windows Server 2008 returns this */ + return NT_STATUS_NOT_SUPPORTED; +} + + +/* + samr_Connect3 +*/ +static NTSTATUS dcesrv_samr_Connect3(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_Connect3 *r) +{ + struct samr_Connect c; + + c = (struct samr_Connect) { + .in.system_name = NULL, + .in.access_mask = r->in.access_mask, + .out.connect_handle = r->out.connect_handle, + }; + + return dcesrv_samr_Connect(dce_call, mem_ctx, &c); +} + + +/* + samr_Connect4 +*/ +static NTSTATUS dcesrv_samr_Connect4(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_Connect4 *r) +{ + struct samr_Connect c; + + c = (struct samr_Connect) { + .in.system_name = NULL, + .in.access_mask = r->in.access_mask, + .out.connect_handle = r->out.connect_handle, + }; + + return dcesrv_samr_Connect(dce_call, mem_ctx, &c); +} + + +/* + samr_Connect5 +*/ +static NTSTATUS dcesrv_samr_Connect5(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_Connect5 *r) +{ + struct samr_Connect c; + NTSTATUS status; + + c = (struct samr_Connect) { + .in.system_name = NULL, + .in.access_mask = r->in.access_mask, + .out.connect_handle = r->out.connect_handle, + }; + + status = dcesrv_samr_Connect(dce_call, mem_ctx, &c); + + r->out.info_out->info1.client_version = SAMR_CONNECT_AFTER_W2K; + r->out.info_out->info1.supported_features = 0; + *r->out.level_out = r->in.level_in; + + return status; +} + + +/* + samr_RidToSid +*/ +static NTSTATUS dcesrv_samr_RidToSid(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_RidToSid *r) +{ + struct samr_domain_state *d_state; + struct dcesrv_handle *h; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + /* form the users SID */ + *r->out.sid = dom_sid_add_rid(mem_ctx, d_state->domain_sid, r->in.rid); + if (!*r->out.sid) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + + +/* + samr_SetDsrmPassword +*/ +static NTSTATUS dcesrv_samr_SetDsrmPassword(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetDsrmPassword *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_ValidatePassword + + For now the call checks the password complexity (if active) and the minimum + password length on level 2 and 3. Level 1 is ignored for now. +*/ +static NTSTATUS dcesrv_samr_ValidatePassword(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_ValidatePassword *r) +{ + struct samr_GetDomPwInfo r2; + struct samr_PwInfo pwInfo; + const char *account = NULL; + DATA_BLOB password; + enum samr_ValidationStatus res; + NTSTATUS status; + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(dce_call->conn->endpoint->ep_description); + enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE; + + if (transport != NCACN_IP_TCP && transport != NCALRPC) { + DCESRV_FAULT(DCERPC_FAULT_ACCESS_DENIED); + } + + dcesrv_call_auth_info(dce_call, NULL, &auth_level); + if (auth_level != DCERPC_AUTH_LEVEL_PRIVACY) { + DCESRV_FAULT(DCERPC_FAULT_ACCESS_DENIED); + } + + (*r->out.rep) = talloc_zero(mem_ctx, union samr_ValidatePasswordRep); + + r2 = (struct samr_GetDomPwInfo) { + .in.domain_name = NULL, + .out.info = &pwInfo, + }; + + status = dcesrv_samr_GetDomPwInfo(dce_call, mem_ctx, &r2); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + switch (r->in.level) { + case NetValidateAuthentication: + /* we don't support this yet */ + return NT_STATUS_NOT_SUPPORTED; + break; + case NetValidatePasswordChange: + account = r->in.req->req2.account.string; + password = data_blob_const(r->in.req->req2.password.string, + r->in.req->req2.password.length); + res = samdb_check_password(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + account, + NULL, /* userPrincipalName */ + NULL, /* displayName/full_name */ + &password, + pwInfo.password_properties, + pwInfo.min_password_length); + (*r->out.rep)->ctr2.status = res; + break; + case NetValidatePasswordReset: + account = r->in.req->req3.account.string; + password = data_blob_const(r->in.req->req3.password.string, + r->in.req->req3.password.length); + res = samdb_check_password(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + account, + NULL, /* userPrincipalName */ + NULL, /* displayName/full_name */ + &password, + pwInfo.password_properties, + pwInfo.min_password_length); + (*r->out.rep)->ctr3.status = res; + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + break; + } + + return NT_STATUS_OK; +} + +static void dcesrv_samr_Opnum68NotUsedOnWire(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_Opnum68NotUsedOnWire *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + +static void dcesrv_samr_Opnum69NotUsedOnWire(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_Opnum69NotUsedOnWire *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + +static void dcesrv_samr_Opnum70NotUsedOnWire(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_Opnum70NotUsedOnWire *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + +static void dcesrv_samr_Opnum71NotUsedOnWire(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_Opnum71NotUsedOnWire *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + +static void dcesrv_samr_Opnum72NotUsedOnWire(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_Opnum72NotUsedOnWire *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_samr_s.c" diff --git a/source4/rpc_server/samr/dcesrv_samr.h b/source4/rpc_server/samr/dcesrv_samr.h new file mode 100644 index 0000000..66038ed --- /dev/null +++ b/source4/rpc_server/samr/dcesrv_samr.h @@ -0,0 +1,88 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the samr pipe - definitions + + Copyright (C) Andrew Tridgell 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "param/param.h" +#include "libds/common/roles.h" + +/* + this type allows us to distinguish handle types +*/ +enum samr_handle { + SAMR_HANDLE_CONNECT, + SAMR_HANDLE_DOMAIN, + SAMR_HANDLE_USER, + SAMR_HANDLE_GROUP, + SAMR_HANDLE_ALIAS +}; + + +/* + state asscoiated with a samr_Connect*() operation +*/ +struct samr_connect_state { + struct ldb_context *sam_ctx; + uint32_t access_mask; +}; + +/* + * Cache of object GUIDS + */ +struct samr_guid_cache { + unsigned handle; + unsigned size; + struct GUID *entries; +}; + +enum samr_guid_cache_id { + SAMR_QUERY_DISPLAY_INFO_CACHE, + SAMR_ENUM_DOMAIN_GROUPS_CACHE, + SAMR_ENUM_DOMAIN_USERS_CACHE, + SAMR_LAST_CACHE +}; + +/* + state associated with a samr_OpenDomain() operation +*/ +struct samr_domain_state { + struct samr_connect_state *connect_state; + void *sam_ctx; + uint32_t access_mask; + struct dom_sid *domain_sid; + const char *domain_name; + struct ldb_dn *domain_dn; + enum server_role role; + bool builtin; + struct loadparm_context *lp_ctx; + struct samr_guid_cache guid_caches[SAMR_LAST_CACHE]; + struct samr_SamEntry *domain_users_cached; +}; + +/* + state associated with a open account handle +*/ +struct samr_account_state { + struct samr_domain_state *domain_state; + void *sam_ctx; + uint32_t access_mask; + struct dom_sid *account_sid; + const char *account_name; + struct ldb_dn *account_dn; +}; diff --git a/source4/rpc_server/samr/samr_password.c b/source4/rpc_server/samr/samr_password.c new file mode 100644 index 0000000..b581be6 --- /dev/null +++ b/source4/rpc_server/samr/samr_password.c @@ -0,0 +1,833 @@ +/* + Unix SMB/CIFS implementation. + + samr server password set/change handling + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "rpc_server/common/common.h" +#include "rpc_server/samr/dcesrv_samr.h" +#include "system/time.h" +#include "lib/crypto/md4.h" +#include "dsdb/common/util.h" +#include "dsdb/samdb/samdb.h" +#include "auth/auth.h" +#include "libcli/auth/libcli_auth.h" +#include "../lib/util/util_ldb.h" +#include "rpc_server/samr/proto.h" +#include "auth/auth_sam.h" +#include "lib/param/loadparm.h" +#include "librpc/rpc/dcerpc_helper.h" +#include "librpc/rpc/dcerpc_samr.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +static void log_password_change_event(struct imessaging_context *msg_ctx, + struct loadparm_context *lp_ctx, + const struct tsocket_address *remote_client_address, + const struct tsocket_address *local_server_address, + const char *auth_description, + const char *password_type, + const char *original_client_name, + const char *account_name_from_db, + NTSTATUS status, + struct dom_sid *sid) +{ + /* + * Forcing this via the NTLM auth structure is not ideal, but + * it is the most practical option right now, and ensures the + * logs are consistent, even if some elements are always NULL. + */ + struct auth_usersupplied_info ui = { + .was_mapped = true, + .client = { + .account_name = original_client_name, + .domain_name = lpcfg_sam_name(lp_ctx), + }, + .mapped = { + .account_name = account_name_from_db, + .domain_name = lpcfg_sam_name(lp_ctx), + }, + .remote_host = remote_client_address, + .local_host = local_server_address, + .service_description = "SAMR Password Change", + .auth_description = auth_description, + .password_type = password_type, + }; + + log_authentication_event(msg_ctx, + lp_ctx, + NULL, + &ui, + status, + ui.mapped.domain_name, + ui.mapped.account_name, + sid); +} +/* + samr_ChangePasswordUser + + So old it is just not worth implementing + because it does not supply a plaintext and so we can't do password + complexity checking and cannot update all the other password hashes. + +*/ +NTSTATUS dcesrv_samr_ChangePasswordUser(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_ChangePasswordUser *r) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +/* + samr_OemChangePasswordUser2 + + No longer implemented as it requires the LM hash +*/ +NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_OemChangePasswordUser2 *r) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +/* + samr_ChangePasswordUser4 +*/ +NTSTATUS dcesrv_samr_ChangePasswordUser4(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_ChangePasswordUser4 *r) +{ +#ifdef HAVE_GNUTLS_PBKDF2 + struct ldb_context *sam_ctx = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *dn = NULL; + const char *samAccountName = NULL; + struct dom_sid *objectSid = NULL; + struct samr_Password *nt_pwd = NULL; + gnutls_datum_t nt_key; + gnutls_datum_t salt = { + .data = r->in.password->salt, + .size = sizeof(r->in.password->salt), + }; + uint8_t cdk_data[16] = {0}; + DATA_BLOB cdk = { + .data = cdk_data, + .length = sizeof(cdk_data), + }; + struct auth_session_info *call_session_info = NULL; + struct auth_session_info *old_session_info = NULL; + NTSTATUS status = NT_STATUS_WRONG_PASSWORD; + int rc; + + r->out.result = NT_STATUS_WRONG_PASSWORD; + + if (r->in.password == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (r->in.password->PBKDF2Iterations < 5000 || + r->in.password->PBKDF2Iterations > 1000000) { + return NT_STATUS_INVALID_PARAMETER; + } + /* + * Connect to a SAMDB with system privileges for fetching the old + * password hashes. + */ + sam_ctx = samdb_connect(mem_ctx, + dce_call->event_ctx, + dce_call->conn->dce_ctx->lp_ctx, + system_session(dce_call->conn->dce_ctx->lp_ctx), + dce_call->conn->remote_address, + 0); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + rc = ldb_transaction_start(sam_ctx); + if (rc != LDB_SUCCESS) { + DBG_WARNING("Failed to start transaction: %s\n", + ldb_errstring(sam_ctx)); + return NT_STATUS_TRANSACTION_ABORTED; + } + + /* + * We use authsam_search_account() to be consistent with the + * other callers in the bad password and audit log handling + * systems. It ensures we get DSDB_SEARCH_SHOW_EXTENDED_DN. + */ + status = authsam_search_account(mem_ctx, + sam_ctx, + r->in.account->string, + ldb_get_default_basedn(sam_ctx), + &msg); + if (!NT_STATUS_IS_OK(status)) { + ldb_transaction_cancel(sam_ctx); + goto done; + } + + dn = msg->dn; + samAccountName = ldb_msg_find_attr_as_string(msg, "samAccountName", NULL); + objectSid = samdb_result_dom_sid(msg, msg, "objectSid"); + + status = samdb_result_passwords(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + msg, + &nt_pwd); + if (!NT_STATUS_IS_OK(status)) { + ldb_transaction_cancel(sam_ctx); + goto done; + } + + if (nt_pwd == NULL) { + ldb_transaction_cancel(sam_ctx); + status = NT_STATUS_WRONG_PASSWORD; + goto done; + } + + nt_key = (gnutls_datum_t){ + .data = nt_pwd->hash, + .size = sizeof(nt_pwd->hash), + }; + + rc = gnutls_pbkdf2(GNUTLS_MAC_SHA512, + &nt_key, + &salt, + r->in.password->PBKDF2Iterations, + cdk.data, + cdk.length); + if (rc < 0) { + ldb_transaction_cancel(sam_ctx); + status = NT_STATUS_WRONG_PASSWORD; + goto done; + } + + /* Drop to user privileges for the password change */ + + old_session_info = ldb_get_opaque(sam_ctx, DSDB_SESSION_INFO); + call_session_info = dcesrv_call_session_info(dce_call); + + rc = ldb_set_opaque(sam_ctx, DSDB_SESSION_INFO, call_session_info); + if (rc != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ctx); + status = NT_STATUS_INVALID_SYSTEM_SERVICE; + goto done; + } + + status = samr_set_password_aes(dce_call, + mem_ctx, + &cdk, + sam_ctx, + dn, + NULL, + r->in.password, + DSDB_PASSWORD_CHECKED_AND_CORRECT); + BURN_DATA(cdk_data); + + /* Restore our privileges to system level */ + if (old_session_info != NULL) { + ldb_set_opaque(sam_ctx, DSDB_SESSION_INFO, old_session_info); + } + + if (!NT_STATUS_IS_OK(status)) { + ldb_transaction_cancel(sam_ctx); + goto done; + } + + /* And this confirms it in a transaction commit */ + rc = ldb_transaction_commit(sam_ctx); + if (rc != LDB_SUCCESS) { + DBG_WARNING("Failed to commit transaction to change password " + "on %s: %s\n", + ldb_dn_get_linearized(dn), + ldb_errstring(sam_ctx)); + status = NT_STATUS_TRANSACTION_ABORTED; + goto done; + } + + status = NT_STATUS_OK; +done: + { + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + + log_password_change_event(imsg_ctx, + dce_call->conn->dce_ctx->lp_ctx, + dce_call->conn->remote_address, + dce_call->conn->local_address, + "samr_ChangePasswordUser4", + "AES using NTLM-hash", + r->in.account->string, + samAccountName, + status, + objectSid); + } + + /* Only update the badPwdCount if we found the user */ + if (NT_STATUS_EQUAL(status, NT_STATUS_WRONG_PASSWORD)) { + authsam_update_bad_pwd_count(sam_ctx, + msg, + ldb_get_default_basedn(sam_ctx)); + } else if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { + /* + * Don't give the game away: (don't allow anonymous users to + * prove the existence of usernames) + */ + status = NT_STATUS_WRONG_PASSWORD; + } + + return status; +#else /* HAVE_GNUTLS_PBKDF2 */ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +#endif /* HAVE_GNUTLS_PBKDF2 */ +} + +/* + samr_ChangePasswordUser3 +*/ +NTSTATUS dcesrv_samr_ChangePasswordUser3(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_ChangePasswordUser3 *r) +{ + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + NTSTATUS status = NT_STATUS_WRONG_PASSWORD; + DATA_BLOB new_password; + struct ldb_context *sam_ctx = NULL; + struct ldb_dn *user_dn = NULL; + int ret; + struct ldb_message *msg = NULL; + struct samr_Password *nt_pwd; + struct samr_DomInfo1 *dominfo = NULL; + struct userPwdChangeFailureInformation *reject = NULL; + enum samPwdChangeReason reason = SAM_PWD_CHANGE_NO_ERROR; + uint8_t new_nt_hash[16]; + struct samr_Password nt_verifier; + const char *user_samAccountName = NULL; + struct dom_sid *user_objectSid = NULL; + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + enum ntlm_auth_level ntlm_auth_level + = lpcfg_ntlm_auth(lp_ctx); + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t nt_session_key; + struct auth_session_info *call_session_info = NULL; + struct auth_session_info *old_session_info = NULL; + int rc; + + *r->out.dominfo = NULL; + *r->out.reject = NULL; + + /* this call should be disabled without NTLM auth */ + if (ntlm_auth_level == NTLM_AUTH_DISABLED) { + DBG_WARNING("NTLM password changes not" + "permitted by configuration.\n"); + return NT_STATUS_NTLM_BLOCKED; + } + + if (r->in.nt_password == NULL || + r->in.nt_verifier == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* Connect to a SAMDB with system privileges for fetching the old pw + * hashes. */ + sam_ctx = dcesrv_samdb_connect_as_system(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + ret = ldb_transaction_start(sam_ctx); + if (ret != LDB_SUCCESS) { + DEBUG(1, ("Failed to start transaction: %s\n", ldb_errstring(sam_ctx))); + return NT_STATUS_TRANSACTION_ABORTED; + } + + /* + * We use authsam_search_account() to be consistent with the + * other callers in the bad password and audit log handling + * systems. It ensures we get DSDB_SEARCH_SHOW_EXTENDED_DN. + */ + status = authsam_search_account(mem_ctx, + sam_ctx, + r->in.account->string, + ldb_get_default_basedn(sam_ctx), + &msg); + if (!NT_STATUS_IS_OK(status)) { + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + user_dn = msg->dn; + user_samAccountName = ldb_msg_find_attr_as_string(msg, "samAccountName", NULL); + user_objectSid = samdb_result_dom_sid(mem_ctx, msg, "objectSid"); + + status = samdb_result_passwords(mem_ctx, lp_ctx, + msg, &nt_pwd); + if (!NT_STATUS_IS_OK(status) ) { + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + if (!nt_pwd) { + status = NT_STATUS_WRONG_PASSWORD; + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + /* decrypt the password we have been given */ + nt_session_key = (gnutls_datum_t) { + .data = nt_pwd->hash, + .size = sizeof(nt_pwd->hash), + }; + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &nt_session_key, + NULL); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + rc = gnutls_cipher_decrypt(cipher_hnd, + r->in.nt_password->data, + 516); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + if (!extract_pw_from_buffer(mem_ctx, r->in.nt_password->data, &new_password)) { + DEBUG(3,("samr: failed to decode password buffer\n")); + status = NT_STATUS_WRONG_PASSWORD; + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + if (r->in.nt_verifier == NULL) { + status = NT_STATUS_WRONG_PASSWORD; + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + /* check NT verifier */ + mdfour(new_nt_hash, new_password.data, new_password.length); + + rc = E_old_pw_hash(new_nt_hash, nt_pwd->hash, nt_verifier.hash); + if (rc != 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + ldb_transaction_cancel(sam_ctx); + goto failed; + } + if (!mem_equal_const_time(nt_verifier.hash, r->in.nt_verifier->hash, 16)) { + status = NT_STATUS_WRONG_PASSWORD; + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + /* Drop to user privileges for the password change */ + + old_session_info = ldb_get_opaque(sam_ctx, DSDB_SESSION_INFO); + call_session_info = dcesrv_call_session_info(dce_call); + + ret = ldb_set_opaque(sam_ctx, DSDB_SESSION_INFO, call_session_info); + if (ret != LDB_SUCCESS) { + status = NT_STATUS_INVALID_SYSTEM_SERVICE; + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + /* Performs the password modification. We pass the old hashes read out + * from the database since they were already checked against the user- + * provided ones. */ + status = samdb_set_password(sam_ctx, mem_ctx, + user_dn, NULL, + &new_password, + NULL, + DSDB_PASSWORD_CHECKED_AND_CORRECT, + &reason, + &dominfo); + + /* Restore our privileges to system level */ + if (old_session_info != NULL) { + ldb_set_opaque(sam_ctx, DSDB_SESSION_INFO, old_session_info); + } + + if (!NT_STATUS_IS_OK(status)) { + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + /* And this confirms it in a transaction commit */ + ret = ldb_transaction_commit(sam_ctx); + if (ret != LDB_SUCCESS) { + DEBUG(1,("Failed to commit transaction to change password on %s: %s\n", + ldb_dn_get_linearized(user_dn), + ldb_errstring(sam_ctx))); + status = NT_STATUS_TRANSACTION_ABORTED; + goto failed; + } + + status = NT_STATUS_OK; + +failed: + + log_password_change_event(imsg_ctx, + lp_ctx, + dce_call->conn->remote_address, + dce_call->conn->local_address, + "samr_ChangePasswordUser3", + "RC4/DES using NTLM-hash", + r->in.account->string, + user_samAccountName, + status, + user_objectSid); + if (NT_STATUS_IS_OK(status)) { + return NT_STATUS_OK; + } + + /* Only update the badPwdCount if we found the user */ + if (NT_STATUS_EQUAL(status, NT_STATUS_WRONG_PASSWORD)) { + NTSTATUS bad_pwd_status; + + bad_pwd_status = authsam_update_bad_pwd_count( + sam_ctx, msg, ldb_get_default_basedn(sam_ctx)); + if (NT_STATUS_EQUAL(bad_pwd_status, NT_STATUS_ACCOUNT_LOCKED_OUT)) { + status = bad_pwd_status; + } + } else if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { + /* Don't give the game away: (don't allow anonymous users to prove the existence of usernames) */ + status = NT_STATUS_WRONG_PASSWORD; + } + + reject = talloc_zero(mem_ctx, struct userPwdChangeFailureInformation); + if (reject != NULL) { + reject->extendedFailureReason = reason; + + *r->out.reject = reject; + } + + *r->out.dominfo = dominfo; + + return status; +} + +/* + samr_ChangePasswordUser2 + + easy - just a subset of samr_ChangePasswordUser3 +*/ +NTSTATUS dcesrv_samr_ChangePasswordUser2(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_ChangePasswordUser2 *r) +{ + struct samr_ChangePasswordUser3 r2; + struct samr_DomInfo1 *dominfo = NULL; + struct userPwdChangeFailureInformation *reject = NULL; + + r2.in.server = r->in.server; + r2.in.account = r->in.account; + r2.in.nt_password = r->in.nt_password; + r2.in.nt_verifier = r->in.nt_verifier; + r2.in.lm_change = r->in.lm_change; + r2.in.lm_password = r->in.lm_password; + r2.in.lm_verifier = r->in.lm_verifier; + r2.in.password3 = NULL; + r2.out.dominfo = &dominfo; + r2.out.reject = &reject; + + return dcesrv_samr_ChangePasswordUser3(dce_call, mem_ctx, &r2); +} + + +/* + set password via a samr_CryptPassword buffer +*/ +NTSTATUS samr_set_password(struct dcesrv_call_state *dce_call, + struct ldb_context *sam_ctx, + struct ldb_dn *account_dn, struct ldb_dn *domain_dn, + TALLOC_CTX *mem_ctx, + struct samr_CryptPassword *pwbuf) +{ + NTSTATUS nt_status; + DATA_BLOB new_password; + DATA_BLOB session_key = data_blob(NULL, 0); + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t _session_key; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + int rc; + bool encrypted; + + encrypted = dcerpc_is_transport_encrypted(session_info); + if (lpcfg_weak_crypto(lp_ctx) == SAMBA_WEAK_CRYPTO_DISALLOWED && + !encrypted) { + return NT_STATUS_ACCESS_DENIED; + } + + nt_status = dcesrv_transport_session_key(dce_call, &session_key); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_NOTICE("samr: failed to get session key: %s\n", + nt_errstr(nt_status)); + return nt_status; + } + + _session_key = (gnutls_datum_t) { + .data = session_key.data, + .size = session_key.length, + }; + + /* + * This is safe to support as we only have a session key + * over a SMB connection which we force to be encrypted. + */ + GNUTLS_FIPS140_SET_LAX_MODE(); + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &_session_key, + NULL); + if (rc < 0) { + GNUTLS_FIPS140_SET_STRICT_MODE(); + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto out; + } + + rc = gnutls_cipher_decrypt(cipher_hnd, + pwbuf->data, + 516); + gnutls_cipher_deinit(cipher_hnd); + GNUTLS_FIPS140_SET_STRICT_MODE(); + if (rc < 0) { + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto out; + } + + if (!extract_pw_from_buffer(mem_ctx, pwbuf->data, &new_password)) { + DEBUG(3,("samr: failed to decode password buffer\n")); + return NT_STATUS_WRONG_PASSWORD; + } + + /* set the password - samdb needs to know both the domain and user DNs, + so the domain password policy can be used */ + nt_status = samdb_set_password(sam_ctx, + mem_ctx, + account_dn, + domain_dn, + &new_password, + NULL, + DSDB_PASSWORD_RESET, + NULL, + NULL); +out: + return nt_status; +} + + +/* + set password via a samr_CryptPasswordEx buffer +*/ +NTSTATUS samr_set_password_ex(struct dcesrv_call_state *dce_call, + struct ldb_context *sam_ctx, + struct ldb_dn *account_dn, + struct ldb_dn *domain_dn, + TALLOC_CTX *mem_ctx, + struct samr_CryptPasswordEx *pwbuf) +{ + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + NTSTATUS nt_status; + DATA_BLOB new_password; + + /* The confounder is in the last 16 bytes of the buffer */ + DATA_BLOB confounder = data_blob_const(&pwbuf->data[516], 16); + DATA_BLOB pw_data = data_blob_const(pwbuf->data, 516); + DATA_BLOB session_key = data_blob(NULL, 0); + int rc; + bool encrypted; + + nt_status = dcesrv_transport_session_key(dce_call, &session_key); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(3,("samr: failed to get session key: %s " + "=> NT_STATUS_WRONG_PASSWORD\n", + nt_errstr(nt_status))); + return NT_STATUS_WRONG_PASSWORD; + } + + encrypted = dcerpc_is_transport_encrypted(session_info); + if (lpcfg_weak_crypto(lp_ctx) == SAMBA_WEAK_CRYPTO_DISALLOWED && + !encrypted) { + return NT_STATUS_ACCESS_DENIED; + } + + GNUTLS_FIPS140_SET_LAX_MODE(); + rc = samba_gnutls_arcfour_confounded_md5(&confounder, + &session_key, + &pw_data, + SAMBA_GNUTLS_DECRYPT); + GNUTLS_FIPS140_SET_STRICT_MODE(); + if (rc < 0) { + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + goto out; + } + + if (!extract_pw_from_buffer(mem_ctx, pwbuf->data, &new_password)) { + DEBUG(3,("samr: failed to decode password buffer\n")); + nt_status = NT_STATUS_WRONG_PASSWORD; + goto out; + } + + /* set the password - samdb needs to know both the domain and user DNs, + so the domain password policy can be used */ + nt_status = samdb_set_password(sam_ctx, + mem_ctx, + account_dn, + domain_dn, + &new_password, + NULL, + DSDB_PASSWORD_RESET, + NULL, + NULL); + ZERO_ARRAY_LEN(new_password.data, + new_password.length); + +out: + return nt_status; +} + +/* + set password via encrypted NT and LM hash buffers +*/ +NTSTATUS samr_set_password_buffers(struct dcesrv_call_state *dce_call, + struct ldb_context *sam_ctx, + struct ldb_dn *account_dn, + struct ldb_dn *domain_dn, + TALLOC_CTX *mem_ctx, + const uint8_t *lm_pwd_hash, + const uint8_t *nt_pwd_hash) +{ + struct samr_Password *d_lm_pwd_hash = NULL, *d_nt_pwd_hash = NULL; + uint8_t random_session_key[16] = { 0, }; + DATA_BLOB session_key = data_blob(NULL, 0); + DATA_BLOB in, out; + NTSTATUS nt_status = NT_STATUS_OK; + int rc; + + nt_status = dcesrv_transport_session_key(dce_call, &session_key); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_USER_SESSION_KEY)) { + DEBUG(3,("samr: failed to get session key: %s " + "=> use a random session key\n", + nt_errstr(nt_status))); + + /* + * Windows just uses a random key + */ + generate_random_buffer(random_session_key, + sizeof(random_session_key)); + session_key = data_blob_const(random_session_key, + sizeof(random_session_key)); + nt_status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + if (nt_pwd_hash != NULL) { + in = data_blob_const(nt_pwd_hash, 16); + out = data_blob_talloc_zero(mem_ctx, 16); + + rc = sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_DECRYPT); + if (rc != 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + + d_nt_pwd_hash = (struct samr_Password *) out.data; + } + + if ((d_lm_pwd_hash != NULL) || (d_nt_pwd_hash != NULL)) { + nt_status = samdb_set_password(sam_ctx, mem_ctx, account_dn, + domain_dn, NULL, + d_nt_pwd_hash, + DSDB_PASSWORD_RESET, + NULL, NULL); + } + + return nt_status; +} + +NTSTATUS samr_set_password_aes(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *cdk, + struct ldb_context *sam_ctx, + struct ldb_dn *account_dn, + struct ldb_dn *domain_dn, + struct samr_EncryptedPasswordAES *pwbuf, + enum dsdb_password_checked old_password_checked) +{ + DATA_BLOB pw_data = data_blob_null; + DATA_BLOB new_password = data_blob_null; + const DATA_BLOB ciphertext = + data_blob_const(pwbuf->cipher, pwbuf->cipher_len); + DATA_BLOB iv = data_blob_const(pwbuf->salt, sizeof(pwbuf->salt)); + NTSTATUS nt_status = NT_STATUS_OK; + bool ok; + + nt_status = samba_gnutls_aead_aes_256_cbc_hmac_sha512_decrypt( + mem_ctx, + &ciphertext, + cdk, + &samr_aes256_enc_key_salt, + &samr_aes256_mac_key_salt, + &iv, + pwbuf->auth_data, + &pw_data); + if (!NT_STATUS_IS_OK(nt_status)) { + return NT_STATUS_WRONG_PASSWORD; + } + + ok = extract_pwd_blob_from_buffer514(mem_ctx, + pw_data.data, + &new_password); + TALLOC_FREE(pw_data.data); + if (!ok) { + DBG_NOTICE("samr: failed to decode password buffer\n"); + return NT_STATUS_WRONG_PASSWORD; + } + + nt_status = samdb_set_password(sam_ctx, + mem_ctx, + account_dn, + domain_dn, + &new_password, + NULL, + old_password_checked, + NULL, + NULL); + TALLOC_FREE(new_password.data); + + return nt_status; +} |