diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
commit | 8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch) | |
tree | 4099e8021376c7d8c05bdf8503093d80e9c7bad0 /librpc/rpc | |
parent | Initial commit. (diff) | |
download | samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip |
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'librpc/rpc')
-rw-r--r-- | librpc/rpc/binding.c | 1502 | ||||
-rw-r--r-- | librpc/rpc/binding_handle.c | 568 | ||||
-rw-r--r-- | librpc/rpc/dcerpc_error.c | 153 | ||||
-rw-r--r-- | librpc/rpc/dcerpc_helper.c | 132 | ||||
-rw-r--r-- | librpc/rpc/dcerpc_helper.h | 26 | ||||
-rw-r--r-- | librpc/rpc/dcerpc_pkt_auth.c | 500 | ||||
-rw-r--r-- | librpc/rpc/dcerpc_pkt_auth.h | 59 | ||||
-rw-r--r-- | librpc/rpc/dcerpc_samr.h | 42 | ||||
-rw-r--r-- | librpc/rpc/dcerpc_util.c | 1140 | ||||
-rw-r--r-- | librpc/rpc/dcerpc_util.h | 85 | ||||
-rw-r--r-- | librpc/rpc/dcesrv_auth.c | 703 | ||||
-rw-r--r-- | librpc/rpc/dcesrv_core.c | 3369 | ||||
-rw-r--r-- | librpc/rpc/dcesrv_core.h | 715 | ||||
-rw-r--r-- | librpc/rpc/dcesrv_handles.c | 372 | ||||
-rw-r--r-- | librpc/rpc/dcesrv_mgmt.c | 169 | ||||
-rw-r--r-- | librpc/rpc/dcesrv_reply.c | 309 | ||||
-rw-r--r-- | librpc/rpc/rpc_common.h | 412 | ||||
-rw-r--r-- | librpc/rpc/server/netlogon/schannel_util.c | 570 | ||||
-rw-r--r-- | librpc/rpc/server/netlogon/schannel_util.h | 54 |
19 files changed, 10880 insertions, 0 deletions
diff --git a/librpc/rpc/binding.c b/librpc/rpc/binding.c new file mode 100644 index 0000000..eaf3430 --- /dev/null +++ b/librpc/rpc/binding.c @@ -0,0 +1,1502 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc utility functions + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Jelmer Vernooij 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + Copyright (C) Rafal Szczesniak 2006 + Copyright (C) Stefan Metzmacher 2014 + + 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 "../../lib/util/util_net.h" +#include "librpc/gen_ndr/ndr_epmapper.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/rpc/dcerpc.h" +#include "rpc_common.h" + +#undef strcasecmp +#undef strncasecmp + +#define MAX_PROTSEQ 10 + +struct dcerpc_binding { + enum dcerpc_transport_t transport; + struct GUID object; + const char *object_string; + const char *host; + const char *target_hostname; + const char *target_principal; + const char *endpoint; + const char **options; + uint32_t flags; + uint32_t assoc_group_id; + char assoc_group_string[11]; /* 0x3456789a + '\0' */ +}; + +static const struct { + const char *name; + enum dcerpc_transport_t transport; + int num_protocols; + enum epm_protocol protseq[MAX_PROTSEQ]; +} transports[] = { + { "ncacn_np", NCACN_NP, 3, + { EPM_PROTOCOL_NCACN, EPM_PROTOCOL_SMB, EPM_PROTOCOL_NETBIOS }}, + { "ncacn_ip_tcp", NCACN_IP_TCP, 3, + { EPM_PROTOCOL_NCACN, EPM_PROTOCOL_TCP, EPM_PROTOCOL_IP } }, + { "ncacn_http", NCACN_HTTP, 3, + { EPM_PROTOCOL_NCACN, EPM_PROTOCOL_HTTP, EPM_PROTOCOL_IP } }, + { "ncadg_ip_udp", NCACN_IP_UDP, 3, + { EPM_PROTOCOL_NCADG, EPM_PROTOCOL_UDP, EPM_PROTOCOL_IP } }, + { "ncalrpc", NCALRPC, 2, + { EPM_PROTOCOL_NCALRPC, EPM_PROTOCOL_NAMED_PIPE } }, + { "ncacn_unix_stream", NCACN_UNIX_STREAM, 2, + { EPM_PROTOCOL_NCACN, EPM_PROTOCOL_UNIX_DS } }, + { "ncadg_unix_dgram", NCADG_UNIX_DGRAM, 2, + { EPM_PROTOCOL_NCADG, EPM_PROTOCOL_UNIX_DS } }, + { "ncacn_at_dsp", NCACN_AT_DSP, 3, + { EPM_PROTOCOL_NCACN, EPM_PROTOCOL_APPLETALK, EPM_PROTOCOL_DSP } }, + { "ncadg_at_ddp", NCADG_AT_DDP, 3, + { EPM_PROTOCOL_NCADG, EPM_PROTOCOL_APPLETALK, EPM_PROTOCOL_DDP } }, + { "ncacn_vns_ssp", NCACN_VNS_SPP, 3, + { EPM_PROTOCOL_NCACN, EPM_PROTOCOL_STREETTALK, EPM_PROTOCOL_VINES_SPP } }, + { "ncacn_vns_ipc", NCACN_VNS_IPC, 3, + { EPM_PROTOCOL_NCACN, EPM_PROTOCOL_STREETTALK, EPM_PROTOCOL_VINES_IPC }, }, + { "ncadg_ipx", NCADG_IPX, 2, + { EPM_PROTOCOL_NCADG, EPM_PROTOCOL_IPX }, + }, + { "ncacn_spx", NCACN_SPX, 3, + /* I guess some MS programmer confused the identifier for + * EPM_PROTOCOL_UUID (0x0D or 13) with the one for + * EPM_PROTOCOL_SPX (0x13) here. -- jelmer*/ + { EPM_PROTOCOL_NCACN, EPM_PROTOCOL_NCALRPC, EPM_PROTOCOL_UUID }, + }, +}; + +static const struct ncacn_option { + const char *name; + uint32_t flag; +} ncacn_options[] = { + {"sign", DCERPC_SIGN}, + {"seal", DCERPC_SEAL}, + {"connect", DCERPC_CONNECT}, + {"spnego", DCERPC_AUTH_SPNEGO}, + {"ntlm", DCERPC_AUTH_NTLM}, + {"krb5", DCERPC_AUTH_KRB5}, + {"schannel", DCERPC_SCHANNEL | DCERPC_SCHANNEL_AUTO}, + {"validate", DCERPC_DEBUG_VALIDATE_BOTH}, + {"print", DCERPC_DEBUG_PRINT_BOTH}, + {"padcheck", DCERPC_DEBUG_PAD_CHECK}, + {"bigendian", DCERPC_PUSH_BIGENDIAN}, + {"smb1", DCERPC_SMB1}, + {"smb2", DCERPC_SMB2}, + {"ndr64", DCERPC_NDR64}, + {"packet", DCERPC_PACKET}, +}; + +static const struct ncacn_option *ncacn_option_by_name(const char *name) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(ncacn_options); i++) { + int ret; + + ret = strcasecmp(ncacn_options[i].name, name); + if (ret != 0) { + continue; + } + + return &ncacn_options[i]; + } + + return NULL; +} + +const char *epm_floor_string(TALLOC_CTX *mem_ctx, struct epm_floor *epm_floor) +{ + struct ndr_syntax_id syntax; + NTSTATUS status; + + switch(epm_floor->lhs.protocol) { + case EPM_PROTOCOL_UUID: + status = dcerpc_floor_get_uuid_full(epm_floor, &syntax); + if (NT_STATUS_IS_OK(status)) { + /* lhs is used: UUID */ + struct GUID_txt_buf buf; + + if (GUID_equal(&syntax.uuid, &ndr_transfer_syntax_ndr.uuid)) { + return "NDR"; + } + + if (GUID_equal(&syntax.uuid, &ndr_transfer_syntax_ndr64.uuid)) { + return "NDR64"; + } + + return talloc_asprintf( + mem_ctx, + " uuid %s/0x%02x", + GUID_buf_string(&syntax.uuid, &buf), + syntax.if_version); + } else { /* IPX */ + return talloc_asprintf(mem_ctx, "IPX:%s", + data_blob_hex_string_upper(mem_ctx, &epm_floor->rhs.uuid.unknown)); + } + + case EPM_PROTOCOL_NCACN: + return "RPC-C"; + + case EPM_PROTOCOL_NCADG: + return "RPC"; + + case EPM_PROTOCOL_NCALRPC: + return "NCALRPC"; + + case EPM_PROTOCOL_DNET_NSP: + return "DNET/NSP"; + + case EPM_PROTOCOL_IP: + return talloc_asprintf(mem_ctx, "IP:%s", epm_floor->rhs.ip.ipaddr); + + case EPM_PROTOCOL_NAMED_PIPE: + return talloc_asprintf(mem_ctx, "NAMED-PIPE:%s", epm_floor->rhs.named_pipe.path); + + case EPM_PROTOCOL_SMB: + return talloc_asprintf(mem_ctx, "SMB:%s", epm_floor->rhs.smb.unc); + + case EPM_PROTOCOL_UNIX_DS: + return talloc_asprintf(mem_ctx, "Unix:%s", epm_floor->rhs.unix_ds.path); + + case EPM_PROTOCOL_NETBIOS: + return talloc_asprintf(mem_ctx, "NetBIOS:%s", epm_floor->rhs.netbios.name); + + case EPM_PROTOCOL_NETBEUI: + return "NETBeui"; + + case EPM_PROTOCOL_SPX: + return "SPX"; + + case EPM_PROTOCOL_NB_IPX: + return "NB_IPX"; + + case EPM_PROTOCOL_HTTP: + return talloc_asprintf(mem_ctx, "HTTP:%"PRIu16, epm_floor->rhs.http.port); + + case EPM_PROTOCOL_TCP: + return talloc_asprintf(mem_ctx, "TCP:%"PRIu16, epm_floor->rhs.tcp.port); + + case EPM_PROTOCOL_UDP: + return talloc_asprintf(mem_ctx, "UDP:%"PRIu16, epm_floor->rhs.udp.port); + + default: + return talloc_asprintf(mem_ctx, "UNK(%02x):", epm_floor->lhs.protocol); + } +} + + +/* + form a binding string from a binding structure +*/ +_PUBLIC_ char *dcerpc_binding_string(TALLOC_CTX *mem_ctx, const struct dcerpc_binding *b) +{ + char *s = NULL; + size_t i; + const char *t_name = NULL; + bool option_section = false; + const char *target_hostname = NULL; + + if (b->transport != NCA_UNKNOWN) { + t_name = derpc_transport_string_by_transport(b->transport); + if (!t_name) { + return NULL; + } + } + + s = talloc_strdup(mem_ctx, ""); + + if (!GUID_all_zero(&b->object)) { + struct GUID_txt_buf buf; + talloc_asprintf_addbuf( + &s, "%s@", GUID_buf_string(&b->object, &buf)); + } + + if (t_name != NULL) { + talloc_asprintf_addbuf(&s, "%s:", t_name); + } + + if (b->host) { + talloc_asprintf_addbuf(&s, "%s", b->host); + } + + target_hostname = b->target_hostname; + if (target_hostname != NULL && b->host != NULL) { + if (strcmp(target_hostname, b->host) == 0) { + target_hostname = NULL; + } + } + + option_section = + (b->endpoint != NULL) || + (target_hostname != NULL) || + (b->target_principal != NULL) || + (b->assoc_group_id != 0) || + (b->options != NULL) || + (b->flags != 0); + + if (!option_section) { + return s; + } + + talloc_asprintf_addbuf(&s, "["); + + if (b->endpoint) { + talloc_asprintf_addbuf(&s, "%s", b->endpoint); + } + + for (i=0;i<ARRAY_SIZE(ncacn_options);i++) { + if (!(b->flags & ncacn_options[i].flag)) { + continue; + } + + talloc_asprintf_addbuf(&s, ",%s", ncacn_options[i].name); + } + + if (target_hostname) { + talloc_asprintf_addbuf( + &s, ",target_hostname=%s", b->target_hostname); + } + + if (b->target_principal) { + talloc_asprintf_addbuf( + &s, ",target_principal=%s", b->target_principal); + } + + if (b->assoc_group_id != 0) { + talloc_asprintf_addbuf( + &s, ",assoc_group_id=0x%08x", b->assoc_group_id); + } + + for (i=0;b->options && b->options[i];i++) { + talloc_asprintf_addbuf(&s, ",%s", b->options[i]); + } + + talloc_asprintf_addbuf(&s, "]"); + + return s; +} + +/* + parse a binding string into a dcerpc_binding structure +*/ +_PUBLIC_ NTSTATUS dcerpc_parse_binding(TALLOC_CTX *mem_ctx, const char *_s, struct dcerpc_binding **b_out) +{ + char *_t; + struct dcerpc_binding *b; + char *s; + char *options = NULL; + char *p; + size_t i; + NTSTATUS status; + + b = talloc_zero(mem_ctx, struct dcerpc_binding); + if (!b) { + return NT_STATUS_NO_MEMORY; + } + + _t = talloc_strdup(b, _s); + if (_t == NULL) { + talloc_free(b); + return NT_STATUS_NO_MEMORY; + } + + s = _t; + + p = strchr(s, '['); + if (p) { + char *q = p + strlen(p) - 1; + if (*q != ']') { + talloc_free(b); + return NT_STATUS_INVALID_PARAMETER_MIX; + } + *p = '\0'; + *q = '\0'; + options = p + 1; + } + + p = strchr(s, '@'); + + if (p && PTR_DIFF(p, s) == 36) { /* 36 is the length of a UUID */ + *p = '\0'; + + status = dcerpc_binding_set_string_option(b, "object", s); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(b); + return status; + } + + s = p + 1; + } + + p = strchr(s, ':'); + + if (p == NULL) { + b->transport = NCA_UNKNOWN; + } else if (is_ipaddress_v6(s)) { + b->transport = NCA_UNKNOWN; + } else { + *p = '\0'; + + status = dcerpc_binding_set_string_option(b, "transport", s); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(b); + return status; + } + + s = p + 1; + } + + if (strlen(s) > 0) { + status = dcerpc_binding_set_string_option(b, "host", s); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(b); + return status; + } + + b->target_hostname = talloc_strdup(b, b->host); + if (b->target_hostname == NULL) { + talloc_free(b); + return NT_STATUS_NO_MEMORY; + } + } + + for (i=0; options != NULL; i++) { + const char *name = options; + const char *value = NULL; + + p = strchr(options, ','); + if (p != NULL) { + *p = '\0'; + options = p+1; + } else { + options = NULL; + } + + p = strchr(name, '='); + if (p != NULL) { + *p = '\0'; + value = p + 1; + } + + if (value == NULL) { + /* + * If it's not a key=value pair + * it might be a ncacn_option + * or if it's the first option + * it's the endpoint. + */ + const struct ncacn_option *no = NULL; + + value = name; + + no = ncacn_option_by_name(name); + if (no == NULL) { + if (i > 0) { + /* + * we don't allow unknown options + */ + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + /* + * This is the endpoint + */ + name = "endpoint"; + if (strlen(value) == 0) { + value = NULL; + } + } + } + + status = dcerpc_binding_set_string_option(b, name, value); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(b); + return status; + } + } + + talloc_free(_t); + *b_out = b; + return NT_STATUS_OK; +} + +_PUBLIC_ struct GUID dcerpc_binding_get_object(const struct dcerpc_binding *b) +{ + return b->object; +} + +_PUBLIC_ NTSTATUS dcerpc_binding_set_object(struct dcerpc_binding *b, + struct GUID object) +{ + char *tmp = discard_const_p(char, b->object_string); + + if (GUID_all_zero(&object)) { + talloc_free(tmp); + b->object_string = NULL; + ZERO_STRUCT(b->object); + return NT_STATUS_OK; + } + + b->object_string = GUID_string(b, &object); + if (b->object_string == NULL) { + b->object_string = tmp; + return NT_STATUS_NO_MEMORY; + } + talloc_free(tmp); + + b->object = object; + return NT_STATUS_OK; +} + +_PUBLIC_ enum dcerpc_transport_t dcerpc_binding_get_transport(const struct dcerpc_binding *b) +{ + return b->transport; +} + +_PUBLIC_ NTSTATUS dcerpc_binding_set_transport(struct dcerpc_binding *b, + enum dcerpc_transport_t transport) +{ + NTSTATUS status; + + /* + * TODO: we may want to check the transport value is + * wellknown. + */ + if (b->transport == transport) { + return NT_STATUS_OK; + } + + /* + * This implicitly resets the endpoint + * as the endpoint is transport specific. + * + * It also resets the assoc group as it's + * also endpoint specific. + * + * TODO: in future we may reset more options + * here. + */ + status = dcerpc_binding_set_string_option(b, "endpoint", NULL); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + b->assoc_group_id = 0; + + b->transport = transport; + return NT_STATUS_OK; +} + +_PUBLIC_ void dcerpc_binding_get_auth_info(const struct dcerpc_binding *b, + enum dcerpc_AuthType *_auth_type, + enum dcerpc_AuthLevel *_auth_level) +{ + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + if (b->flags & DCERPC_AUTH_SPNEGO) { + auth_type = DCERPC_AUTH_TYPE_SPNEGO; + } else if (b->flags & DCERPC_AUTH_KRB5) { + auth_type = DCERPC_AUTH_TYPE_KRB5; + } else if (b->flags & DCERPC_SCHANNEL) { + auth_type = DCERPC_AUTH_TYPE_SCHANNEL; + } else if (b->flags & DCERPC_AUTH_NTLM) { + auth_type = DCERPC_AUTH_TYPE_NTLMSSP; + } else { + auth_type = DCERPC_AUTH_TYPE_NONE; + } + + if (b->flags & DCERPC_SEAL) { + auth_level = DCERPC_AUTH_LEVEL_PRIVACY; + } else if (b->flags & DCERPC_SIGN) { + auth_level = DCERPC_AUTH_LEVEL_INTEGRITY; + } else if (b->flags & DCERPC_CONNECT) { + auth_level = DCERPC_AUTH_LEVEL_CONNECT; + } else if (b->flags & DCERPC_PACKET) { + auth_level = DCERPC_AUTH_LEVEL_PACKET; + } else if (auth_type != DCERPC_AUTH_TYPE_NONE) { + auth_level = DCERPC_AUTH_LEVEL_INTEGRITY; + } else { + auth_level = DCERPC_AUTH_LEVEL_NONE; + } + + if (_auth_type != NULL) { + *_auth_type = auth_type; + } + + if (_auth_level != NULL) { + *_auth_level = auth_level; + } +} + +_PUBLIC_ uint32_t dcerpc_binding_get_assoc_group_id(const struct dcerpc_binding *b) +{ + return b->assoc_group_id; +} + +_PUBLIC_ NTSTATUS dcerpc_binding_set_assoc_group_id(struct dcerpc_binding *b, + uint32_t assoc_group_id) +{ + b->assoc_group_id = assoc_group_id; + return NT_STATUS_OK; +} + +_PUBLIC_ struct ndr_syntax_id dcerpc_binding_get_abstract_syntax(const struct dcerpc_binding *b) +{ + const char *s = dcerpc_binding_get_string_option(b, "abstract_syntax"); + bool ok; + struct ndr_syntax_id id; + + if (s == NULL) { + return ndr_syntax_id_null; + } + + ok = ndr_syntax_id_from_string(s, &id); + if (!ok) { + return ndr_syntax_id_null; + } + + return id; +} + +_PUBLIC_ NTSTATUS dcerpc_binding_set_abstract_syntax(struct dcerpc_binding *b, + const struct ndr_syntax_id *syntax) +{ + NTSTATUS status; + struct ndr_syntax_id_buf buf; + + if (syntax == NULL) { + status = dcerpc_binding_set_string_option(b, "abstract_syntax", NULL); + return status; + } + + if (ndr_syntax_id_equal(&ndr_syntax_id_null, syntax)) { + status = dcerpc_binding_set_string_option(b, "abstract_syntax", NULL); + return status; + } + + status = dcerpc_binding_set_string_option( + b, "abstract_syntax", ndr_syntax_id_buf_string(syntax, &buf)); + return status; +} + +_PUBLIC_ const char *dcerpc_binding_get_string_option(const struct dcerpc_binding *b, + const char *name) +{ + struct { + const char *name; + const char *value; +#define _SPECIAL(x) { .name = #x, .value = b->x, } + } specials[] = { + { .name = "object", .value = b->object_string, }, + _SPECIAL(host), + _SPECIAL(endpoint), + _SPECIAL(target_hostname), + _SPECIAL(target_principal), +#undef _SPECIAL + }; + const struct ncacn_option *no = NULL; + size_t name_len = strlen(name); + size_t i; + int ret; + + ret = strcmp(name, "transport"); + if (ret == 0) { + return derpc_transport_string_by_transport(b->transport); + } + + ret = strcmp(name, "assoc_group_id"); + if (ret == 0) { + char *tmp = discard_const_p(char, b->assoc_group_string); + + if (b->assoc_group_id == 0) { + return NULL; + } + + snprintf(tmp, sizeof(b->assoc_group_string), + "0x%08x", b->assoc_group_id); + return (const char *)b->assoc_group_string; + } + + for (i=0; i < ARRAY_SIZE(specials); i++) { + ret = strcmp(specials[i].name, name); + if (ret != 0) { + continue; + } + + return specials[i].value; + } + + no = ncacn_option_by_name(name); + if (no != NULL) { + if (b->flags & no->flag) { + return no->name; + } + + return NULL; + } + + if (b->options == NULL) { + return NULL; + } + + for (i=0; b->options[i]; i++) { + const char *o = b->options[i]; + const char *vs = NULL; + + ret = strncmp(name, o, name_len); + if (ret != 0) { + continue; + } + + if (o[name_len] != '=') { + continue; + } + + vs = &o[name_len + 1]; + + return vs; + } + + return NULL; +} + +_PUBLIC_ char *dcerpc_binding_copy_string_option(TALLOC_CTX *mem_ctx, + const struct dcerpc_binding *b, + const char *name) +{ + const char *c = dcerpc_binding_get_string_option(b, name); + char *v; + + if (c == NULL) { + errno = ENOENT; + return NULL; + } + + v = talloc_strdup(mem_ctx, c); + if (v == NULL) { + errno = ENOMEM; + return NULL; + } + + return v; +} + +_PUBLIC_ NTSTATUS dcerpc_binding_set_string_option(struct dcerpc_binding *b, + const char *name, + const char *value) +{ + struct { + const char *name; + const char **ptr; +#define _SPECIAL(x) { .name = #x, .ptr = &b->x, } + } specials[] = { + _SPECIAL(host), + _SPECIAL(endpoint), + _SPECIAL(target_hostname), + _SPECIAL(target_principal), +#undef _SPECIAL + }; + const struct ncacn_option *no = NULL; + size_t name_len = strlen(name); + const char *opt = NULL; + char *tmp; + size_t i; + int ret; + + /* + * Note: value == NULL, means delete it. + * value != NULL means add or reset. + */ + + ret = strcmp(name, "transport"); + if (ret == 0) { + enum dcerpc_transport_t t = dcerpc_transport_by_name(value); + + if (t == NCA_UNKNOWN && value != NULL) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + return dcerpc_binding_set_transport(b, t); + } + + ret = strcmp(name, "object"); + if (ret == 0) { + NTSTATUS status; + struct GUID uuid = GUID_zero(); + + if (value != NULL) { + DATA_BLOB blob; + blob = data_blob_string_const(value); + if (blob.length != 36) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + status = GUID_from_data_blob(&blob, &uuid); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return dcerpc_binding_set_object(b, uuid); + } + + ret = strcmp(name, "assoc_group_id"); + if (ret == 0) { + uint32_t assoc_group_id = 0; + + if (value != NULL) { + char c; + + ret = sscanf(value, "0x%08x%c", &assoc_group_id, &c); + if (ret != 1) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + } + + return dcerpc_binding_set_assoc_group_id(b, assoc_group_id); + } + + for (i=0; i < ARRAY_SIZE(specials); i++) { + ret = strcmp(specials[i].name, name); + if (ret != 0) { + continue; + } + + tmp = discard_const_p(char, *specials[i].ptr); + + if (value == NULL) { + talloc_free(tmp); + *specials[i].ptr = NULL; + return NT_STATUS_OK; + } + + if (value[0] == '\0') { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + *specials[i].ptr = talloc_strdup(b, value); + if (*specials[i].ptr == NULL) { + *specials[i].ptr = tmp; + return NT_STATUS_NO_MEMORY; + } + talloc_free(tmp); + + return NT_STATUS_OK; + } + + no = ncacn_option_by_name(name); + if (no != NULL) { + if (value == NULL) { + b->flags &= ~no->flag; + return NT_STATUS_OK; + } + + ret = strcasecmp(no->name, value); + if (ret != 0) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + b->flags |= no->flag; + return NT_STATUS_OK; + } + + for (i=0; b->options && b->options[i]; i++) { + const char *o = b->options[i]; + + ret = strncmp(name, o, name_len); + if (ret != 0) { + continue; + } + + if (o[name_len] != '=') { + continue; + } + + opt = o; + break; + } + + if (opt == NULL) { + const char **n; + + if (value == NULL) { + return NT_STATUS_OK; + } + + n = talloc_realloc(b, b->options, const char *, i + 2); + if (n == NULL) { + return NT_STATUS_NO_MEMORY; + } + n[i] = NULL; + n[i + 1] = NULL; + b->options = n; + } + + tmp = discard_const_p(char, opt); + + if (value == NULL) { + for (;b->options[i];i++) { + b->options[i] = b->options[i+1]; + } + talloc_free(tmp); + return NT_STATUS_OK; + } + + b->options[i] = talloc_asprintf(b->options, "%s=%s", + name, value); + if (b->options[i] == NULL) { + b->options[i] = tmp; + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +_PUBLIC_ uint32_t dcerpc_binding_get_flags(const struct dcerpc_binding *b) +{ + return b->flags; +} + +_PUBLIC_ NTSTATUS dcerpc_binding_set_flags(struct dcerpc_binding *b, + uint32_t additional, + uint32_t clear) +{ + /* + * TODO: in future we may want to reject invalid combinations + */ + b->flags &= ~clear; + b->flags |= additional; + + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS dcerpc_floor_get_uuid_full(const struct epm_floor *epm_floor, + struct ndr_syntax_id *syntax) +{ + TALLOC_CTX *mem_ctx = talloc_init("floor_get_lhs_data"); + struct ndr_pull *ndr; + enum ndr_err_code ndr_err; + uint16_t if_version=0; + + *syntax = (struct ndr_syntax_id) { .if_version = 0, }; + + if (epm_floor->lhs.protocol != EPM_PROTOCOL_UUID) { + talloc_free(mem_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + ndr = ndr_pull_init_blob(&epm_floor->lhs.lhs_data, mem_ctx); + if (ndr == NULL) { + talloc_free(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + ndr->flags |= LIBNDR_FLAG_NOALIGN; + + ndr_err = ndr_pull_GUID(ndr, NDR_SCALARS | NDR_BUFFERS, &syntax->uuid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(mem_ctx); + return ndr_map_error2ntstatus(ndr_err); + } + + ndr_err = ndr_pull_uint16(ndr, NDR_SCALARS, &if_version); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(mem_ctx); + return ndr_map_error2ntstatus(ndr_err); + } + + syntax->if_version = if_version; + + TALLOC_FREE(ndr); + + ndr = ndr_pull_init_blob(&epm_floor->rhs.uuid.unknown, mem_ctx); + if (ndr == NULL) { + talloc_free(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + ndr->flags |= LIBNDR_FLAG_NOALIGN; + + ndr_err = ndr_pull_uint16(ndr, NDR_SCALARS, &if_version); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(mem_ctx); + return ndr_map_error2ntstatus(ndr_err); + } + + syntax->if_version |= (((uint32_t)if_version) << 16) & 0xffff0000; + + talloc_free(mem_ctx); + + return NT_STATUS_OK; +} + +static DATA_BLOB dcerpc_floor_pack_lhs_data(TALLOC_CTX *mem_ctx, const struct ndr_syntax_id *syntax) +{ + DATA_BLOB blob; + enum ndr_err_code ndr_err; + struct ndr_push *ndr; + + ndr = ndr_push_init_ctx(mem_ctx); + if (ndr == NULL) { + return data_blob_null; + } + + ndr->flags |= LIBNDR_FLAG_NOALIGN; + + ndr_err = ndr_push_GUID(ndr, NDR_SCALARS | NDR_BUFFERS, &syntax->uuid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return data_blob_null; + } + ndr_err = ndr_push_uint16(ndr, NDR_SCALARS, syntax->if_version); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return data_blob_null; + } + + blob = ndr_push_blob(ndr); + talloc_steal(mem_ctx, blob.data); + talloc_free(ndr); + return blob; +} + +static bool dcerpc_floor_pack_rhs_if_version_data( + TALLOC_CTX *mem_ctx, const struct ndr_syntax_id *syntax, + DATA_BLOB *pblob) +{ + DATA_BLOB blob; + struct ndr_push *ndr = ndr_push_init_ctx(mem_ctx); + enum ndr_err_code ndr_err; + + if (ndr == NULL) { + return false; + } + + ndr->flags |= LIBNDR_FLAG_NOALIGN; + + ndr_err = ndr_push_uint16(ndr, NDR_SCALARS, syntax->if_version >> 16); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return false; + } + + blob = ndr_push_blob(ndr); + talloc_steal(mem_ctx, blob.data); + talloc_free(ndr); + *pblob = blob; + return true; +} + +static NTSTATUS dcerpc_floor_pack_uuid_full(TALLOC_CTX *mem_ctx, + struct epm_floor *floor, + const struct ndr_syntax_id *syntax) +{ + bool ok; + + floor->lhs.protocol = EPM_PROTOCOL_UUID; + + floor->lhs.lhs_data = dcerpc_floor_pack_lhs_data(mem_ctx, syntax); + if (floor->lhs.lhs_data.data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ok = dcerpc_floor_pack_rhs_if_version_data(mem_ctx, syntax, + &floor->rhs.uuid.unknown); + if (!ok) { + data_blob_free(&floor->lhs.lhs_data); + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +char *dcerpc_floor_get_rhs_data(TALLOC_CTX *mem_ctx, struct epm_floor *epm_floor) +{ + switch (epm_floor->lhs.protocol) { + case EPM_PROTOCOL_TCP: + if (epm_floor->rhs.tcp.port == 0) return NULL; + return talloc_asprintf(mem_ctx, "%"PRIu16, epm_floor->rhs.tcp.port); + + case EPM_PROTOCOL_UDP: + if (epm_floor->rhs.udp.port == 0) return NULL; + return talloc_asprintf(mem_ctx, "%"PRIu16, epm_floor->rhs.udp.port); + + case EPM_PROTOCOL_HTTP: + if (epm_floor->rhs.http.port == 0) return NULL; + return talloc_asprintf(mem_ctx, "%"PRIu16, epm_floor->rhs.http.port); + + case EPM_PROTOCOL_IP: + return talloc_strdup(mem_ctx, epm_floor->rhs.ip.ipaddr); + + case EPM_PROTOCOL_NCACN: + return NULL; + + case EPM_PROTOCOL_NCADG: + return NULL; + + case EPM_PROTOCOL_SMB: + if (strlen(epm_floor->rhs.smb.unc) == 0) return NULL; + return talloc_strdup(mem_ctx, epm_floor->rhs.smb.unc); + + case EPM_PROTOCOL_NAMED_PIPE: + if (strlen(epm_floor->rhs.named_pipe.path) == 0) return NULL; + return talloc_strdup(mem_ctx, epm_floor->rhs.named_pipe.path); + + case EPM_PROTOCOL_NETBIOS: + if (strlen(epm_floor->rhs.netbios.name) == 0) return NULL; + return talloc_strdup(mem_ctx, epm_floor->rhs.netbios.name); + + case EPM_PROTOCOL_NCALRPC: + return NULL; + + case EPM_PROTOCOL_VINES_SPP: + return talloc_asprintf(mem_ctx, "%"PRIu16, epm_floor->rhs.vines_spp.port); + + case EPM_PROTOCOL_VINES_IPC: + return talloc_asprintf(mem_ctx, "%"PRIu16, epm_floor->rhs.vines_ipc.port); + + case EPM_PROTOCOL_STREETTALK: + return talloc_strdup(mem_ctx, epm_floor->rhs.streettalk.streettalk); + + case EPM_PROTOCOL_UNIX_DS: + if (strlen(epm_floor->rhs.unix_ds.path) == 0) return NULL; + return talloc_strdup(mem_ctx, epm_floor->rhs.unix_ds.path); + + case EPM_PROTOCOL_NULL: + return NULL; + + default: + DEBUG(0,("Unsupported lhs protocol %d\n", epm_floor->lhs.protocol)); + break; + } + + return NULL; +} + +static NTSTATUS dcerpc_floor_set_rhs_data(TALLOC_CTX *mem_ctx, + struct epm_floor *epm_floor, + const char *data) +{ + if (data == NULL) { + data = ""; + } + + switch (epm_floor->lhs.protocol) { + case EPM_PROTOCOL_TCP: + epm_floor->rhs.tcp.port = atoi(data); + return NT_STATUS_OK; + + case EPM_PROTOCOL_UDP: + epm_floor->rhs.udp.port = atoi(data); + return NT_STATUS_OK; + + case EPM_PROTOCOL_HTTP: + epm_floor->rhs.http.port = atoi(data); + return NT_STATUS_OK; + + case EPM_PROTOCOL_IP: + if (!is_ipaddress_v4(data)) { + data = "0.0.0.0"; + } + epm_floor->rhs.ip.ipaddr = talloc_strdup(mem_ctx, data); + NT_STATUS_HAVE_NO_MEMORY(epm_floor->rhs.ip.ipaddr); + return NT_STATUS_OK; + + case EPM_PROTOCOL_NCACN: + epm_floor->rhs.ncacn.minor_version = 0; + return NT_STATUS_OK; + + case EPM_PROTOCOL_NCADG: + epm_floor->rhs.ncadg.minor_version = 0; + return NT_STATUS_OK; + + case EPM_PROTOCOL_SMB: + epm_floor->rhs.smb.unc = talloc_strdup(mem_ctx, data); + NT_STATUS_HAVE_NO_MEMORY(epm_floor->rhs.smb.unc); + return NT_STATUS_OK; + + case EPM_PROTOCOL_NAMED_PIPE: + epm_floor->rhs.named_pipe.path = talloc_strdup(mem_ctx, data); + NT_STATUS_HAVE_NO_MEMORY(epm_floor->rhs.named_pipe.path); + return NT_STATUS_OK; + + case EPM_PROTOCOL_NETBIOS: + epm_floor->rhs.netbios.name = talloc_strdup(mem_ctx, data); + NT_STATUS_HAVE_NO_MEMORY(epm_floor->rhs.netbios.name); + return NT_STATUS_OK; + + case EPM_PROTOCOL_NCALRPC: + return NT_STATUS_OK; + + case EPM_PROTOCOL_VINES_SPP: + epm_floor->rhs.vines_spp.port = atoi(data); + return NT_STATUS_OK; + + case EPM_PROTOCOL_VINES_IPC: + epm_floor->rhs.vines_ipc.port = atoi(data); + return NT_STATUS_OK; + + case EPM_PROTOCOL_STREETTALK: + epm_floor->rhs.streettalk.streettalk = talloc_strdup(mem_ctx, data); + NT_STATUS_HAVE_NO_MEMORY(epm_floor->rhs.streettalk.streettalk); + return NT_STATUS_OK; + + case EPM_PROTOCOL_UNIX_DS: + epm_floor->rhs.unix_ds.path = talloc_strdup(mem_ctx, data); + NT_STATUS_HAVE_NO_MEMORY(epm_floor->rhs.unix_ds.path); + return NT_STATUS_OK; + + case EPM_PROTOCOL_NULL: + return NT_STATUS_OK; + + default: + DEBUG(0,("Unsupported lhs protocol %d\n", epm_floor->lhs.protocol)); + break; + } + + return NT_STATUS_NOT_SUPPORTED; +} + +enum dcerpc_transport_t dcerpc_transport_by_endpoint_protocol(int prot) +{ + size_t i; + + /* Find a transport that has 'prot' as 4th protocol */ + for (i=0;i<ARRAY_SIZE(transports);i++) { + if (transports[i].num_protocols >= 2 && + transports[i].protseq[1] == prot) { + return transports[i].transport; + } + } + + /* Unknown transport */ + return (unsigned int)-1; +} + +_PUBLIC_ enum dcerpc_transport_t dcerpc_transport_by_tower(const struct epm_tower *tower) +{ + size_t i; + + /* Find a transport that matches this tower */ + for (i=0;i<ARRAY_SIZE(transports);i++) { + int j; + if (transports[i].num_protocols != tower->num_floors - 2) { + continue; + } + + for (j = 0; j < transports[i].num_protocols && j < MAX_PROTSEQ; j++) { + if (transports[i].protseq[j] != tower->floors[j+2].lhs.protocol) { + break; + } + } + + if (j == transports[i].num_protocols) { + return transports[i].transport; + } + } + + /* Unknown transport */ + return (unsigned int)-1; +} + +_PUBLIC_ const char *derpc_transport_string_by_transport(enum dcerpc_transport_t t) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(transports); i++) { + if (t == transports[i].transport) { + return transports[i].name; + } + } + return NULL; +} + +_PUBLIC_ enum dcerpc_transport_t dcerpc_transport_by_name(const char *name) +{ + size_t i; + + if (name == NULL) { + return NCA_UNKNOWN; + } + + for (i=0; i<ARRAY_SIZE(transports);i++) { + if (strcasecmp(name, transports[i].name) == 0) { + return transports[i].transport; + } + } + + return NCA_UNKNOWN; +} + +_PUBLIC_ NTSTATUS dcerpc_binding_from_tower(TALLOC_CTX *mem_ctx, + struct epm_tower *tower, + struct dcerpc_binding **b_out) +{ + NTSTATUS status; + struct dcerpc_binding *b; + enum dcerpc_transport_t transport; + struct ndr_syntax_id abstract_syntax; + char *endpoint = NULL; + char *host = NULL; + + /* + * A tower needs to have at least 4 floors to carry useful + * information. Floor 3 is the transport identifier which defines + * how many floors are required at least. + */ + if (tower->num_floors < 4) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = dcerpc_parse_binding(mem_ctx, "", &b); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + transport = dcerpc_transport_by_tower(tower); + if (transport == NCA_UNKNOWN) { + talloc_free(b); + return NT_STATUS_NOT_SUPPORTED; + } + + status = dcerpc_binding_set_transport(b, transport); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(b); + return status; + } + + /* Set abstract syntax */ + status = dcerpc_floor_get_uuid_full(&tower->floors[0], &abstract_syntax); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(b); + return status; + } + + status = dcerpc_binding_set_abstract_syntax(b, &abstract_syntax); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(b); + return status; + } + + /* Ignore floor 1, it contains the NDR version info */ + + /* Set endpoint */ + errno = 0; + if (tower->num_floors >= 4) { + endpoint = dcerpc_floor_get_rhs_data(b, &tower->floors[3]); + } + if (errno != 0) { + int saved_errno = errno; + talloc_free(b); + return map_nt_error_from_unix_common(saved_errno); + } + + status = dcerpc_binding_set_string_option(b, "endpoint", endpoint); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(b); + return status; + } + TALLOC_FREE(endpoint); + + /* Set network address */ + errno = 0; + if (tower->num_floors >= 5) { + host = dcerpc_floor_get_rhs_data(b, &tower->floors[4]); + } + if (errno != 0) { + int saved_errno = errno; + talloc_free(b); + return map_nt_error_from_unix_common(saved_errno); + } + + status = dcerpc_binding_set_string_option(b, "host", host); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(b); + return status; + } + status = dcerpc_binding_set_string_option(b, "target_hostname", host); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(b); + return status; + } + TALLOC_FREE(host); + + *b_out = b; + return NT_STATUS_OK; +} + +_PUBLIC_ struct dcerpc_binding *dcerpc_binding_dup(TALLOC_CTX *mem_ctx, + const struct dcerpc_binding *b) +{ + struct dcerpc_binding *n; + uint32_t count; + + n = talloc_zero(mem_ctx, struct dcerpc_binding); + if (n == NULL) { + return NULL; + } + + n->transport = b->transport; + n->object = b->object; + n->flags = b->flags; + n->assoc_group_id = b->assoc_group_id; + + if (b->object_string != NULL) { + n->object_string = talloc_strdup(n, b->object_string); + if (n->object_string == NULL) { + goto nomem; + } + } + if (b->host != NULL) { + n->host = talloc_strdup(n, b->host); + if (n->host == NULL) { + goto nomem; + } + } + + if (b->target_hostname != NULL) { + n->target_hostname = talloc_strdup(n, b->target_hostname); + if (n->target_hostname == NULL) { + goto nomem; + } + } + + if (b->target_principal != NULL) { + n->target_principal = talloc_strdup(n, b->target_principal); + if (n->target_principal == NULL) { + goto nomem; + } + } + + if (b->endpoint != NULL) { + n->endpoint = talloc_strdup(n, b->endpoint); + if (n->endpoint == NULL) { + goto nomem; + } + } + + for (count = 0; b->options && b->options[count]; count++); + + if (count > 0) { + uint32_t i; + + n->options = talloc_array(n, const char *, count + 1); + if (n->options == NULL) { + goto nomem; + } + + for (i = 0; i < count; i++) { + n->options[i] = talloc_strdup(n->options, b->options[i]); + if (n->options[i] == NULL) { + goto nomem; + } + } + n->options[count] = NULL; + } + + return n; +nomem: + TALLOC_FREE(n); + return NULL; +} + +_PUBLIC_ NTSTATUS dcerpc_binding_build_tower(TALLOC_CTX *mem_ctx, + const struct dcerpc_binding *binding, + struct epm_tower *tower) +{ + const enum epm_protocol *protseq = NULL; + size_t i, num_protocols = 0; + struct ndr_syntax_id abstract_syntax; + NTSTATUS status; + + /* Find transport */ + for (i=0;i<ARRAY_SIZE(transports);i++) { + if (transports[i].transport == binding->transport) { + protseq = transports[i].protseq; + num_protocols = transports[i].num_protocols; + break; + } + } + + if (i == ARRAY_SIZE(transports)) { + DEBUG(0, ("Unable to find transport with id '%d'\n", binding->transport)); + return NT_STATUS_UNSUCCESSFUL; + } + + tower->num_floors = 2 + num_protocols; + tower->floors = talloc_array(mem_ctx, struct epm_floor, tower->num_floors); + if (tower->floors == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* Floor 0 */ + abstract_syntax = dcerpc_binding_get_abstract_syntax(binding); + status = dcerpc_floor_pack_uuid_full(tower->floors, + &tower->floors[0], + &abstract_syntax); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* Floor 1 */ + status = dcerpc_floor_pack_uuid_full(tower->floors, + &tower->floors[1], + &ndr_transfer_syntax_ndr); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* Floor 2 to num_protocols */ + for (i = 0; i < num_protocols; i++) { + tower->floors[2 + i].lhs.protocol = protseq[i]; + tower->floors[2 + i].lhs.lhs_data = data_blob_null; + ZERO_STRUCT(tower->floors[2 + i].rhs); + status = dcerpc_floor_set_rhs_data(tower->floors, + &tower->floors[2 + i], + NULL); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + /* The 4th floor contains the endpoint */ + if (num_protocols >= 2 && binding->endpoint) { + status = dcerpc_floor_set_rhs_data(tower->floors, + &tower->floors[3], + binding->endpoint); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + /* The 5th contains the network address */ + if (num_protocols >= 3 && binding->host) { + status = dcerpc_floor_set_rhs_data(tower->floors, + &tower->floors[4], + binding->host); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return NT_STATUS_OK; +} diff --git a/librpc/rpc/binding_handle.c b/librpc/rpc/binding_handle.c new file mode 100644 index 0000000..41675e1 --- /dev/null +++ b/librpc/rpc/binding_handle.c @@ -0,0 +1,568 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc binding handle functions + + Copyright (C) Stefan Metzmacher 2010 + + 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 <tevent.h> +#include "../lib/util/tevent_ntstatus.h" +#include "librpc/rpc/dcerpc.h" +#include "rpc_common.h" + +struct dcerpc_binding_handle { + void *private_data; + const struct dcerpc_binding_handle_ops *ops; + const char *location; + const struct GUID *object; + const struct ndr_interface_table *table; + struct tevent_context *sync_ev; +}; + +static int dcerpc_binding_handle_destructor(struct dcerpc_binding_handle *b) +{ + return 0; +} + +struct dcerpc_binding_handle *_dcerpc_binding_handle_create(TALLOC_CTX *mem_ctx, + const struct dcerpc_binding_handle_ops *ops, + const struct GUID *object, + const struct ndr_interface_table *table, + void *pstate, + size_t psize, + const char *type, + const char *location) +{ + struct dcerpc_binding_handle *h; + void **ppstate = (void **)pstate; + void *state; + + h = talloc_zero(mem_ctx, struct dcerpc_binding_handle); + if (h == NULL) { + return NULL; + } + h->ops = ops; + h->location = location; + h->object = object; + h->table = table; + + state = talloc_zero_size(h, psize); + if (state == NULL) { + talloc_free(h); + return NULL; + } + talloc_set_name_const(state, type); + + h->private_data = state; + + talloc_set_destructor(h, dcerpc_binding_handle_destructor); + + *ppstate = state; + return h; +} + +void *_dcerpc_binding_handle_data(struct dcerpc_binding_handle *h) +{ + return h->private_data; +} + +void dcerpc_binding_handle_set_sync_ev(struct dcerpc_binding_handle *h, + struct tevent_context *ev) +{ + h->sync_ev = ev; +} + +bool dcerpc_binding_handle_is_connected(struct dcerpc_binding_handle *h) +{ + return h->ops->is_connected(h); +} + +uint32_t dcerpc_binding_handle_set_timeout(struct dcerpc_binding_handle *h, + uint32_t timeout) +{ + return h->ops->set_timeout(h, timeout); +} + +void dcerpc_binding_handle_auth_info(struct dcerpc_binding_handle *h, + enum dcerpc_AuthType *auth_type, + enum dcerpc_AuthLevel *auth_level) +{ + enum dcerpc_AuthType _auth_type; + enum dcerpc_AuthLevel _auth_level; + + if (auth_type == NULL) { + auth_type = &_auth_type; + } + + if (auth_level == NULL) { + auth_level = &_auth_level; + } + + *auth_type = DCERPC_AUTH_TYPE_NONE; + *auth_level = DCERPC_AUTH_LEVEL_NONE; + + if (h->ops->auth_info == NULL) { + return; + } + + h->ops->auth_info(h, auth_type, auth_level); +} + +struct dcerpc_binding_handle_raw_call_state { + const struct dcerpc_binding_handle_ops *ops; + uint8_t *out_data; + size_t out_length; + uint32_t out_flags; +}; + +static void dcerpc_binding_handle_raw_call_done(struct tevent_req *subreq); + +struct tevent_req *dcerpc_binding_handle_raw_call_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *h, + const struct GUID *object, + uint32_t opnum, + uint32_t in_flags, + const uint8_t *in_data, + size_t in_length) +{ + struct tevent_req *req; + struct dcerpc_binding_handle_raw_call_state *state; + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct dcerpc_binding_handle_raw_call_state); + if (req == NULL) { + return NULL; + } + state->ops = h->ops; + state->out_data = NULL; + state->out_length = 0; + state->out_flags = 0; + + if (h->object != NULL) { + /* + * If an object is set on the binding handle, + * per request object passing is not allowed. + */ + if (object != NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE); + return tevent_req_post(req, ev); + } + + /* + * We use the object from the binding handle + */ + object = h->object; + } + + subreq = state->ops->raw_call_send(state, ev, h, + object, opnum, + in_flags, in_data, in_length); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, dcerpc_binding_handle_raw_call_done, req); + + return req; +} + +static void dcerpc_binding_handle_raw_call_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct dcerpc_binding_handle_raw_call_state *state = + tevent_req_data(req, + struct dcerpc_binding_handle_raw_call_state); + NTSTATUS error; + + error = state->ops->raw_call_recv(subreq, state, + &state->out_data, + &state->out_length, + &state->out_flags); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, error)) { + return; + } + + tevent_req_done(req); +} + +NTSTATUS dcerpc_binding_handle_raw_call_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint8_t **out_data, + size_t *out_length, + uint32_t *out_flags) +{ + struct dcerpc_binding_handle_raw_call_state *state = + tevent_req_data(req, + struct dcerpc_binding_handle_raw_call_state); + NTSTATUS error; + + if (tevent_req_is_nterror(req, &error)) { + tevent_req_received(req); + return error; + } + + *out_data = talloc_move(mem_ctx, &state->out_data); + *out_length = state->out_length; + *out_flags = state->out_flags; + tevent_req_received(req); + return NT_STATUS_OK; +} + +NTSTATUS dcerpc_binding_handle_raw_call(struct dcerpc_binding_handle *h, + const struct GUID *object, + uint32_t opnum, + uint32_t in_flags, + const uint8_t *in_data, + size_t in_length, + TALLOC_CTX *mem_ctx, + uint8_t **out_data, + size_t *out_length, + uint32_t *out_flags) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *subreq; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + /* + * TODO: allow only one sync call + */ + + if (h->sync_ev) { + ev = h->sync_ev; + } else { + ev = samba_tevent_context_init(frame); + } + if (ev == NULL) { + goto fail; + } + + subreq = dcerpc_binding_handle_raw_call_send(frame, ev, + h, object, opnum, + in_flags, + in_data, + in_length); + if (subreq == NULL) { + goto fail; + } + + if (!tevent_req_poll_ntstatus(subreq, ev, &status)) { + goto fail; + } + + status = dcerpc_binding_handle_raw_call_recv(subreq, + mem_ctx, + out_data, + out_length, + out_flags); +fail: + TALLOC_FREE(frame); + return status; +} + +struct dcerpc_binding_handle_disconnect_state { + const struct dcerpc_binding_handle_ops *ops; +}; + +static void dcerpc_binding_handle_disconnect_done(struct tevent_req *subreq); + +struct tevent_req *dcerpc_binding_handle_disconnect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *h) +{ + struct tevent_req *req; + struct dcerpc_binding_handle_disconnect_state *state; + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct dcerpc_binding_handle_disconnect_state); + if (req == NULL) { + return NULL; + } + + state->ops = h->ops; + + subreq = state->ops->disconnect_send(state, ev, h); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, dcerpc_binding_handle_disconnect_done, req); + + return req; +} + +static void dcerpc_binding_handle_disconnect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct dcerpc_binding_handle_disconnect_state *state = + tevent_req_data(req, + struct dcerpc_binding_handle_disconnect_state); + NTSTATUS error; + + error = state->ops->disconnect_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, error)) { + return; + } + + tevent_req_done(req); +} + +NTSTATUS dcerpc_binding_handle_disconnect_recv(struct tevent_req *req) +{ + NTSTATUS error; + + if (tevent_req_is_nterror(req, &error)) { + tevent_req_received(req); + return error; + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +struct dcerpc_binding_handle_call_state { + struct dcerpc_binding_handle *h; + const struct ndr_interface_call *call; + TALLOC_CTX *r_mem; + void *r_ptr; + struct ndr_push *push; + DATA_BLOB request; + DATA_BLOB response; + struct ndr_pull *pull; +}; + +static void dcerpc_binding_handle_call_done(struct tevent_req *subreq); + +struct tevent_req *dcerpc_binding_handle_call_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *h, + const struct GUID *object, + const struct ndr_interface_table *table, + uint32_t opnum, + TALLOC_CTX *r_mem, + void *r_ptr) +{ + struct tevent_req *req; + struct dcerpc_binding_handle_call_state *state; + struct tevent_req *subreq; + enum ndr_err_code ndr_err; + + req = tevent_req_create(mem_ctx, &state, + struct dcerpc_binding_handle_call_state); + if (req == NULL) { + return NULL; + } + + if (table != h->table) { + tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE); + return tevent_req_post(req, ev); + } + + if (opnum >= table->num_calls) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return tevent_req_post(req, ev); + } + + state->h = h; + state->call = &table->calls[opnum]; + + state->r_mem = r_mem; + state->r_ptr = r_ptr; + + /* setup for a ndr_push_* call */ + state->push = ndr_push_init_ctx(state); + if (tevent_req_nomem(state->push, req)) { + return tevent_req_post(req, ev); + } + + if (h->ops->ref_alloc && h->ops->ref_alloc(h)) { + state->push->flags |= LIBNDR_FLAG_REF_ALLOC; + } + + if (h->ops->push_bigendian && h->ops->push_bigendian(h)) { + state->push->flags |= LIBNDR_FLAG_BIGENDIAN; + } + + if (h->ops->use_ndr64 && h->ops->use_ndr64(h)) { + state->push->flags |= LIBNDR_FLAG_NDR64; + } + + if (h->ops->do_ndr_print) { + h->ops->do_ndr_print(h, NDR_IN | NDR_SET_VALUES, + state->r_ptr, state->call); + } + + /* push the structure into a blob */ + ndr_err = state->call->ndr_push(state->push, NDR_IN, state->r_ptr); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS error; + error = ndr_map_error2ntstatus(ndr_err); + if (h->ops->ndr_push_failed) { + h->ops->ndr_push_failed(h, error, + state->r_ptr, + state->call); + } + tevent_req_nterror(req, error); + return tevent_req_post(req, ev); + } + + /* retrieve the blob */ + state->request = ndr_push_blob(state->push); + + if (h->ops->ndr_validate_in) { + NTSTATUS error; + error = h->ops->ndr_validate_in(h, state, + &state->request, + state->call); + if (!NT_STATUS_IS_OK(error)) { + tevent_req_nterror(req, error); + return tevent_req_post(req, ev); + } + } + + subreq = dcerpc_binding_handle_raw_call_send(state, ev, + h, object, opnum, + state->push->flags, + state->request.data, + state->request.length); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, dcerpc_binding_handle_call_done, req); + + return req; +} + +static void dcerpc_binding_handle_call_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct dcerpc_binding_handle_call_state *state = + tevent_req_data(req, + struct dcerpc_binding_handle_call_state); + struct dcerpc_binding_handle *h = state->h; + NTSTATUS error; + uint32_t out_flags = 0; + enum ndr_err_code ndr_err; + + error = dcerpc_binding_handle_raw_call_recv(subreq, state, + &state->response.data, + &state->response.length, + &out_flags); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, error)) { + return; + } + + state->pull = ndr_pull_init_blob(&state->response, state); + if (tevent_req_nomem(state->pull, req)) { + return; + } + state->pull->flags = state->push->flags; + + if (out_flags & LIBNDR_FLAG_BIGENDIAN) { + state->pull->flags |= LIBNDR_FLAG_BIGENDIAN; + } else { + state->pull->flags &= ~LIBNDR_FLAG_BIGENDIAN; + } + + state->pull->current_mem_ctx = state->r_mem; + + /* pull the structure from the blob */ + ndr_err = state->call->ndr_pull(state->pull, NDR_OUT, state->r_ptr); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + error = ndr_map_error2ntstatus(ndr_err); + if (h->ops->ndr_pull_failed) { + h->ops->ndr_pull_failed(h, error, + &state->response, + state->call); + } + tevent_req_nterror(req, error); + return; + } + + if (h->ops->do_ndr_print) { + h->ops->do_ndr_print(h, NDR_OUT, + state->r_ptr, state->call); + } + + if (h->ops->ndr_validate_out) { + error = h->ops->ndr_validate_out(h, + state->pull, + state->r_ptr, + state->call); + if (!NT_STATUS_IS_OK(error)) { + tevent_req_nterror(req, error); + return; + } + } + + tevent_req_done(req); +} + +NTSTATUS dcerpc_binding_handle_call_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +NTSTATUS dcerpc_binding_handle_call(struct dcerpc_binding_handle *h, + const struct GUID *object, + const struct ndr_interface_table *table, + uint32_t opnum, + TALLOC_CTX *r_mem, + void *r_ptr) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *subreq; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + /* + * TODO: allow only one sync call + */ + + if (h->sync_ev) { + ev = h->sync_ev; + } else { + ev = samba_tevent_context_init(frame); + } + if (ev == NULL) { + goto fail; + } + + subreq = dcerpc_binding_handle_call_send(frame, ev, + h, object, table, + opnum, r_mem, r_ptr); + if (subreq == NULL) { + goto fail; + } + + if (!tevent_req_poll_ntstatus(subreq, ev, &status)) { + goto fail; + } + + status = dcerpc_binding_handle_call_recv(subreq); +fail: + TALLOC_FREE(frame); + return status; +} diff --git a/librpc/rpc/dcerpc_error.c b/librpc/rpc/dcerpc_error.c new file mode 100644 index 0000000..d5b5b66 --- /dev/null +++ b/librpc/rpc/dcerpc_error.c @@ -0,0 +1,153 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc fault functions + + Copyright (C) Stefan Metzmacher 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 "includes.h" +#include "librpc/rpc/dcerpc.h" +#include "rpc_common.h" + +struct dcerpc_fault_table { + const char *errstr; + uint32_t faultcode; + NTSTATUS nt_status; +}; + +static const struct dcerpc_fault_table dcerpc_faults[] = +{ +#define _FAULT_STR(x, s) { .errstr = #x , .faultcode = x, .nt_status = s } +#define _FAULT_STR_NO_NT_MAPPING(x) _FAULT_STR(x, NT_STATUS_RPC_NOT_RPC_ERROR) + _FAULT_STR(DCERPC_NCA_S_COMM_FAILURE, NT_STATUS_RPC_COMM_FAILURE), + _FAULT_STR(DCERPC_NCA_S_OP_RNG_ERROR, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE), + _FAULT_STR(DCERPC_NCA_S_UNKNOWN_IF, NT_STATUS_RPC_UNKNOWN_IF), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_WRONG_BOOT_TIME), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_YOU_CRASHED), + _FAULT_STR(DCERPC_NCA_S_PROTO_ERROR, NT_STATUS_RPC_PROTOCOL_ERROR), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_OUT_ARGS_TOO_BIG), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_SERVER_TOO_BUSY), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_STRING_TOO_LARGE), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_UNSUPPORTED_TYPE), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_ADDR_ERROR), + _FAULT_STR(DCERPC_NCA_S_FAULT_FP_DIV_BY_ZERO, NT_STATUS_RPC_FP_DIV_ZERO), + _FAULT_STR(DCERPC_NCA_S_FAULT_FP_UNDERFLOW, NT_STATUS_RPC_FP_UNDERFLOW), + _FAULT_STR(DCERPC_NCA_S_FAULT_FP_OVERRFLOW, NT_STATUS_RPC_FP_OVERFLOW), + _FAULT_STR(DCERPC_NCA_S_FAULT_INT_DIV_BY_ZERO, NT_STATUS_RPC_FP_DIV_ZERO), + _FAULT_STR(DCERPC_NCA_S_FAULT_INT_OVERFLOW, NT_STATUS_RPC_FP_OVERFLOW), + /* + * Our callers expect NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE + * instead of NT_STATUS_RPC_INVALID_TAG. + */ + _FAULT_STR(DCERPC_NCA_S_FAULT_INVALID_TAG, NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE), + _FAULT_STR(DCERPC_NCA_S_FAULT_INVALID_TAG, NT_STATUS_RPC_INVALID_TAG), + _FAULT_STR(DCERPC_NCA_S_FAULT_INVALID_BOUND, NT_STATUS_RPC_INVALID_BOUND), + _FAULT_STR(DCERPC_NCA_S_FAULT_RPC_VERSION_MISMATCH, NT_STATUS_RPC_PROTOCOL_ERROR), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_UNSPEC_REJECT), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_BAD_ACTID), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_WHO_ARE_YOU_FAILED), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_MANAGER_NOT_ENTERED), + _FAULT_STR(DCERPC_NCA_S_FAULT_CANCEL, NT_STATUS_RPC_CALL_CANCELLED), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_ILL_INST), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_FP_ERROR), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_UNUSED_1C000011), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_UNSPEC), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_REMOTE_COMM_FAILURE), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_PIPE_EMPTY), + _FAULT_STR(DCERPC_NCA_S_FAULT_PIPE_CLOSED, NT_STATUS_RPC_PIPE_CLOSED), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_PIPE_ORDER), + _FAULT_STR(DCERPC_NCA_S_FAULT_PIPE_DISCIPLINE, NT_STATUS_RPC_PIPE_DISCIPLINE_ERROR), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_PIPE_COMM_ERROR), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_PIPE_MEMORY), + _FAULT_STR(DCERPC_NCA_S_FAULT_CONTEXT_MISMATCH, NT_STATUS_RPC_SS_CONTEXT_MISMATCH), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_REMOTE_NO_MEMORY), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_INVALID_PRES_CONTEXT_ID), + _FAULT_STR(DCERPC_NCA_S_UNSUPPORTED_AUTHN_LEVEL, NT_STATUS_RPC_UNSUPPORTED_AUTHN_LEVEL), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_UNUSED_1C00001E), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_INVALID_CHECKSUM), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_INVALID_CRC), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_USER_DEFINED), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_TX_OPEN_FAILED), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_CODESET_CONV_ERROR), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_OBJECT_NOT_FOUND), + _FAULT_STR_NO_NT_MAPPING(DCERPC_NCA_S_FAULT_NO_CLIENT_STUB), + _FAULT_STR(DCERPC_FAULT_OTHER, NT_STATUS_RPC_CALL_FAILED), + _FAULT_STR(DCERPC_FAULT_ACCESS_DENIED, NT_STATUS_ACCESS_DENIED), + _FAULT_STR(DCERPC_FAULT_SERVER_UNAVAILABLE, NT_STATUS_RPC_SERVER_UNAVAILABLE), + _FAULT_STR(DCERPC_FAULT_NO_CALL_ACTIVE, NT_STATUS_RPC_NO_CALL_ACTIVE), + _FAULT_STR(DCERPC_FAULT_CANT_PERFORM, NT_STATUS_EPT_CANT_PERFORM_OP), + _FAULT_STR(DCERPC_FAULT_OUT_OF_RESOURCES, NT_STATUS_RPC_OUT_OF_RESOURCES), + _FAULT_STR(DCERPC_FAULT_BAD_STUB_DATA, NT_STATUS_RPC_BAD_STUB_DATA), + _FAULT_STR(DCERPC_FAULT_SEC_PKG_ERROR, NT_STATUS_RPC_SEC_PKG_ERROR), + { .faultcode = 0 } +#undef _FAULT_STR +}; + +_PUBLIC_ const char *dcerpc_errstr(TALLOC_CTX *mem_ctx, uint32_t fault_code) +{ + int idx = 0; + WERROR werr = W_ERROR(fault_code); + + while (dcerpc_faults[idx].errstr != NULL) { + if (dcerpc_faults[idx].faultcode == fault_code) { + return dcerpc_faults[idx].errstr; + } + idx++; + } + + return win_errstr(werr); +} + +_PUBLIC_ NTSTATUS dcerpc_fault_to_nt_status(uint32_t fault_code) +{ + int idx = 0; + WERROR werr = W_ERROR(fault_code); + + if (fault_code == 0) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + while (dcerpc_faults[idx].errstr != NULL) { + if (dcerpc_faults[idx].faultcode == fault_code) { + return dcerpc_faults[idx].nt_status; + } + idx++; + } + + return werror_to_ntstatus(werr); +} + +_PUBLIC_ uint32_t dcerpc_fault_from_nt_status(NTSTATUS nt_status) +{ + int idx = 0; + WERROR werr; + + if (NT_STATUS_IS_OK(nt_status)) { + return DCERPC_NCA_S_PROTO_ERROR; + } + + while (dcerpc_faults[idx].errstr != NULL) { + if (NT_STATUS_EQUAL(dcerpc_faults[idx].nt_status, nt_status)) { + return dcerpc_faults[idx].faultcode; + } + idx++; + } + + werr = ntstatus_to_werror(nt_status); + + return W_ERROR_V(werr); +} diff --git a/librpc/rpc/dcerpc_helper.c b/librpc/rpc/dcerpc_helper.c new file mode 100644 index 0000000..e1589f9 --- /dev/null +++ b/librpc/rpc/dcerpc_helper.c @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2020 Andreas Schneider <asn@samba.org> + * + * 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/security.h" +#include "librpc/gen_ndr/auth.h" +#include "lib/crypto/gnutls_helpers.h" +#include "libcli/security/dom_sid.h" +#include "libcli/security/security_token.h" +#include "libcli/smb/smb2_constants.h" + +#include "dcerpc_helper.h" + +static bool smb3_sid_parse(const struct dom_sid *sid, + uint16_t *pdialect, + uint16_t *pencrypt, + uint16_t *pcipher) +{ + uint16_t dialect; + uint16_t encrypt; + uint16_t cipher; + + if (sid->sub_auths[0] != global_sid_Samba_SMB3.sub_auths[0]) { + return false; + } + + dialect = sid->sub_auths[1]; + if (dialect > 0x03ff) { + return false; + } + + encrypt = sid->sub_auths[2]; + if (encrypt > 0x0002) { + return false; + } + + cipher = sid->sub_auths[3]; + if (cipher > 256) { + /* + * It is unlikely that we + * ever have more then 256 + * encryption algorithms + */ + return false; + } + + if (pdialect != NULL) { + *pdialect = dialect; + } + + if (pencrypt != NULL) { + *pencrypt = encrypt; + } + + if (pcipher != NULL) { + *pcipher = cipher; + } + + return true; +} + +bool dcerpc_is_transport_encrypted(struct auth_session_info *session_info) +{ + struct security_token *token = session_info->security_token; + struct dom_sid smb3_dom_sid = global_sid_Samba_SMB3; + const struct dom_sid *smb3_sid = NULL; + uint16_t dialect = 0; + uint16_t encrypt = 0; + uint16_t cipher = 0; + size_t num_smb3_sids; + bool ok; + + num_smb3_sids = security_token_count_flag_sids(token, + &smb3_dom_sid, + 3, + &smb3_sid); + if (num_smb3_sids > 1) { + DBG_ERR("ERROR: The SMB3 SID has been detected %zu times\n", + num_smb3_sids); + return false; + } + + if (smb3_sid == NULL) { + return false; + } + + ok = smb3_sid_parse(smb3_sid, &dialect, &encrypt, &cipher); + if (!ok) { + DBG_ERR("Failed to parse SMB3 SID!\n"); + return false; + } + + DBG_DEBUG("SMB SID - dialect: %#04x, encrypt: %#04x, cipher: %#04x\n", + dialect, + encrypt, + cipher); + + if (dialect < SMB3_DIALECT_REVISION_300) { + DBG_DEBUG("Invalid SMB3 dialect!\n"); + return false; + } + + if (encrypt != DCERPC_SMB_ENCRYPTION_REQUIRED) { + DBG_DEBUG("Invalid SMB3 encryption!\n"); + return false; + } + + switch (cipher) { + case SMB2_ENCRYPTION_AES128_CCM: + case SMB2_ENCRYPTION_AES128_GCM: + break; + default: + DBG_DEBUG("Invalid SMB3 cipher!\n"); + return false; + } + + return true; +} diff --git a/librpc/rpc/dcerpc_helper.h b/librpc/rpc/dcerpc_helper.h new file mode 100644 index 0000000..c0f09ee --- /dev/null +++ b/librpc/rpc/dcerpc_helper.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020 Andreas Schneider <asn@samba.org> + * + * 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/>. + */ + +#ifndef _DCERPC_HELPER_H +#define _DCERPC_HELPER_H + +#define DCERPC_SMB_ENCRYPTION_OFF 0x0000 +#define DCERPC_SMB_ENCRYPTION_REQUIRED 0x0002 + +bool dcerpc_is_transport_encrypted(struct auth_session_info *session_info); + +#endif /* _DCERPC_HELPER_H */ diff --git a/librpc/rpc/dcerpc_pkt_auth.c b/librpc/rpc/dcerpc_pkt_auth.c new file mode 100644 index 0000000..5eb9c44 --- /dev/null +++ b/librpc/rpc/dcerpc_pkt_auth.c @@ -0,0 +1,500 @@ +/* + Unix SMB/CIFS implementation. + raw dcerpc operations + + Copyright (C) Andrew Tridgell 2003-2005 + Copyright (C) Jelmer Vernooij 2004-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 "replace.h" +#include "system/network.h" +#include <tevent.h> +#include "lib/util/talloc_stack.h" +#include "lib/util/debug.h" +#include "lib/util/byteorder.h" +#include "lib/util/samba_util.h" +#include "librpc/rpc/dcerpc.h" +#include "librpc/rpc/dcerpc_util.h" +#include "librpc/rpc/dcerpc_pkt_auth.h" +#include "librpc/gen_ndr/ndr_dcerpc.h" +#include "rpc_common.h" +#include "lib/util/bitmap.h" +#include "auth/gensec/gensec.h" +#include "lib/util/mkdir_p.h" +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/crypto.h> + +NTSTATUS dcerpc_ncacn_pull_pkt_auth(const struct dcerpc_auth *auth_state, + struct gensec_security *gensec, + bool check_pkt_auth_fields, + TALLOC_CTX *mem_ctx, + enum dcerpc_pkt_type ptype, + uint8_t required_flags, + uint8_t optional_flags, + uint8_t payload_offset, + DATA_BLOB *payload_and_verifier, + DATA_BLOB *raw_packet, + const struct ncacn_packet *pkt) +{ + NTSTATUS status; + struct dcerpc_auth auth; + uint32_t auth_length; + + if (auth_state == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + status = dcerpc_verify_ncacn_packet_header(pkt, ptype, + payload_and_verifier->length, + required_flags, optional_flags); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + switch (auth_state->auth_level) { + case DCERPC_AUTH_LEVEL_PRIVACY: + case DCERPC_AUTH_LEVEL_INTEGRITY: + case DCERPC_AUTH_LEVEL_PACKET: + break; + + case DCERPC_AUTH_LEVEL_CONNECT: + if (pkt->auth_length != 0) { + break; + } + return NT_STATUS_OK; + case DCERPC_AUTH_LEVEL_NONE: + if (pkt->auth_length != 0) { + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; + + default: + return NT_STATUS_RPC_UNSUPPORTED_AUTHN_LEVEL; + } + + if (pkt->auth_length == 0) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + if (gensec == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + status = dcerpc_pull_auth_trailer(pkt, mem_ctx, + payload_and_verifier, + &auth, &auth_length, false); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (payload_and_verifier->length < auth_length) { + /* + * should be checked in dcerpc_pull_auth_trailer() + */ + return NT_STATUS_INTERNAL_ERROR; + } + + payload_and_verifier->length -= auth_length; + + if (payload_and_verifier->length < auth.auth_pad_length) { + /* + * should be checked in dcerpc_pull_auth_trailer() + */ + return NT_STATUS_INTERNAL_ERROR; + } + + if (check_pkt_auth_fields) { + if (auth.auth_type != auth_state->auth_type) { + return NT_STATUS_ACCESS_DENIED; + } + + if (auth.auth_level != auth_state->auth_level) { + return NT_STATUS_ACCESS_DENIED; + } + + if (auth.auth_context_id != auth_state->auth_context_id) { + return NT_STATUS_ACCESS_DENIED; + } + } + + /* check signature or unseal the packet */ + switch (auth_state->auth_level) { + case DCERPC_AUTH_LEVEL_PRIVACY: + status = gensec_unseal_packet(gensec, + raw_packet->data + payload_offset, + payload_and_verifier->length, + raw_packet->data, + raw_packet->length - + auth.credentials.length, + &auth.credentials); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_RPC_SEC_PKG_ERROR; + } + memcpy(payload_and_verifier->data, + raw_packet->data + payload_offset, + payload_and_verifier->length); + break; + + case DCERPC_AUTH_LEVEL_INTEGRITY: + case DCERPC_AUTH_LEVEL_PACKET: + status = gensec_check_packet(gensec, + payload_and_verifier->data, + payload_and_verifier->length, + raw_packet->data, + raw_packet->length - + auth.credentials.length, + &auth.credentials); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_RPC_SEC_PKG_ERROR; + } + break; + + case DCERPC_AUTH_LEVEL_CONNECT: + /* for now we ignore possible signatures here */ + break; + + default: + return NT_STATUS_RPC_UNSUPPORTED_AUTHN_LEVEL; + } + + /* + * remove the indicated amount of padding + * + * A possible overflow is checked above. + */ + payload_and_verifier->length -= auth.auth_pad_length; + + return NT_STATUS_OK; +} + +NTSTATUS dcerpc_ncacn_push_pkt_auth(const struct dcerpc_auth *auth_state, + struct gensec_security *gensec, + TALLOC_CTX *mem_ctx, + DATA_BLOB *raw_packet, + size_t sig_size, + uint8_t payload_offset, + const DATA_BLOB *payload, + const struct ncacn_packet *pkt) +{ + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status; + enum ndr_err_code ndr_err; + struct ndr_push *ndr = NULL; + uint32_t payload_length; + uint32_t whole_length; + DATA_BLOB blob = data_blob_null; + DATA_BLOB sig = data_blob_null; + struct dcerpc_auth _out_auth_info; + struct dcerpc_auth *out_auth_info = NULL; + + *raw_packet = data_blob_null; + + if (auth_state == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_ERROR; + } + + switch (auth_state->auth_level) { + case DCERPC_AUTH_LEVEL_PRIVACY: + case DCERPC_AUTH_LEVEL_INTEGRITY: + case DCERPC_AUTH_LEVEL_PACKET: + if (sig_size == 0) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_ERROR; + } + + if (gensec == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_ERROR; + } + + _out_auth_info = (struct dcerpc_auth) { + .auth_type = auth_state->auth_type, + .auth_level = auth_state->auth_level, + .auth_context_id = auth_state->auth_context_id, + }; + out_auth_info = &_out_auth_info; + break; + + case DCERPC_AUTH_LEVEL_CONNECT: + /* + * TODO: let the gensec mech decide if it wants to generate a + * signature that might be needed for schannel... + */ + if (sig_size != 0) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_ERROR; + } + + if (gensec == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_ERROR; + } + break; + + case DCERPC_AUTH_LEVEL_NONE: + if (sig_size != 0) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_ERROR; + } + break; + + default: + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_ERROR; + } + + ndr = ndr_push_init_ctx(frame); + if (ndr == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + ndr_err = ndr_push_ncacn_packet(ndr, NDR_SCALARS|NDR_BUFFERS, pkt); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + TALLOC_FREE(frame); + return ndr_map_error2ntstatus(ndr_err); + } + + if (out_auth_info != NULL) { + /* + * pad to 16 byte multiple in the payload portion of the + * packet. This matches what w2k3 does. Note that we can't use + * ndr_push_align() as that is relative to the start of the + * whole packet, whereas w2k8 wants it relative to the start + * of the stub. + */ + out_auth_info->auth_pad_length = + DCERPC_AUTH_PAD_LENGTH(payload->length); + ndr_err = ndr_push_zero(ndr, out_auth_info->auth_pad_length); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + TALLOC_FREE(frame); + return ndr_map_error2ntstatus(ndr_err); + } + + payload_length = payload->length + + out_auth_info->auth_pad_length; + + ndr_err = ndr_push_dcerpc_auth(ndr, NDR_SCALARS|NDR_BUFFERS, + out_auth_info); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + TALLOC_FREE(frame); + return ndr_map_error2ntstatus(ndr_err); + } + + whole_length = ndr->offset; + + ndr_err = ndr_push_zero(ndr, sig_size); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + TALLOC_FREE(frame); + return ndr_map_error2ntstatus(ndr_err); + } + } else { + payload_length = payload->length; + whole_length = ndr->offset; + } + + /* extract the whole packet as a blob */ + blob = ndr_push_blob(ndr); + + /* + * Setup the frag and auth length in the packet buffer. + * This is needed if the GENSEC mech does AEAD signing + * of the packet headers. The signature itself will be + * appended later. + */ + dcerpc_set_frag_length(&blob, blob.length); + dcerpc_set_auth_length(&blob, sig_size); + + /* sign or seal the packet */ + switch (auth_state->auth_level) { + case DCERPC_AUTH_LEVEL_PRIVACY: + status = gensec_seal_packet(gensec, + frame, + blob.data + payload_offset, + payload_length, + blob.data, + whole_length, + &sig); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + break; + + case DCERPC_AUTH_LEVEL_INTEGRITY: + case DCERPC_AUTH_LEVEL_PACKET: + status = gensec_sign_packet(gensec, + frame, + blob.data + payload_offset, + payload_length, + blob.data, + whole_length, + &sig); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + break; + + case DCERPC_AUTH_LEVEL_CONNECT: + case DCERPC_AUTH_LEVEL_NONE: + break; + + default: + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_ERROR; + } + + if (sig.length != sig_size) { + TALLOC_FREE(frame); + return NT_STATUS_RPC_SEC_PKG_ERROR; + } + + if (sig_size != 0) { + memcpy(blob.data + whole_length, sig.data, sig_size); + } + + *raw_packet = blob; + talloc_steal(mem_ctx, raw_packet->data); + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +#ifdef DEVELOPER + +/* + * Save valid, well-formed DCE/RPC stubs to use as a seed for + * ndr_fuzz_X + */ +void dcerpc_save_ndr_fuzz_seed(TALLOC_CTX *mem_ctx, + DATA_BLOB raw_blob, + const char *dump_dir, + const char *iface_name, + ndr_flags_type flags, + int opnum, + bool ndr64) +{ + char *fname = NULL; + const char *sub_dir = NULL; + TALLOC_CTX *temp_ctx = talloc_new(mem_ctx); + DATA_BLOB blob; + int ret, rc; + uint8_t digest[20]; + DATA_BLOB digest_blob; + char *digest_hex; + uint16_t fuzz_flags = 0; + + /* + * We want to save the 'stub' in a per-pipe subdirectory, with + * the ndr_fuzz_X header 4 byte header. For the sake of + * convenience (this is a developer only function), we mkdir + * -p the sub-directories when they are needed. + */ + + if (dump_dir == NULL) { + return; + } + + temp_ctx = talloc_stackframe(); + + sub_dir = talloc_asprintf(temp_ctx, "%s/%s", + dump_dir, + iface_name); + if (sub_dir == NULL) { + talloc_free(temp_ctx); + return; + } + ret = mkdir_p(sub_dir, 0755); + if (ret && errno != EEXIST) { + DBG_ERR("could not create %s\n", sub_dir); + talloc_free(temp_ctx); + return; + } + + blob.length = raw_blob.length + 4; + blob.data = talloc_array(sub_dir, + uint8_t, + blob.length); + if (blob.data == NULL) { + DBG_ERR("could not allocate for fuzz seeds! (%s)\n", + iface_name); + talloc_free(temp_ctx); + return; + } + + if (ndr64) { + fuzz_flags = 4; + } + if (flags & NDR_IN) { + fuzz_flags |= 1; + } else if (flags & NDR_OUT) { + fuzz_flags |= 2; + } + + SSVAL(blob.data, 0, fuzz_flags); + SSVAL(blob.data, 2, opnum); + + memcpy(&blob.data[4], + raw_blob.data, + raw_blob.length); + + /* + * This matches how oss-fuzz names the corpus input files, due + * to a preference from libFuzzer + */ + rc = gnutls_hash_fast(GNUTLS_DIG_SHA1, + blob.data, + blob.length, + digest); + if (rc < 0) { + /* + * This prints a better error message, eg if SHA1 is + * disabled + */ + NTSTATUS status = gnutls_error_to_ntstatus(rc, + NT_STATUS_HASH_NOT_SUPPORTED); + DBG_ERR("Failed to generate SHA1 to save fuzz seed: %s\n", + nt_errstr(status)); + talloc_free(temp_ctx); + return; + } + + digest_blob.data = digest; + digest_blob.length = sizeof(digest); + digest_hex = data_blob_hex_string_lower(temp_ctx, &digest_blob); + + fname = talloc_asprintf(temp_ctx, "%s/%s", + sub_dir, + digest_hex); + if (fname == NULL) { + talloc_free(temp_ctx); + return; + } + + /* + * If this fails, it is most likely because that file already + * exists. This is fine, it means we already have this + * sample + */ + file_save(fname, + blob.data, + blob.length); + + talloc_free(temp_ctx); +} + +#endif /*if DEVELOPER, enveloping _dcesrv_save_ndr_fuzz_seed() */ diff --git a/librpc/rpc/dcerpc_pkt_auth.h b/librpc/rpc/dcerpc_pkt_auth.h new file mode 100644 index 0000000..1dcee12 --- /dev/null +++ b/librpc/rpc/dcerpc_pkt_auth.h @@ -0,0 +1,59 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2010-2011 + Copyright (C) Andrew Tridgell 2010-2011 + Copyright (C) Simo Sorce 2010 + + 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/>. +*/ + +#ifndef __LIBRPC_RPC_DCERPC_PKT_AUTH_H__ +#define __LIBRPC_RPC_DCERPC_PKT_AUTH_H__ + +#include "replace.h" +#include <talloc.h> +#include "lib/util/data_blob.h" +#include "libcli/util/ntstatus.h" +#include "librpc/rpc/rpc_common.h" +#include "librpc/gen_ndr/dcerpc.h" + +NTSTATUS dcerpc_ncacn_pull_pkt_auth(const struct dcerpc_auth *auth_state, + struct gensec_security *gensec, + bool check_pkt_auth_fields, + TALLOC_CTX *mem_ctx, + enum dcerpc_pkt_type ptype, + uint8_t required_flags, + uint8_t optional_flags, + uint8_t payload_offset, + DATA_BLOB *payload_and_verifier, + DATA_BLOB *raw_packet, + const struct ncacn_packet *pkt); +NTSTATUS dcerpc_ncacn_push_pkt_auth(const struct dcerpc_auth *auth_state, + struct gensec_security *gensec, + TALLOC_CTX *mem_ctx, + DATA_BLOB *raw_packet, + size_t sig_size, + uint8_t payload_offset, + const DATA_BLOB *payload, + const struct ncacn_packet *pkt); +struct tevent_req *dcerpc_read_ncacn_packet_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream); +NTSTATUS dcerpc_read_ncacn_packet_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct ncacn_packet **pkt, + DATA_BLOB *buffer); + +#endif diff --git a/librpc/rpc/dcerpc_samr.h b/librpc/rpc/dcerpc_samr.h new file mode 100644 index 0000000..dac89a9 --- /dev/null +++ b/librpc/rpc/dcerpc_samr.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 Andreas Schneider <asn@samba.org> + * + * 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/>. + */ + +#ifndef _DCERPC_SAMR_H +#define _DCERPC_SAMR_H + +#include <util/discard.h> +#include "lib/util/data_blob.h" + +#define SAMR_AES256_ENC_KEY_STRING \ + "Microsoft SAM encryption key AEAD-AES-256-CBC-HMAC-SHA512 16" +#define SAMR_AES256_ENC_KEY_STRING_LEN 61 /* Including terminating null byte */ + +#define SAMR_AES256_MAC_KEY_STRING \ + "Microsoft SAM MAC key AEAD-AES-256-CBC-HMAC-SHA512 16" +#define SAMR_AES256_MAC_KEY_STRING_LEN 54 /* Including terminating null byte */ + +static const DATA_BLOB samr_aes256_enc_key_salt = { + .data = discard_const_p(uint8_t, SAMR_AES256_ENC_KEY_STRING), + .length = SAMR_AES256_ENC_KEY_STRING_LEN, +}; + +static const DATA_BLOB samr_aes256_mac_key_salt = { + .data = discard_const_p(uint8_t, SAMR_AES256_MAC_KEY_STRING), + .length = SAMR_AES256_MAC_KEY_STRING_LEN, +}; + +#endif /* _DCERPC_SAMR_H */ diff --git a/librpc/rpc/dcerpc_util.c b/librpc/rpc/dcerpc_util.c new file mode 100644 index 0000000..e6f7fa6 --- /dev/null +++ b/librpc/rpc/dcerpc_util.c @@ -0,0 +1,1140 @@ +/* + Unix SMB/CIFS implementation. + raw dcerpc operations + + Copyright (C) Andrew Tridgell 2003-2005 + Copyright (C) Jelmer Vernooij 2004-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 "system/network.h" +#include <tevent.h> +#include "lib/tsocket/tsocket.h" +#include "lib/util/tevent_ntstatus.h" +#include "librpc/rpc/dcerpc.h" +#include "librpc/rpc/dcerpc_util.h" +#include "librpc/gen_ndr/ndr_dcerpc.h" +#include "rpc_common.h" +#include "lib/util/bitmap.h" + +#undef strncasecmp + +/* we need to be able to get/set the fragment length without doing a full + decode */ +void dcerpc_set_frag_length(DATA_BLOB *blob, uint16_t v) +{ + SMB_ASSERT(blob->length >= DCERPC_NCACN_PAYLOAD_OFFSET); + + if (CVAL(blob->data,DCERPC_DREP_OFFSET) & DCERPC_DREP_LE) { + SSVAL(blob->data, DCERPC_FRAG_LEN_OFFSET, v); + } else { + RSSVAL(blob->data, DCERPC_FRAG_LEN_OFFSET, v); + } +} + +uint16_t dcerpc_get_frag_length(const DATA_BLOB *blob) +{ + SMB_ASSERT(blob->length >= DCERPC_NCACN_PAYLOAD_OFFSET); + + if (CVAL(blob->data,DCERPC_DREP_OFFSET) & DCERPC_DREP_LE) { + return SVAL(blob->data, DCERPC_FRAG_LEN_OFFSET); + } else { + return RSVAL(blob->data, DCERPC_FRAG_LEN_OFFSET); + } +} + +void dcerpc_set_auth_length(DATA_BLOB *blob, uint16_t v) +{ + SMB_ASSERT(blob->length >= DCERPC_NCACN_PAYLOAD_OFFSET); + + if (CVAL(blob->data,DCERPC_DREP_OFFSET) & DCERPC_DREP_LE) { + SSVAL(blob->data, DCERPC_AUTH_LEN_OFFSET, v); + } else { + RSSVAL(blob->data, DCERPC_AUTH_LEN_OFFSET, v); + } +} + +uint16_t dcerpc_get_auth_length(const DATA_BLOB *blob) +{ + SMB_ASSERT(blob->length >= DCERPC_NCACN_PAYLOAD_OFFSET); + + if (CVAL(blob->data,DCERPC_DREP_OFFSET) & DCERPC_DREP_LE) { + return SVAL(blob->data, DCERPC_AUTH_LEN_OFFSET); + } else { + return RSVAL(blob->data, DCERPC_AUTH_LEN_OFFSET); + } +} + +uint8_t dcerpc_get_endian_flag(DATA_BLOB *blob) +{ + SMB_ASSERT(blob->length >= DCERPC_NCACN_PAYLOAD_OFFSET); + + return blob->data[DCERPC_DREP_OFFSET]; +} + +static uint16_t dcerpc_get_auth_context_offset(const DATA_BLOB *blob) +{ + uint16_t frag_len = dcerpc_get_frag_length(blob); + uint16_t auth_len = dcerpc_get_auth_length(blob); + uint16_t min_offset; + uint16_t offset; + + if (auth_len == 0) { + return 0; + } + + if (frag_len > blob->length) { + return 0; + } + + if (auth_len > frag_len) { + return 0; + } + + min_offset = DCERPC_NCACN_PAYLOAD_OFFSET + DCERPC_AUTH_TRAILER_LENGTH; + offset = frag_len - auth_len; + if (offset < min_offset) { + return 0; + } + offset -= DCERPC_AUTH_TRAILER_LENGTH; + + return offset; +} + +uint8_t dcerpc_get_auth_type(const DATA_BLOB *blob) +{ + uint16_t offset; + + offset = dcerpc_get_auth_context_offset(blob); + if (offset == 0) { + return 0; + } + + /* + * auth_typw is in the 1st byte + * of the auth trailer + */ + offset += 0; + + return blob->data[offset]; +} + +uint8_t dcerpc_get_auth_level(const DATA_BLOB *blob) +{ + uint16_t offset; + + offset = dcerpc_get_auth_context_offset(blob); + if (offset == 0) { + return 0; + } + + /* + * auth_level is in 2nd byte + * of the auth trailer + */ + offset += 1; + + return blob->data[offset]; +} + +uint32_t dcerpc_get_auth_context_id(const DATA_BLOB *blob) +{ + uint16_t offset; + + offset = dcerpc_get_auth_context_offset(blob); + if (offset == 0) { + return 0; + } + + /* + * auth_context_id is in the last 4 byte + * of the auth trailer + */ + offset += 4; + + if (CVAL(blob->data,DCERPC_DREP_OFFSET) & DCERPC_DREP_LE) { + return IVAL(blob->data, offset); + } else { + return RIVAL(blob->data, offset); + } +} + +/** +* @brief Decodes a ncacn_packet +* +* @param mem_ctx The memory context on which to allocate the packet +* elements +* @param blob The blob of data to decode +* @param r An empty ncacn_packet, must not be NULL +* +* @return a NTSTATUS error code +*/ +NTSTATUS dcerpc_pull_ncacn_packet(TALLOC_CTX *mem_ctx, + const DATA_BLOB *blob, + struct ncacn_packet *r) +{ + enum ndr_err_code ndr_err; + struct ndr_pull *ndr; + + ndr = ndr_pull_init_blob(blob, mem_ctx); + if (!ndr) { + return NT_STATUS_NO_MEMORY; + } + + ndr_err = ndr_pull_ncacn_packet(ndr, NDR_SCALARS|NDR_BUFFERS, r); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(ndr); + return ndr_map_error2ntstatus(ndr_err); + } + talloc_free(ndr); + + if (r->frag_length != blob->length) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + return NT_STATUS_OK; +} + +/** +* @brief Pull a dcerpc_auth structure, taking account of any auth +* padding in the blob. For request/response packets we pass +* the whole data blob, so auth_data_only must be set to false +* as the blob contains data+pad+auth and no just pad+auth. +* +* @param pkt - The ncacn_packet structure +* @param mem_ctx - The mem_ctx used to allocate dcerpc_auth elements +* @param pkt_trailer - The packet trailer data, usually the trailing +* auth_info blob, but in the request/response case +* this is the stub_and_verifier blob. +* @param auth - A preallocated dcerpc_auth *empty* structure +* @param auth_length - The length of the auth trail, sum of auth header +* length and pkt->auth_length +* @param auth_data_only - Whether the pkt_trailer includes only the auth_blob +* (+ padding) or also other data. +* +* @return - A NTSTATUS error code. +*/ +NTSTATUS dcerpc_pull_auth_trailer(const struct ncacn_packet *pkt, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *pkt_trailer, + struct dcerpc_auth *auth, + uint32_t *_auth_length, + bool auth_data_only) +{ + struct ndr_pull *ndr; + enum ndr_err_code ndr_err; + uint16_t data_and_pad; + uint16_t auth_length; + uint32_t tmp_length; + uint32_t max_pad_len = 0; + + ZERO_STRUCTP(auth); + if (_auth_length != NULL) { + *_auth_length = 0; + + if (auth_data_only) { + return NT_STATUS_INTERNAL_ERROR; + } + } else { + if (!auth_data_only) { + return NT_STATUS_INTERNAL_ERROR; + } + } + + /* Paranoia checks for auth_length. The caller should check this... */ + if (pkt->auth_length == 0) { + return NT_STATUS_INTERNAL_ERROR; + } + + /* Paranoia checks for auth_length. The caller should check this... */ + if (pkt->auth_length > pkt->frag_length) { + return NT_STATUS_INTERNAL_ERROR; + } + tmp_length = DCERPC_NCACN_PAYLOAD_OFFSET; + tmp_length += DCERPC_AUTH_TRAILER_LENGTH; + tmp_length += pkt->auth_length; + if (tmp_length > pkt->frag_length) { + return NT_STATUS_INTERNAL_ERROR; + } + if (pkt_trailer->length > UINT16_MAX) { + return NT_STATUS_INTERNAL_ERROR; + } + + auth_length = DCERPC_AUTH_TRAILER_LENGTH + pkt->auth_length; + if (pkt_trailer->length < auth_length) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + data_and_pad = pkt_trailer->length - auth_length; + + ndr = ndr_pull_init_blob(pkt_trailer, mem_ctx); + if (!ndr) { + return NT_STATUS_NO_MEMORY; + } + + if (!(pkt->drep[0] & DCERPC_DREP_LE)) { + ndr->flags |= LIBNDR_FLAG_BIGENDIAN; + } + + ndr_err = ndr_pull_advance(ndr, data_and_pad); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(ndr); + return ndr_map_error2ntstatus(ndr_err); + } + + ndr_err = ndr_pull_dcerpc_auth(ndr, NDR_SCALARS|NDR_BUFFERS, auth); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(ndr); + ZERO_STRUCTP(auth); + return ndr_map_error2ntstatus(ndr_err); + } + + /* + * Make sure the padding would not exceed + * the frag_length. + * + * Here we assume at least 24 bytes for the + * payload specific header the value of + * DCERPC_{REQUEST,RESPONSE}_LENGTH. + * + * We use this also for BIND_*, ALTER_* and AUTH3 pdus. + * + * We need this check before we ignore possible + * invalid values. See also bug #11982. + * + * This check is mainly used to generate the correct + * error for BIND_*, ALTER_* and AUTH3 pdus. + * + * We always have the 'if (data_and_pad < auth->auth_pad_length)' + * protection for REQUEST and RESPONSE pdus, where the + * auth_pad_length field is actually used by the caller. + */ + tmp_length = DCERPC_REQUEST_LENGTH; + tmp_length += DCERPC_AUTH_TRAILER_LENGTH; + tmp_length += pkt->auth_length; + if (tmp_length < pkt->frag_length) { + max_pad_len = pkt->frag_length - tmp_length; + } + if (max_pad_len < auth->auth_pad_length) { + DEBUG(1, (__location__ ": ERROR: pad length too large. " + "max %"PRIu32" got %"PRIu8"\n", + max_pad_len, + auth->auth_pad_length)); + talloc_free(ndr); + ZERO_STRUCTP(auth); + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + /* + * This is a workaround for a bug in old + * Samba releases. For BIND_ACK <= 3.5.x + * and for ALTER_RESP <= 4.2.x (see bug #11061) + * + * See also bug #11982. + */ + if (auth_data_only && data_and_pad == 0 && + auth->auth_pad_length > 0) { + /* + * we need to ignore invalid auth_pad_length + * values for BIND_*, ALTER_* and AUTH3 pdus. + */ + auth->auth_pad_length = 0; + } + + if (data_and_pad < auth->auth_pad_length) { + DBG_WARNING(__location__ ": ERROR: pad length too long. " + "Calculated %"PRIu16" (pkt_trailer->length=%zu - auth_length=%"PRIu16") " + "was less than auth_pad_length=%"PRIu8"\n", + data_and_pad, + pkt_trailer->length, + auth_length, + auth->auth_pad_length); + talloc_free(ndr); + ZERO_STRUCTP(auth); + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + if (auth_data_only && data_and_pad > auth->auth_pad_length) { + DBG_WARNING(__location__ ": ERROR: auth_data_only pad length mismatch. " + "Client sent a longer BIND packet than expected by %"PRIu16" bytes " + "(pkt_trailer->length=%zu - auth_length=%"PRIu16") " + "= %"PRIu16" auth_pad_length=%"PRIu8"\n", + data_and_pad - auth->auth_pad_length, + pkt_trailer->length, + auth_length, + data_and_pad, + auth->auth_pad_length); + talloc_free(ndr); + ZERO_STRUCTP(auth); + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + if (auth_data_only && data_and_pad != auth->auth_pad_length) { + DBG_WARNING(__location__ ": ERROR: auth_data_only pad length mismatch. " + "Calculated %"PRIu16" (pkt_trailer->length=%zu - auth_length=%"PRIu16") " + "but auth_pad_length=%"PRIu8"\n", + data_and_pad, + pkt_trailer->length, + auth_length, + auth->auth_pad_length); + talloc_free(ndr); + ZERO_STRUCTP(auth); + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + DBG_DEBUG("auth_pad_length %"PRIu8"\n", + auth->auth_pad_length); + + talloc_steal(mem_ctx, auth->credentials.data); + talloc_free(ndr); + + if (_auth_length != NULL) { + *_auth_length = auth_length; + } + + return NT_STATUS_OK; +} + +/** +* @brief Verify the fields in ncacn_packet header. +* +* @param pkt - The ncacn_packet structure +* @param ptype - The expected PDU type +* @param max_auth_info - The maximum size of a possible auth trailer +* @param required_flags - The required flags for the pdu. +* @param optional_flags - The possible optional flags for the pdu. +* +* @return - A NTSTATUS error code. +*/ +NTSTATUS dcerpc_verify_ncacn_packet_header(const struct ncacn_packet *pkt, + enum dcerpc_pkt_type ptype, + size_t max_auth_info, + uint8_t required_flags, + uint8_t optional_flags) +{ + if (pkt->rpc_vers != 5) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + if (pkt->rpc_vers_minor != 0) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + if (pkt->auth_length > pkt->frag_length) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + if (pkt->ptype != ptype) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + if (max_auth_info > UINT16_MAX) { + return NT_STATUS_INTERNAL_ERROR; + } + + if (pkt->auth_length > 0) { + size_t max_auth_length; + + if (max_auth_info <= DCERPC_AUTH_TRAILER_LENGTH) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + max_auth_length = max_auth_info - DCERPC_AUTH_TRAILER_LENGTH; + + if (pkt->auth_length > max_auth_length) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + } + + if ((pkt->pfc_flags & required_flags) != required_flags) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + if (pkt->pfc_flags & ~(optional_flags|required_flags)) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + if (pkt->drep[0] & ~DCERPC_DREP_LE) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + if (pkt->drep[1] != 0) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + if (pkt->drep[2] != 0) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + if (pkt->drep[3] != 0) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + return NT_STATUS_OK; +} + +struct dcerpc_read_ncacn_packet_state { +#if 0 + struct { + } caller; +#endif + DATA_BLOB buffer; + struct ncacn_packet *pkt; +}; + +static int dcerpc_read_ncacn_packet_next_vector(struct tstream_context *stream, + void *private_data, + TALLOC_CTX *mem_ctx, + struct iovec **_vector, + size_t *_count); +static void dcerpc_read_ncacn_packet_done(struct tevent_req *subreq); + +struct tevent_req *dcerpc_read_ncacn_packet_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream) +{ + struct tevent_req *req; + struct dcerpc_read_ncacn_packet_state *state; + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct dcerpc_read_ncacn_packet_state); + if (req == NULL) { + return NULL; + } + + state->pkt = talloc_zero(state, struct ncacn_packet); + if (tevent_req_nomem(state->pkt, req)) { + goto post; + } + + subreq = tstream_readv_pdu_send(state, ev, + stream, + dcerpc_read_ncacn_packet_next_vector, + state); + if (tevent_req_nomem(subreq, req)) { + goto post; + } + tevent_req_set_callback(subreq, dcerpc_read_ncacn_packet_done, req); + + return req; + post: + tevent_req_post(req, ev); + return req; +} + +static int dcerpc_read_ncacn_packet_next_vector(struct tstream_context *stream, + void *private_data, + TALLOC_CTX *mem_ctx, + struct iovec **_vector, + size_t *_count) +{ + struct dcerpc_read_ncacn_packet_state *state = + talloc_get_type_abort(private_data, + struct dcerpc_read_ncacn_packet_state); + struct iovec *vector; + off_t ofs = 0; + + if (state->buffer.length == 0) { + /* + * first get enough to read the fragment length + * + * We read the full fixed ncacn_packet header + * in order to make wireshark happy with + * pcap files from socket_wrapper. + */ + ofs = 0; + state->buffer.length = DCERPC_NCACN_PAYLOAD_OFFSET; + state->buffer.data = talloc_array(state, uint8_t, + state->buffer.length); + if (!state->buffer.data) { + return -1; + } + } else if (state->buffer.length == DCERPC_NCACN_PAYLOAD_OFFSET) { + /* now read the fragment length and allocate the full buffer */ + size_t frag_len = dcerpc_get_frag_length(&state->buffer); + + ofs = state->buffer.length; + + if (frag_len <= ofs) { + /* + * With frag_len == ofs, we are done, this is likely + * a DCERPC_PKT_CO_CANCEL and DCERPC_PKT_ORPHANED + * without any payload. + * + * Otherwise it's a broken packet and we + * let the caller deal with it. + */ + *_vector = NULL; + *_count = 0; + return 0; + } + + state->buffer.data = talloc_realloc(state, + state->buffer.data, + uint8_t, frag_len); + if (!state->buffer.data) { + return -1; + } + state->buffer.length = frag_len; + } else { + /* if we reach this we have a full fragment */ + *_vector = NULL; + *_count = 0; + return 0; + } + + /* now create the vector that we want to be filled */ + vector = talloc_array(mem_ctx, struct iovec, 1); + if (!vector) { + return -1; + } + + vector[0].iov_base = (void *) (state->buffer.data + ofs); + vector[0].iov_len = state->buffer.length - ofs; + + *_vector = vector; + *_count = 1; + return 0; +} + +static void dcerpc_read_ncacn_packet_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct dcerpc_read_ncacn_packet_state *state = tevent_req_data(req, + struct dcerpc_read_ncacn_packet_state); + int ret; + int sys_errno; + NTSTATUS status; + + ret = tstream_readv_pdu_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + status = map_nt_error_from_unix_common(sys_errno); + tevent_req_nterror(req, status); + return; + } + + status = dcerpc_pull_ncacn_packet(state->pkt, + &state->buffer, + state->pkt); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +NTSTATUS dcerpc_read_ncacn_packet_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct ncacn_packet **pkt, + DATA_BLOB *buffer) +{ + struct dcerpc_read_ncacn_packet_state *state = tevent_req_data(req, + struct dcerpc_read_ncacn_packet_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *pkt = talloc_move(mem_ctx, &state->pkt); + if (buffer) { + buffer->data = talloc_move(mem_ctx, &state->buffer.data); + buffer->length = state->buffer.length; + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +const char *dcerpc_default_transport_endpoint(TALLOC_CTX *mem_ctx, + enum dcerpc_transport_t transport, + const struct ndr_interface_table *table) +{ + NTSTATUS status; + const char *p = NULL; + const char *endpoint = NULL; + uint32_t i; + struct dcerpc_binding *default_binding = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + /* Find one of the default pipes for this interface */ + + for (i = 0; i < table->endpoints->count; i++) { + enum dcerpc_transport_t dtransport; + const char *dendpoint; + + status = dcerpc_parse_binding(frame, table->endpoints->names[i], + &default_binding); + if (!NT_STATUS_IS_OK(status)) { + continue; + } + + dtransport = dcerpc_binding_get_transport(default_binding); + dendpoint = dcerpc_binding_get_string_option(default_binding, + "endpoint"); + if (dendpoint == NULL) { + TALLOC_FREE(default_binding); + continue; + } + + if (transport == NCA_UNKNOWN) { + transport = dtransport; + } + + if (transport != dtransport) { + TALLOC_FREE(default_binding); + continue; + } + + p = dendpoint; + break; + } + + if (p == NULL) { + goto done; + } + + /* + * extract the pipe name without \\pipe from for example + * ncacn_np:[\\pipe\\epmapper] + */ + if (transport == NCACN_NP) { + if (strncasecmp(p, "\\pipe\\", 6) == 0) { + p += 6; + } + if (p[0] == '\\') { + p += 1; + } + } + + endpoint = talloc_strdup(mem_ctx, p); + + done: + talloc_free(frame); + return endpoint; +} + +struct dcerpc_sec_vt_header2 dcerpc_sec_vt_header2_from_ncacn_packet(const struct ncacn_packet *pkt) +{ + struct dcerpc_sec_vt_header2 ret; + + ZERO_STRUCT(ret); + ret.ptype = pkt->ptype; + memcpy(&ret.drep, pkt->drep, sizeof(ret.drep)); + ret.call_id = pkt->call_id; + + switch (pkt->ptype) { + case DCERPC_PKT_REQUEST: + ret.context_id = pkt->u.request.context_id; + ret.opnum = pkt->u.request.opnum; + break; + + case DCERPC_PKT_RESPONSE: + ret.context_id = pkt->u.response.context_id; + break; + + case DCERPC_PKT_FAULT: + ret.context_id = pkt->u.fault.context_id; + break; + + default: + break; + } + + return ret; +} + +bool dcerpc_sec_vt_header2_equal(const struct dcerpc_sec_vt_header2 *v1, + const struct dcerpc_sec_vt_header2 *v2) +{ + if (v1->ptype != v2->ptype) { + return false; + } + + if (memcmp(v1->drep, v2->drep, sizeof(v1->drep)) != 0) { + return false; + } + + if (v1->call_id != v2->call_id) { + return false; + } + + if (v1->context_id != v2->context_id) { + return false; + } + + if (v1->opnum != v2->opnum) { + return false; + } + + return true; +} + +static bool dcerpc_sec_vt_is_valid(const struct dcerpc_sec_verification_trailer *r) +{ + bool ret = false; + TALLOC_CTX *frame = talloc_stackframe(); + struct bitmap *commands_seen; + int i; + + if (r->count.count == 0) { + ret = true; + goto done; + } + + if (memcmp(r->magic, DCERPC_SEC_VT_MAGIC, sizeof(r->magic)) != 0) { + goto done; + } + + commands_seen = bitmap_talloc(frame, DCERPC_SEC_VT_COMMAND_ENUM + 1); + if (commands_seen == NULL) { + goto done; + } + + for (i=0; i < r->count.count; i++) { + enum dcerpc_sec_vt_command_enum cmd = + r->commands[i].command & DCERPC_SEC_VT_COMMAND_ENUM; + + if (bitmap_query(commands_seen, cmd)) { + /* Each command must appear at most once. */ + goto done; + } + bitmap_set(commands_seen, cmd); + + switch (cmd) { + case DCERPC_SEC_VT_COMMAND_BITMASK1: + case DCERPC_SEC_VT_COMMAND_PCONTEXT: + case DCERPC_SEC_VT_COMMAND_HEADER2: + break; + default: + if ((r->commands[i].u._unknown.length % 4) != 0) { + goto done; + } + break; + } + } + ret = true; +done: + TALLOC_FREE(frame); + return ret; +} + +static bool dcerpc_sec_vt_bitmask_check(const uint32_t *bitmask1, + struct dcerpc_sec_vt *c) +{ + if (bitmask1 == NULL) { + if (c->command & DCERPC_SEC_VT_MUST_PROCESS) { + DEBUG(10, ("SEC_VT check Bitmask1 must_process_command " + "failed\n")); + return false; + } + + return true; + } + + if ((c->u.bitmask1 & DCERPC_SEC_VT_CLIENT_SUPPORTS_HEADER_SIGNING) + && (!(*bitmask1 & DCERPC_SEC_VT_CLIENT_SUPPORTS_HEADER_SIGNING))) { + DEBUG(10, ("SEC_VT check Bitmask1 client_header_signing " + "failed\n")); + return false; + } + return true; +} + +static bool dcerpc_sec_vt_pctx_check(const struct dcerpc_sec_vt_pcontext *pcontext, + struct dcerpc_sec_vt *c) +{ + bool ok; + + if (pcontext == NULL) { + if (c->command & DCERPC_SEC_VT_MUST_PROCESS) { + DEBUG(10, ("SEC_VT check Pcontext must_process_command " + "failed\n")); + return false; + } + + return true; + } + + ok = ndr_syntax_id_equal(&pcontext->abstract_syntax, + &c->u.pcontext.abstract_syntax); + if (!ok) { + struct ndr_syntax_id_buf buf1, buf2; + DEBUG(10, ("SEC_VT check pcontext abstract_syntax failed: " + "%s vs. %s\n", + ndr_syntax_id_buf_string( + &pcontext->abstract_syntax, &buf1), + ndr_syntax_id_buf_string( + &c->u.pcontext.abstract_syntax, &buf2))); + return false; + } + ok = ndr_syntax_id_equal(&pcontext->transfer_syntax, + &c->u.pcontext.transfer_syntax); + if (!ok) { + struct ndr_syntax_id_buf buf1, buf2; + DEBUG(10, ("SEC_VT check pcontext transfer_syntax failed: " + "%s vs. %s\n", + ndr_syntax_id_buf_string( + &pcontext->transfer_syntax, &buf1), + ndr_syntax_id_buf_string( + &c->u.pcontext.transfer_syntax, &buf2))); + return false; + } + + return true; +} + +static bool dcerpc_sec_vt_hdr2_check(const struct dcerpc_sec_vt_header2 *header2, + struct dcerpc_sec_vt *c) +{ + if (header2 == NULL) { + if (c->command & DCERPC_SEC_VT_MUST_PROCESS) { + DEBUG(10, ("SEC_VT check Header2 must_process_command failed\n")); + return false; + } + + return true; + } + + if (!dcerpc_sec_vt_header2_equal(header2, &c->u.header2)) { + DEBUG(10, ("SEC_VT check Header2 failed\n")); + return false; + } + + return true; +} + +bool dcerpc_sec_verification_trailer_check( + const struct dcerpc_sec_verification_trailer *vt, + const uint32_t *bitmask1, + const struct dcerpc_sec_vt_pcontext *pcontext, + const struct dcerpc_sec_vt_header2 *header2) +{ + size_t i; + + if (!dcerpc_sec_vt_is_valid(vt)) { + return false; + } + + for (i=0; i < vt->count.count; i++) { + bool ok; + struct dcerpc_sec_vt *c = &vt->commands[i]; + + switch (c->command & DCERPC_SEC_VT_COMMAND_ENUM) { + case DCERPC_SEC_VT_COMMAND_BITMASK1: + ok = dcerpc_sec_vt_bitmask_check(bitmask1, c); + if (!ok) { + return false; + } + break; + + case DCERPC_SEC_VT_COMMAND_PCONTEXT: + ok = dcerpc_sec_vt_pctx_check(pcontext, c); + if (!ok) { + return false; + } + break; + + case DCERPC_SEC_VT_COMMAND_HEADER2: { + ok = dcerpc_sec_vt_hdr2_check(header2, c); + if (!ok) { + return false; + } + break; + } + + default: + if (c->command & DCERPC_SEC_VT_MUST_PROCESS) { + DEBUG(10, ("SEC_VT check Unknown must_process_command failed\n")); + return false; + } + + break; + } + } + + return true; +} + +static const struct ndr_syntax_id dcerpc_bind_time_features_prefix = { + .uuid = { + .time_low = 0x6cb71c2c, + .time_mid = 0x9812, + .time_hi_and_version = 0x4540, + .clock_seq = {0x00, 0x00}, + .node = {0x00,0x00,0x00,0x00,0x00,0x00} + }, + .if_version = 1, +}; + +bool dcerpc_extract_bind_time_features(struct ndr_syntax_id s, uint64_t *_features) +{ + uint8_t values[8]; + uint64_t features = 0; + + values[0] = s.uuid.clock_seq[0]; + values[1] = s.uuid.clock_seq[1]; + values[2] = s.uuid.node[0]; + values[3] = s.uuid.node[1]; + values[4] = s.uuid.node[2]; + values[5] = s.uuid.node[3]; + values[6] = s.uuid.node[4]; + values[7] = s.uuid.node[5]; + + ZERO_STRUCT(s.uuid.clock_seq); + ZERO_STRUCT(s.uuid.node); + + if (!ndr_syntax_id_equal(&s, &dcerpc_bind_time_features_prefix)) { + if (_features != NULL) { + *_features = 0; + } + return false; + } + + features = BVAL(values, 0); + + if (_features != NULL) { + *_features = features; + } + + return true; +} + +struct ndr_syntax_id dcerpc_construct_bind_time_features(uint64_t features) +{ + struct ndr_syntax_id s = dcerpc_bind_time_features_prefix; + uint8_t values[8]; + + SBVAL(values, 0, features); + + s.uuid.clock_seq[0] = values[0]; + s.uuid.clock_seq[1] = values[1]; + s.uuid.node[0] = values[2]; + s.uuid.node[1] = values[3]; + s.uuid.node[2] = values[4]; + s.uuid.node[3] = values[5]; + s.uuid.node[4] = values[6]; + s.uuid.node[5] = values[7]; + + return s; +} + +NTSTATUS dcerpc_generic_session_key(DATA_BLOB *session_key) +{ + *session_key = data_blob_null; + + /* this took quite a few CPU cycles to find ... */ + session_key->data = discard_const_p(unsigned char, "SystemLibraryDTC"); + session_key->length = 16; + return NT_STATUS_OK; +} + +/* + push a ncacn_packet into a blob, potentially with auth info +*/ +NTSTATUS dcerpc_ncacn_push_auth(DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + struct ncacn_packet *pkt, + struct dcerpc_auth *auth_info) +{ + struct ndr_push *ndr; + enum ndr_err_code ndr_err; + + ndr = ndr_push_init_ctx(mem_ctx); + if (!ndr) { + return NT_STATUS_NO_MEMORY; + } + + if (auth_info) { + pkt->auth_length = auth_info->credentials.length; + } else { + pkt->auth_length = 0; + } + + ndr_err = ndr_push_ncacn_packet(ndr, NDR_SCALARS|NDR_BUFFERS, pkt); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + if (auth_info) { +#if 0 + /* the s3 rpc server doesn't handle auth padding in + bind requests. Use zero auth padding to keep us + working with old servers */ + uint32_t offset = ndr->offset; + ndr_err = ndr_push_align(ndr, 16); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + auth_info->auth_pad_length = ndr->offset - offset; +#else + auth_info->auth_pad_length = 0; +#endif + ndr_err = ndr_push_dcerpc_auth(ndr, NDR_SCALARS|NDR_BUFFERS, auth_info); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + } + + *blob = ndr_push_blob(ndr); + + /* fill in the frag length */ + dcerpc_set_frag_length(blob, blob->length); + + return NT_STATUS_OK; +} + +/* + log a rpc packet in a format suitable for ndrdump. This is especially useful + for sealed packets, where ethereal cannot easily see the contents + + this triggers if "dcesrv:stubs directory" is set and present + for all packets that fail to parse +*/ +void dcerpc_log_packet(const char *packet_log_dir, + const char *interface_name, + uint32_t opnum, ndr_flags_type flags, + const DATA_BLOB *pkt, + const char *why) +{ + const int num_examples = 20; + int i; + + if (packet_log_dir == NULL) { + return; + } + + for (i=0;i<num_examples;i++) { + char *name=NULL; + int ret; + bool saved; + ret = asprintf(&name, "%s/%s-%"PRIu32".%d.%s.%s", + packet_log_dir, interface_name, opnum, i, + (flags&NDR_IN)?"in":"out", + why); + if (ret == -1) { + return; + } + + saved = file_save(name, pkt->data, pkt->length); + if (saved) { + DBG_DEBUG("Logged rpc packet to %s\n", name); + free(name); + break; + } + free(name); + } +} diff --git a/librpc/rpc/dcerpc_util.h b/librpc/rpc/dcerpc_util.h new file mode 100644 index 0000000..4e49e3e --- /dev/null +++ b/librpc/rpc/dcerpc_util.h @@ -0,0 +1,85 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2010-2011 + Copyright (C) Andrew Tridgell 2010-2011 + Copyright (C) Simo Sorce 2010 + + 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/>. +*/ + +#ifndef __LIBRPC_RPC_DCERPC_UTIL_H__ +#define __LIBRPC_RPC_DCERPC_UTIL_H__ + +#include "replace.h" +#include <talloc.h> +#include "lib/util/data_blob.h" +#include "librpc/rpc/rpc_common.h" +#include "librpc/gen_ndr/dcerpc.h" + +void dcerpc_set_frag_length(DATA_BLOB *blob, uint16_t v); +uint16_t dcerpc_get_frag_length(const DATA_BLOB *blob); +void dcerpc_set_auth_length(DATA_BLOB *blob, uint16_t v); +uint16_t dcerpc_get_auth_length(const DATA_BLOB *blob); +uint8_t dcerpc_get_endian_flag(DATA_BLOB *blob); +uint8_t dcerpc_get_auth_type(const DATA_BLOB *blob); +uint8_t dcerpc_get_auth_level(const DATA_BLOB *blob); +uint32_t dcerpc_get_auth_context_id(const DATA_BLOB *blob); +const char *dcerpc_default_transport_endpoint(TALLOC_CTX *mem_ctx, + enum dcerpc_transport_t transport, + const struct ndr_interface_table *table); + +NTSTATUS dcerpc_pull_ncacn_packet(TALLOC_CTX *mem_ctx, + const DATA_BLOB *blob, + struct ncacn_packet *r); + +/** +* @brief Pull a dcerpc_auth structure, taking account of any auth +* padding in the blob. For request/response packets we pass +* the whole data blob, so auth_data_only must be set to false +* as the blob contains data+pad+auth and no just pad+auth. +* +* @param pkt - The ncacn_packet structure +* @param mem_ctx - The mem_ctx used to allocate dcerpc_auth elements +* @param pkt_trailer - The packet trailer data, usually the trailing +* auth_info blob, but in the request/response case +* this is the stub_and_verifier blob. +* @param auth - A preallocated dcerpc_auth *empty* structure +* @param auth_length - The length of the auth trail, sum of auth header +* length and pkt->auth_length +* @param auth_data_only - Whether the pkt_trailer includes only the auth_blob +* (+ padding) or also other data. +* +* @return - A NTSTATUS error code. +*/ +NTSTATUS dcerpc_pull_auth_trailer(const struct ncacn_packet *pkt, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *pkt_trailer, + struct dcerpc_auth *auth, + uint32_t *auth_length, + bool auth_data_only); +NTSTATUS dcerpc_verify_ncacn_packet_header(const struct ncacn_packet *pkt, + enum dcerpc_pkt_type ptype, + size_t max_auth_info, + uint8_t required_flags, + uint8_t optional_flags); +struct tevent_req *dcerpc_read_ncacn_packet_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream); +NTSTATUS dcerpc_read_ncacn_packet_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct ncacn_packet **pkt, + DATA_BLOB *buffer); + +#endif diff --git a/librpc/rpc/dcesrv_auth.c b/librpc/rpc/dcesrv_auth.c new file mode 100644 index 0000000..1fc6255 --- /dev/null +++ b/librpc/rpc/dcesrv_auth.c @@ -0,0 +1,703 @@ +/* + Unix SMB/CIFS implementation. + + server side dcerpc authentication code + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Stefan (metze) Metzmacher 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 "includes.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/rpc/dcesrv_core_proto.h" +#include "librpc/rpc/dcerpc_util.h" +#include "librpc/rpc/dcerpc_pkt_auth.h" +#include "librpc/gen_ndr/ndr_dcerpc.h" +#include "auth/credentials/credentials.h" +#include "auth/gensec/gensec.h" +#include "auth/auth.h" +#include "param/param.h" + +static NTSTATUS dcesrv_auth_negotiate_hdr_signing(struct dcesrv_call_state *call, + struct ncacn_packet *pkt) +{ + struct dcesrv_connection *dce_conn = call->conn; + struct dcesrv_auth *a = NULL; + + if (!(call->pkt.pfc_flags & DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN)) { + return NT_STATUS_OK; + } + + if (dce_conn->client_hdr_signing) { + if (dce_conn->negotiated_hdr_signing && pkt != NULL) { + pkt->pfc_flags |= DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN; + } + return NT_STATUS_OK; + } + + dce_conn->client_hdr_signing = true; + dce_conn->negotiated_hdr_signing = dce_conn->support_hdr_signing; + + if (!dce_conn->negotiated_hdr_signing) { + return NT_STATUS_OK; + } + + if (pkt != NULL) { + pkt->pfc_flags |= DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN; + } + + a = call->conn->default_auth_state; + if (a->gensec_security != NULL) { + gensec_want_feature(a->gensec_security, + GENSEC_FEATURE_SIGN_PKT_HEADER); + } + + for (a = call->conn->auth_states; a != NULL; a = a->next) { + if (a->gensec_security == NULL) { + continue; + } + + gensec_want_feature(a->gensec_security, + GENSEC_FEATURE_SIGN_PKT_HEADER); + } + + return NT_STATUS_OK; +} + +static bool dcesrv_auth_prepare_gensec(struct dcesrv_call_state *call) +{ + struct dcesrv_connection *dce_conn = call->conn; + struct dcesrv_auth *auth = call->auth_state; + struct dcesrv_context_callbacks *cb = call->conn->dce_ctx->callbacks; + NTSTATUS status; + + if (auth->auth_started) { + return false; + } + + auth->auth_started = true; + + if (auth->auth_invalid) { + return false; + } + + if (auth->auth_finished) { + return false; + } + + if (auth->gensec_security != NULL) { + return false; + } + + switch (call->in_auth_info.auth_level) { + case DCERPC_AUTH_LEVEL_CONNECT: + case DCERPC_AUTH_LEVEL_CALL: + case DCERPC_AUTH_LEVEL_PACKET: + case DCERPC_AUTH_LEVEL_INTEGRITY: + case DCERPC_AUTH_LEVEL_PRIVACY: + /* + * We evaluate auth_type only if auth_level was valid + */ + break; + default: + /* + * Setting DCERPC_AUTH_LEVEL_NONE, + * gives the caller the reject_reason + * as auth_context_id. + * + * Note: DCERPC_AUTH_LEVEL_NONE == 1 + */ + auth->auth_type = DCERPC_AUTH_TYPE_NONE; + auth->auth_level = DCERPC_AUTH_LEVEL_NONE; + auth->auth_context_id = DCERPC_BIND_NAK_REASON_NOT_SPECIFIED; + return false; + } + + auth->auth_type = call->in_auth_info.auth_type; + auth->auth_level = call->in_auth_info.auth_level; + auth->auth_context_id = call->in_auth_info.auth_context_id; + + cb->auth.become_root(); + status = cb->auth.gensec_prepare( + auth, + call, + &auth->gensec_security, + cb->auth.private_data); + cb->auth.unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to call samba_server_gensec_start %s\n", + nt_errstr(status))); + return false; + } + + /* + * We have to call this because we set the target_service for + * Kerberos to NULL above, and in any case we wish to log a + * more specific service target. + * + */ + status = gensec_set_target_service_description(auth->gensec_security, + "DCE/RPC"); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to call gensec_set_target_service_description %s\n", + nt_errstr(status))); + return false; + } + + if (call->conn->remote_address != NULL) { + status = gensec_set_remote_address(auth->gensec_security, + call->conn->remote_address); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to call gensec_set_remote_address() %s\n", + nt_errstr(status))); + return false; + } + } + + if (call->conn->local_address != NULL) { + status = gensec_set_local_address(auth->gensec_security, + call->conn->local_address); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to call gensec_set_local_address() %s\n", + nt_errstr(status))); + return false; + } + } + + status = gensec_start_mech_by_authtype(auth->gensec_security, auth->auth_type, + auth->auth_level); + if (!NT_STATUS_IS_OK(status)) { + const char *backend_name = + gensec_get_name_by_authtype(auth->gensec_security, + auth->auth_type); + + DEBUG(3, ("Failed to start GENSEC mechanism for DCERPC server: " + "auth_type=%d (%s), auth_level=%d: %s\n", + (int)auth->auth_type, backend_name, + (int)auth->auth_level, + nt_errstr(status))); + + /* + * Setting DCERPC_AUTH_LEVEL_NONE, + * gives the caller the reject_reason + * as auth_context_id. + * + * Note: DCERPC_AUTH_LEVEL_NONE == 1 + */ + auth->auth_type = DCERPC_AUTH_TYPE_NONE; + auth->auth_level = DCERPC_AUTH_LEVEL_NONE; + if (backend_name != NULL) { + auth->auth_context_id = + DCERPC_BIND_NAK_REASON_INVALID_CHECKSUM; + } else { + auth->auth_context_id = + DCERPC_BIND_NAK_REASON_INVALID_AUTH_TYPE; + } + return false; + } + + if (dce_conn->negotiated_hdr_signing) { + gensec_want_feature(auth->gensec_security, + GENSEC_FEATURE_SIGN_PKT_HEADER); + } + + return true; +} + +static void dcesrv_default_auth_state_finish_bind(struct dcesrv_call_state *call) +{ + SMB_ASSERT(call->pkt.ptype == DCERPC_PKT_BIND); + + if (call->auth_state == call->conn->default_auth_state) { + return; + } + + if (call->conn->default_auth_state->auth_started) { + return; + } + + if (call->conn->default_auth_state->auth_invalid) { + return; + } + + call->conn->default_auth_state->auth_type = DCERPC_AUTH_TYPE_NONE; + call->conn->default_auth_state->auth_level = DCERPC_AUTH_LEVEL_NONE; + call->conn->default_auth_state->auth_context_id = 0; + call->conn->default_auth_state->auth_started = true; + call->conn->default_auth_state->auth_finished = true; + + /* + * + * We defer log_successful_dcesrv_authz_event() + * to dcesrv_default_auth_state_prepare_request() + * + * As we don't want to trigger authz_events + * just for alter_context requests without authentication + */ +} + +void dcesrv_default_auth_state_prepare_request(struct dcesrv_call_state *call) +{ + struct dcesrv_connection *dce_conn = call->conn; + struct dcesrv_auth *auth = call->auth_state; + struct dcesrv_context_callbacks *cb = call->conn->dce_ctx->callbacks; + + if (auth->auth_audited) { + return; + } + + if (call->pkt.ptype != DCERPC_PKT_REQUEST) { + return; + } + + if (auth != dce_conn->default_auth_state) { + return; + } + + if (auth->auth_invalid) { + return; + } + + if (!auth->auth_finished) { + return; + } + + if (cb->log.successful_authz == NULL) { + return; + } + + cb->log.successful_authz(call, cb->log.private_data); +} + +/* + parse any auth information from a dcerpc bind request + return false if we can't handle the auth request for some + reason (in which case we send a bind_nak) +*/ +bool dcesrv_auth_bind(struct dcesrv_call_state *call) +{ + struct ncacn_packet *pkt = &call->pkt; + struct dcesrv_auth *auth = call->auth_state; + struct dcesrv_context_callbacks *cb = call->conn->dce_ctx->callbacks; + NTSTATUS status; + + if (pkt->auth_length == 0) { + auth->auth_type = DCERPC_AUTH_TYPE_NONE; + auth->auth_level = DCERPC_AUTH_LEVEL_NONE; + auth->auth_context_id = 0; + auth->auth_started = true; + + if (cb->log.successful_authz != NULL) { + cb->log.successful_authz(call, cb->log.private_data); + } + + return true; + } + + status = dcerpc_pull_auth_trailer(pkt, call, &pkt->u.bind.auth_info, + &call->in_auth_info, + NULL, true); + if (!NT_STATUS_IS_OK(status)) { + /* + * Setting DCERPC_AUTH_LEVEL_NONE, + * gives the caller the reject_reason + * as auth_context_id. + * + * Note: DCERPC_AUTH_LEVEL_NONE == 1 + */ + auth->auth_type = DCERPC_AUTH_TYPE_NONE; + auth->auth_level = DCERPC_AUTH_LEVEL_NONE; + auth->auth_context_id = + DCERPC_BIND_NAK_REASON_PROTOCOL_VERSION_NOT_SUPPORTED; + return false; + } + + return dcesrv_auth_prepare_gensec(call); +} + +NTSTATUS dcesrv_auth_complete(struct dcesrv_call_state *call, NTSTATUS status) +{ + struct dcesrv_auth *auth = call->auth_state; + struct dcesrv_context_callbacks *cb = call->conn->dce_ctx->callbacks; + const char *pdu = "<unknown>"; + + switch (call->pkt.ptype) { + case DCERPC_PKT_BIND: + pdu = "BIND"; + break; + case DCERPC_PKT_ALTER: + pdu = "ALTER"; + break; + case DCERPC_PKT_AUTH3: + pdu = "AUTH3"; + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + DEBUG(4, ("GENSEC not finished at %s\n", pdu)); + return NT_STATUS_RPC_SEC_PKG_ERROR; + } + break; + default: + return NT_STATUS_INTERNAL_ERROR; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + return NT_STATUS_OK; + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(4, ("GENSEC mech rejected the incoming authentication " + "at %s: %s\n", pdu, nt_errstr(status))); + return status; + } + + cb->auth.become_root(); + status = gensec_session_info(auth->gensec_security, + auth, + &auth->session_info); + cb->auth.unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to establish session_info: %s\n", + nt_errstr(status))); + return status; + } + auth->auth_finished = true; + + if (auth->auth_level == DCERPC_AUTH_LEVEL_CONNECT && + !call->conn->got_explicit_auth_level_connect) + { + call->conn->default_auth_level_connect = auth; + } + + if (call->pkt.ptype != DCERPC_PKT_AUTH3) { + return NT_STATUS_OK; + } + + if (call->out_auth_info->credentials.length != 0) { + DEBUG(4, ("GENSEC produced output token (len=%zu) at %s\n", + call->out_auth_info->credentials.length, pdu)); + return NT_STATUS_RPC_SEC_PKG_ERROR; + } + + return NT_STATUS_OK; +} + +/* + add any auth information needed in a bind ack, and process the authentication + information found in the bind. +*/ +NTSTATUS dcesrv_auth_prepare_bind_ack(struct dcesrv_call_state *call, struct ncacn_packet *pkt) +{ + struct dcesrv_connection *dce_conn = call->conn; + struct dcesrv_auth *auth = call->auth_state; + NTSTATUS status; + + status = dcesrv_auth_negotiate_hdr_signing(call, pkt); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + dce_conn->allow_alter = true; + dcesrv_default_auth_state_finish_bind(call); + + if (call->pkt.auth_length == 0) { + auth->auth_finished = true; + return NT_STATUS_OK; + } + + /* We can't work without an existing gensec state */ + if (auth->gensec_security == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + call->_out_auth_info = (struct dcerpc_auth) { + .auth_type = auth->auth_type, + .auth_level = auth->auth_level, + .auth_context_id = auth->auth_context_id, + }; + call->out_auth_info = &call->_out_auth_info; + + return NT_STATUS_OK; +} + +/* + process the final stage of a auth request +*/ +bool dcesrv_auth_prepare_auth3(struct dcesrv_call_state *call) +{ + struct ncacn_packet *pkt = &call->pkt; + struct dcesrv_auth *auth = call->auth_state; + NTSTATUS status; + + if (pkt->auth_length == 0) { + return false; + } + + if (auth->auth_finished) { + return false; + } + + if (auth->auth_invalid) { + return false; + } + + /* We can't work without an existing gensec state */ + if (auth->gensec_security == NULL) { + return false; + } + + status = dcerpc_pull_auth_trailer(pkt, call, &pkt->u.auth3.auth_info, + &call->in_auth_info, NULL, true); + if (!NT_STATUS_IS_OK(status)) { + /* + * Windows returns DCERPC_NCA_S_FAULT_REMOTE_NO_MEMORY + * instead of DCERPC_NCA_S_PROTO_ERROR. + */ + call->fault_code = DCERPC_NCA_S_FAULT_REMOTE_NO_MEMORY; + return false; + } + + if (call->in_auth_info.auth_type != auth->auth_type) { + return false; + } + + if (call->in_auth_info.auth_level != auth->auth_level) { + return false; + } + + if (call->in_auth_info.auth_context_id != auth->auth_context_id) { + return false; + } + + call->_out_auth_info = (struct dcerpc_auth) { + .auth_type = auth->auth_type, + .auth_level = auth->auth_level, + .auth_context_id = auth->auth_context_id, + }; + call->out_auth_info = &call->_out_auth_info; + + return true; +} + +/* + parse any auth information from a dcerpc alter request + return false if we can't handle the auth request for some + reason (in which case we send a bind_nak (is this true for here?)) +*/ +bool dcesrv_auth_alter(struct dcesrv_call_state *call) +{ + struct ncacn_packet *pkt = &call->pkt; + struct dcesrv_auth *auth = call->auth_state; + NTSTATUS status; + + /* on a pure interface change there is no auth blob */ + if (pkt->auth_length == 0) { + if (!auth->auth_finished) { + return false; + } + return true; + } + + if (auth->auth_finished) { + call->fault_code = DCERPC_FAULT_ACCESS_DENIED; + return false; + } + + status = dcerpc_pull_auth_trailer(pkt, call, &pkt->u.alter.auth_info, + &call->in_auth_info, NULL, true); + if (!NT_STATUS_IS_OK(status)) { + call->fault_code = DCERPC_NCA_S_PROTO_ERROR; + return false; + } + + if (!auth->auth_started) { + bool ok; + + ok = dcesrv_auth_prepare_gensec(call); + if (!ok) { + call->fault_code = DCERPC_FAULT_ACCESS_DENIED; + return false; + } + + return true; + } + + if (call->in_auth_info.auth_type == DCERPC_AUTH_TYPE_NONE) { + call->fault_code = DCERPC_FAULT_ACCESS_DENIED; + return false; + } + + if (auth->auth_invalid) { + return false; + } + + if (call->in_auth_info.auth_type != auth->auth_type) { + return false; + } + + if (call->in_auth_info.auth_level != auth->auth_level) { + return false; + } + + if (call->in_auth_info.auth_context_id != auth->auth_context_id) { + return false; + } + + return true; +} + +/* + add any auth information needed in a alter ack, and process the authentication + information found in the alter. +*/ +NTSTATUS dcesrv_auth_prepare_alter_ack(struct dcesrv_call_state *call, struct ncacn_packet *pkt) +{ + struct dcesrv_auth *auth = call->auth_state; + NTSTATUS status; + + status = dcesrv_auth_negotiate_hdr_signing(call, pkt); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* on a pure interface change there is no auth_info structure + setup */ + if (call->pkt.auth_length == 0) { + return NT_STATUS_OK; + } + + if (auth->gensec_security == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + call->_out_auth_info = (struct dcerpc_auth) { + .auth_type = auth->auth_type, + .auth_level = auth->auth_level, + .auth_context_id = auth->auth_context_id, + }; + call->out_auth_info = &call->_out_auth_info; + + return NT_STATUS_OK; +} + +/* + check credentials on a packet +*/ +bool dcesrv_auth_pkt_pull(struct dcesrv_call_state *call, + DATA_BLOB *full_packet, + uint8_t required_flags, + uint8_t optional_flags, + uint8_t payload_offset, + DATA_BLOB *payload_and_verifier) +{ + struct ncacn_packet *pkt = &call->pkt; + struct dcesrv_auth *auth = call->auth_state; + const struct dcerpc_auth tmp_auth = { + .auth_type = auth->auth_type, + .auth_level = auth->auth_level, + .auth_context_id = auth->auth_context_id, + }; + bool check_pkt_auth_fields; + NTSTATUS status; + + if (!auth->auth_started) { + return false; + } + + if (!auth->auth_finished) { + call->fault_code = DCERPC_NCA_S_PROTO_ERROR; + return false; + } + + if (auth->auth_invalid) { + return false; + } + + if (call->pkt.pfc_flags & DCERPC_PFC_FLAG_FIRST) { + /* + * The caller most likely checked this + * already, but we better double check. + */ + check_pkt_auth_fields = true; + } else { + /* + * The caller already found first fragment + * and is passing the auth_state of it. + * A server is supposed to use the + * setting of the first fragment and + * completely ignore the values + * on the remaining fragments + */ + check_pkt_auth_fields = false; + } + + status = dcerpc_ncacn_pull_pkt_auth(&tmp_auth, + auth->gensec_security, + check_pkt_auth_fields, + call, + pkt->ptype, + required_flags, + optional_flags, + payload_offset, + payload_and_verifier, + full_packet, + pkt); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROTOCOL_ERROR)) { + call->fault_code = DCERPC_NCA_S_PROTO_ERROR; + return false; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_UNSUPPORTED_AUTHN_LEVEL)) { + call->fault_code = DCERPC_NCA_S_UNSUPPORTED_AUTHN_LEVEL; + return false; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) { + call->fault_code = DCERPC_FAULT_SEC_PKG_ERROR; + return false; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + call->fault_code = DCERPC_FAULT_ACCESS_DENIED; + return false; + } + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + return true; +} + +/* + push a signed or sealed dcerpc request packet into a blob +*/ +bool dcesrv_auth_pkt_push(struct dcesrv_call_state *call, + DATA_BLOB *blob, size_t sig_size, + uint8_t payload_offset, + const DATA_BLOB *payload, + const struct ncacn_packet *pkt) +{ + struct dcesrv_auth *auth = call->auth_state; + const struct dcerpc_auth tmp_auth = { + .auth_type = auth->auth_type, + .auth_level = auth->auth_level, + .auth_context_id = auth->auth_context_id, + }; + NTSTATUS status; + + status = dcerpc_ncacn_push_pkt_auth(&tmp_auth, + auth->gensec_security, + call, blob, sig_size, + payload_offset, + payload, + pkt); + return NT_STATUS_IS_OK(status); +} diff --git a/librpc/rpc/dcesrv_core.c b/librpc/rpc/dcesrv_core.c new file mode 100644 index 0000000..c0a4150 --- /dev/null +++ b/librpc/rpc/dcesrv_core.c @@ -0,0 +1,3369 @@ +/* + Unix SMB/CIFS implementation. + + server side dcerpc core code + + Copyright (C) Andrew Tridgell 2003-2005 + Copyright (C) Stefan (metze) Metzmacher 2004-2005 + Copyright (C) Samuel Cabrero <scabrero@samba.org> 2019 + + 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/rpc/dcesrv_core.h" +#include "librpc/rpc/dcesrv_core_proto.h" +#include "librpc/rpc/dcerpc_util.h" +#include "librpc/gen_ndr/auth.h" +#include "auth/gensec/gensec.h" +#include "lib/util/dlinklist.h" +#include "libcli/security/security.h" +#include "param/param.h" +#include "lib/tsocket/tsocket.h" +#include "librpc/gen_ndr/ndr_dcerpc.h" +#include "lib/util/tevent_ntstatus.h" +#include "system/network.h" +#include "lib/util/idtree_random.h" +#include "nsswitch/winbind_client.h" + +/** + * @file + * @brief DCERPC server + */ + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +#undef strcasecmp + +static NTSTATUS dcesrv_negotiate_contexts(struct dcesrv_call_state *call, + const struct dcerpc_bind *b, + struct dcerpc_ack_ctx *ack_ctx_list); + +/* + see if two endpoints match +*/ +static bool endpoints_match(const struct dcerpc_binding *ep1, + const struct dcerpc_binding *ep2) +{ + enum dcerpc_transport_t t1; + enum dcerpc_transport_t t2; + const char *e1; + const char *e2; + + t1 = dcerpc_binding_get_transport(ep1); + t2 = dcerpc_binding_get_transport(ep2); + + e1 = dcerpc_binding_get_string_option(ep1, "endpoint"); + e2 = dcerpc_binding_get_string_option(ep2, "endpoint"); + + if (t1 != t2) { + return false; + } + + if (!e1 || !e2) { + return e1 == e2; + } + + if (strcasecmp(e1, e2) != 0) { + return false; + } + + return true; +} + +/* + find an endpoint in the dcesrv_context +*/ +_PUBLIC_ NTSTATUS dcesrv_find_endpoint(struct dcesrv_context *dce_ctx, + const struct dcerpc_binding *ep_description, + struct dcesrv_endpoint **_out) +{ + struct dcesrv_endpoint *ep = NULL; + for (ep=dce_ctx->endpoint_list; ep; ep=ep->next) { + if (endpoints_match(ep->ep_description, ep_description)) { + *_out = ep; + return NT_STATUS_OK; + } + } + return NT_STATUS_NOT_FOUND; +} + +/* + find a registered context_id from a bind or alter_context +*/ +static struct dcesrv_connection_context *dcesrv_find_context(struct dcesrv_connection *conn, + uint16_t context_id) +{ + struct dcesrv_connection_context *c; + for (c=conn->contexts;c;c=c->next) { + if (c->context_id == context_id) return c; + } + return NULL; +} + +/* + find the interface operations on any endpoint with this binding +*/ +static const struct dcesrv_interface *find_interface_by_binding(struct dcesrv_context *dce_ctx, + struct dcerpc_binding *binding, + const struct dcesrv_interface *iface) +{ + struct dcesrv_endpoint *ep; + for (ep=dce_ctx->endpoint_list; ep; ep=ep->next) { + if (endpoints_match(ep->ep_description, binding)) { + const struct dcesrv_interface *ret = NULL; + + ret = find_interface_by_syntax_id( + ep, &iface->syntax_id); + if (ret != NULL) { + return ret; + } + } + } + return NULL; +} + +/* + find the interface operations on an endpoint by uuid +*/ +_PUBLIC_ const struct dcesrv_interface *find_interface_by_syntax_id( + const struct dcesrv_endpoint *endpoint, + const struct ndr_syntax_id *interface) +{ + struct dcesrv_if_list *ifl; + for (ifl=endpoint->interface_list; ifl; ifl=ifl->next) { + if (ndr_syntax_id_equal(&ifl->iface->syntax_id, interface)) { + return ifl->iface; + } + } + return NULL; +} + +/* + find the earlier parts of a fragmented call awaiting reassembly +*/ +static struct dcesrv_call_state *dcesrv_find_fragmented_call(struct dcesrv_connection *dce_conn, uint32_t call_id) +{ + struct dcesrv_call_state *c; + for (c=dce_conn->incoming_fragmented_call_list;c;c=c->next) { + if (c->pkt.call_id == call_id) { + return c; + } + } + return NULL; +} + +/* + find a pending request +*/ +static struct dcesrv_call_state *dcesrv_find_pending_call( + struct dcesrv_connection *dce_conn, + uint32_t call_id) +{ + struct dcesrv_call_state *c = NULL; + + for (c = dce_conn->pending_call_list; c != NULL; c = c->next) { + if (c->pkt.call_id == call_id) { + return c; + } + } + + return NULL; +} + +/* + * register a principal for an auth_type + * + * In order to get used in dcesrv_mgmt_inq_princ_name() + */ +_PUBLIC_ NTSTATUS dcesrv_auth_type_principal_register(struct dcesrv_context *dce_ctx, + enum dcerpc_AuthType auth_type, + const char *principal_name) +{ + const char *existing = NULL; + struct dcesrv_ctx_principal *p = NULL; + + existing = dcesrv_auth_type_principal_find(dce_ctx, auth_type); + if (existing != NULL) { + DBG_ERR("auth_type[%u] already registered with principal_name[%s]\n", + auth_type, existing); + return NT_STATUS_ALREADY_REGISTERED; + } + + p = talloc_zero(dce_ctx, struct dcesrv_ctx_principal); + if (p == NULL) { + return NT_STATUS_NO_MEMORY; + } + p->auth_type = auth_type; + p->principal_name = talloc_strdup(p, principal_name); + if (p->principal_name == NULL) { + TALLOC_FREE(p); + return NT_STATUS_NO_MEMORY; + } + + DLIST_ADD_END(dce_ctx->principal_list, p); + return NT_STATUS_OK; +} + +_PUBLIC_ const char *dcesrv_auth_type_principal_find(struct dcesrv_context *dce_ctx, + enum dcerpc_AuthType auth_type) +{ + struct dcesrv_ctx_principal *p = NULL; + + for (p = dce_ctx->principal_list; p != NULL; p = p->next) { + if (p->auth_type == auth_type) { + return p->principal_name; + } + } + + return NULL; +} + +_PUBLIC_ NTSTATUS dcesrv_register_default_auth_types(struct dcesrv_context *dce_ctx, + const char *principal) +{ + const char *realm = lpcfg_realm(dce_ctx->lp_ctx); + NTSTATUS status; + + status = dcesrv_auth_type_principal_register(dce_ctx, + DCERPC_AUTH_TYPE_NTLMSSP, + principal); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + status = dcesrv_auth_type_principal_register(dce_ctx, + DCERPC_AUTH_TYPE_SPNEGO, + principal); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (realm == NULL || realm[0] == '\0') { + return NT_STATUS_OK; + } + + status = dcesrv_auth_type_principal_register(dce_ctx, + DCERPC_AUTH_TYPE_KRB5, + principal); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS dcesrv_register_default_auth_types_machine_principal(struct dcesrv_context *dce_ctx) +{ + const char *realm = lpcfg_realm(dce_ctx->lp_ctx); + const char *nb = lpcfg_netbios_name(dce_ctx->lp_ctx); + char *principal = NULL; + NTSTATUS status; + + if (realm == NULL || realm[0] == '\0') { + return dcesrv_register_default_auth_types(dce_ctx, ""); + } + + principal = talloc_asprintf(talloc_tos(), "%s$@%s", nb, realm); + if (principal == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = dcesrv_register_default_auth_types(dce_ctx, principal); + TALLOC_FREE(principal); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +/* + register an interface on an endpoint + + An endpoint is one unix domain socket (for ncalrpc), one TCP port + (for ncacn_ip_tcp) or one (forwarded) named pipe (for ncacn_np). + + Each endpoint can have many interfaces such as netlogon, lsa or + samr. Some have essentially the full set. + + This is driven from the set of interfaces listed in each IDL file + via the PIDL generated *__op_init_server() functions. +*/ +_PUBLIC_ NTSTATUS dcesrv_interface_register(struct dcesrv_context *dce_ctx, + const char *ep_name, + const char *ncacn_np_secondary_endpoint, + const struct dcesrv_interface *iface, + const struct security_descriptor *sd) +{ + struct dcerpc_binding *binding = NULL; + struct dcerpc_binding *binding2 = NULL; + NTSTATUS ret; + + ret = dcerpc_parse_binding(dce_ctx, ep_name, &binding); + if (NT_STATUS_IS_ERR(ret)) { + DBG_ERR("Trouble parsing binding string '%s'\n", ep_name); + goto out; + } + + if (ncacn_np_secondary_endpoint != NULL) { + ret = dcerpc_parse_binding(dce_ctx, + ncacn_np_secondary_endpoint, + &binding2); + if (NT_STATUS_IS_ERR(ret)) { + DBG_ERR("Trouble parsing 2nd binding string '%s'\n", + ncacn_np_secondary_endpoint); + goto out; + } + } + + ret = dcesrv_interface_register_b(dce_ctx, + binding, + binding2, + iface, + sd); +out: + TALLOC_FREE(binding); + TALLOC_FREE(binding2); + return ret; +} + +_PUBLIC_ NTSTATUS dcesrv_interface_register_b(struct dcesrv_context *dce_ctx, + struct dcerpc_binding *binding, + struct dcerpc_binding *binding2, + const struct dcesrv_interface *iface, + const struct security_descriptor *sd) +{ + struct dcesrv_endpoint *ep; + struct dcesrv_if_list *ifl; + bool add_ep = false; + NTSTATUS status; + enum dcerpc_transport_t transport; + char *ep_string = NULL; + bool use_single_process = true; + const char *ep_process_string; + + /* + * If we are not using handles, there is no need for force + * this service into using a single process. + * + * However, due to the way we listen for RPC packets, we can + * only do this if we have a single service per pipe or TCP + * port, so we still force a single combined process for + * ncalrpc. + */ + if (iface->flags & DCESRV_INTERFACE_FLAGS_HANDLES_NOT_USED) { + use_single_process = false; + } + + transport = dcerpc_binding_get_transport(binding); + if (transport == NCACN_IP_TCP) { + int port; + + /* + * First check if there is already a port specified, eg + * for epmapper on ncacn_ip_tcp:[135] + */ + const char *endpoint + = dcerpc_binding_get_string_option(binding, + "endpoint"); + if (endpoint == NULL) { + port = lpcfg_parm_int(dce_ctx->lp_ctx, NULL, + "rpc server port", iface->name, 0); + + /* + * For RPC services that are not set to use a single + * process, we do not default to using the 'rpc server + * port' because that would cause a double-bind on + * that port. + */ + if (port == 0 && !use_single_process) { + port = lpcfg_rpc_server_port(dce_ctx->lp_ctx); + } + if (port != 0) { + char port_str[6]; + snprintf(port_str, sizeof(port_str), "%u", port); + status = dcerpc_binding_set_string_option(binding, + "endpoint", + port_str); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + } + } + + if (transport == NCACN_NP && binding2 != NULL) { + enum dcerpc_transport_t transport2; + + transport2 = dcerpc_binding_get_transport(binding2); + SMB_ASSERT(transport2 == transport); + } + + /* see if the interface is already registered on the endpoint */ + if (find_interface_by_binding(dce_ctx, binding, iface)!=NULL) { + char *binding_string = dcerpc_binding_string(dce_ctx, binding); + DBG_ERR("Interface '%s' already registered on endpoint '%s'\n", + iface->name, binding_string); + TALLOC_FREE(binding_string); + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + /* check if this endpoint exists + */ + status = dcesrv_find_endpoint(dce_ctx, binding, &ep); + if (NT_STATUS_IS_OK(status)) { + /* + * We want a new port on ncacn_ip_tcp for NETLOGON, so + * it can be multi-process. Other processes can also + * listen on distinct ports, if they have one forced + * in the code above with eg 'rpc server port:drsuapi = 1027' + * + * If we have multiple endpoints on port 0, they each + * get an epemeral port (currently by walking up from + * 1024). + * + * Because one endpoint can only have one process + * model, we add a new IP_TCP endpoint for each model. + * + * This works in conjunction with the forced overwrite + * of ep->use_single_process below. + */ + if (ep->use_single_process != use_single_process + && transport == NCACN_IP_TCP) { + add_ep = true; + } + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND) || add_ep) { + ep = talloc_zero(dce_ctx, struct dcesrv_endpoint); + if (!ep) { + return NT_STATUS_NO_MEMORY; + } + ep->ep_description = dcerpc_binding_dup(ep, binding); + if (transport == NCACN_NP && binding2 != NULL) { + ep->ep_2nd_description = + dcerpc_binding_dup(ep, binding2); + } + add_ep = true; + + /* add mgmt interface */ + ifl = talloc_zero(ep, struct dcesrv_if_list); + if (!ifl) { + TALLOC_FREE(ep); + return NT_STATUS_NO_MEMORY; + } + + ifl->iface = talloc_memdup(ifl, + dcesrv_get_mgmt_interface(), + sizeof(struct dcesrv_interface)); + if (ifl->iface == NULL) { + talloc_free(ep); + return NT_STATUS_NO_MEMORY; + } + + DLIST_ADD(ep->interface_list, ifl); + } else if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("Failed to find endpoint: %s\n", nt_errstr(status)); + return status; + } + + /* + * By default don't force into a single process, but if any + * interface on this endpoint on this service uses handles + * (most do), then we must force into single process mode + * + * By overwriting this each time a new interface is added to + * this endpoint, we end up with the most restrictive setting. + */ + if (use_single_process) { + ep->use_single_process = true; + } + + /* talloc a new interface list element */ + ifl = talloc_zero(ep, struct dcesrv_if_list); + if (!ifl) { + return NT_STATUS_NO_MEMORY; + } + + /* copy the given interface struct to the one on the endpoints interface list */ + ifl->iface = talloc_memdup(ifl, + iface, + sizeof(struct dcesrv_interface)); + if (ifl->iface == NULL) { + talloc_free(ep); + return NT_STATUS_NO_MEMORY; + } + + /* if we have a security descriptor given, + * we should see if we can set it up on the endpoint + */ + if (sd != NULL) { + /* if there's currently no security descriptor given on the endpoint + * we try to set it + */ + if (ep->sd == NULL) { + ep->sd = security_descriptor_copy(ep, sd); + } + + /* if now there's no security descriptor given on the endpoint + * something goes wrong, either we failed to copy the security descriptor + * or there was already one on the endpoint + */ + if (ep->sd != NULL) { + char *binding_string = + dcerpc_binding_string(dce_ctx, binding); + DBG_ERR("Interface '%s' failed to setup a security " + "descriptor on endpoint '%s'\n", + iface->name, binding_string); + TALLOC_FREE(binding_string); + if (add_ep) free(ep); + free(ifl); + return NT_STATUS_OBJECT_NAME_COLLISION; + } + } + + /* finally add the interface on the endpoint */ + DLIST_ADD(ep->interface_list, ifl); + + /* if it's a new endpoint add it to the dcesrv_context */ + if (add_ep) { + DLIST_ADD(dce_ctx->endpoint_list, ep); + } + + /* Re-get the string as we may have set a port */ + ep_string = dcerpc_binding_string(dce_ctx, ep->ep_description); + + if (use_single_process) { + ep_process_string = "single process required"; + } else { + ep_process_string = "multi process compatible"; + } + + DBG_INFO("Interface '%s' registered on endpoint '%s' (%s)\n", + iface->name, ep_string, ep_process_string); + TALLOC_FREE(ep_string); + + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_session_info_session_key(struct dcesrv_auth *auth, + DATA_BLOB *session_key) +{ + if (auth->session_info == NULL) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (auth->session_info->session_key.length == 0) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + *session_key = auth->session_info->session_key; + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_remote_session_key(struct dcesrv_auth *auth, + DATA_BLOB *session_key) +{ + if (auth->auth_type != DCERPC_AUTH_TYPE_NONE) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + return dcesrv_session_info_session_key(auth, session_key); +} + +static NTSTATUS dcesrv_local_fixed_session_key(struct dcesrv_auth *auth, + DATA_BLOB *session_key) +{ + return dcerpc_generic_session_key(session_key); +} + +/* + * Fetch the authentication session key if available. + * + * This is the key generated by a gensec authentication. + * + */ +_PUBLIC_ NTSTATUS dcesrv_auth_session_key(struct dcesrv_call_state *call, + DATA_BLOB *session_key) +{ + struct dcesrv_auth *auth = call->auth_state; + SMB_ASSERT(auth->auth_finished); + return dcesrv_session_info_session_key(auth, session_key); +} + +/* + * Fetch the transport session key if available. + * Typically this is the SMB session key + * or a fixed key for local transports. + * + * The key is always truncated to 16 bytes. +*/ +_PUBLIC_ NTSTATUS dcesrv_transport_session_key(struct dcesrv_call_state *call, + DATA_BLOB *session_key) +{ + struct dcesrv_auth *auth = call->auth_state; + NTSTATUS status; + + SMB_ASSERT(auth->auth_finished); + + if (auth->session_key_fn == NULL) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + status = auth->session_key_fn(auth, session_key); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + session_key->length = MIN(session_key->length, 16); + + return NT_STATUS_OK; +} + +static struct dcesrv_auth *dcesrv_auth_create(struct dcesrv_connection *conn) +{ + const struct dcesrv_endpoint *ep = conn->endpoint; + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(ep->ep_description); + struct dcesrv_auth *auth = NULL; + + auth = talloc_zero(conn, struct dcesrv_auth); + if (auth == NULL) { + return NULL; + } + + switch (transport) { + case NCACN_NP: + auth->session_key_fn = dcesrv_remote_session_key; + break; + case NCALRPC: + case NCACN_UNIX_STREAM: + auth->session_key_fn = dcesrv_local_fixed_session_key; + break; + default: + /* + * All other's get a NULL pointer, which + * results in NT_STATUS_NO_USER_SESSION_KEY + */ + break; + } + + return auth; +} + +/* + connect to a dcerpc endpoint +*/ +_PUBLIC_ NTSTATUS dcesrv_endpoint_connect(struct dcesrv_context *dce_ctx, + TALLOC_CTX *mem_ctx, + const struct dcesrv_endpoint *ep, + struct auth_session_info *session_info, + struct tevent_context *event_ctx, + uint32_t state_flags, + struct dcesrv_connection **_p) +{ + struct dcesrv_auth *auth = NULL; + struct dcesrv_connection *p = NULL; + + if (!session_info) { + return NT_STATUS_ACCESS_DENIED; + } + + p = talloc_zero(mem_ctx, struct dcesrv_connection); + if (p == NULL) { + goto nomem; + } + + p->dce_ctx = dce_ctx; + p->endpoint = ep; + p->packet_log_dir = lpcfg_parm_string(dce_ctx->lp_ctx, + NULL, + "dcesrv", + "stubs directory"); + p->event_ctx = event_ctx; + p->state_flags = state_flags; + p->allow_bind = true; + p->max_recv_frag = 5840; + p->max_xmit_frag = 5840; + p->max_total_request_size = DCERPC_NCACN_REQUEST_DEFAULT_MAX_SIZE; + + p->support_hdr_signing = lpcfg_parm_bool(dce_ctx->lp_ctx, + NULL, + "dcesrv", + "header signing", + true); + p->max_auth_states = lpcfg_parm_ulong(dce_ctx->lp_ctx, + NULL, + "dcesrv", + "max auth states", + 2049); + + auth = dcesrv_auth_create(p); + if (auth == NULL) { + goto nomem; + } + + auth->session_info = talloc_reference(auth, session_info); + if (auth->session_info == NULL) { + goto nomem; + } + + p->default_auth_state = auth; + + p->preferred_transfer = dce_ctx->preferred_transfer; + + *_p = p; + return NT_STATUS_OK; +nomem: + TALLOC_FREE(p); + return NT_STATUS_NO_MEMORY; +} + +/* + move a call from an existing linked list to the specified list. This + prevents bugs where we forget to remove the call from a previous + list when moving it. + */ +static void dcesrv_call_set_list(struct dcesrv_call_state *call, + enum dcesrv_call_list list) +{ + switch (call->list) { + case DCESRV_LIST_NONE: + break; + case DCESRV_LIST_CALL_LIST: + DLIST_REMOVE(call->conn->call_list, call); + break; + case DCESRV_LIST_FRAGMENTED_CALL_LIST: + DLIST_REMOVE(call->conn->incoming_fragmented_call_list, call); + break; + case DCESRV_LIST_PENDING_CALL_LIST: + DLIST_REMOVE(call->conn->pending_call_list, call); + break; + } + call->list = list; + switch (list) { + case DCESRV_LIST_NONE: + break; + case DCESRV_LIST_CALL_LIST: + DLIST_ADD_END(call->conn->call_list, call); + break; + case DCESRV_LIST_FRAGMENTED_CALL_LIST: + DLIST_ADD_END(call->conn->incoming_fragmented_call_list, call); + break; + case DCESRV_LIST_PENDING_CALL_LIST: + DLIST_ADD_END(call->conn->pending_call_list, call); + break; + } +} + +static void dcesrv_call_disconnect_after(struct dcesrv_call_state *call, + const char *reason) +{ + struct dcesrv_auth *a = NULL; + + if (call->conn->terminate != NULL) { + return; + } + + call->conn->allow_bind = false; + call->conn->allow_alter = false; + + call->conn->default_auth_state->auth_invalid = true; + + for (a = call->conn->auth_states; a != NULL; a = a->next) { + a->auth_invalid = true; + } + + call->terminate_reason = talloc_strdup(call, reason); + if (call->terminate_reason == NULL) { + call->terminate_reason = __location__; + } +} + +/* + return a dcerpc bind_nak +*/ +static NTSTATUS dcesrv_bind_nak(struct dcesrv_call_state *call, uint32_t reason) +{ + struct ncacn_packet pkt; + struct dcerpc_bind_nak_version version; + struct data_blob_list_item *rep; + NTSTATUS status; + static const uint8_t _pad[3] = { 0, }; + + /* + * We add the call to the pending_call_list + * in order to defer the termination. + */ + dcesrv_call_disconnect_after(call, "dcesrv_bind_nak"); + + /* setup a bind_nak */ + dcesrv_init_hdr(&pkt, lpcfg_rpc_big_endian(call->conn->dce_ctx->lp_ctx)); + pkt.auth_length = 0; + pkt.call_id = call->pkt.call_id; + pkt.ptype = DCERPC_PKT_BIND_NAK; + pkt.pfc_flags = DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST; + pkt.u.bind_nak.reject_reason = reason; + version.rpc_vers = 5; + version.rpc_vers_minor = 0; + pkt.u.bind_nak.num_versions = 1; + pkt.u.bind_nak.versions = &version; + pkt.u.bind_nak._pad = data_blob_const(_pad, sizeof(_pad)); + + rep = talloc_zero(call, struct data_blob_list_item); + if (!rep) { + return NT_STATUS_NO_MEMORY; + } + + status = dcerpc_ncacn_push_auth(&rep->blob, call, &pkt, NULL); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + dcerpc_set_frag_length(&rep->blob, rep->blob.length); + + DLIST_ADD_END(call->replies, rep); + dcesrv_call_set_list(call, DCESRV_LIST_CALL_LIST); + + if (call->conn->call_list && call->conn->call_list->replies) { + if (call->conn->transport.report_output_data) { + call->conn->transport.report_output_data(call->conn); + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS _dcesrv_fault_disconnect_flags(struct dcesrv_call_state *call, + uint32_t fault_code, + uint8_t extra_flags, + const char *func, + const char *location) +{ + const char *reason = NULL; + + reason = talloc_asprintf(call, "%s:%s: fault=%u (%s) flags=0x%x", + func, location, + fault_code, + dcerpc_errstr(call, fault_code), + extra_flags); + if (reason == NULL) { + reason = location; + } + + /* + * We add the call to the pending_call_list + * in order to defer the termination. + */ + + dcesrv_call_disconnect_after(call, reason); + + return dcesrv_fault_with_flags(call, fault_code, extra_flags); +} + +#define dcesrv_fault_disconnect(call, fault_code) \ + _dcesrv_fault_disconnect_flags(call, fault_code, \ + DCERPC_PFC_FLAG_DID_NOT_EXECUTE, \ + __func__, __location__) +#define dcesrv_fault_disconnect0(call, fault_code) \ + _dcesrv_fault_disconnect_flags(call, fault_code, 0, \ + __func__, __location__) + +static int dcesrv_connection_context_destructor(struct dcesrv_connection_context *c) +{ + DLIST_REMOVE(c->conn->contexts, c); + + if (c->iface && c->iface->unbind) { + c->iface->unbind(c, c->iface); + c->iface = NULL; + } + + return 0; +} + +static void dcesrv_prepare_context_auth(struct dcesrv_call_state *dce_call) +{ + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + const struct dcesrv_endpoint *endpoint = dce_call->conn->endpoint; + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(endpoint->ep_description); + struct dcesrv_connection_context *context = dce_call->context; + const struct dcesrv_interface *iface = context->iface; + + context->min_auth_level = DCERPC_AUTH_LEVEL_NONE; + + if (transport == NCALRPC) { + context->allow_connect = true; + return; + } + + /* + * allow overwrite per interface + * allow dcerpc auth level connect:<interface> + */ + context->allow_connect = lpcfg_allow_dcerpc_auth_level_connect(lp_ctx); + context->allow_connect = lpcfg_parm_bool(lp_ctx, NULL, + "allow dcerpc auth level connect", + iface->name, + context->allow_connect); +} + +NTSTATUS dcesrv_interface_bind_require_integrity(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + /* + * For connection oriented DCERPC DCERPC_AUTH_LEVEL_PACKET (4) + * has the same behavior as DCERPC_AUTH_LEVEL_INTEGRITY (5). + */ + context->min_auth_level = DCERPC_AUTH_LEVEL_PACKET; + return NT_STATUS_OK; +} + +NTSTATUS dcesrv_interface_bind_require_privacy(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + context->min_auth_level = DCERPC_AUTH_LEVEL_PRIVACY; + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS dcesrv_interface_bind_reject_connect(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + struct loadparm_context *lp_ctx = context->conn->dce_ctx->lp_ctx; + const struct dcesrv_endpoint *endpoint = context->conn->endpoint; + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(endpoint->ep_description); + + if (transport == NCALRPC) { + context->allow_connect = true; + return NT_STATUS_OK; + } + + /* + * allow overwrite per interface + * allow dcerpc auth level connect:<interface> + */ + context->allow_connect = false; + context->allow_connect = lpcfg_parm_bool(lp_ctx, NULL, + "allow dcerpc auth level connect", + iface->name, + context->allow_connect); + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS dcesrv_interface_bind_allow_connect(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + struct loadparm_context *lp_ctx = context->conn->dce_ctx->lp_ctx; + const struct dcesrv_endpoint *endpoint = context->conn->endpoint; + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(endpoint->ep_description); + + if (transport == NCALRPC) { + context->allow_connect = true; + return NT_STATUS_OK; + } + + /* + * allow overwrite per interface + * allow dcerpc auth level connect:<interface> + */ + context->allow_connect = true; + context->allow_connect = lpcfg_parm_bool(lp_ctx, NULL, + "allow dcerpc auth level connect", + iface->name, + context->allow_connect); + return NT_STATUS_OK; +} + +struct dcesrv_conn_auth_wait_context { + struct tevent_req *req; + bool done; + NTSTATUS status; +}; + +struct dcesrv_conn_auth_wait_state { + uint8_t dummy; +}; + +static struct tevent_req *dcesrv_conn_auth_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct dcesrv_conn_auth_wait_context *auth_wait = + talloc_get_type_abort(private_data, + struct dcesrv_conn_auth_wait_context); + struct tevent_req *req = NULL; + struct dcesrv_conn_auth_wait_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct dcesrv_conn_auth_wait_state); + if (req == NULL) { + return NULL; + } + auth_wait->req = req; + + tevent_req_defer_callback(req, ev); + + if (!auth_wait->done) { + return req; + } + + if (tevent_req_nterror(req, auth_wait->status)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS dcesrv_conn_auth_wait_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +static NTSTATUS dcesrv_conn_auth_wait_setup(struct dcesrv_connection *conn) +{ + struct dcesrv_conn_auth_wait_context *auth_wait = NULL; + + if (conn->wait_send != NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + auth_wait = talloc_zero(conn, struct dcesrv_conn_auth_wait_context); + if (auth_wait == NULL) { + return NT_STATUS_NO_MEMORY; + } + + conn->wait_private = auth_wait; + conn->wait_send = dcesrv_conn_auth_wait_send; + conn->wait_recv = dcesrv_conn_auth_wait_recv; + return NT_STATUS_OK; +} + +static void dcesrv_conn_auth_wait_finished(struct dcesrv_connection *conn, + NTSTATUS status) +{ + struct dcesrv_conn_auth_wait_context *auth_wait = + talloc_get_type_abort(conn->wait_private, + struct dcesrv_conn_auth_wait_context); + + auth_wait->done = true; + auth_wait->status = status; + + if (auth_wait->req == NULL) { + return; + } + + if (tevent_req_nterror(auth_wait->req, status)) { + return; + } + + tevent_req_done(auth_wait->req); +} + +static NTSTATUS dcesrv_auth_reply(struct dcesrv_call_state *call); + +static void dcesrv_bind_done(struct tevent_req *subreq); + +/* + handle a bind request +*/ +static NTSTATUS dcesrv_bind(struct dcesrv_call_state *call) +{ + struct dcesrv_connection *conn = call->conn; + struct dcesrv_context *dce_ctx = conn->dce_ctx; + struct ncacn_packet *pkt = &call->ack_pkt; + NTSTATUS status; + uint32_t extra_flags = 0; + uint16_t max_req = 0; + uint16_t max_rep = 0; + struct dcerpc_binding *ep_2nd_description = NULL; + const char *endpoint = NULL; + struct dcesrv_auth *auth = call->auth_state; + struct dcesrv_context_callbacks *cb = call->conn->dce_ctx->callbacks; + struct dcerpc_ack_ctx *ack_ctx_list = NULL; + struct dcerpc_ack_ctx *ack_features = NULL; + struct tevent_req *subreq = NULL; + size_t i; + + status = dcerpc_verify_ncacn_packet_header(&call->pkt, + DCERPC_PKT_BIND, + call->pkt.u.bind.auth_info.length, + 0, /* required flags */ + DCERPC_PFC_FLAG_FIRST | + DCERPC_PFC_FLAG_LAST | + DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN | + 0x08 | /* this is not defined, but should be ignored */ + DCERPC_PFC_FLAG_CONC_MPX | + DCERPC_PFC_FLAG_DID_NOT_EXECUTE | + DCERPC_PFC_FLAG_MAYBE | + DCERPC_PFC_FLAG_OBJECT_UUID); + if (!NT_STATUS_IS_OK(status)) { + return dcesrv_bind_nak(call, + DCERPC_BIND_NAK_REASON_PROTOCOL_VERSION_NOT_SUPPORTED); + } + + /* max_recv_frag and max_xmit_frag result always in the same value! */ + max_req = MIN(call->pkt.u.bind.max_xmit_frag, + call->pkt.u.bind.max_recv_frag); + /* + * The values are between 2048 and 5840 tested against Windows 2012R2 + * via ncacn_ip_tcp on port 135. + */ + max_req = MAX(2048, max_req); + max_rep = MIN(max_req, conn->max_recv_frag); + /* They are truncated to an 8 byte boundary. */ + max_rep &= 0xFFF8; + + /* max_recv_frag and max_xmit_frag result always in the same value! */ + conn->max_recv_frag = max_rep; + conn->max_xmit_frag = max_rep; + + status = dce_ctx->callbacks->assoc_group.find( + call, dce_ctx->callbacks->assoc_group.private_data); + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("Failed to find assoc_group 0x%08x: %s\n", + call->pkt.u.bind.assoc_group_id, nt_errstr(status)); + return dcesrv_bind_nak(call, 0); + } + + if (call->pkt.u.bind.num_contexts < 1) { + return dcesrv_bind_nak(call, 0); + } + + ack_ctx_list = talloc_zero_array(call, struct dcerpc_ack_ctx, + call->pkt.u.bind.num_contexts); + if (ack_ctx_list == NULL) { + return dcesrv_bind_nak(call, 0); + } + + /* + * Set some sane defaults (required by dcesrv_negotiate_contexts()/ + * dcesrv_check_or_create_context()) and do some protocol validation + * and set sane defaults. + */ + for (i = 0; i < call->pkt.u.bind.num_contexts; i++) { + const struct dcerpc_ctx_list *c = &call->pkt.u.bind.ctx_list[i]; + struct dcerpc_ack_ctx *a = &ack_ctx_list[i]; + bool is_feature = false; + uint64_t features = 0; + + if (c->num_transfer_syntaxes == 0) { + return dcesrv_bind_nak(call, 0); + } + + a->result = DCERPC_BIND_ACK_RESULT_PROVIDER_REJECTION; + a->reason.value = DCERPC_BIND_ACK_REASON_ABSTRACT_SYNTAX_NOT_SUPPORTED; + + /* + * It's only treated as bind time feature request, if the first + * transfer_syntax matches, all others are ignored. + */ + is_feature = dcerpc_extract_bind_time_features(c->transfer_syntaxes[0], + &features); + if (!is_feature) { + continue; + } + + if (ack_features != NULL) { + /* + * Only one bind time feature context is allowed. + */ + return dcesrv_bind_nak(call, 0); + } + ack_features = a; + + a->result = DCERPC_BIND_ACK_RESULT_NEGOTIATE_ACK; + a->reason.negotiate = 0; + if (features & DCERPC_BIND_TIME_SECURITY_CONTEXT_MULTIPLEXING) { + if (conn->max_auth_states != 0) { + a->reason.negotiate |= + DCERPC_BIND_TIME_SECURITY_CONTEXT_MULTIPLEXING; + } + } + if (features & DCERPC_BIND_TIME_KEEP_CONNECTION_ON_ORPHAN) { + a->reason.negotiate |= + DCERPC_BIND_TIME_KEEP_CONNECTION_ON_ORPHAN; + } + + conn->assoc_group->bind_time_features = a->reason.negotiate; + } + + /* + * Try to negotiate one new presentation context. + * + * Deep in here we locate the iface (by uuid) that the client + * requested, from the list of interfaces on the + * call->conn->endpoint, and call iface->bind() on that iface. + * + * call->conn was set up at the accept() of the socket, and + * call->conn->endpoint has a list of interfaces restricted to + * this port or pipe. + */ + status = dcesrv_negotiate_contexts(call, &call->pkt.u.bind, ack_ctx_list); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROTOCOL_ERROR)) { + return dcesrv_bind_nak(call, 0); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * At this point we still don't know which interface (eg + * netlogon, lsa, drsuapi) the caller requested in this bind! + * The most recently added context is available as the first + * element in the linked list at call->conn->contexts, that is + * call->conn->contexts->iface, but they may not have + * requested one at all! + */ + + if ((call->pkt.pfc_flags & DCERPC_PFC_FLAG_CONC_MPX) && + (call->state_flags & DCESRV_CALL_STATE_FLAG_MULTIPLEXED)) { + call->conn->state_flags |= DCESRV_CALL_STATE_FLAG_MULTIPLEXED; + extra_flags |= DCERPC_PFC_FLAG_CONC_MPX; + } + + if (call->state_flags & DCESRV_CALL_STATE_FLAG_PROCESS_PENDING_CALL) { + conn->state_flags |= DCESRV_CALL_STATE_FLAG_PROCESS_PENDING_CALL; + } + + /* + * After finding the interface and setting up the NDR + * transport negotiation etc, handle any authentication that + * is being requested. + */ + if (!dcesrv_auth_bind(call)) { + + if (auth->auth_level == DCERPC_AUTH_LEVEL_NONE) { + /* + * With DCERPC_AUTH_LEVEL_NONE, we get the + * reject_reason in auth->auth_context_id. + */ + return dcesrv_bind_nak(call, auth->auth_context_id); + } + + /* + * This must a be a temporary failure e.g. talloc or invalid + * configuration, e.g. no machine account. + */ + return dcesrv_bind_nak(call, + DCERPC_BIND_NAK_REASON_TEMPORARY_CONGESTION); + } + + /* setup a bind_ack */ + dcesrv_init_hdr(pkt, lpcfg_rpc_big_endian(dce_ctx->lp_ctx)); + pkt->auth_length = 0; + pkt->call_id = call->pkt.call_id; + pkt->ptype = DCERPC_PKT_BIND_ACK; + pkt->pfc_flags = DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST | extra_flags; + pkt->u.bind_ack.max_xmit_frag = conn->max_xmit_frag; + pkt->u.bind_ack.max_recv_frag = conn->max_recv_frag; + pkt->u.bind_ack.assoc_group_id = conn->assoc_group->id; + + ep_2nd_description = conn->endpoint->ep_2nd_description; + if (ep_2nd_description == NULL) { + ep_2nd_description = conn->endpoint->ep_description; + } + + endpoint = dcerpc_binding_get_string_option( + ep_2nd_description, + "endpoint"); + if (endpoint == NULL) { + endpoint = ""; + } + + pkt->u.bind_ack.secondary_address = endpoint; + pkt->u.bind_ack.num_results = call->pkt.u.bind.num_contexts; + pkt->u.bind_ack.ctx_list = ack_ctx_list; + pkt->u.bind_ack.auth_info = data_blob_null; + + status = dcesrv_auth_prepare_bind_ack(call, pkt); + if (!NT_STATUS_IS_OK(status)) { + return dcesrv_bind_nak(call, 0); + } + + if (auth->auth_finished) { + return dcesrv_auth_reply(call); + } + + cb->auth.become_root(); + subreq = gensec_update_send(call, call->event_ctx, + auth->gensec_security, + call->in_auth_info.credentials); + cb->auth.unbecome_root(); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY; + } + tevent_req_set_callback(subreq, dcesrv_bind_done, call); + + return dcesrv_conn_auth_wait_setup(conn); +} + +static void dcesrv_bind_done(struct tevent_req *subreq) +{ + struct dcesrv_call_state *call = + tevent_req_callback_data(subreq, + struct dcesrv_call_state); + struct dcesrv_connection *conn = call->conn; + struct dcesrv_context_callbacks *cb = call->conn->dce_ctx->callbacks; + NTSTATUS status; + + cb->auth.become_root(); + status = gensec_update_recv(subreq, call, + &call->out_auth_info->credentials); + cb->auth.unbecome_root(); + TALLOC_FREE(subreq); + + status = dcesrv_auth_complete(call, status); + if (!NT_STATUS_IS_OK(status)) { + status = dcesrv_bind_nak(call, 0); + dcesrv_conn_auth_wait_finished(conn, status); + return; + } + + status = dcesrv_auth_reply(call); + dcesrv_conn_auth_wait_finished(conn, status); + return; +} + +static NTSTATUS dcesrv_auth_reply(struct dcesrv_call_state *call) +{ + struct ncacn_packet *pkt = &call->ack_pkt; + struct data_blob_list_item *rep = NULL; + NTSTATUS status; + + rep = talloc_zero(call, struct data_blob_list_item); + if (!rep) { + return NT_STATUS_NO_MEMORY; + } + + status = dcerpc_ncacn_push_auth(&rep->blob, + call, + pkt, + call->out_auth_info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + dcerpc_set_frag_length(&rep->blob, rep->blob.length); + + DLIST_ADD_END(call->replies, rep); + dcesrv_call_set_list(call, DCESRV_LIST_CALL_LIST); + + if (call->conn->call_list && call->conn->call_list->replies) { + if (call->conn->transport.report_output_data) { + call->conn->transport.report_output_data(call->conn); + } + } + + return NT_STATUS_OK; +} + + +static void dcesrv_auth3_done(struct tevent_req *subreq); + +/* + handle a auth3 request +*/ +static NTSTATUS dcesrv_auth3(struct dcesrv_call_state *call) +{ + struct dcesrv_connection *conn = call->conn; + struct dcesrv_auth *auth = call->auth_state; + struct dcesrv_context_callbacks *cb = call->conn->dce_ctx->callbacks; + struct tevent_req *subreq = NULL; + NTSTATUS status; + + if (!auth->auth_started) { + return dcesrv_fault_disconnect(call, DCERPC_NCA_S_PROTO_ERROR); + } + + if (auth->auth_finished) { + return dcesrv_fault_disconnect(call, DCERPC_NCA_S_PROTO_ERROR); + } + + status = dcerpc_verify_ncacn_packet_header(&call->pkt, + DCERPC_PKT_AUTH3, + call->pkt.u.auth3.auth_info.length, + 0, /* required flags */ + DCERPC_PFC_FLAG_FIRST | + DCERPC_PFC_FLAG_LAST | + DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN | + 0x08 | /* this is not defined, but should be ignored */ + DCERPC_PFC_FLAG_CONC_MPX | + DCERPC_PFC_FLAG_DID_NOT_EXECUTE | + DCERPC_PFC_FLAG_MAYBE | + DCERPC_PFC_FLAG_OBJECT_UUID); + if (!NT_STATUS_IS_OK(status)) { + return dcesrv_fault_disconnect(call, DCERPC_NCA_S_PROTO_ERROR); + } + + /* handle the auth3 in the auth code */ + if (!dcesrv_auth_prepare_auth3(call)) { + /* + * we don't send a reply to a auth3 request, + * except by a fault. + * + * In anycase we mark the connection as + * invalid. + */ + auth->auth_invalid = true; + if (call->fault_code != 0) { + return dcesrv_fault_disconnect(call, call->fault_code); + } + TALLOC_FREE(call); + return NT_STATUS_OK; + } + + cb->auth.become_root(); + subreq = gensec_update_send(call, call->event_ctx, + auth->gensec_security, + call->in_auth_info.credentials); + cb->auth.unbecome_root(); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY; + } + tevent_req_set_callback(subreq, dcesrv_auth3_done, call); + + return dcesrv_conn_auth_wait_setup(conn); +} + +static void dcesrv_auth3_done(struct tevent_req *subreq) +{ + struct dcesrv_call_state *call = + tevent_req_callback_data(subreq, + struct dcesrv_call_state); + struct dcesrv_connection *conn = call->conn; + struct dcesrv_auth *auth = call->auth_state; + struct dcesrv_context_callbacks *cb = call->conn->dce_ctx->callbacks; + NTSTATUS status; + + cb->auth.become_root(); + status = gensec_update_recv(subreq, call, + &call->out_auth_info->credentials); + cb->auth.unbecome_root(); + TALLOC_FREE(subreq); + + status = dcesrv_auth_complete(call, status); + if (!NT_STATUS_IS_OK(status)) { + /* + * we don't send a reply to a auth3 request, + * except by a fault. + * + * In anycase we mark the connection as + * invalid. + */ + auth->auth_invalid = true; + if (call->fault_code != 0) { + status = dcesrv_fault_disconnect(call, call->fault_code); + dcesrv_conn_auth_wait_finished(conn, status); + return; + } + TALLOC_FREE(call); + dcesrv_conn_auth_wait_finished(conn, NT_STATUS_OK); + return; + } + + /* + * we don't send a reply to a auth3 request. + */ + TALLOC_FREE(call); + dcesrv_conn_auth_wait_finished(conn, NT_STATUS_OK); + return; +} + + +static NTSTATUS dcesrv_check_or_create_context(struct dcesrv_call_state *call, + const struct dcerpc_bind *b, + const struct dcerpc_ctx_list *ctx, + struct dcerpc_ack_ctx *ack, + bool validate_only, + const struct ndr_syntax_id *supported_transfer) +{ + struct dcesrv_connection_context *context; + const struct dcesrv_interface *iface; + NTSTATUS status; + const struct ndr_syntax_id *selected_transfer = NULL; + size_t i; + bool ok; + + if (b == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + if (ctx == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + if (ctx->num_transfer_syntaxes < 1) { + return NT_STATUS_INTERNAL_ERROR; + } + if (ack == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + if (supported_transfer == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + switch (ack->result) { + case DCERPC_BIND_ACK_RESULT_ACCEPTANCE: + case DCERPC_BIND_ACK_RESULT_NEGOTIATE_ACK: + /* + * We is already completed. + */ + return NT_STATUS_OK; + default: + break; + } + + ack->result = DCERPC_BIND_ACK_RESULT_PROVIDER_REJECTION; + ack->reason.value = DCERPC_BIND_ACK_REASON_ABSTRACT_SYNTAX_NOT_SUPPORTED; + + iface = find_interface_by_syntax_id( + call->conn->endpoint, &ctx->abstract_syntax); + if (iface == NULL) { + struct ndr_syntax_id_buf buf; + DBG_NOTICE("Request for unknown dcerpc interface %s\n", + ndr_syntax_id_buf_string( + &ctx->abstract_syntax, &buf)); + /* + * We report this only via ack->result + */ + return NT_STATUS_OK; + } + + ack->result = DCERPC_BIND_ACK_RESULT_PROVIDER_REJECTION; + ack->reason.value = DCERPC_BIND_ACK_REASON_TRANSFER_SYNTAXES_NOT_SUPPORTED; + + if (validate_only) { + /* + * We report this only via ack->result + */ + return NT_STATUS_OK; + } + + for (i = 0; i < ctx->num_transfer_syntaxes; i++) { + /* + * we only do NDR encoded dcerpc for now. + */ + ok = ndr_syntax_id_equal(&ctx->transfer_syntaxes[i], + supported_transfer); + if (ok) { + selected_transfer = supported_transfer; + break; + } + } + + context = dcesrv_find_context(call->conn, ctx->context_id); + if (context != NULL) { + ok = ndr_syntax_id_equal(&context->iface->syntax_id, + &ctx->abstract_syntax); + if (!ok) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + if (selected_transfer != NULL) { + ok = ndr_syntax_id_equal(&context->transfer_syntax, + selected_transfer); + if (!ok) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + ack->result = DCERPC_BIND_ACK_RESULT_ACCEPTANCE; + ack->reason.value = DCERPC_BIND_ACK_REASON_NOT_SPECIFIED; + ack->syntax = context->transfer_syntax; + } + + /* + * We report this only via ack->result + */ + return NT_STATUS_OK; + } + + if (selected_transfer == NULL) { + /* + * We report this only via ack->result + */ + return NT_STATUS_OK; + } + + ack->result = DCERPC_BIND_ACK_RESULT_USER_REJECTION; + ack->reason.value = DCERPC_BIND_ACK_REASON_LOCAL_LIMIT_EXCEEDED; + + /* add this context to the list of available context_ids */ + context = talloc_zero(call->conn, struct dcesrv_connection_context); + if (context == NULL) { + return NT_STATUS_NO_MEMORY; + } + context->conn = call->conn; + context->context_id = ctx->context_id; + context->iface = iface; + context->transfer_syntax = *selected_transfer; + context->ndr64 = ndr_syntax_id_equal(&context->transfer_syntax, + &ndr_transfer_syntax_ndr64); + DLIST_ADD(call->conn->contexts, context); + call->context = context; + talloc_set_destructor(context, dcesrv_connection_context_destructor); + + dcesrv_prepare_context_auth(call); + + /* + * Multiplex is supported by default + */ + call->state_flags |= DCESRV_CALL_STATE_FLAG_MULTIPLEXED; + + status = iface->bind(context, iface); + call->context = NULL; + if (!NT_STATUS_IS_OK(status)) { + /* we don't want to trigger the iface->unbind() hook */ + context->iface = NULL; + talloc_free(context); + /* + * We report this only via ack->result + */ + return NT_STATUS_OK; + } + + ack->result = DCERPC_BIND_ACK_RESULT_ACCEPTANCE; + ack->reason.value = DCERPC_BIND_ACK_REASON_NOT_SPECIFIED; + ack->syntax = context->transfer_syntax; + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_negotiate_contexts(struct dcesrv_call_state *call, + const struct dcerpc_bind *b, + struct dcerpc_ack_ctx *ack_ctx_list) +{ + NTSTATUS status; + size_t i; + bool validate_only = false; + bool preferred_ndr32; + + /* + * Try to negotiate one new presentation context, + * using our preferred transfer syntax. + */ + for (i = 0; i < b->num_contexts; i++) { + const struct dcerpc_ctx_list *c = &b->ctx_list[i]; + struct dcerpc_ack_ctx *a = &ack_ctx_list[i]; + + status = dcesrv_check_or_create_context(call, b, c, a, + validate_only, + call->conn->preferred_transfer); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (a->result == DCERPC_BIND_ACK_RESULT_ACCEPTANCE) { + /* + * We managed to negotiate one context. + * + * => we're done. + */ + validate_only = true; + } + } + + preferred_ndr32 = ndr_syntax_id_equal(&ndr_transfer_syntax_ndr, + call->conn->preferred_transfer); + if (preferred_ndr32) { + /* + * We're done. + */ + return NT_STATUS_OK; + } + + /* + * Try to negotiate one new presentation context, + * using NDR 32 as fallback. + */ + for (i = 0; i < b->num_contexts; i++) { + const struct dcerpc_ctx_list *c = &b->ctx_list[i]; + struct dcerpc_ack_ctx *a = &ack_ctx_list[i]; + + status = dcesrv_check_or_create_context(call, b, c, a, + validate_only, + &ndr_transfer_syntax_ndr); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (a->result == DCERPC_BIND_ACK_RESULT_ACCEPTANCE) { + /* + * We managed to negotiate one context. + * + * => we're done. + */ + validate_only = true; + } + } + + return NT_STATUS_OK; +} + +static void dcesrv_alter_done(struct tevent_req *subreq); + +/* + handle a alter context request +*/ +static NTSTATUS dcesrv_alter(struct dcesrv_call_state *call) +{ + struct dcesrv_connection *conn = call->conn; + NTSTATUS status; + bool auth_ok = false; + struct ncacn_packet *pkt = &call->ack_pkt; + uint32_t extra_flags = 0; + struct dcesrv_auth *auth = call->auth_state; + struct dcesrv_context_callbacks *cb = call->conn->dce_ctx->callbacks; + struct dcerpc_ack_ctx *ack_ctx_list = NULL; + struct tevent_req *subreq = NULL; + size_t i; + + if (!call->conn->allow_alter) { + return dcesrv_fault_disconnect(call, DCERPC_NCA_S_PROTO_ERROR); + } + + status = dcerpc_verify_ncacn_packet_header(&call->pkt, + DCERPC_PKT_ALTER, + call->pkt.u.alter.auth_info.length, + 0, /* required flags */ + DCERPC_PFC_FLAG_FIRST | + DCERPC_PFC_FLAG_LAST | + DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN | + 0x08 | /* this is not defined, but should be ignored */ + DCERPC_PFC_FLAG_CONC_MPX | + DCERPC_PFC_FLAG_DID_NOT_EXECUTE | + DCERPC_PFC_FLAG_MAYBE | + DCERPC_PFC_FLAG_OBJECT_UUID); + if (!NT_STATUS_IS_OK(status)) { + return dcesrv_fault_disconnect(call, DCERPC_NCA_S_PROTO_ERROR); + } + + auth_ok = dcesrv_auth_alter(call); + if (!auth_ok) { + if (call->fault_code != 0) { + return dcesrv_fault_disconnect(call, call->fault_code); + } + } + + if (call->pkt.u.alter.num_contexts < 1) { + return dcesrv_fault_disconnect(call, DCERPC_NCA_S_PROTO_ERROR); + } + + ack_ctx_list = talloc_zero_array(call, struct dcerpc_ack_ctx, + call->pkt.u.alter.num_contexts); + if (ack_ctx_list == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* + * Set some sane defaults (required by dcesrv_negotiate_contexts()/ + * dcesrv_check_or_create_context()) and do some protocol validation + * and set sane defaults. + */ + for (i = 0; i < call->pkt.u.alter.num_contexts; i++) { + const struct dcerpc_ctx_list *c = &call->pkt.u.alter.ctx_list[i]; + struct dcerpc_ack_ctx *a = &ack_ctx_list[i]; + + if (c->num_transfer_syntaxes == 0) { + return dcesrv_fault_disconnect(call, + DCERPC_NCA_S_PROTO_ERROR); + } + + a->result = DCERPC_BIND_ACK_RESULT_PROVIDER_REJECTION; + a->reason.value = DCERPC_BIND_ACK_REASON_ABSTRACT_SYNTAX_NOT_SUPPORTED; + } + + /* + * Try to negotiate one new presentation context. + */ + status = dcesrv_negotiate_contexts(call, &call->pkt.u.alter, ack_ctx_list); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROTOCOL_ERROR)) { + return dcesrv_fault_disconnect(call, DCERPC_NCA_S_PROTO_ERROR); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if ((call->pkt.pfc_flags & DCERPC_PFC_FLAG_CONC_MPX) && + (call->state_flags & DCESRV_CALL_STATE_FLAG_MULTIPLEXED)) { + call->conn->state_flags |= DCESRV_CALL_STATE_FLAG_MULTIPLEXED; + extra_flags |= DCERPC_PFC_FLAG_CONC_MPX; + } + + if (call->state_flags & DCESRV_CALL_STATE_FLAG_PROCESS_PENDING_CALL) { + call->conn->state_flags |= DCESRV_CALL_STATE_FLAG_PROCESS_PENDING_CALL; + } + + /* handle any authentication that is being requested */ + if (!auth_ok) { + if (call->in_auth_info.auth_type != auth->auth_type) { + return dcesrv_fault_disconnect(call, + DCERPC_FAULT_SEC_PKG_ERROR); + } + return dcesrv_fault_disconnect(call, DCERPC_FAULT_ACCESS_DENIED); + } + + dcesrv_init_hdr(pkt, lpcfg_rpc_big_endian(call->conn->dce_ctx->lp_ctx)); + pkt->auth_length = 0; + pkt->call_id = call->pkt.call_id; + pkt->ptype = DCERPC_PKT_ALTER_RESP; + pkt->pfc_flags = DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST | extra_flags; + pkt->u.alter_resp.max_xmit_frag = call->conn->max_xmit_frag; + pkt->u.alter_resp.max_recv_frag = call->conn->max_recv_frag; + pkt->u.alter_resp.assoc_group_id = call->conn->assoc_group->id; + pkt->u.alter_resp.secondary_address = ""; + pkt->u.alter_resp.num_results = call->pkt.u.alter.num_contexts; + pkt->u.alter_resp.ctx_list = ack_ctx_list; + pkt->u.alter_resp.auth_info = data_blob_null; + + status = dcesrv_auth_prepare_alter_ack(call, pkt); + if (!NT_STATUS_IS_OK(status)) { + return dcesrv_fault_disconnect(call, DCERPC_FAULT_SEC_PKG_ERROR); + } + + if (auth->auth_finished) { + return dcesrv_auth_reply(call); + } + + cb->auth.become_root(); + subreq = gensec_update_send(call, call->event_ctx, + auth->gensec_security, + call->in_auth_info.credentials); + cb->auth.unbecome_root(); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY; + } + tevent_req_set_callback(subreq, dcesrv_alter_done, call); + + return dcesrv_conn_auth_wait_setup(conn); +} + +static void dcesrv_alter_done(struct tevent_req *subreq) +{ + struct dcesrv_call_state *call = + tevent_req_callback_data(subreq, + struct dcesrv_call_state); + struct dcesrv_connection *conn = call->conn; + struct dcesrv_context_callbacks *cb = call->conn->dce_ctx->callbacks; + NTSTATUS status; + + cb->auth.become_root(); + status = gensec_update_recv(subreq, call, + &call->out_auth_info->credentials); + cb->auth.unbecome_root(); + TALLOC_FREE(subreq); + + status = dcesrv_auth_complete(call, status); + if (!NT_STATUS_IS_OK(status)) { + status = dcesrv_fault_disconnect(call, DCERPC_FAULT_SEC_PKG_ERROR); + dcesrv_conn_auth_wait_finished(conn, status); + return; + } + + status = dcesrv_auth_reply(call); + dcesrv_conn_auth_wait_finished(conn, status); + return; +} + +/* + possibly save the call for inspection with ndrdump + */ +static void dcesrv_save_call(struct dcesrv_call_state *call, const char *why) +{ +#ifdef DEVELOPER + dcerpc_log_packet(call->conn->packet_log_dir, + call->context->iface->name, + call->pkt.u.request.opnum, + NDR_IN, + &call->pkt.u.request.stub_and_verifier, + why); +#endif +} + +#ifdef DEVELOPER +/* + Save the call for use as a seed for fuzzing. + + This is only enabled in a developer build, and only has effect if the + "dcesrv fuzz directory" param is set. +*/ +void _dcesrv_save_ndr_fuzz_seed(DATA_BLOB call_blob, + struct dcesrv_call_state *call, + ndr_flags_type flags) +{ + const char *dump_dir = lpcfg_parm_string(call->conn->dce_ctx->lp_ctx, + NULL, + "dcesrv", "fuzz directory"); + + dcerpc_save_ndr_fuzz_seed(call, + call_blob, + dump_dir, + call->context->iface->name, + flags, + call->pkt.u.request.opnum, + call->ndr_pull->flags & LIBNDR_FLAG_NDR64); +} +#endif /*if DEVELOPER, enveloping _dcesrv_save_ndr_fuzz_seed() */ + + +static NTSTATUS dcesrv_check_verification_trailer(struct dcesrv_call_state *call) +{ + TALLOC_CTX *frame = talloc_stackframe(); + const uint32_t bitmask1 = call->conn->client_hdr_signing ? + DCERPC_SEC_VT_CLIENT_SUPPORTS_HEADER_SIGNING : 0; + const struct dcerpc_sec_vt_pcontext pcontext = { + .abstract_syntax = call->context->iface->syntax_id, + .transfer_syntax = call->context->transfer_syntax, + }; + const struct dcerpc_sec_vt_header2 header2 = + dcerpc_sec_vt_header2_from_ncacn_packet(&call->pkt); + enum ndr_err_code ndr_err; + struct dcerpc_sec_verification_trailer *vt = NULL; + NTSTATUS status = NT_STATUS_OK; + bool ok; + + SMB_ASSERT(call->pkt.ptype == DCERPC_PKT_REQUEST); + + ndr_err = ndr_pop_dcerpc_sec_verification_trailer(call->ndr_pull, + frame, &vt); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + goto done; + } + + ok = dcerpc_sec_verification_trailer_check(vt, &bitmask1, + &pcontext, &header2); + if (!ok) { + status = NT_STATUS_ACCESS_DENIED; + goto done; + } +done: + TALLOC_FREE(frame); + return status; +} + +/* + handle a dcerpc request packet +*/ +static NTSTATUS dcesrv_request(struct dcesrv_call_state *call) +{ + const struct dcesrv_endpoint *endpoint = call->conn->endpoint; + struct dcesrv_auth *auth = call->auth_state; + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(endpoint->ep_description); + struct ndr_pull *pull; + bool turn_winbind_on = false; + NTSTATUS status; + + if (auth->auth_invalid) { + return dcesrv_fault_disconnect(call, DCERPC_NCA_S_PROTO_ERROR); + } + + if (!auth->auth_finished) { + return dcesrv_fault_disconnect(call, DCERPC_NCA_S_PROTO_ERROR); + } + + /* if authenticated, and the mech we use can't do async replies, don't use them... */ + if (auth->gensec_security != NULL && + !gensec_have_feature(auth->gensec_security, GENSEC_FEATURE_ASYNC_REPLIES)) { + call->state_flags &= ~DCESRV_CALL_STATE_FLAG_MAY_ASYNC; + } + + if (call->context == NULL) { + return dcesrv_fault_with_flags(call, DCERPC_NCA_S_UNKNOWN_IF, + DCERPC_PFC_FLAG_DID_NOT_EXECUTE); + } + + switch (auth->auth_level) { + case DCERPC_AUTH_LEVEL_NONE: + case DCERPC_AUTH_LEVEL_PACKET: + case DCERPC_AUTH_LEVEL_INTEGRITY: + case DCERPC_AUTH_LEVEL_PRIVACY: + break; + default: + if (!call->context->allow_connect) { + char *addr; + + addr = tsocket_address_string(call->conn->remote_address, + call); + + DEBUG(2, ("%s: restrict auth_level_connect access " + "to [%s] with auth[type=0x%x,level=0x%x] " + "on [%s] from [%s]\n", + __func__, call->context->iface->name, + auth->auth_type, + auth->auth_level, + derpc_transport_string_by_transport(transport), + addr)); + return dcesrv_fault(call, DCERPC_FAULT_ACCESS_DENIED); + } + break; + } + + if (auth->auth_level < call->context->min_auth_level) { + char *addr; + + addr = tsocket_address_string(call->conn->remote_address, call); + + DEBUG(2, ("%s: restrict access by min_auth_level[0x%x] " + "to [%s] with auth[type=0x%x,level=0x%x] " + "on [%s] from [%s]\n", + __func__, + call->context->min_auth_level, + call->context->iface->name, + auth->auth_type, + auth->auth_level, + derpc_transport_string_by_transport(transport), + addr)); + return dcesrv_fault(call, DCERPC_FAULT_ACCESS_DENIED); + } + + pull = ndr_pull_init_blob(&call->pkt.u.request.stub_and_verifier, call); + NT_STATUS_HAVE_NO_MEMORY(pull); + + pull->flags |= LIBNDR_FLAG_REF_ALLOC; + + call->ndr_pull = pull; + + if (!(call->pkt.drep[0] & DCERPC_DREP_LE)) { + pull->flags |= LIBNDR_FLAG_BIGENDIAN; + } + + status = dcesrv_check_verification_trailer(call); + if (!NT_STATUS_IS_OK(status)) { + uint32_t faultcode = DCERPC_FAULT_OTHER; + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + faultcode = DCERPC_FAULT_ACCESS_DENIED; + } + DEBUG(10, ("dcesrv_check_verification_trailer failed: %s\n", + nt_errstr(status))); + return dcesrv_fault(call, faultcode); + } + + if (call->context->ndr64) { + call->ndr_pull->flags |= LIBNDR_FLAG_NDR64; + } + + /* unravel the NDR for the packet */ + status = call->context->iface->ndr_pull(call, call, pull, &call->r); + if (!NT_STATUS_IS_OK(status)) { + uint8_t extra_flags = 0; + if (call->fault_code == DCERPC_FAULT_OP_RNG_ERROR) { + /* we got an unknown call */ + DEBUG(3,(__location__ ": Unknown RPC call %"PRIu16" on %s\n", + call->pkt.u.request.opnum, + call->context->iface->name)); + dcesrv_save_call(call, "unknown"); + extra_flags |= DCERPC_PFC_FLAG_DID_NOT_EXECUTE; + } else { + dcesrv_save_call(call, "pullfail"); + } + + return dcesrv_fault_with_flags(call, call->fault_code, extra_flags); + } + + dcesrv_save_ndr_fuzz_seed(call->pkt.u.request.stub_and_verifier, + call, + NDR_IN); + + if (pull->offset != pull->data_size) { + dcesrv_save_call(call, "extrabytes"); + DEBUG(3,("Warning: %"PRIu32" extra bytes in incoming RPC request\n", + pull->data_size - pull->offset)); + } + + if (call->state_flags & DCESRV_CALL_STATE_FLAG_WINBIND_OFF) { + bool winbind_active = !winbind_env_set(); + if (winbind_active) { + DBG_DEBUG("turning winbind off\n"); + (void)winbind_off(); + turn_winbind_on = true; + } + } + + /* call the dispatch function */ + status = call->context->iface->dispatch(call, call, call->r); + + if (turn_winbind_on) { + DBG_DEBUG("turning winbind on\n"); + (void)winbind_on(); + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(5,("dcerpc fault in call %s:%02x - %s\n", + call->context->iface->name, + call->pkt.u.request.opnum, + dcerpc_errstr(pull, call->fault_code))); + return dcesrv_fault(call, call->fault_code); + } + + /* add the call to the pending list */ + dcesrv_call_set_list(call, DCESRV_LIST_PENDING_CALL_LIST); + + if (call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return NT_STATUS_OK; + } + + return dcesrv_reply(call); +} + + +/* + remove the call from the right list when freed + */ +static int dcesrv_call_dequeue(struct dcesrv_call_state *call) +{ + dcesrv_call_set_list(call, DCESRV_LIST_NONE); + return 0; +} + +_PUBLIC_ const struct tsocket_address *dcesrv_connection_get_local_address(struct dcesrv_connection *conn) +{ + return conn->local_address; +} + +_PUBLIC_ const struct tsocket_address *dcesrv_connection_get_remote_address(struct dcesrv_connection *conn) +{ + return conn->remote_address; +} + +/* + process some input to a dcerpc endpoint server. +*/ +static NTSTATUS dcesrv_process_ncacn_packet(struct dcesrv_connection *dce_conn, + struct ncacn_packet *pkt, + DATA_BLOB blob) +{ + NTSTATUS status; + struct dcesrv_call_state *call; + struct dcesrv_call_state *existing = NULL; + size_t num_auth_ctx = 0; + enum dcerpc_AuthType auth_type = 0; + enum dcerpc_AuthLevel auth_level = 0; + uint32_t auth_context_id = 0; + bool auth_invalid = false; + + call = talloc_zero(dce_conn, struct dcesrv_call_state); + if (!call) { + data_blob_free(&blob); + talloc_free(pkt); + return NT_STATUS_NO_MEMORY; + } + call->conn = dce_conn; + call->event_ctx = dce_conn->event_ctx; + call->state_flags = call->conn->state_flags; + call->time = timeval_current(); + call->list = DCESRV_LIST_NONE; + + talloc_steal(call, pkt); + talloc_steal(call, blob.data); + call->pkt = *pkt; + + if (dce_conn->max_auth_states == 0) { + call->auth_state = dce_conn->default_auth_state; + } else if (call->pkt.auth_length == 0) { + if (call->pkt.ptype == DCERPC_PKT_REQUEST && + dce_conn->default_auth_level_connect != NULL) + { + call->auth_state = dce_conn->default_auth_level_connect; + } else { + call->auth_state = dce_conn->default_auth_state; + } + } + + if (call->auth_state == NULL) { + struct dcesrv_auth *a = NULL; + bool check_type_level = true; + + auth_type = dcerpc_get_auth_type(&blob); + auth_level = dcerpc_get_auth_level(&blob); + auth_context_id = dcerpc_get_auth_context_id(&blob); + + if (call->pkt.ptype == DCERPC_PKT_REQUEST) { + if (!(call->pkt.pfc_flags & DCERPC_PFC_FLAG_FIRST)) { + check_type_level = false; + } + dce_conn->default_auth_level_connect = NULL; + if (auth_level == DCERPC_AUTH_LEVEL_CONNECT) { + dce_conn->got_explicit_auth_level_connect = true; + } + } + + for (a = dce_conn->auth_states; a != NULL; a = a->next) { + num_auth_ctx++; + + if (a->auth_context_id != auth_context_id) { + continue; + } + + if (a->auth_type != auth_type) { + auth_invalid = true; + } + if (a->auth_level != auth_level) { + auth_invalid = true; + } + + if (check_type_level && auth_invalid) { + a->auth_invalid = true; + } + + DLIST_PROMOTE(dce_conn->auth_states, a); + call->auth_state = a; + break; + } + } + + if (call->auth_state == NULL) { + struct dcesrv_auth *a = NULL; + + if (num_auth_ctx >= dce_conn->max_auth_states) { + return dcesrv_fault_disconnect(call, + DCERPC_NCA_S_PROTO_ERROR); + } + + a = dcesrv_auth_create(dce_conn); + if (a == NULL) { + talloc_free(call); + return NT_STATUS_NO_MEMORY; + } + DLIST_ADD(dce_conn->auth_states, a); + if (call->pkt.ptype == DCERPC_PKT_REQUEST) { + /* + * This can never be valid. + */ + auth_invalid = true; + a->auth_invalid = true; + } + call->auth_state = a; + } + + talloc_set_destructor(call, dcesrv_call_dequeue); + + if (call->conn->allow_bind) { + /* + * Only one bind is possible per connection + */ + call->conn->allow_bind = false; + return dcesrv_bind(call); + } + + /* we have to check the signing here, before combining the + pdus */ + if (call->pkt.ptype == DCERPC_PKT_REQUEST) { + dcesrv_default_auth_state_prepare_request(call); + + if (call->auth_state->auth_started && + !call->auth_state->auth_finished) { + return dcesrv_fault_disconnect(call, + DCERPC_NCA_S_PROTO_ERROR); + } + + status = dcerpc_verify_ncacn_packet_header(&call->pkt, + DCERPC_PKT_REQUEST, + call->pkt.u.request.stub_and_verifier.length, + 0, /* required_flags */ + DCERPC_PFC_FLAG_FIRST | + DCERPC_PFC_FLAG_LAST | + DCERPC_PFC_FLAG_PENDING_CANCEL | + 0x08 | /* this is not defined, but should be ignored */ + DCERPC_PFC_FLAG_CONC_MPX | + DCERPC_PFC_FLAG_DID_NOT_EXECUTE | + DCERPC_PFC_FLAG_MAYBE | + DCERPC_PFC_FLAG_OBJECT_UUID); + if (!NT_STATUS_IS_OK(status)) { + return dcesrv_fault_disconnect(call, + DCERPC_NCA_S_PROTO_ERROR); + } + + if (call->pkt.frag_length > DCERPC_FRAG_MAX_SIZE) { + /* + * We don't use dcesrv_fault_disconnect() + * here, because we don't want to set + * DCERPC_PFC_FLAG_DID_NOT_EXECUTE + * + * Note that we don't check against the negotiated + * max_recv_frag, but a hard coded value. + */ + return dcesrv_fault_disconnect0(call, DCERPC_NCA_S_PROTO_ERROR); + } + + if (call->pkt.pfc_flags & DCERPC_PFC_FLAG_FIRST) { + if (dce_conn->pending_call_list != NULL) { + /* + * concurrent requests are only allowed + * if DCERPC_PFC_FLAG_CONC_MPX was negotiated. + */ + if (!(dce_conn->state_flags & DCESRV_CALL_STATE_FLAG_MULTIPLEXED)) { + return dcesrv_fault_disconnect0(call, + DCERPC_NCA_S_PROTO_ERROR); + } + } + /* only one request is possible in the fragmented list */ + if (dce_conn->incoming_fragmented_call_list != NULL) { + call->fault_code = DCERPC_NCA_S_PROTO_ERROR; + + existing = dcesrv_find_fragmented_call(dce_conn, + call->pkt.call_id); + if (existing != NULL && call->auth_state != existing->auth_state) { + call->context = dcesrv_find_context(call->conn, + call->pkt.u.request.context_id); + + if (call->pkt.auth_length != 0 && existing->context == call->context) { + call->fault_code = DCERPC_FAULT_SEC_PKG_ERROR; + } + } + if (!(dce_conn->state_flags & DCESRV_CALL_STATE_FLAG_MULTIPLEXED)) { + /* + * Without DCERPC_PFC_FLAG_CONC_MPX + * we need to return the FAULT on the + * already existing call. + * + * This is important to get the + * call_id and context_id right. + */ + dce_conn->incoming_fragmented_call_list->fault_code = call->fault_code; + TALLOC_FREE(call); + call = dce_conn->incoming_fragmented_call_list; + } + if (existing != NULL) { + call->context = existing->context; + } + return dcesrv_fault_disconnect0(call, call->fault_code); + } + if (call->pkt.pfc_flags & DCERPC_PFC_FLAG_PENDING_CANCEL) { + return dcesrv_fault_disconnect(call, + DCERPC_FAULT_NO_CALL_ACTIVE); + } + call->context = dcesrv_find_context(call->conn, + call->pkt.u.request.context_id); + if (call->context == NULL) { + return dcesrv_fault_with_flags(call, DCERPC_NCA_S_UNKNOWN_IF, + DCERPC_PFC_FLAG_DID_NOT_EXECUTE); + } + } else { + int cmp; + + existing = dcesrv_find_fragmented_call(dce_conn, + call->pkt.call_id); + if (existing == NULL) { + if (!(dce_conn->state_flags & DCESRV_CALL_STATE_FLAG_MULTIPLEXED)) { + /* + * Without DCERPC_PFC_FLAG_CONC_MPX + * we need to return the FAULT on the + * already existing call. + * + * This is important to get the + * call_id and context_id right. + */ + if (dce_conn->incoming_fragmented_call_list != NULL) { + TALLOC_FREE(call); + call = dce_conn->incoming_fragmented_call_list; + } + return dcesrv_fault_disconnect0(call, + DCERPC_NCA_S_PROTO_ERROR); + } + if (dce_conn->incoming_fragmented_call_list != NULL) { + return dcesrv_fault_disconnect0(call, DCERPC_NCA_S_PROTO_ERROR); + } + call->context = dcesrv_find_context(call->conn, + call->pkt.u.request.context_id); + if (call->context == NULL) { + return dcesrv_fault_with_flags(call, DCERPC_NCA_S_UNKNOWN_IF, + DCERPC_PFC_FLAG_DID_NOT_EXECUTE); + } + if (auth_invalid) { + return dcesrv_fault_disconnect0(call, + DCERPC_FAULT_ACCESS_DENIED); + } + return dcesrv_fault_disconnect0(call, + DCERPC_NCA_S_PROTO_ERROR); + } + + if (call->pkt.ptype != existing->pkt.ptype) { + /* trying to play silly buggers are we? */ + return dcesrv_fault_disconnect(existing, + DCERPC_NCA_S_PROTO_ERROR); + } + cmp = memcmp(call->pkt.drep, existing->pkt.drep, + sizeof(pkt->drep)); + if (cmp != 0) { + return dcesrv_fault_disconnect(existing, + DCERPC_NCA_S_PROTO_ERROR); + } + call->auth_state = existing->auth_state; + call->context = existing->context; + } + } + + if (call->pkt.ptype == DCERPC_PKT_REQUEST) { + bool ok; + uint8_t payload_offset = DCERPC_REQUEST_LENGTH; + + if (call->pkt.pfc_flags & DCERPC_PFC_FLAG_OBJECT_UUID) { + payload_offset += 16; + } + + ok = dcesrv_auth_pkt_pull(call, &blob, + 0, /* required_flags */ + DCERPC_PFC_FLAG_FIRST | + DCERPC_PFC_FLAG_LAST | + DCERPC_PFC_FLAG_PENDING_CANCEL | + 0x08 | /* this is not defined, but should be ignored */ + DCERPC_PFC_FLAG_CONC_MPX | + DCERPC_PFC_FLAG_DID_NOT_EXECUTE | + DCERPC_PFC_FLAG_MAYBE | + DCERPC_PFC_FLAG_OBJECT_UUID, + payload_offset, + &call->pkt.u.request.stub_and_verifier); + if (!ok) { + /* + * We don't use dcesrv_fault_disconnect() + * here, because we don't want to set + * DCERPC_PFC_FLAG_DID_NOT_EXECUTE + */ + if (call->fault_code == 0) { + call->fault_code = DCERPC_FAULT_ACCESS_DENIED; + } + return dcesrv_fault_disconnect0(call, call->fault_code); + } + } + + /* see if this is a continued packet */ + if (existing != NULL) { + struct dcerpc_request *er = &existing->pkt.u.request; + const struct dcerpc_request *nr = &call->pkt.u.request; + size_t available; + size_t alloc_size; + size_t alloc_hint; + + /* + * Up to 4 MByte are allowed by all fragments + */ + available = dce_conn->max_total_request_size; + if (er->stub_and_verifier.length > available) { + return dcesrv_fault_disconnect0(existing, + DCERPC_FAULT_ACCESS_DENIED); + } + available -= er->stub_and_verifier.length; + if (nr->alloc_hint > available) { + return dcesrv_fault_disconnect0(existing, + DCERPC_FAULT_ACCESS_DENIED); + } + if (nr->stub_and_verifier.length > available) { + return dcesrv_fault_disconnect0(existing, + DCERPC_FAULT_ACCESS_DENIED); + } + alloc_hint = er->stub_and_verifier.length + nr->alloc_hint; + /* allocate at least 1 byte */ + alloc_hint = MAX(alloc_hint, 1); + alloc_size = er->stub_and_verifier.length + + nr->stub_and_verifier.length; + alloc_size = MAX(alloc_size, alloc_hint); + + er->stub_and_verifier.data = + talloc_realloc(existing, + er->stub_and_verifier.data, + uint8_t, alloc_size); + if (er->stub_and_verifier.data == NULL) { + TALLOC_FREE(call); + return dcesrv_fault_with_flags(existing, + DCERPC_FAULT_OUT_OF_RESOURCES, + DCERPC_PFC_FLAG_DID_NOT_EXECUTE); + } + memcpy(er->stub_and_verifier.data + + er->stub_and_verifier.length, + nr->stub_and_verifier.data, + nr->stub_and_verifier.length); + er->stub_and_verifier.length += nr->stub_and_verifier.length; + + existing->pkt.pfc_flags |= (call->pkt.pfc_flags & DCERPC_PFC_FLAG_LAST); + + TALLOC_FREE(call); + call = existing; + } + + /* this may not be the last pdu in the chain - if its isn't then + just put it on the incoming_fragmented_call_list and wait for the rest */ + if (call->pkt.ptype == DCERPC_PKT_REQUEST && + !(call->pkt.pfc_flags & DCERPC_PFC_FLAG_LAST)) { + /* + * Up to 4 MByte are allowed by all fragments + */ + if (call->pkt.u.request.alloc_hint > dce_conn->max_total_request_size) { + return dcesrv_fault_disconnect0(call, + DCERPC_FAULT_ACCESS_DENIED); + } + dcesrv_call_set_list(call, DCESRV_LIST_FRAGMENTED_CALL_LIST); + return NT_STATUS_OK; + } + + /* This removes any fragments we may have had stashed away */ + dcesrv_call_set_list(call, DCESRV_LIST_NONE); + + switch (call->pkt.ptype) { + case DCERPC_PKT_BIND: + status = dcesrv_bind_nak(call, + DCERPC_BIND_NAK_REASON_NOT_SPECIFIED); + break; + case DCERPC_PKT_AUTH3: + status = dcesrv_auth3(call); + break; + case DCERPC_PKT_ALTER: + status = dcesrv_alter(call); + break; + case DCERPC_PKT_REQUEST: + status = dcesrv_request(call); + break; + case DCERPC_PKT_CO_CANCEL: + existing = dcesrv_find_fragmented_call(dce_conn, + call->pkt.call_id); + if (existing != NULL) { + /* + * If the call is still waiting for + * more fragments, it's not pending yet, + * for now we just remember we got CO_CANCEL, + * but ignore it otherwise. + * + * This matches what windows is doing... + */ + existing->got_co_cancel = true; + SMB_ASSERT(existing->subreq == NULL); + existing = NULL; + } + existing = dcesrv_find_pending_call(dce_conn, + call->pkt.call_id); + if (existing != NULL) { + /* + * Give the backend a chance to react + * on CO_CANCEL, but note it's ignored + * by default. + */ + existing->got_co_cancel = true; + if (existing->subreq != NULL) { + tevent_req_cancel(existing->subreq); + } + existing = NULL; + } + status = NT_STATUS_OK; + TALLOC_FREE(call); + break; + case DCERPC_PKT_ORPHANED: + existing = dcesrv_find_fragmented_call(dce_conn, + call->pkt.call_id); + if (existing != NULL) { + /* + * If the call is still waiting for + * more fragments, it's not pending yet, + * for now we just remember we got ORPHANED, + * but ignore it otherwise. + * + * This matches what windows is doing... + */ + existing->got_orphaned = true; + SMB_ASSERT(existing->subreq == NULL); + existing = NULL; + } + existing = dcesrv_find_pending_call(dce_conn, + call->pkt.call_id); + if (existing != NULL) { + /* + * Give the backend a chance to react + * on ORPHANED, but note it's ignored + * by default. + */ + existing->got_orphaned = true; + if (existing->subreq != NULL) { + tevent_req_cancel(existing->subreq); + } + existing = NULL; + } + status = NT_STATUS_OK; + TALLOC_FREE(call); + break; + case DCERPC_PKT_BIND_ACK: + case DCERPC_PKT_BIND_NAK: + case DCERPC_PKT_ALTER_RESP: + case DCERPC_PKT_RESPONSE: + case DCERPC_PKT_FAULT: + case DCERPC_PKT_SHUTDOWN: + default: + status = dcesrv_fault_disconnect(call, DCERPC_NCA_S_PROTO_ERROR); + break; + } + + /* if we are going to be sending a reply then add + it to the list of pending calls. We add it to the end to keep the call + list in the order we will answer */ + if (!NT_STATUS_IS_OK(status)) { + talloc_free(call); + } + + return status; +} + +_PUBLIC_ NTSTATUS dcesrv_init_context(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct dcesrv_context_callbacks *cb, + struct dcesrv_context **_dce_ctx) +{ + struct dcesrv_context *dce_ctx; + + if (cb == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + dce_ctx = talloc_zero(mem_ctx, struct dcesrv_context); + NT_STATUS_HAVE_NO_MEMORY(dce_ctx); + + if (uid_wrapper_enabled()) { + setenv("UID_WRAPPER_MYUID", "1", 1); + } + dce_ctx->initial_euid = geteuid(); + if (uid_wrapper_enabled()) { + unsetenv("UID_WRAPPER_MYUID"); + } + + dce_ctx->endpoint_list = NULL; + dce_ctx->lp_ctx = lp_ctx; + dce_ctx->assoc_groups_idr = idr_init(dce_ctx); + if (dce_ctx->assoc_groups_idr == NULL) { + TALLOC_FREE(dce_ctx); + return NT_STATUS_NO_MEMORY; + } + dce_ctx->broken_connections = NULL; + dce_ctx->callbacks = cb; + + /* + * For now we only support NDR32. + */ + dce_ctx->preferred_transfer = &ndr_transfer_syntax_ndr; + + *_dce_ctx = dce_ctx; + return NT_STATUS_OK; +} + +/** + * @brief Set callback functions on an existing dcesrv_context + * + * This allows to reset callbacks initially set via + * dcesrv_init_context() + * + * @param[in] dce_ctx The context to set the callbacks on + * @param[in] cb The callbacks to set on dce_ctx + */ +_PUBLIC_ void dcesrv_context_set_callbacks( + struct dcesrv_context *dce_ctx, + struct dcesrv_context_callbacks *cb) +{ + dce_ctx->callbacks = cb; +} + +_PUBLIC_ NTSTATUS dcesrv_init_ep_servers(struct dcesrv_context *dce_ctx, + const char **endpoint_servers) +{ + NTSTATUS status; + int i; + + if (endpoint_servers == NULL) { + DBG_ERR("No endpoint servers configured\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + for (i=0;endpoint_servers[i];i++) { + status = dcesrv_init_ep_server(dce_ctx, endpoint_servers[i]); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("failed to init endpoint server = '%s': %s\n", + endpoint_servers[i], nt_errstr(status)); + return status; + } + } + + return NT_STATUS_OK; +} + +/* the list of currently registered DCERPC endpoint servers. + */ +static struct ep_server { + struct dcesrv_endpoint_server *ep_server; +} *ep_servers = NULL; +static int num_ep_servers = 0; + +_PUBLIC_ NTSTATUS dcesrv_init_registered_ep_servers( + struct dcesrv_context *dce_ctx) +{ + NTSTATUS status; + int i; + + for (i = 0; i < num_ep_servers; i++) { + status = dcesrv_init_ep_server(dce_ctx, + ep_servers[i].ep_server->name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS dcesrv_init_ep_server(struct dcesrv_context *dce_ctx, + const char *ep_server_name) +{ + struct dcesrv_endpoint_server *ep_server = NULL; + NTSTATUS status; + + ep_server = discard_const_p(struct dcesrv_endpoint_server, + dcesrv_ep_server_byname(ep_server_name)); + if (ep_server == NULL) { + DBG_ERR("Failed to find endpoint server '%s'\n", + ep_server_name); + return NT_STATUS_INTERNAL_ERROR; + } + + if (ep_server->initialized) { + return NT_STATUS_OK; + } + + status = ep_server->init_server(dce_ctx, ep_server); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to init endpoint server '%s': %s\n", + ep_server_name, nt_errstr(status)); + return status; + } + + ep_server->initialized = true; + + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS dcesrv_shutdown_registered_ep_servers( + struct dcesrv_context *dce_ctx) +{ + NTSTATUS status; + int i; + + for (i = 0; i < num_ep_servers; i++) { + status = dcesrv_shutdown_ep_server(dce_ctx, + ep_servers[i].ep_server->name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS dcesrv_shutdown_ep_server(struct dcesrv_context *dce_ctx, + const char *ep_server_name) +{ + struct dcesrv_endpoint_server *ep_server = NULL; + NTSTATUS status; + + ep_server = discard_const_p(struct dcesrv_endpoint_server, + dcesrv_ep_server_byname(ep_server_name)); + if (ep_server == NULL) { + DBG_ERR("Failed to find endpoint server '%s'\n", + ep_server_name); + return NT_STATUS_INTERNAL_ERROR; + } + + if (!ep_server->initialized) { + return NT_STATUS_OK; + } + + DBG_INFO("Shutting down DCE/RPC endpoint server '%s'\n", + ep_server_name); + + status = ep_server->shutdown_server(dce_ctx, ep_server); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to shutdown endpoint server '%s': %s\n", + ep_server_name, nt_errstr(status)); + return status; + } + + ep_server->initialized = false; + + return NT_STATUS_OK; +} + +/* + register a DCERPC endpoint server. + + The 'name' can be later used by other backends to find the operations + structure for this backend. + +*/ +_PUBLIC_ NTSTATUS dcerpc_register_ep_server(const struct dcesrv_endpoint_server *ep_server) +{ + + if (dcesrv_ep_server_byname(ep_server->name) != NULL) { + /* its already registered! */ + DEBUG(0,("DCERPC endpoint server '%s' already registered\n", + ep_server->name)); + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + ep_servers = realloc_p(ep_servers, struct ep_server, num_ep_servers+1); + if (!ep_servers) { + smb_panic("out of memory in dcerpc_register"); + } + + ep_servers[num_ep_servers].ep_server = smb_xmemdup(ep_server, sizeof(*ep_server)); + ep_servers[num_ep_servers].ep_server->name = smb_xstrdup(ep_server->name); + + num_ep_servers++; + + DEBUG(3,("DCERPC endpoint server '%s' registered\n", + ep_server->name)); + + return NT_STATUS_OK; +} + +/* + return the operations structure for a named backend of the specified type +*/ +_PUBLIC_ const struct dcesrv_endpoint_server *dcesrv_ep_server_byname(const char *name) +{ + int i; + + for (i=0;i<num_ep_servers;i++) { + if (strcmp(ep_servers[i].ep_server->name, name) == 0) { + return ep_servers[i].ep_server; + } + } + + return NULL; +} + +/* + return the DCERPC module version, and the size of some critical types + This can be used by endpoint server modules to either detect compilation errors, or provide + multiple implementations for different smbd compilation options in one module +*/ +const struct dcesrv_critical_sizes *dcerpc_module_version(void) +{ + static const struct dcesrv_critical_sizes critical_sizes = { + DCERPC_MODULE_VERSION, + sizeof(struct dcesrv_context), + sizeof(struct dcesrv_endpoint), + sizeof(struct dcesrv_endpoint_server), + sizeof(struct dcesrv_interface), + sizeof(struct dcesrv_if_list), + sizeof(struct dcesrv_connection), + sizeof(struct dcesrv_call_state), + sizeof(struct dcesrv_auth), + sizeof(struct dcesrv_handle) + }; + + return &critical_sizes; +} + +_PUBLIC_ void dcesrv_terminate_connection(struct dcesrv_connection *dce_conn, const char *reason) +{ + struct dcesrv_context *dce_ctx = dce_conn->dce_ctx; + struct dcesrv_call_state *c = NULL, *n = NULL; + struct dcesrv_auth *a = NULL; + + dce_conn->wait_send = NULL; + dce_conn->wait_recv = NULL; + dce_conn->wait_private = NULL; + + dce_conn->allow_bind = false; + dce_conn->allow_alter = false; + + dce_conn->default_auth_state->auth_invalid = true; + + for (a = dce_conn->auth_states; a != NULL; a = a->next) { + a->auth_invalid = true; + } + +no_pending: + if (dce_conn->pending_call_list == NULL) { + char *full_reason = talloc_asprintf(dce_conn, "dcesrv: %s", reason); + + DLIST_REMOVE(dce_ctx->broken_connections, dce_conn); + dce_conn->transport.terminate_connection(dce_conn, + full_reason ? full_reason : reason); + return; + } + + if (dce_conn->terminate != NULL) { + return; + } + + DEBUG(3,("dcesrv: terminating connection due to '%s' deferred due to pending calls\n", + reason)); + dce_conn->terminate = talloc_strdup(dce_conn, reason); + if (dce_conn->terminate == NULL) { + dce_conn->terminate = "dcesrv: deferred terminating connection - no memory"; + } + DLIST_ADD_END(dce_ctx->broken_connections, dce_conn); + + for (c = dce_conn->pending_call_list; c != NULL; c = n) { + n = c->next; + + c->got_disconnect = true; + if (c->subreq != NULL) { + tevent_req_cancel(c->subreq); + } + } + + if (dce_conn->pending_call_list == NULL) { + /* + * tevent_req_cancel() was able to made progress + * and we don't have pending calls anymore. + */ + goto no_pending; + } +} + +_PUBLIC_ void dcesrv_cleanup_broken_connections(struct dcesrv_context *dce_ctx) +{ + struct dcesrv_connection *cur, *next; + + next = dce_ctx->broken_connections; + while (next != NULL) { + cur = next; + next = cur->next; + + if (cur->state_flags & DCESRV_CALL_STATE_FLAG_PROCESS_PENDING_CALL) { + struct dcesrv_connection_context *context_cur, *context_next; + + context_next = cur->contexts; + while (context_next != NULL) { + context_cur = context_next; + context_next = context_cur->next; + + dcesrv_connection_context_destructor(context_cur); + } + } + + dcesrv_terminate_connection(cur, cur->terminate); + } +} + +struct dcesrv_sock_reply_state { + struct dcesrv_connection *dce_conn; + struct dcesrv_call_state *call; + struct iovec iov; +}; + +static void dcesrv_sock_reply_done(struct tevent_req *subreq); +static void dcesrv_call_terminate_step1(struct tevent_req *subreq); + +_PUBLIC_ void dcesrv_sock_report_output_data(struct dcesrv_connection *dce_conn) +{ + struct dcesrv_call_state *call; + + call = dce_conn->call_list; + if (!call || !call->replies) { + return; + } + + while (call->replies) { + struct data_blob_list_item *rep = call->replies; + struct dcesrv_sock_reply_state *substate; + struct tevent_req *subreq; + + substate = talloc_zero(call, struct dcesrv_sock_reply_state); + if (!substate) { + dcesrv_terminate_connection(dce_conn, "no memory"); + return; + } + + substate->dce_conn = dce_conn; + substate->call = NULL; + + DLIST_REMOVE(call->replies, rep); + + if (call->replies == NULL && call->terminate_reason == NULL) { + substate->call = call; + } + + substate->iov.iov_base = (void *) rep->blob.data; + substate->iov.iov_len = rep->blob.length; + + subreq = tstream_writev_queue_send(substate, + dce_conn->event_ctx, + dce_conn->stream, + dce_conn->send_queue, + &substate->iov, 1); + if (!subreq) { + dcesrv_terminate_connection(dce_conn, "no memory"); + return; + } + tevent_req_set_callback(subreq, dcesrv_sock_reply_done, + substate); + } + + if (call->terminate_reason != NULL) { + struct tevent_req *subreq; + + subreq = tevent_queue_wait_send(call, + dce_conn->event_ctx, + dce_conn->send_queue); + if (!subreq) { + dcesrv_terminate_connection(dce_conn, __location__); + return; + } + tevent_req_set_callback(subreq, dcesrv_call_terminate_step1, + call); + } + + DLIST_REMOVE(call->conn->call_list, call); + call->list = DCESRV_LIST_NONE; +} + +static void dcesrv_sock_reply_done(struct tevent_req *subreq) +{ + struct dcesrv_sock_reply_state *substate = tevent_req_callback_data(subreq, + struct dcesrv_sock_reply_state); + int ret; + int sys_errno; + NTSTATUS status; + struct dcesrv_call_state *call = substate->call; + + ret = tstream_writev_queue_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + status = map_nt_error_from_unix_common(sys_errno); + dcesrv_terminate_connection(substate->dce_conn, nt_errstr(status)); + return; + } + + talloc_free(substate); + if (call) { + talloc_free(call); + } +} + +static void dcesrv_call_terminate_step2(struct tevent_req *subreq); + +static void dcesrv_call_terminate_step1(struct tevent_req *subreq) +{ + struct dcesrv_call_state *call = tevent_req_callback_data(subreq, + struct dcesrv_call_state); + bool ok; + struct timeval tv; + + /* make sure we stop send queue before removing subreq */ + tevent_queue_stop(call->conn->send_queue); + + ok = tevent_queue_wait_recv(subreq); + TALLOC_FREE(subreq); + if (!ok) { + dcesrv_terminate_connection(call->conn, __location__); + return; + } + + /* disconnect after 200 usecs */ + tv = timeval_current_ofs_usec(200); + subreq = tevent_wakeup_send(call, call->conn->event_ctx, tv); + if (subreq == NULL) { + dcesrv_terminate_connection(call->conn, __location__); + return; + } + tevent_req_set_callback(subreq, dcesrv_call_terminate_step2, + call); +} + +static void dcesrv_call_terminate_step2(struct tevent_req *subreq) +{ + struct dcesrv_call_state *call = tevent_req_callback_data(subreq, + struct dcesrv_call_state); + bool ok; + + ok = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (!ok) { + dcesrv_terminate_connection(call->conn, __location__); + return; + } + + dcesrv_terminate_connection(call->conn, call->terminate_reason); +} + +static void dcesrv_conn_wait_done(struct tevent_req *subreq); + +static void dcesrv_read_fragment_done(struct tevent_req *subreq) +{ + struct dcesrv_connection *dce_conn = tevent_req_callback_data(subreq, + struct dcesrv_connection); + struct dcesrv_context *dce_ctx = dce_conn->dce_ctx; + struct ncacn_packet *pkt; + DATA_BLOB buffer; + NTSTATUS status; + + if (dce_conn->terminate) { + /* + * if the current connection is broken + * we need to clean it up before any other connection + */ + dcesrv_terminate_connection(dce_conn, dce_conn->terminate); + dcesrv_cleanup_broken_connections(dce_ctx); + return; + } + + dcesrv_cleanup_broken_connections(dce_ctx); + + status = dcerpc_read_ncacn_packet_recv(subreq, dce_conn, + &pkt, &buffer); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + dcesrv_terminate_connection(dce_conn, nt_errstr(status)); + return; + } + + dcesrv_loop_next_packet(dce_conn, pkt, buffer); +} + +/** + * @brief Start the dcesrv loop, inducing the bind as a blob + * + * Like dcesrv_connection_loop_start() but used from connections + * where the caller has already read the dcerpc bind packet from + * the socket and is available as a DATA_BLOB. + * + * @param[in] dce_conn The connection to start + * @param[in] pkt The parsed bind packet + * @param[in] buffer The full binary bind including auth data + */ +void dcesrv_loop_next_packet( + struct dcesrv_connection *dce_conn, + struct ncacn_packet *pkt, + DATA_BLOB buffer) +{ + struct tevent_req *subreq = NULL; + NTSTATUS status; + + status = dcesrv_process_ncacn_packet(dce_conn, pkt, buffer); + if (!NT_STATUS_IS_OK(status)) { + dcesrv_terminate_connection(dce_conn, nt_errstr(status)); + return; + } + + /* + * This is used to block the connection during + * pending authentication. + */ + if (dce_conn->wait_send != NULL) { + subreq = dce_conn->wait_send(dce_conn, + dce_conn->event_ctx, + dce_conn->wait_private); + if (!subreq) { + status = NT_STATUS_NO_MEMORY; + dcesrv_terminate_connection(dce_conn, nt_errstr(status)); + return; + } + tevent_req_set_callback(subreq, dcesrv_conn_wait_done, dce_conn); + return; + } + + subreq = dcerpc_read_ncacn_packet_send(dce_conn, + dce_conn->event_ctx, + dce_conn->stream); + if (!subreq) { + status = NT_STATUS_NO_MEMORY; + dcesrv_terminate_connection(dce_conn, nt_errstr(status)); + return; + } + tevent_req_set_callback(subreq, dcesrv_read_fragment_done, dce_conn); +} + +static void dcesrv_conn_wait_done(struct tevent_req *subreq) +{ + struct dcesrv_connection *dce_conn = tevent_req_callback_data(subreq, + struct dcesrv_connection); + struct dcesrv_context *dce_ctx = dce_conn->dce_ctx; + NTSTATUS status; + + if (dce_conn->terminate) { + /* + * if the current connection is broken + * we need to clean it up before any other connection + */ + dcesrv_terminate_connection(dce_conn, dce_conn->terminate); + dcesrv_cleanup_broken_connections(dce_ctx); + return; + } + + dcesrv_cleanup_broken_connections(dce_ctx); + + status = dce_conn->wait_recv(subreq); + dce_conn->wait_send = NULL; + dce_conn->wait_recv = NULL; + dce_conn->wait_private = NULL; + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + dcesrv_terminate_connection(dce_conn, nt_errstr(status)); + return; + } + + status = dcesrv_connection_loop_start(dce_conn); + if (!NT_STATUS_IS_OK(status)) { + dcesrv_terminate_connection(dce_conn, nt_errstr(status)); + return; + } +} + +/** + * retrieve credentials from a dce_call + */ +_PUBLIC_ struct cli_credentials *dcesrv_call_credentials(struct dcesrv_call_state *dce_call) +{ + struct dcesrv_auth *auth = dce_call->auth_state; + SMB_ASSERT(auth->auth_finished); + return auth->session_info->credentials; +} + +/** + * returns true if this is an authenticated call + */ +_PUBLIC_ bool dcesrv_call_authenticated(struct dcesrv_call_state *dce_call) +{ + struct dcesrv_auth *auth = dce_call->auth_state; + enum security_user_level level; + SMB_ASSERT(auth->auth_finished); + level = security_session_user_level(auth->session_info, NULL); + return level >= SECURITY_USER; +} + +/** + * retrieve account_name for a dce_call + */ +_PUBLIC_ const char *dcesrv_call_account_name(struct dcesrv_call_state *dce_call) +{ + struct dcesrv_auth *auth = dce_call->auth_state; + SMB_ASSERT(auth->auth_finished); + return auth->session_info->info->account_name; +} + +/** + * retrieve session_info from a dce_call + */ +_PUBLIC_ struct auth_session_info *dcesrv_call_session_info(struct dcesrv_call_state *dce_call) +{ + struct dcesrv_auth *auth = dce_call->auth_state; + SMB_ASSERT(auth->auth_finished); + return auth->session_info; +} + +/** + * retrieve auth type/level from a dce_call + */ +_PUBLIC_ void dcesrv_call_auth_info(struct dcesrv_call_state *dce_call, + enum dcerpc_AuthType *auth_type, + enum dcerpc_AuthLevel *auth_level) +{ + struct dcesrv_auth *auth = dce_call->auth_state; + + SMB_ASSERT(auth->auth_finished); + + if (auth_type != NULL) { + *auth_type = auth->auth_type; + } + if (auth_level != NULL) { + *auth_level = auth->auth_level; + } +} + +_PUBLIC_ NTSTATUS dcesrv_connection_loop_start(struct dcesrv_connection *conn) +{ + struct tevent_req *subreq; + + subreq = dcerpc_read_ncacn_packet_send(conn, + conn->event_ctx, + conn->stream); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY; + } + tevent_req_set_callback(subreq, dcesrv_read_fragment_done, conn); + + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS dcesrv_call_dispatch_local(struct dcesrv_call_state *call) +{ + NTSTATUS status; + struct ndr_pull *pull = NULL; + struct ndr_push *push = NULL; + struct data_blob_list_item *rep = NULL; + + pull = ndr_pull_init_blob(&call->pkt.u.request.stub_and_verifier, + call); + if (pull == NULL) { + return NT_STATUS_NO_MEMORY; + } + + pull->flags |= LIBNDR_FLAG_REF_ALLOC; + + call->ndr_pull = pull; + + /* unravel the NDR for the packet */ + status = call->context->iface->ndr_pull(call, call, pull, &call->r); + if (!NT_STATUS_IS_OK(status)) { + DBG_INFO("DCE/RPC fault in call %s:%02X - %s\n", + call->context->iface->name, + call->pkt.u.request.opnum, + dcerpc_errstr(call, call->fault_code)); + return dcerpc_fault_to_nt_status(call->fault_code); + } + + status = call->context->iface->local(call, call, call->r); + if (!NT_STATUS_IS_OK(status)) { + DBG_INFO("DCE/RPC fault in call %s:%02X - %s\n", + call->context->iface->name, + call->pkt.u.request.opnum, + dcerpc_errstr(call, call->fault_code)); + return dcerpc_fault_to_nt_status(call->fault_code); + } + + /* This can never go async for now! */ + SMB_ASSERT(!(call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC)); + + /* call the reply function */ + status = call->context->iface->reply(call, call, call->r); + if (!NT_STATUS_IS_OK(status)) { + DBG_INFO("DCE/RPC fault in call %s:%02X - %s\n", + call->context->iface->name, + call->pkt.u.request.opnum, + dcerpc_errstr(call, call->fault_code)); + return dcerpc_fault_to_nt_status(call->fault_code); + } + + push = ndr_push_init_ctx(call); + if (push == NULL) { + return NT_STATUS_NO_MEMORY; + } + + push->ptr_count = call->ndr_pull->ptr_count; + + status = call->context->iface->ndr_push(call, call, push, call->r); + if (!NT_STATUS_IS_OK(status)) { + DBG_INFO("DCE/RPC fault in call %s:%02X - %s\n", + call->context->iface->name, + call->pkt.u.request.opnum, + dcerpc_errstr(call, call->fault_code)); + return dcerpc_fault_to_nt_status(call->fault_code); + } + + rep = talloc_zero(call, struct data_blob_list_item); + if (rep == NULL) { + return NT_STATUS_NO_MEMORY; + } + + rep->blob = ndr_push_blob(push); + DLIST_ADD_END(call->replies, rep); + + return NT_STATUS_OK; +} diff --git a/librpc/rpc/dcesrv_core.h b/librpc/rpc/dcesrv_core.h new file mode 100644 index 0000000..3758c8d --- /dev/null +++ b/librpc/rpc/dcesrv_core.h @@ -0,0 +1,715 @@ +/* + Unix SMB/CIFS implementation. + + server side dcerpc defines + + Copyright (C) Andrew Tridgell 2003-2005 + Copyright (C) Stefan (metze) Metzmacher 2004-2005 + Copyright (C) Samuel Cabrero <scabrero@samba.org> 2019 + + 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/>. +*/ + +#ifndef _LIBRPC_RPC_DCESRV_CORE_H_ +#define _LIBRPC_RPC_DCESRV_CORE_H_ + +#include "librpc/rpc/rpc_common.h" +#include "librpc/ndr/libndr.h" +#include "librpc/gen_ndr/security.h" + +/* modules can use the following to determine if the interface has changed + * please increment the version number after each interface change + * with a comment and maybe update struct dcesrv_critical_sizes. + */ +/* version 1 - initial version - metze */ +#define DCERPC_MODULE_VERSION 1 + +struct dcesrv_connection; +struct dcesrv_call_state; +struct dcesrv_auth; +struct dcesrv_connection_context; +struct dcesrv_iface_state; +struct cli_credentials; + +struct dcesrv_interface { + const char *name; + struct ndr_syntax_id syntax_id; + + /* this function is called when the client binds to this interface */ + NTSTATUS (*bind)(struct dcesrv_connection_context *, const struct dcesrv_interface *); + + /* this function is called when the client disconnects the endpoint */ + void (*unbind)(struct dcesrv_connection_context *, const struct dcesrv_interface *); + + /* the ndr_pull function for the chosen interface. + */ + NTSTATUS (*ndr_pull)(struct dcesrv_call_state *, TALLOC_CTX *, struct ndr_pull *, void **); + + /* the dispatch function for the chosen interface. + */ + NTSTATUS (*dispatch)(struct dcesrv_call_state *, TALLOC_CTX *, void *); + + /* the reply function for the chosen interface. + */ + NTSTATUS (*reply)(struct dcesrv_call_state *, TALLOC_CTX *, void *); + + /* the ndr_push function for the chosen interface. + */ + NTSTATUS (*ndr_push)(struct dcesrv_call_state *, TALLOC_CTX *, struct ndr_push *, const void *); + + /* the local dispatch function for the chosen interface. + */ + NTSTATUS (*local)(struct dcesrv_call_state *, TALLOC_CTX *, void *); + + /* for any private use by the interface code */ + const void *private_data; + + uint64_t flags; +}; + +#define DCESRV_INTERFACE_FLAGS_HANDLES_NOT_USED 0x00000001 + +enum dcesrv_call_list { + DCESRV_LIST_NONE, + DCESRV_LIST_CALL_LIST, + DCESRV_LIST_FRAGMENTED_CALL_LIST, + DCESRV_LIST_PENDING_CALL_LIST +}; + +struct data_blob_list_item { + struct data_blob_list_item *prev,*next; + DATA_BLOB blob; +}; + +/* the state of an ongoing dcerpc call */ +struct dcesrv_call_state { + struct dcesrv_call_state *next, *prev; + struct dcesrv_auth *auth_state; + struct dcesrv_connection *conn; + struct dcesrv_connection_context *context; + struct ncacn_packet pkt; + + /* + * Used during async bind/alter_context. + */ + struct ncacn_packet ack_pkt; + + /* + which list this request is in, if any + */ + enum dcesrv_call_list list; + + /* the backend can mark the call + * with DCESRV_CALL_STATE_FLAG_ASYNC + * that will cause the frontend to not touch r->out + * and skip the reply + * + * this is only allowed to the backend when DCESRV_CALL_STATE_FLAG_MAY_ASYNC + * is already set by the frontend + * + * the backend then needs to call dcesrv_reply() when it's + * ready to send the reply + */ +#define DCESRV_CALL_STATE_FLAG_ASYNC (1<<0) +#define DCESRV_CALL_STATE_FLAG_MAY_ASYNC (1<<1) +#define DCESRV_CALL_STATE_FLAG_MULTIPLEXED (1<<3) +#define DCESRV_CALL_STATE_FLAG_PROCESS_PENDING_CALL (1<<4) +#define DCESRV_CALL_STATE_FLAG_WINBIND_OFF (1 << 5) + uint32_t state_flags; + + /* the time the request arrived in the server */ + struct timeval time; + + /* the backend can use this event context for async replies */ + struct tevent_context *event_ctx; + + /* this is the pointer to the allocated function struct */ + void *r; + + /* + * that's the ndr pull context used in dcesrv_request() + * needed by dcesrv_reply() to carry over information + * for full pointer support. + */ + struct ndr_pull *ndr_pull; + + DATA_BLOB input; + + struct data_blob_list_item *replies; + + /* this is used by the boilerplate code to generate DCERPC faults */ + uint32_t fault_code; + + /* the reason why we terminate the connection after sending a response */ + const char *terminate_reason; + + /* temporary auth_info fields */ + struct dcerpc_auth in_auth_info; + struct dcerpc_auth _out_auth_info; + struct dcerpc_auth *out_auth_info; + + /* + * Optional subreq for pending calls, + * will be used to call tevent_req_cancel() + * if the connection terminates, + * we got an ORPHANED PDU + * or got a CO_CANCEL PDU + */ + bool got_disconnect; + bool got_orphaned; + bool got_co_cancel; + struct tevent_req *subreq; +}; + +/* +* DCERPC Handles +* -------------- +* The various handles that are used in the RPC servers should be +* created and fetch using the dcesrv_handle_* functions. +* +* Use +* dcesrv_handle_create(struct dcesrv_call_state \*, uint8 handle_type) +* to obtain a new handle of the specified type. Handle types are +* unique within each pipe. +* +* The handle can later be fetched again using: +* +* struct dcesrv_handle *dcesrv_handle_lookup( +* struct dcesrv_call_state *dce_call, +* struct policy_handle *p, +* uint8 handle_type) +* +* and destroyed by: +* +* TALLOC_FREE(struct dcesrv_handle *). +* +* User data should be stored in the 'data' member of the dcesrv_handle +* struct. +*/ + +#define DCESRV_HANDLE_ANY 255 + +/* a dcerpc handle in internal format */ +struct dcesrv_handle { + struct dcesrv_handle *next, *prev; + struct dcesrv_assoc_group *assoc_group; + struct policy_handle wire_handle; + struct dom_sid sid; + enum dcerpc_AuthLevel min_auth_level; + const struct dcesrv_interface *iface; + void *data; +}; + +/* hold the authentication state information */ +struct dcesrv_auth { + struct dcesrv_auth *prev, *next; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + uint32_t auth_context_id; + struct gensec_security *gensec_security; + struct auth_session_info *session_info; + NTSTATUS (*session_key_fn)(struct dcesrv_auth *, DATA_BLOB *session_key); + bool auth_started; + bool auth_finished; + bool auth_audited; + bool auth_invalid; +}; + +struct dcesrv_connection_context { + struct dcesrv_connection_context *next, *prev; + uint16_t context_id; + + /* the connection this is on */ + struct dcesrv_connection *conn; + + /* the ndr function table for the chosen interface */ + const struct dcesrv_interface *iface; + + /* + * the minimum required auth level for this interface + */ + enum dcerpc_AuthLevel min_auth_level; + bool allow_connect; + + /* the negotiated transfer syntax */ + struct ndr_syntax_id transfer_syntax; + bool ndr64; +}; + + +/* the state associated with a dcerpc server connection */ +struct dcesrv_connection { + /* for the broken_connections DLIST */ + struct dcesrv_connection *prev, *next; + + /* the top level context for this server */ + struct dcesrv_context *dce_ctx; + + /* the endpoint that was opened */ + const struct dcesrv_endpoint *endpoint; + + /* a list of established context_ids */ + struct dcesrv_connection_context *contexts; + + /* the state of the current incoming call fragments */ + struct dcesrv_call_state *incoming_fragmented_call_list; + + /* the state of the async pending calls */ + struct dcesrv_call_state *pending_call_list; + + /* the state of the current outgoing calls */ + struct dcesrv_call_state *call_list; + + /* the maximum size the client wants to receive */ + uint16_t max_recv_frag; + uint16_t max_xmit_frag; + + DATA_BLOB partial_input; + + /* the event_context that will be used for this connection */ + struct tevent_context *event_ctx; + + /* is this connection pending termination? If so, why? */ + const char *terminate; + + const char *packet_log_dir; + + /* this is the default state_flags for dcesrv_call_state structs */ + uint32_t state_flags; + + struct { + void *private_data; + void (*report_output_data)(struct dcesrv_connection *); + void (*terminate_connection)(struct dcesrv_connection *, + const char *); + } transport; + + struct tstream_context *stream; + struct tevent_queue *send_queue; + + const struct tsocket_address *local_address; + const struct tsocket_address *remote_address; + + /* the current authentication state */ + struct dcesrv_auth *default_auth_state; + size_t max_auth_states; + struct dcesrv_auth *auth_states; + bool got_explicit_auth_level_connect; + struct dcesrv_auth *default_auth_level_connect; + bool client_hdr_signing; + bool support_hdr_signing; + bool negotiated_hdr_signing; + + /* + * remember which pdu types are allowed + */ + bool allow_bind; + bool allow_alter; + + /* the association group the connection belongs to */ + struct dcesrv_assoc_group *assoc_group; + + /* The maximum total payload of reassembled request pdus */ + size_t max_total_request_size; + + /* + * Our preferred transfer syntax. + */ + const struct ndr_syntax_id *preferred_transfer; + + /* + * This is used to block the connection during + * pending authentication. + */ + struct tevent_req *(*wait_send)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data); + NTSTATUS (*wait_recv)(struct tevent_req *req); + void *wait_private; +}; + + +struct dcesrv_endpoint_server { + /* this is the name of the endpoint server */ + const char *name; + + /* true if the endpoint server has been initialized */ + bool initialized; + + /* this function should register endpoints and some other setup stuff, + * it is called when the dcesrv_context gets initialized. + */ + NTSTATUS (*init_server)(struct dcesrv_context *, const struct dcesrv_endpoint_server *); + + /* this function should cleanup endpoint server state and unregister + * the endpoint server from dcesrv_context */ + NTSTATUS (*shutdown_server)(struct dcesrv_context *, const struct dcesrv_endpoint_server *); + + /* this function can be used by other endpoint servers to + * ask for a dcesrv_interface implementation + * - iface must be reference to an already existing struct ! + */ + bool (*interface_by_uuid)(struct dcesrv_interface *iface, const struct GUID *, uint32_t); + + /* this function can be used by other endpoint servers to + * ask for a dcesrv_interface implementation + * - iface must be reference to an already existing struct ! + */ + bool (*interface_by_name)(struct dcesrv_interface *iface, const char *); +}; + + +/* one association groups */ +struct dcesrv_assoc_group { + /* the wire id */ + uint32_t id; + + /* The transport this is valid on */ + enum dcerpc_transport_t transport; + + /* list of handles in this association group */ + struct dcesrv_handle *handles; + + /* + * list of iface states per assoc/conn + */ + struct dcesrv_iface_state *iface_states; + + /* parent context */ + struct dcesrv_context *dce_ctx; + + /* the negotiated bind time features */ + uint16_t bind_time_features; +}; + +struct dcesrv_context_callbacks { + struct { + void (*successful_authz)( + struct dcesrv_call_state *call, void *private_data); + void *private_data; + } log; + struct { + NTSTATUS (*gensec_prepare)( + TALLOC_CTX *mem_ctx, + struct dcesrv_call_state *call, + struct gensec_security **out, + void *private_data); + void *private_data; + void (*become_root)(void); + void (*unbecome_root)(void); + } auth; + struct { + NTSTATUS (*find)( + struct dcesrv_call_state *call, void *private_data); + void *private_data; + } assoc_group; +}; + +/* server-wide context information for the dcerpc server */ +struct dcesrv_context { + /* + * The euid at startup time. + * + * This is required for DCERPC_AUTH_TYPE_NCALRPC_AS_SYSTEM + */ + uid_t initial_euid; + + /* the list of endpoints that have registered + * by the configured endpoint servers + */ + struct dcesrv_endpoint { + struct dcesrv_endpoint *next, *prev; + /* the type and location of the endpoint */ + struct dcerpc_binding *ep_description; + /* the secondary endpoint description for the BIND_ACK */ + struct dcerpc_binding *ep_2nd_description; + /* the security descriptor for smb named pipes */ + struct security_descriptor *sd; + /* the list of interfaces available on this endpoint */ + struct dcesrv_if_list { + struct dcesrv_if_list *next, *prev; + struct dcesrv_interface *iface; + } *interface_list; + + /* + * Should this service be run in a single process (so far only + * NETLOGON is not run in a single process) + */ + bool use_single_process; + } *endpoint_list; + + /* + * registered auth_type/principals + * for dcesrv_mgmt_inq_princ_name() + */ + struct dcesrv_ctx_principal { + struct dcesrv_ctx_principal *next, *prev; + enum dcerpc_AuthType auth_type; + const char *principal_name; + } *principal_list; + + /* loadparm context to use for this connection */ + struct loadparm_context *lp_ctx; + + struct idr_context *assoc_groups_idr; + uint32_t assoc_groups_num; + + struct dcesrv_connection *broken_connections; + + /* + * Our preferred transfer syntax. + */ + const struct ndr_syntax_id *preferred_transfer; + + struct dcesrv_context_callbacks *callbacks; +}; + +/* this structure is used by modules to determine the size of some critical types */ +struct dcesrv_critical_sizes { + int interface_version; + int sizeof_dcesrv_context; + int sizeof_dcesrv_endpoint; + int sizeof_dcesrv_endpoint_server; + int sizeof_dcesrv_interface; + int sizeof_dcesrv_if_list; + int sizeof_dcesrv_connection; + int sizeof_dcesrv_call_state; + int sizeof_dcesrv_auth; + int sizeof_dcesrv_handle; +}; + +NTSTATUS dcesrv_auth_type_principal_register(struct dcesrv_context *dce_ctx, + enum dcerpc_AuthType auth_type, + const char *principal_name); +const char *dcesrv_auth_type_principal_find(struct dcesrv_context *dce_ctx, + enum dcerpc_AuthType auth_type); +NTSTATUS dcesrv_register_default_auth_types(struct dcesrv_context *dce_ctx, + const char *principal); +NTSTATUS dcesrv_register_default_auth_types_machine_principal(struct dcesrv_context *dce_ctx); +NTSTATUS dcesrv_interface_register(struct dcesrv_context *dce_ctx, + const char *ep_name, + const char *ncacn_np_secondary_endpoint, + const struct dcesrv_interface *iface, + const struct security_descriptor *sd); +NTSTATUS dcesrv_interface_register_b(struct dcesrv_context *dce_ctx, + struct dcerpc_binding *binding, + struct dcerpc_binding *binding2, + const struct dcesrv_interface *iface, + const struct security_descriptor *sd); +NTSTATUS dcerpc_register_ep_server(const struct dcesrv_endpoint_server *ep_server); +NTSTATUS dcesrv_init_ep_servers(struct dcesrv_context *dce_ctx, + const char **ep_servers); +NTSTATUS dcesrv_init_registered_ep_servers(struct dcesrv_context *dce_ctx); +NTSTATUS dcesrv_shutdown_registered_ep_servers(struct dcesrv_context *dce_ctx); +NTSTATUS dcesrv_init_ep_server(struct dcesrv_context *dce_ctx, + const char *ep_server_name); +NTSTATUS dcesrv_shutdown_ep_server(struct dcesrv_context *dce_ctx, + const char *name); +const struct dcesrv_endpoint_server *dcesrv_ep_server_byname(const char *name); + +NTSTATUS dcesrv_init_context(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct dcesrv_context_callbacks *cb, + struct dcesrv_context **_dce_ctx); +void dcesrv_context_set_callbacks( + struct dcesrv_context *dce_ctx, + struct dcesrv_context_callbacks *cb); + +/* + * Use dcesrv_async_reply() in async code + */ +NTSTATUS dcesrv_reply(struct dcesrv_call_state *call); +void _dcesrv_async_reply(struct dcesrv_call_state *call, + const char *func, + const char *location); +#define dcesrv_async_reply(__call) \ + _dcesrv_async_reply(__call, __func__, __location__) + +struct dcesrv_handle *dcesrv_handle_create(struct dcesrv_call_state *call, + uint8_t handle_type); + +struct dcesrv_handle *dcesrv_handle_lookup(struct dcesrv_call_state *call, + const struct policy_handle *p, + uint8_t handle_type); + +const struct tsocket_address *dcesrv_connection_get_local_address(struct dcesrv_connection *conn); +const struct tsocket_address *dcesrv_connection_get_remote_address(struct dcesrv_connection *conn); + +/* + * Fetch the authentication session key if available. + * + * This is the key generated by a gensec authentication. + */ +NTSTATUS dcesrv_auth_session_key(struct dcesrv_call_state *call, + DATA_BLOB *session_key); + +/* + * Fetch the transport session key if available. + * Typically this is the SMB session key + * or a fixed key for local transports. + * + * The key is always truncated to 16 bytes. +*/ +NTSTATUS dcesrv_transport_session_key(struct dcesrv_call_state *call, + DATA_BLOB *session_key); + +/* a useful macro for generating a RPC fault in the backend code */ +#define DCESRV_FAULT(code) do { \ + dce_call->fault_code = code; \ + return r->out.result; \ +} while(0) + +/* a useful macro for generating a RPC fault in the backend code */ +#define DCESRV_FAULT_VOID(code) do { \ + dce_call->fault_code = code; \ + return; \ +} while(0) + +/* a useful macro for checking the validity of a dcerpc policy handle + and giving the right fault code if invalid */ +#define DCESRV_CHECK_HANDLE(h) do {if (!(h)) DCESRV_FAULT(DCERPC_FAULT_CONTEXT_MISMATCH); } while (0) + +/* this checks for a valid policy handle, and gives a fault if an + invalid handle or retval if the handle is of the + wrong type */ +#define DCESRV_PULL_HANDLE_RETVAL(h, inhandle, t, retval) do { \ + (h) = dcesrv_handle_lookup(dce_call, (inhandle), DCESRV_HANDLE_ANY); \ + DCESRV_CHECK_HANDLE(h); \ + if ((t) != DCESRV_HANDLE_ANY && (h)->wire_handle.handle_type != (t)) { \ + return retval; \ + } \ +} while (0) + +/* this checks for a valid policy handle and gives a dcerpc fault + if its the wrong type of handle */ +#define DCESRV_PULL_HANDLE_FAULT(h, inhandle, t) do { \ + (h) = dcesrv_handle_lookup(dce_call, (inhandle), t); \ + DCESRV_CHECK_HANDLE(h); \ +} while (0) + +#define DCESRV_PULL_HANDLE(h, inhandle, t) DCESRV_PULL_HANDLE_RETVAL(h, inhandle, t, NT_STATUS_INVALID_HANDLE) +#define DCESRV_PULL_HANDLE_WERR(h, inhandle, t) DCESRV_PULL_HANDLE_RETVAL(h, inhandle, t, WERR_INVALID_HANDLE) + +/** + * retrieve credentials from a dce_call + */ +_PUBLIC_ struct cli_credentials *dcesrv_call_credentials(struct dcesrv_call_state *dce_call); + +/** + * returns true if this is an authenticated call + */ +_PUBLIC_ bool dcesrv_call_authenticated(struct dcesrv_call_state *dce_call); + +/** + * retrieve account_name for a dce_call + */ +_PUBLIC_ const char *dcesrv_call_account_name(struct dcesrv_call_state *dce_call); + +/** + * retrieve session_info from a dce_call + */ +_PUBLIC_ struct auth_session_info *dcesrv_call_session_info(struct dcesrv_call_state *dce_call); + +/** + * retrieve auth type/level from a dce_call + */ +_PUBLIC_ void dcesrv_call_auth_info(struct dcesrv_call_state *dce_call, + enum dcerpc_AuthType *auth_type, + enum dcerpc_AuthLevel *auth_level); + +_PUBLIC_ NTSTATUS dcesrv_interface_bind_require_integrity(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface); +_PUBLIC_ NTSTATUS dcesrv_interface_bind_require_privacy(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface); +_PUBLIC_ NTSTATUS dcesrv_interface_bind_reject_connect(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface); +_PUBLIC_ NTSTATUS dcesrv_interface_bind_allow_connect(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface); + +_PUBLIC_ NTSTATUS _dcesrv_iface_state_store_assoc( + struct dcesrv_call_state *call, + uint64_t magic, + void *ptr, + const char *location); +#define dcesrv_iface_state_store_assoc(call, magic, ptr) \ + _dcesrv_iface_state_store_assoc((call), (magic), (ptr), \ + __location__) +_PUBLIC_ void *_dcesrv_iface_state_find_assoc( + struct dcesrv_call_state *call, + uint64_t magic); +#define dcesrv_iface_state_find_assoc(call, magic, _type) \ + talloc_get_type( \ + _dcesrv_iface_state_find_assoc((call), (magic)), \ + _type) + +_PUBLIC_ NTSTATUS _dcesrv_iface_state_store_conn( + struct dcesrv_call_state *call, + uint64_t magic, + void *_pptr, + const char *location); +#define dcesrv_iface_state_store_conn(call, magic, ptr) \ + _dcesrv_iface_state_store_conn((call), (magic), (ptr), \ + __location__) +_PUBLIC_ void *_dcesrv_iface_state_find_conn( + struct dcesrv_call_state *call, + uint64_t magic); +#define dcesrv_iface_state_find_conn(call, magic, _type) \ + talloc_get_type( \ + _dcesrv_iface_state_find_conn((call), (magic)), \ + _type) + +_PUBLIC_ void dcesrv_cleanup_broken_connections(struct dcesrv_context *dce_ctx); + +_PUBLIC_ NTSTATUS dcesrv_endpoint_connect(struct dcesrv_context *dce_ctx, + TALLOC_CTX *mem_ctx, + const struct dcesrv_endpoint *ep, + struct auth_session_info *session_info, + struct tevent_context *event_ctx, + uint32_t state_flags, + struct dcesrv_connection **_p); +_PUBLIC_ NTSTATUS dcesrv_find_endpoint(struct dcesrv_context *dce_ctx, + const struct dcerpc_binding *ep_description, + struct dcesrv_endpoint **_out); + +_PUBLIC_ void dcesrv_terminate_connection(struct dcesrv_connection *dce_conn, + const char *reason); +_PUBLIC_ void dcesrv_sock_report_output_data(struct dcesrv_connection *dce_conn); + +_PUBLIC_ NTSTATUS dcesrv_connection_loop_start(struct dcesrv_connection *conn); + +_PUBLIC_ void dcesrv_loop_next_packet( + struct dcesrv_connection *dce_conn, + struct ncacn_packet *pkt, + DATA_BLOB buffer); + +_PUBLIC_ NTSTATUS dcesrv_call_dispatch_local(struct dcesrv_call_state *call); + +_PUBLIC_ const struct dcesrv_interface *find_interface_by_syntax_id( + const struct dcesrv_endpoint *endpoint, + const struct ndr_syntax_id *interface); + +void _dcesrv_save_ndr_fuzz_seed(DATA_BLOB call_blob, + struct dcesrv_call_state *call, + ndr_flags_type flags); + +#if DEVELOPER +#define dcesrv_save_ndr_fuzz_seed(stub, call, flags) \ + _dcesrv_save_ndr_fuzz_seed(stub, call, flags) +#else +#define dcesrv_save_ndr_fuzz_seed(stub, call, flags) \ + /* */ +#endif + + +#endif /* _LIBRPC_RPC_DCESRV_CORE_H_ */ diff --git a/librpc/rpc/dcesrv_handles.c b/librpc/rpc/dcesrv_handles.c new file mode 100644 index 0000000..b8719d8 --- /dev/null +++ b/librpc/rpc/dcesrv_handles.c @@ -0,0 +1,372 @@ +/* + Unix SMB/CIFS implementation. + + server side dcerpc handle code + + Copyright (C) Andrew Tridgell 2003 + + 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 "lib/util/dlinklist.h" +#include "rpc_server/dcerpc_server.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/auth.h" + +/* + destroy a rpc handle +*/ +static int dcesrv_handle_destructor(struct dcesrv_handle *h) +{ + DLIST_REMOVE(h->assoc_group->handles, h); + return 0; +} + + +/* + allocate a new rpc handle +*/ +_PUBLIC_ +struct dcesrv_handle *dcesrv_handle_create(struct dcesrv_call_state *call, + uint8_t handle_type) +{ + struct dcesrv_connection_context *context = call->context; + struct auth_session_info *session_info = + dcesrv_call_session_info(call); + struct dcesrv_handle *h; + struct dom_sid *sid; + + /* + * For simplicity, ensure we abort here for an interface that + * has no handles (programmer error) + */ + SMB_ASSERT((context->iface->flags & DCESRV_INTERFACE_FLAGS_HANDLES_NOT_USED) == 0); + + sid = &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]; + + h = talloc_zero(context->conn->assoc_group, struct dcesrv_handle); + if (!h) { + return NULL; + } + h->data = NULL; + sid_copy(&h->sid, sid); + h->min_auth_level = call->auth_state->auth_level; + h->assoc_group = context->conn->assoc_group; + h->iface = context->iface; + h->wire_handle.handle_type = handle_type; + h->wire_handle.uuid = GUID_random(); + + DLIST_ADD(context->conn->assoc_group->handles, h); + + talloc_set_destructor(h, dcesrv_handle_destructor); + + return h; +} + +/** + find an internal handle given a wire handle. If the wire handle is NULL then + allocate a new handle +*/ + +_PUBLIC_ +struct dcesrv_handle *dcesrv_handle_lookup(struct dcesrv_call_state *call, + const struct policy_handle *p, + uint8_t handle_type) +{ + struct dcesrv_connection_context *context = call->context; + struct auth_session_info *session_info = + dcesrv_call_session_info(call); + struct dcesrv_handle *h; + struct dom_sid *sid; + + /* + * For simplicity, ensure we abort here for an interface that + * has no handles (programmer error) + */ + SMB_ASSERT((context->iface->flags & DCESRV_INTERFACE_FLAGS_HANDLES_NOT_USED) == 0); + + sid = &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]; + + if (ndr_policy_handle_empty(p)) { + /* TODO: we should probably return a NULL handle here */ + return dcesrv_handle_create(call, handle_type); + } + + if (handle_type != DCESRV_HANDLE_ANY && + p->handle_type != handle_type) { + DBG_WARNING("client gave us the wrong handle type " + "(%"PRIu32" should be %"PRIu8")\n", + p->handle_type, + handle_type); + return NULL; + } + + for (h=context->conn->assoc_group->handles; h; h=h->next) { + if (h->wire_handle.handle_type == p->handle_type && + GUID_equal(&p->uuid, &h->wire_handle.uuid)) { + break; + } + } + + if (h == NULL) { + /* not found */ + return NULL; + } + + if (!dom_sid_equal(&h->sid, sid)) { + struct dom_sid_buf buf1, buf2; + DBG_ERR("Attempt to use invalid sid %s - %s\n", + dom_sid_str_buf(&h->sid, &buf1), + dom_sid_str_buf(sid, &buf2)); + return NULL; + } + + if (call->auth_state->auth_level < h->min_auth_level) { + DBG_ERR("Attempt to use invalid auth_level %u < %u\n", + call->auth_state->auth_level, + h->min_auth_level); + return NULL; + } + + if (h->iface != context->iface) { + DBG_ERR("Attempt to use invalid iface\n"); + return NULL; + } + + return h; +} + +struct dcesrv_iface_state { + struct dcesrv_iface_state *prev, *next; + struct dcesrv_assoc_group *assoc; + const struct dcesrv_interface *iface; + struct dom_sid owner; + const struct dcesrv_connection *conn; + const struct dcesrv_auth *auth; + const struct dcesrv_connection_context *pres; + uint64_t magic; + void *ptr; + const char *location; +}; + +static int dcesrv_iface_state_destructor(struct dcesrv_iface_state *istate) +{ + DLIST_REMOVE(istate->assoc->iface_states, istate); + return 0; +} + +static void *dcesrv_iface_state_find(struct dcesrv_assoc_group *assoc, + const struct dcesrv_interface *iface, + const struct dom_sid *owner, + const struct dcesrv_connection *conn, + const struct dcesrv_auth *auth, + const struct dcesrv_connection_context *pres, + uint64_t magic, + const void *ptr) +{ + struct dcesrv_iface_state *cur = NULL; + + for (cur = assoc->iface_states; cur != NULL; cur = cur->next) { + bool match; + + SMB_ASSERT(cur->assoc == assoc); + + if (cur->ptr == ptr) { + return cur->ptr; + } + + if (cur->iface != iface) { + continue; + } + + match = dom_sid_equal(&cur->owner, owner); + if (!match) { + continue; + } + + if (cur->conn != conn) { + continue; + } + + if (cur->auth != auth) { + continue; + } + + if (cur->pres != pres) { + continue; + } + + if (cur->magic != magic) { + continue; + } + + return cur->ptr; + } + + return NULL; +} + +static NTSTATUS dcesrv_iface_state_store(struct dcesrv_assoc_group *assoc, + const struct dcesrv_interface *iface, + const struct dom_sid *owner, + const struct dcesrv_connection *conn, + const struct dcesrv_auth *auth, + const struct dcesrv_connection_context *pres, + uint64_t magic, + TALLOC_CTX *mem_ctx, + void *ptr, + const char *location) +{ + struct dcesrv_iface_state *istate = NULL; + void *optr = NULL; + + optr = dcesrv_iface_state_find(assoc, + iface, + owner, + conn, + auth, + pres, + magic, + ptr); + if (optr != NULL) { + return NT_STATUS_OBJECTID_EXISTS; + } + + istate = talloc_zero(ptr, struct dcesrv_iface_state); + if (istate == NULL) { + return NT_STATUS_NO_MEMORY; + } + + *istate = (struct dcesrv_iface_state) { + .assoc = assoc, + .iface = iface, + .owner = *owner, + .conn = conn, + .auth = auth, + .pres = pres, + .magic = magic, + .location = location, + }; + + istate->ptr = talloc_steal(mem_ctx, ptr); + + talloc_set_destructor(istate, dcesrv_iface_state_destructor); + + DLIST_ADD_END(assoc->iface_states, istate); + + return NT_STATUS_OK; +} + +NTSTATUS _dcesrv_iface_state_store_assoc(struct dcesrv_call_state *call, + uint64_t magic, + void *ptr, + const char *location) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(call); + const struct dom_sid *owner = + &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]; + NTSTATUS status; + + status = dcesrv_iface_state_store(call->conn->assoc_group, + call->context->iface, + owner, + NULL, /* conn */ + NULL, /* auth */ + NULL, /* pres */ + magic, + call->conn->assoc_group, /* mem_ctx */ + ptr, + location); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +void *_dcesrv_iface_state_find_assoc(struct dcesrv_call_state *call, uint64_t magic) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(call); + const struct dom_sid *owner = + &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]; + void *ptr = NULL; + + ptr = dcesrv_iface_state_find(call->conn->assoc_group, + call->context->iface, + owner, + NULL, /* conn */ + NULL, /* auth */ + NULL, /* pres */ + magic, + NULL); /* ptr */ + if (ptr == NULL) { + return NULL; + } + + return ptr; +} + +NTSTATUS _dcesrv_iface_state_store_conn(struct dcesrv_call_state *call, + uint64_t magic, + void *ptr, + const char *location) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(call); + const struct dom_sid *owner = + &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]; + NTSTATUS status; + + status = dcesrv_iface_state_store(call->conn->assoc_group, + call->context->iface, + owner, + call->conn, + call->auth_state, + call->context, + magic, + call->conn, /* mem_ctx */ + ptr, + location); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +void *_dcesrv_iface_state_find_conn(struct dcesrv_call_state *call, uint64_t magic) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(call); + const struct dom_sid *owner = + &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]; + void *ptr = NULL; + + ptr = dcesrv_iface_state_find(call->conn->assoc_group, + call->context->iface, + owner, + call->conn, + call->auth_state, + call->context, + magic, + NULL); /* ptr */ + if (ptr == NULL) { + return NULL; + } + + return ptr; +} diff --git a/librpc/rpc/dcesrv_mgmt.c b/librpc/rpc/dcesrv_mgmt.c new file mode 100644 index 0000000..8f00e91 --- /dev/null +++ b/librpc/rpc/dcesrv_mgmt.c @@ -0,0 +1,169 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the mgmt pipe + + Copyright (C) Jelmer Vernooij 2006 + + 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/rpc/dcesrv_core.h" +#include "librpc/rpc/dcesrv_core_proto.h" +#include "librpc/gen_ndr/ndr_mgmt.h" + +#define DCESRV_INTERFACE_MGMT_BIND(context, iface) \ + dcesrv_interface_mgmt_bind(context, iface) +/* + * This #define allows the mgmt interface to accept invalid + * association groups, because association groups are to coordinate + * handles, and handles are not used in mgmt. This in turn avoids + * the need to coordinate these across multiple possible NETLOGON + * processes, as an mgmt interface is added to each + */ + +#define DCESRV_INTERFACE_MGMT_FLAGS DCESRV_INTERFACE_FLAGS_HANDLES_NOT_USED + +static NTSTATUS dcesrv_interface_mgmt_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + return dcesrv_interface_bind_allow_connect(context, iface); +} + +/* + mgmt_inq_if_ids +*/ +static WERROR dcesrv_mgmt_inq_if_ids(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct mgmt_inq_if_ids *r) +{ + const struct dcesrv_endpoint *ep = dce_call->conn->endpoint; + struct dcesrv_if_list *l = NULL; + struct rpc_if_id_vector_t *vector = NULL; + + vector = talloc(mem_ctx, struct rpc_if_id_vector_t); + if (vector == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + vector->count = 0; + vector->if_id = NULL; + + for (l = ep->interface_list; l; l = l->next) { + bool filter; + + filter = ndr_syntax_id_equal(&l->iface->syntax_id, &ndr_table_mgmt.syntax_id); + if (filter) { + /* + * We should not return the mgmt syntax itself here + */ + continue; + } + + vector->count++; + vector->if_id = talloc_realloc(vector, vector->if_id, struct ndr_syntax_id_p, vector->count); + if (vector->if_id == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + vector->if_id[vector->count-1].id = &l->iface->syntax_id; + } + + *r->out.if_id_vector = vector; + return WERR_OK; +} + + +/* + mgmt_inq_stats +*/ +static WERROR dcesrv_mgmt_inq_stats(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct mgmt_inq_stats *r) +{ + if (r->in.max_count != MGMT_STATS_ARRAY_MAX_SIZE) + return WERR_NOT_SUPPORTED; + + r->out.statistics->statistics = talloc_zero_array(mem_ctx, + uint32_t, + r->in.max_count); + if (r->out.statistics->statistics == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + r->out.statistics->count = r->in.max_count; + /* FIXME */ + r->out.statistics->statistics[MGMT_STATS_CALLS_IN] = 0; + r->out.statistics->statistics[MGMT_STATS_CALLS_OUT] = 0; + r->out.statistics->statistics[MGMT_STATS_PKTS_IN] = 0; + r->out.statistics->statistics[MGMT_STATS_PKTS_OUT] = 0; + + return WERR_OK; +} + + +/* + mgmt_is_server_listening +*/ +static uint32_t dcesrv_mgmt_is_server_listening(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct mgmt_is_server_listening *r) +{ + *r->out.status = 0; + return 1; +} + + +/* + mgmt_stop_server_listening +*/ +static WERROR dcesrv_mgmt_stop_server_listening(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct mgmt_stop_server_listening *r) +{ + return WERR_ACCESS_DENIED; +} + + +/* + mgmt_inq_princ_name +*/ +static WERROR dcesrv_mgmt_inq_princ_name(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct mgmt_inq_princ_name *r) +{ + const char *principal = NULL; + + if (r->in.princ_name_size < 1) { + DCESRV_FAULT(DCERPC_FAULT_BAD_STUB_DATA); + } + + r->out.princ_name = ""; + + principal = dcesrv_auth_type_principal_find(dce_call->conn->dce_ctx, + r->in.authn_proto); + if (principal == NULL) { + return WERR_RPC_S_UNKNOWN_AUTHN_SERVICE; + } + + if (strlen(principal) + 1 > r->in.princ_name_size) { + return WERR_INSUFFICIENT_BUFFER; + } + + r->out.princ_name = principal; + return WERR_OK; +} + + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_mgmt_s.c" + +const struct dcesrv_interface *dcesrv_get_mgmt_interface(void) +{ + return &dcesrv_mgmt_interface; +} diff --git a/librpc/rpc/dcesrv_reply.c b/librpc/rpc/dcesrv_reply.c new file mode 100644 index 0000000..6d60516 --- /dev/null +++ b/librpc/rpc/dcesrv_reply.c @@ -0,0 +1,309 @@ +/* + Unix SMB/CIFS implementation. + + server side dcerpc common code + + Copyright (C) Andrew Tridgell 2003-2010 + Copyright (C) Stefan (metze) Metzmacher 2004-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 "librpc/rpc/dcesrv_core.h" +#include "librpc/rpc/dcesrv_core_proto.h" +#include "librpc/rpc/dcerpc_util.h" +#include "auth/gensec/gensec.h" +#include "lib/util/dlinklist.h" +#include "param/param.h" + +/* + move a call from an existing linked list to the specified list. This + prevents bugs where we forget to remove the call from a previous + list when moving it. + */ +static void dcesrv_call_set_list(struct dcesrv_call_state *call, + enum dcesrv_call_list list) +{ + switch (call->list) { + case DCESRV_LIST_NONE: + break; + case DCESRV_LIST_CALL_LIST: + DLIST_REMOVE(call->conn->call_list, call); + break; + case DCESRV_LIST_FRAGMENTED_CALL_LIST: + DLIST_REMOVE(call->conn->incoming_fragmented_call_list, call); + break; + case DCESRV_LIST_PENDING_CALL_LIST: + DLIST_REMOVE(call->conn->pending_call_list, call); + break; + } + call->list = list; + switch (list) { + case DCESRV_LIST_NONE: + break; + case DCESRV_LIST_CALL_LIST: + DLIST_ADD_END(call->conn->call_list, call); + break; + case DCESRV_LIST_FRAGMENTED_CALL_LIST: + DLIST_ADD_END(call->conn->incoming_fragmented_call_list, call); + break; + case DCESRV_LIST_PENDING_CALL_LIST: + DLIST_ADD_END(call->conn->pending_call_list, call); + break; + } +} + + +void dcesrv_init_hdr(struct ncacn_packet *pkt, bool bigendian) +{ + pkt->rpc_vers = 5; + pkt->rpc_vers_minor = 0; + if (bigendian) { + pkt->drep[0] = 0; + } else { + pkt->drep[0] = DCERPC_DREP_LE; + } + pkt->drep[1] = 0; + pkt->drep[2] = 0; + pkt->drep[3] = 0; +} + + +/* + return a dcerpc fault +*/ +NTSTATUS dcesrv_fault_with_flags(struct dcesrv_call_state *call, + uint32_t fault_code, + uint8_t extra_flags) +{ + struct ncacn_packet pkt; + struct data_blob_list_item *rep; + NTSTATUS status; + + if (call->conn->terminate != NULL) { + /* + * If we're already disconnecting + * we should just drop a possible + * response + */ + talloc_free(call); + return NT_STATUS_OK; + } + + /* setup a fault */ + dcesrv_init_hdr(&pkt, lpcfg_rpc_big_endian(call->conn->dce_ctx->lp_ctx)); + pkt.auth_length = 0; + pkt.call_id = call->pkt.call_id; + pkt.ptype = DCERPC_PKT_FAULT; + pkt.pfc_flags = DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST | extra_flags; + pkt.u.fault.alloc_hint = 24; + if (call->context != NULL) { + pkt.u.fault.context_id = call->context->context_id; + } else { + pkt.u.fault.context_id = 0; + } + pkt.u.fault.cancel_count = 0; + pkt.u.fault.flags = 0; + pkt.u.fault.status = fault_code; + pkt.u.fault.reserved = 0; + pkt.u.fault.error_and_verifier = data_blob_null; + + rep = talloc_zero(call, struct data_blob_list_item); + if (!rep) { + return NT_STATUS_NO_MEMORY; + } + + status = dcerpc_ncacn_push_auth(&rep->blob, call, &pkt, NULL); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + dcerpc_set_frag_length(&rep->blob, rep->blob.length); + + DLIST_ADD_END(call->replies, rep); + dcesrv_call_set_list(call, DCESRV_LIST_CALL_LIST); + + if (call->conn->call_list && call->conn->call_list->replies) { + if (call->conn->transport.report_output_data) { + call->conn->transport.report_output_data(call->conn); + } + } + + return NT_STATUS_OK; +} + +NTSTATUS dcesrv_fault(struct dcesrv_call_state *call, uint32_t fault_code) +{ + return dcesrv_fault_with_flags(call, fault_code, 0); +} + +_PUBLIC_ NTSTATUS dcesrv_reply(struct dcesrv_call_state *call) +{ + struct ndr_push *push; + NTSTATUS status; + DATA_BLOB stub; + uint32_t total_length, chunk_size; + struct dcesrv_connection_context *context = call->context; + struct dcesrv_auth *auth = call->auth_state; + size_t sig_size = 0; + + /* + * call the reply function, + * it's mostly for debug messages + * and dcesrv_fault() also checks for + * (call->conn->terminate != NULL) internally. + */ + status = context->iface->reply(call, call, call->r); + if (!NT_STATUS_IS_OK(status)) { + return dcesrv_fault(call, call->fault_code); + } + + if (call->conn->terminate != NULL) { + /* + * If we're already disconnecting + * we should just drop a possible + * response + */ + talloc_free(call); + return NT_STATUS_OK; + } + + /* form the reply NDR */ + push = ndr_push_init_ctx(call); + NT_STATUS_HAVE_NO_MEMORY(push); + + /* carry over the pointer count to the reply in case we are + using full pointer. See NDR specification for full + pointers */ + push->ptr_count = call->ndr_pull->ptr_count; + + if (lpcfg_rpc_big_endian(call->conn->dce_ctx->lp_ctx)) { + push->flags |= LIBNDR_FLAG_BIGENDIAN; + } + + if (context->ndr64) { + push->flags |= LIBNDR_FLAG_NDR64; + } + + status = context->iface->ndr_push(call, call, push, call->r); + if (!NT_STATUS_IS_OK(status)) { + return dcesrv_fault(call, call->fault_code); + } + + stub = ndr_push_blob(push); + + dcesrv_save_ndr_fuzz_seed(stub, + call, + NDR_OUT); + + total_length = stub.length; + + /* we can write a full max_recv_frag size, minus the dcerpc + request header size */ + chunk_size = call->conn->max_xmit_frag; + chunk_size -= DCERPC_REQUEST_LENGTH; + if (auth->auth_finished && auth->gensec_security != NULL) { + size_t max_payload = chunk_size; + + max_payload -= DCERPC_AUTH_TRAILER_LENGTH; + max_payload -= (max_payload % DCERPC_AUTH_PAD_ALIGNMENT); + + sig_size = gensec_sig_size(auth->gensec_security, + max_payload); + if (sig_size) { + chunk_size -= DCERPC_AUTH_TRAILER_LENGTH; + chunk_size -= sig_size; + } + } + chunk_size -= (chunk_size % DCERPC_AUTH_PAD_ALIGNMENT); + + do { + uint32_t length; + struct data_blob_list_item *rep; + struct ncacn_packet pkt; + bool ok; + + rep = talloc_zero(call, struct data_blob_list_item); + NT_STATUS_HAVE_NO_MEMORY(rep); + + length = MIN(chunk_size, stub.length); + + /* form the dcerpc response packet */ + dcesrv_init_hdr(&pkt, + lpcfg_rpc_big_endian(call->conn->dce_ctx->lp_ctx)); + pkt.auth_length = 0; + pkt.call_id = call->pkt.call_id; + pkt.ptype = DCERPC_PKT_RESPONSE; + pkt.pfc_flags = 0; + if (stub.length == total_length) { + pkt.pfc_flags |= DCERPC_PFC_FLAG_FIRST; + } + if (length == stub.length) { + pkt.pfc_flags |= DCERPC_PFC_FLAG_LAST; + } + pkt.u.response.alloc_hint = stub.length; + /* + * bug for bug, feature for feature... + * + * Windows truncates the context_id with & 0xFF, + * so we do. + */ + pkt.u.response.context_id = context->context_id & 0xFF; + pkt.u.response.cancel_count = 0; + pkt.u.response.stub_and_verifier.data = stub.data; + pkt.u.response.stub_and_verifier.length = length; + + ok = dcesrv_auth_pkt_push(call, &rep->blob, sig_size, + DCERPC_RESPONSE_LENGTH, + &pkt.u.response.stub_and_verifier, + &pkt); + if (!ok) { + return dcesrv_fault(call, DCERPC_FAULT_OTHER); + } + + dcerpc_set_frag_length(&rep->blob, rep->blob.length); + + DLIST_ADD_END(call->replies, rep); + + stub.data += length; + stub.length -= length; + } while (stub.length != 0); + + /* move the call from the pending to the finished calls list */ + dcesrv_call_set_list(call, DCESRV_LIST_CALL_LIST); + + if (call->conn->call_list && call->conn->call_list->replies) { + if (call->conn->transport.report_output_data) { + call->conn->transport.report_output_data(call->conn); + } + } + + return NT_STATUS_OK; +} + +_PUBLIC_ void _dcesrv_async_reply(struct dcesrv_call_state *call, + const char *func, + const char *location) +{ + struct dcesrv_connection *conn = call->conn; + NTSTATUS status; + + status = dcesrv_reply(call); + if (!NT_STATUS_IS_OK(status)) { + D_ERR("%s: %s: dcesrv_async_reply() failed - %s\n", + func, location, nt_errstr(status)); + dcesrv_terminate_connection(conn, nt_errstr(status)); + } +} diff --git a/librpc/rpc/rpc_common.h b/librpc/rpc/rpc_common.h new file mode 100644 index 0000000..7655710 --- /dev/null +++ b/librpc/rpc/rpc_common.h @@ -0,0 +1,412 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2010-2011 + Copyright (C) Andrew Tridgell 2010-2011 + Copyright (C) Simo Sorce 2010 + + 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/>. +*/ + +#ifndef __DEFAULT_LIBRPC_RPCCOMMON_H__ +#define __DEFAULT_LIBRPC_RPCCOMMON_H__ + +#include "lib/util/data_blob.h" + +#include "gen_ndr/dcerpc.h" +#include "lib/util/attr.h" + +#include "librpc/ndr/libndr.h" + +struct dcerpc_binding_handle; +struct GUID; +struct ndr_interface_table; +struct ndr_interface_call; +struct ndr_push; +struct ndr_pull; +struct ncacn_packet; +struct epm_floor; +struct epm_tower; +struct tevent_context; +struct tstream_context; +struct gensec_security; + +enum dcerpc_transport_t { + NCA_UNKNOWN, NCACN_NP, NCACN_IP_TCP, NCACN_IP_UDP, NCACN_VNS_IPC, + NCACN_VNS_SPP, NCACN_AT_DSP, NCADG_AT_DDP, NCALRPC, NCACN_UNIX_STREAM, + NCADG_UNIX_DGRAM, NCACN_HTTP, NCADG_IPX, NCACN_SPX, NCACN_INTERNAL }; + +/** this describes a binding to a particular transport/pipe */ +struct dcerpc_binding; + +/* dcerpc pipe flags */ +#define DCERPC_DEBUG_PRINT_IN (1<<0) +#define DCERPC_DEBUG_PRINT_OUT (1<<1) +#define DCERPC_DEBUG_PRINT_BOTH (DCERPC_DEBUG_PRINT_IN | DCERPC_DEBUG_PRINT_OUT) + +#define DCERPC_DEBUG_VALIDATE_IN (1<<2) +#define DCERPC_DEBUG_VALIDATE_OUT (1<<3) +#define DCERPC_DEBUG_VALIDATE_BOTH (DCERPC_DEBUG_VALIDATE_IN | DCERPC_DEBUG_VALIDATE_OUT) + +#define DCERPC_CONNECT (1<<4) +#define DCERPC_SIGN (1<<5) +#define DCERPC_SEAL (1<<6) + +#define DCERPC_PUSH_BIGENDIAN (1<<7) +#define DCERPC_PULL_BIGENDIAN (1<<8) + +#define DCERPC_SCHANNEL (1<<9) + +#define DCERPC_ANON_FALLBACK (1<<10) + +/* use a 128 bit session key */ +#define DCERPC_SCHANNEL_128 (1<<12) + +/* check incoming pad bytes */ +#define DCERPC_DEBUG_PAD_CHECK (1<<13) + +/* set LIBNDR_FLAG_REF_ALLOC flag when decoding NDR */ +#define DCERPC_NDR_REF_ALLOC (1<<14) + +#define DCERPC_AUTH_OPTIONS (DCERPC_SEAL|DCERPC_SIGN|DCERPC_SCHANNEL|DCERPC_AUTH_SPNEGO|DCERPC_AUTH_KRB5|DCERPC_AUTH_NTLM) + +/* select spnego auth */ +#define DCERPC_AUTH_SPNEGO (1<<15) + +/* select krb5 auth */ +#define DCERPC_AUTH_KRB5 (1<<16) + +#define DCERPC_SMB2 (1<<17) + +/* select NTLM auth */ +#define DCERPC_AUTH_NTLM (1<<18) + +/* this triggers the DCERPC_PFC_FLAG_CONC_MPX flag in the bind request */ +#define DCERPC_CONCURRENT_MULTIPLEX (1<<19) + +/* this indicates DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN flag was negotiated */ +#define DCERPC_HEADER_SIGNING (1<<20) + +/* use NDR64 transport */ +#define DCERPC_NDR64 (1<<21) + +/* handle upgrades or downgrades automatically */ +#define DCERPC_SCHANNEL_AUTO (1<<23) + +/* use aes schannel with hmac-sh256 session key */ +#define DCERPC_SCHANNEL_AES (1<<24) + +/* this triggers the DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN flag in the bind request */ +#define DCERPC_PROPOSE_HEADER_SIGNING (1<<25) + +#define DCERPC_PACKET (1<<26) + +#define DCERPC_SMB1 (1<<27) + +/* The following definitions come from ../librpc/rpc/dcerpc_error.c */ + +const char *dcerpc_errstr(TALLOC_CTX *mem_ctx, uint32_t fault_code); +NTSTATUS dcerpc_fault_to_nt_status(uint32_t fault_code); +uint32_t dcerpc_fault_from_nt_status(NTSTATUS nt_status); + +/* The following definitions come from ../librpc/rpc/binding.c */ + +const char *epm_floor_string(TALLOC_CTX *mem_ctx, struct epm_floor *epm_floor); +char *dcerpc_floor_get_rhs_data(TALLOC_CTX *mem_ctx, struct epm_floor *epm_floor); +enum dcerpc_transport_t dcerpc_transport_by_endpoint_protocol(int prot); +struct dcerpc_binding *dcerpc_binding_dup(TALLOC_CTX *mem_ctx, + const struct dcerpc_binding *b); +NTSTATUS dcerpc_binding_build_tower(TALLOC_CTX *mem_ctx, + const struct dcerpc_binding *binding, + struct epm_tower *tower); +NTSTATUS dcerpc_binding_from_tower(TALLOC_CTX *mem_ctx, + struct epm_tower *tower, + struct dcerpc_binding **b_out); +NTSTATUS dcerpc_parse_binding(TALLOC_CTX *mem_ctx, const char *s, struct dcerpc_binding **b_out); +char *dcerpc_binding_string(TALLOC_CTX *mem_ctx, const struct dcerpc_binding *b); +struct GUID dcerpc_binding_get_object(const struct dcerpc_binding *b); +NTSTATUS dcerpc_binding_set_object(struct dcerpc_binding *b, + struct GUID object); +enum dcerpc_transport_t dcerpc_binding_get_transport(const struct dcerpc_binding *b); +NTSTATUS dcerpc_binding_set_transport(struct dcerpc_binding *b, + enum dcerpc_transport_t transport); +void dcerpc_binding_get_auth_info(const struct dcerpc_binding *b, + enum dcerpc_AuthType *_auth_type, + enum dcerpc_AuthLevel *_auth_level); +uint32_t dcerpc_binding_get_assoc_group_id(const struct dcerpc_binding *b); +NTSTATUS dcerpc_binding_set_assoc_group_id(struct dcerpc_binding *b, + uint32_t assoc_group_id); +struct ndr_syntax_id dcerpc_binding_get_abstract_syntax(const struct dcerpc_binding *b); +NTSTATUS dcerpc_binding_set_abstract_syntax(struct dcerpc_binding *b, + const struct ndr_syntax_id *syntax); +const char *dcerpc_binding_get_string_option(const struct dcerpc_binding *b, + const char *name); +char *dcerpc_binding_copy_string_option(TALLOC_CTX *mem_ctx, + const struct dcerpc_binding *b, + const char *name); +NTSTATUS dcerpc_binding_set_string_option(struct dcerpc_binding *b, + const char *name, + const char *value); +uint32_t dcerpc_binding_get_flags(const struct dcerpc_binding *b); +NTSTATUS dcerpc_binding_set_flags(struct dcerpc_binding *b, + uint32_t additional, + uint32_t clear); +NTSTATUS dcerpc_floor_get_uuid_full(const struct epm_floor *epm_floor, struct ndr_syntax_id *syntax); +const char *derpc_transport_string_by_transport(enum dcerpc_transport_t t); +enum dcerpc_transport_t dcerpc_transport_by_name(const char *name); +enum dcerpc_transport_t dcerpc_transport_by_tower(const struct epm_tower *tower); + +/* The following definitions come from ../librpc/rpc/binding_handle.c */ + +struct dcerpc_binding_handle_ops { + const char *name; + + bool (*is_connected)(struct dcerpc_binding_handle *h); + uint32_t (*set_timeout)(struct dcerpc_binding_handle *h, + uint32_t timeout); + + void (*auth_info)(struct dcerpc_binding_handle *h, + enum dcerpc_AuthType *auth_type, + enum dcerpc_AuthLevel *auth_level); + + struct tevent_req *(*raw_call_send)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *h, + const struct GUID *object, + uint32_t opnum, + uint32_t in_flags, + const uint8_t *in_data, + size_t in_length); + NTSTATUS (*raw_call_recv)(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint8_t **out_data, + size_t *out_length, + uint32_t *out_flags); + + struct tevent_req *(*disconnect_send)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *h); + NTSTATUS (*disconnect_recv)(struct tevent_req *req); + + /* TODO: remove the following functions */ + bool (*push_bigendian)(struct dcerpc_binding_handle *h); + bool (*ref_alloc)(struct dcerpc_binding_handle *h); + bool (*use_ndr64)(struct dcerpc_binding_handle *h); + void (*do_ndr_print)(struct dcerpc_binding_handle *h, + ndr_flags_type ndr_flags, + const void *struct_ptr, + const struct ndr_interface_call *call); + void (*ndr_push_failed)(struct dcerpc_binding_handle *h, + NTSTATUS error, + const void *struct_ptr, + const struct ndr_interface_call *call); + void (*ndr_pull_failed)(struct dcerpc_binding_handle *h, + NTSTATUS error, + const DATA_BLOB *blob, + const struct ndr_interface_call *call); + NTSTATUS (*ndr_validate_in)(struct dcerpc_binding_handle *h, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *blob, + const struct ndr_interface_call *call); + NTSTATUS (*ndr_validate_out)(struct dcerpc_binding_handle *h, + struct ndr_pull *pull_in, + const void *struct_ptr, + const struct ndr_interface_call *call); +}; + +struct dcerpc_binding_handle *_dcerpc_binding_handle_create(TALLOC_CTX *mem_ctx, + const struct dcerpc_binding_handle_ops *ops, + const struct GUID *object, + const struct ndr_interface_table *table, + void *pstate, + size_t psize, + const char *type, + const char *location); +#define dcerpc_binding_handle_create(mem_ctx, ops, object, table, \ + state, type, location) \ + _dcerpc_binding_handle_create(mem_ctx, ops, object, table, \ + state, sizeof(type), #type, location) + +void *_dcerpc_binding_handle_data(struct dcerpc_binding_handle *h); +#define dcerpc_binding_handle_data(_h, _type) \ + talloc_get_type_abort(_dcerpc_binding_handle_data(_h), _type) + +_DEPRECATED_ void dcerpc_binding_handle_set_sync_ev(struct dcerpc_binding_handle *h, + struct tevent_context *ev); + +bool dcerpc_binding_handle_is_connected(struct dcerpc_binding_handle *h); + +uint32_t dcerpc_binding_handle_set_timeout(struct dcerpc_binding_handle *h, + uint32_t timeout); + +void dcerpc_binding_handle_auth_info(struct dcerpc_binding_handle *h, + enum dcerpc_AuthType *auth_type, + enum dcerpc_AuthLevel *auth_level); + +struct tevent_req *dcerpc_binding_handle_raw_call_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *h, + const struct GUID *object, + uint32_t opnum, + uint32_t in_flags, + const uint8_t *in_data, + size_t in_length); +NTSTATUS dcerpc_binding_handle_raw_call_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint8_t **out_data, + size_t *out_length, + uint32_t *out_flags); +NTSTATUS dcerpc_binding_handle_raw_call(struct dcerpc_binding_handle *h, + const struct GUID *object, + uint32_t opnum, + uint32_t in_flags, + const uint8_t *in_data, + size_t in_length, + TALLOC_CTX *mem_ctx, + uint8_t **out_data, + size_t *out_length, + uint32_t *out_flags); + +struct tevent_req *dcerpc_binding_handle_disconnect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *h); +NTSTATUS dcerpc_binding_handle_disconnect_recv(struct tevent_req *req); + +struct tevent_req *dcerpc_binding_handle_call_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *h, + const struct GUID *object, + const struct ndr_interface_table *table, + uint32_t opnum, + TALLOC_CTX *r_mem, + void *r_ptr); +NTSTATUS dcerpc_binding_handle_call_recv(struct tevent_req *req); +NTSTATUS dcerpc_binding_handle_call(struct dcerpc_binding_handle *h, + const struct GUID *object, + const struct ndr_interface_table *table, + uint32_t opnum, + TALLOC_CTX *r_mem, + void *r_ptr); + +/** + * Extract header information from a ncacn_packet + * as a dcerpc_sec_vt_header2 as used by the security verification trailer. + * + * @param[in] pkt a packet + * + * @return a dcerpc_sec_vt_header2 + */ +struct dcerpc_sec_vt_header2 dcerpc_sec_vt_header2_from_ncacn_packet(const struct ncacn_packet *pkt); + + +/** + * Test if two dcerpc_sec_vt_header2 structures are equal + * without consideration of reserved fields. + * + * @param v1 a pointer to a dcerpc_sec_vt_header2 structure + * @param v2 a pointer to a dcerpc_sec_vt_header2 structure + * + * @retval true if *v1 equals *v2 + */ +bool dcerpc_sec_vt_header2_equal(const struct dcerpc_sec_vt_header2 *v1, + const struct dcerpc_sec_vt_header2 *v2); + +/** + * Check for consistency of the security verification trailer with the PDU header. + * See <a href="http://msdn.microsoft.com/en-us/library/cc243559.aspx">MS-RPCE 2.2.2.13</a>. + * A check with an empty trailer succeeds. + * + * @param[in] vt a pointer to the security verification trailer. + * @param[in] bitmask1 which flags were negotiated on the connection. + * @param[in] pcontext the syntaxes negotiated for the presentation context. + * @param[in] header2 some fields from the PDU header. + * + * @retval true on success. + */ +bool dcerpc_sec_verification_trailer_check( + const struct dcerpc_sec_verification_trailer *vt, + const uint32_t *bitmask1, + const struct dcerpc_sec_vt_pcontext *pcontext, + const struct dcerpc_sec_vt_header2 *header2); + +/** + * @brief check and optionally extract the Bind Time Features from + * the given ndr_syntax_id. + * + * <a href="http://msdn.microsoft.com/en-us/library/cc243715.aspx">MS-RPCE 3.3.1.5.3 Bind Time Feature Negotiation</a>. + * + * @param[in] s the syntax that should be checked. + * + * @param[out] features This is optional, it will be filled with the extracted + * features the on success, otherwise it's filled with 0. + * + * @return true if the syntax matches the 6CB71C2C-9812-4540 prefix with version 1, false otherwise. + * + * @see dcerpc_construct_bind_time_features + */ +bool dcerpc_extract_bind_time_features(struct ndr_syntax_id syntax, uint64_t *features); + +/** + * @brief Construct a ndr_syntax_id used for Bind Time Features Negotiation. + * + * <a href="http://msdn.microsoft.com/en-us/library/cc243715.aspx">MS-RPCE 3.3.1.5.3 Bind Time Feature Negotiation</a>. + * + * @param[in] features The supported features. + * + * @return The ndr_syntax_id with the given features. + * + * @see dcerpc_extract_bind_time_features + */ +struct ndr_syntax_id dcerpc_construct_bind_time_features(uint64_t features); + +#define DCERPC_AUTH_PAD_LENGTH(stub_length) (\ + (((stub_length) % DCERPC_AUTH_PAD_ALIGNMENT) > 0)?\ + (DCERPC_AUTH_PAD_ALIGNMENT - (stub_length) % DCERPC_AUTH_PAD_ALIGNMENT):\ + 0) + +NTSTATUS dcerpc_generic_session_key(DATA_BLOB *session_key); + +NTSTATUS dcerpc_ncacn_push_auth(DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + struct ncacn_packet *pkt, + struct dcerpc_auth *auth_info); + +void dcerpc_log_packet(const char *packet_log_dir, + const char *interface_name, + uint32_t opnum, ndr_flags_type flags, + const DATA_BLOB *pkt, + const char *why); + +#ifdef DEVELOPER +void dcerpc_save_ndr_fuzz_seed(TALLOC_CTX *mem_ctx, + DATA_BLOB raw_blob, + const char *dump_dir, + const char *iface_name, + ndr_flags_type flags, + int opnum, + bool ndr64); +#else +static inline void dcerpc_save_ndr_fuzz_seed(TALLOC_CTX *mem_ctx, + DATA_BLOB raw_blob, + const char *dump_dir, + const char *iface_name, + ndr_flags_type flags, + int opnum, + bool ndr64) +{ + return; +} +#endif + +#endif /* __DEFAULT_LIBRPC_RPCCOMMON_H__ */ diff --git a/librpc/rpc/server/netlogon/schannel_util.c b/librpc/rpc/server/netlogon/schannel_util.c new file mode 100644 index 0000000..b14497b --- /dev/null +++ b/librpc/rpc/server/netlogon/schannel_util.c @@ -0,0 +1,570 @@ +/* + Unix SMB/CIFS implementation. + + netlogon schannel utility functions + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2008 + Copyright (C) Stefan Metzmacher <metze@samba.org> 2005 + Copyright (C) Matthias Dieter Wallnöfer 2009-2010 + + 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 "schannel_util.h" +#include "param/param.h" +#include "libcli/security/dom_sid.h" +#include "libcli/auth/schannel.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_netlogon.h" +#include "lib/util/util_str_escape.h" + +struct dcesrv_netr_check_schannel_state { + struct dom_sid account_sid; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + bool schannel_global_required; + bool schannel_required; + bool schannel_explicitly_set; + + bool seal_global_required; + bool seal_required; + bool seal_explicitly_set; + + NTSTATUS result; +}; + +static NTSTATUS dcesrv_netr_check_schannel_get_state(struct dcesrv_call_state *dce_call, + const struct netlogon_creds_CredentialState *creds, + enum dcerpc_AuthType auth_type, + enum dcerpc_AuthLevel auth_level, + struct dcesrv_netr_check_schannel_state **_s) +{ + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + int schannel = lpcfg_server_schannel(lp_ctx); + bool schannel_global_required = (schannel == true); + bool schannel_required = schannel_global_required; + const char *explicit_opt = NULL; + bool global_require_seal = lpcfg_server_schannel_require_seal(lp_ctx); + bool require_seal = global_require_seal; + const char *explicit_seal_opt = NULL; +#define DCESRV_NETR_CHECK_SCHANNEL_STATE_MAGIC (NETLOGON_SERVER_PIPE_STATE_MAGIC+1) + struct dcesrv_netr_check_schannel_state *s = NULL; + NTSTATUS status; + + *_s = NULL; + + s = dcesrv_iface_state_find_conn(dce_call, + DCESRV_NETR_CHECK_SCHANNEL_STATE_MAGIC, + struct dcesrv_netr_check_schannel_state); + if (s != NULL) { + if (!dom_sid_equal(&s->account_sid, creds->sid)) { + goto new_state; + } + if (s->auth_type != auth_type) { + goto new_state; + } + if (s->auth_level != auth_level) { + goto new_state; + } + + *_s = s; + return NT_STATUS_OK; + } + +new_state: + TALLOC_FREE(s); + s = talloc_zero(dce_call, + struct dcesrv_netr_check_schannel_state); + if (s == NULL) { + return NT_STATUS_NO_MEMORY; + } + + s->account_sid = *creds->sid; + s->auth_type = auth_type; + s->auth_level = auth_level; + s->result = NT_STATUS_MORE_PROCESSING_REQUIRED; + + /* + * We don't use lpcfg_parm_bool(), as we + * need the explicit_opt pointer in order to + * adjust the debug messages. + */ + explicit_seal_opt = lpcfg_get_parametric(lp_ctx, + NULL, + "server schannel require seal", + creds->account_name); + if (explicit_seal_opt != NULL) { + require_seal = lp_bool(explicit_seal_opt); + } + + /* + * We don't use lpcfg_parm_bool(), as we + * need the explicit_opt pointer in order to + * adjust the debug messages. + */ + explicit_opt = lpcfg_get_parametric(lp_ctx, + NULL, + "server require schannel", + creds->account_name); + if (explicit_opt != NULL) { + schannel_required = lp_bool(explicit_opt); + } + + s->schannel_global_required = schannel_global_required; + s->schannel_required = schannel_required; + s->schannel_explicitly_set = explicit_opt != NULL; + + s->seal_global_required = global_require_seal; + s->seal_required = require_seal; + s->seal_explicitly_set = explicit_seal_opt != NULL; + + status = dcesrv_iface_state_store_conn(dce_call, + DCESRV_NETR_CHECK_SCHANNEL_STATE_MAGIC, + s); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *_s = s; + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_netr_check_schannel_once(struct dcesrv_call_state *dce_call, + struct dcesrv_netr_check_schannel_state *s, + const struct netlogon_creds_CredentialState *creds, + uint16_t opnum) +{ + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + int CVE_2020_1472_warn_level = lpcfg_parm_int(lp_ctx, NULL, + "CVE_2020_1472", "warn_about_unused_debug_level", DBGLVL_ERR); + int CVE_2020_1472_error_level = lpcfg_parm_int(lp_ctx, NULL, + "CVE_2020_1472", "error_debug_level", DBGLVL_ERR); + int CVE_2022_38023_warn_level = lpcfg_parm_int(lp_ctx, NULL, + "CVE_2022_38023", "warn_about_unused_debug_level", DBGLVL_ERR); + int CVE_2022_38023_error_level = lpcfg_parm_int(lp_ctx, NULL, + "CVE_2022_38023", "error_debug_level", DBGLVL_ERR); + TALLOC_CTX *frame = talloc_stackframe(); + unsigned int dbg_lvl = DBGLVL_DEBUG; + const char *opname = "<unknown>"; + const char *reason = "<unknown>"; + + if (opnum < ndr_table_netlogon.num_calls) { + opname = ndr_table_netlogon.calls[opnum].name; + } + + if (s->auth_type == DCERPC_AUTH_TYPE_SCHANNEL) { + if (s->auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + reason = "WITH SEALED"; + } else if (s->auth_level == DCERPC_AUTH_LEVEL_INTEGRITY) { + reason = "WITH SIGNED"; + } else { + reason = "WITH INVALID"; + dbg_lvl = DBGLVL_ERR; + s->result = NT_STATUS_INTERNAL_ERROR; + } + } else { + reason = "WITHOUT"; + } + + if (!NT_STATUS_EQUAL(s->result, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + if (!NT_STATUS_IS_OK(s->result)) { + dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO); + } + + DEBUG(dbg_lvl, ( + "CVE-2020-1472(ZeroLogon)/CVE-2022-38023: " + "%s request (opnum[%u]) %s schannel from " + "client_account[%s] client_computer_name[%s] %s\n", + opname, opnum, reason, + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name), + nt_errstr(s->result))); + TALLOC_FREE(frame); + return s->result; + } + + if (s->auth_type == DCERPC_AUTH_TYPE_SCHANNEL && + s->auth_level == DCERPC_AUTH_LEVEL_PRIVACY) + { + s->result = NT_STATUS_OK; + + if (s->schannel_explicitly_set && !s->schannel_required) { + dbg_lvl = MIN(dbg_lvl, CVE_2020_1472_warn_level); + } else if (!s->schannel_required) { + dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO); + } + if (s->seal_explicitly_set && !s->seal_required) { + dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_warn_level); + } else if (!s->seal_required) { + dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO); + } + + DEBUG(dbg_lvl, ( + "CVE-2020-1472(ZeroLogon)/CVE-2022-38023: " + "%s request (opnum[%u]) %s schannel from " + "client_account[%s] client_computer_name[%s] %s\n", + opname, opnum, reason, + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name), + nt_errstr(s->result))); + + if (s->schannel_explicitly_set && !s->schannel_required) { + DEBUG(CVE_2020_1472_warn_level, ( + "CVE-2020-1472(ZeroLogon): " + "Option 'server require schannel:%s = no' not needed for '%s'!\n", + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name))); + } + + if (s->seal_explicitly_set && !s->seal_required) { + DEBUG(CVE_2022_38023_warn_level, ( + "CVE-2022-38023: " + "Option 'server schannel require seal:%s = no' not needed for '%s'!\n", + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name))); + } + + TALLOC_FREE(frame); + return s->result; + } + + if (s->auth_type == DCERPC_AUTH_TYPE_SCHANNEL) { + if (s->seal_required) { + s->result = NT_STATUS_ACCESS_DENIED; + + if (s->seal_explicitly_set) { + dbg_lvl = DBGLVL_NOTICE; + } else { + dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_error_level); + } + if (s->schannel_explicitly_set && !s->schannel_required) { + dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_warn_level); + } + + DEBUG(dbg_lvl, ( + "CVE-2022-38023: " + "%s request (opnum[%u]) %s schannel from " + "from client_account[%s] client_computer_name[%s] %s\n", + opname, opnum, reason, + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name), + nt_errstr(s->result))); + if (s->seal_explicitly_set) { + D_NOTICE("CVE-2022-38023: Option " + "'server schannel require seal:%s = yes' " + "rejects access for client.\n", + log_escape(frame, creds->account_name)); + } else { + DEBUG(CVE_2020_1472_error_level, ( + "CVE-2022-38023: Check if option " + "'server schannel require seal:%s = no' " + "might be needed for a legacy client.\n", + log_escape(frame, creds->account_name))); + } + if (s->schannel_explicitly_set && !s->schannel_required) { + DEBUG(CVE_2020_1472_warn_level, ( + "CVE-2020-1472(ZeroLogon): Option " + "'server require schannel:%s = no' " + "not needed for '%s'!\n", + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name))); + } + TALLOC_FREE(frame); + return s->result; + } + + s->result = NT_STATUS_OK; + + if (s->schannel_explicitly_set && !s->schannel_required) { + dbg_lvl = MIN(dbg_lvl, CVE_2020_1472_warn_level); + } else if (!s->schannel_required) { + dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO); + } + if (s->seal_explicitly_set && !s->seal_required) { + dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO); + } else if (!s->seal_required) { + dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_error_level); + } + + DEBUG(dbg_lvl, ( + "CVE-2020-1472(ZeroLogon): " + "%s request (opnum[%u]) %s schannel from " + "client_account[%s] client_computer_name[%s] %s\n", + opname, opnum, reason, + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name), + nt_errstr(s->result))); + if (s->schannel_explicitly_set && !s->schannel_required) { + DEBUG(CVE_2020_1472_warn_level, ( + "CVE-2020-1472(ZeroLogon): " + "Option 'server require schannel:%s = no' not needed for '%s'!\n", + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name))); + } + if (s->seal_explicitly_set && !s->seal_required) { + D_INFO("CVE-2022-38023: " + "Option 'server schannel require seal:%s = no' still needed for '%s'!\n", + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name)); + } else if (!s->seal_required) { + /* + * admins should set + * server schannel require seal:COMPUTER$ = no + * in order to avoid the level 0 messages. + * Over time they can switch the global value + * to be strict. + */ + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: " + "Please use 'server schannel require seal:%s = no' " + "for '%s' to avoid this warning!\n", + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name))); + } + + TALLOC_FREE(frame); + return s->result; + } + + if (s->seal_required) { + s->result = NT_STATUS_ACCESS_DENIED; + + if (s->seal_explicitly_set) { + dbg_lvl = MIN(dbg_lvl, DBGLVL_NOTICE); + } else { + dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_error_level); + } + if (!s->schannel_explicitly_set) { + dbg_lvl = MIN(dbg_lvl, CVE_2020_1472_error_level); + } else if (s->schannel_required) { + dbg_lvl = MIN(dbg_lvl, DBGLVL_NOTICE); + } + + DEBUG(dbg_lvl, ( + "CVE-2020-1472(ZeroLogon)/CVE-2022-38023: " + "%s request (opnum[%u]) %s schannel from " + "from client_account[%s] client_computer_name[%s] %s\n", + opname, opnum, reason, + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name), + nt_errstr(s->result))); + if (s->seal_explicitly_set) { + D_NOTICE("CVE-2022-38023: Option " + "'server schannel require seal:%s = yes' " + "rejects access for client.\n", + log_escape(frame, creds->account_name)); + } else { + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: Check if option " + "'server schannel require seal:%s = no' " + "might be needed for a legacy client.\n", + log_escape(frame, creds->account_name))); + } + if (!s->schannel_explicitly_set) { + DEBUG(CVE_2020_1472_error_level, ( + "CVE-2020-1472(ZeroLogon): Check if option " + "'server require schannel:%s = no' " + "might be needed for a legacy client.\n", + log_escape(frame, creds->account_name))); + } else if (s->schannel_required) { + D_NOTICE("CVE-2022-38023: Option " + "'server require schannel:%s = yes' " + "also rejects access for client.\n", + log_escape(frame, creds->account_name)); + } + TALLOC_FREE(frame); + return s->result; + } + + if (s->schannel_required) { + s->result = NT_STATUS_ACCESS_DENIED; + + if (s->schannel_explicitly_set) { + dbg_lvl = MIN(dbg_lvl, DBGLVL_NOTICE); + } else { + dbg_lvl = MIN(dbg_lvl, CVE_2020_1472_error_level); + } + if (!s->seal_explicitly_set) { + dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_error_level); + } + + DEBUG(dbg_lvl, ( + "CVE-2020-1472(ZeroLogon)/CVE-2022-38023: " + "%s request (opnum[%u]) %s schannel from " + "client_account[%s] client_computer_name[%s] %s\n", + opname, opnum, reason, + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name), + nt_errstr(s->result))); + if (s->schannel_explicitly_set) { + D_NOTICE("CVE-2020-1472(ZeroLogon): Option " + "'server require schannel:%s = yes' " + "rejects access for client.\n", + log_escape(frame, creds->account_name)); + } else { + DEBUG(CVE_2020_1472_error_level, ( + "CVE-2020-1472(ZeroLogon): Check if option " + "'server require schannel:%s = no' " + "might be needed for a legacy client.\n", + log_escape(frame, creds->account_name))); + } + if (!s->seal_explicitly_set) { + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: Check if option " + "'server schannel require seal:%s = no' " + "might be needed for a legacy client.\n", + log_escape(frame, creds->account_name))); + } + TALLOC_FREE(frame); + return s->result; + } + + s->result = NT_STATUS_OK; + + if (s->seal_explicitly_set) { + dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO); + } else { + dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_error_level); + } + + if (s->schannel_explicitly_set) { + dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO); + } else { + dbg_lvl = MIN(dbg_lvl, CVE_2020_1472_error_level); + } + + DEBUG(dbg_lvl, ( + "CVE-2020-1472(ZeroLogon)/CVE-2022-38023: " + "%s request (opnum[%u]) %s schannel from " + "client_account[%s] client_computer_name[%s] %s\n", + opname, opnum, reason, + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name), + nt_errstr(s->result))); + + if (s->seal_explicitly_set) { + D_INFO("CVE-2022-38023: Option " + "'server schannel require seal:%s = no' " + "still needed for '%s'!\n", + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name)); + } else { + /* + * admins should set + * server schannel require seal:COMPUTER$ = no + * in order to avoid the level 0 messages. + * Over time they can switch the global value + * to be strict. + */ + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: Please use " + "'server schannel require seal:%s = no' " + "for '%s' to avoid this warning!\n", + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name))); + } + + if (s->schannel_explicitly_set) { + D_INFO("CVE-2020-1472(ZeroLogon): Option " + "'server require schannel:%s = no' " + "still needed for '%s'!\n", + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name)); + } else { + /* + * admins should set + * server require schannel:COMPUTER$ = no + * in order to avoid the level 0 messages. + * Over time they can switch the global value + * to be strict. + */ + DEBUG(CVE_2020_1472_error_level, ( + "CVE-2020-1472(ZeroLogon): " + "Please use 'server require schannel:%s = no' " + "for '%s' to avoid this warning!\n", + log_escape(frame, creds->account_name), + log_escape(frame, creds->computer_name))); + } + + TALLOC_FREE(frame); + return s->result; +} + +NTSTATUS dcesrv_netr_check_schannel(struct dcesrv_call_state *dce_call, + const struct netlogon_creds_CredentialState *creds, + enum dcerpc_AuthType auth_type, + enum dcerpc_AuthLevel auth_level, + uint16_t opnum) +{ + struct dcesrv_netr_check_schannel_state *s = NULL; + NTSTATUS status; + + status = dcesrv_netr_check_schannel_get_state(dce_call, + creds, + auth_type, + auth_level, + &s); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = dcesrv_netr_check_schannel_once(dce_call, s, creds, opnum); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +NTSTATUS dcesrv_netr_creds_server_step_check(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + const char *computer_name, + struct netr_Authenticator *received_authenticator, + struct netr_Authenticator *return_authenticator, + struct netlogon_creds_CredentialState **creds_out) +{ + NTSTATUS nt_status; + struct netlogon_creds_CredentialState *creds = NULL; + enum dcerpc_AuthType auth_type = DCERPC_AUTH_TYPE_NONE; + enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE; + + dcesrv_call_auth_info(dce_call, &auth_type, &auth_level); + + nt_status = schannel_check_creds_state(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + computer_name, + received_authenticator, + return_authenticator, + &creds); + if (!NT_STATUS_IS_OK(nt_status)) { + ZERO_STRUCTP(return_authenticator); + return nt_status; + } + + nt_status = dcesrv_netr_check_schannel(dce_call, + creds, + auth_type, + auth_level, + dce_call->pkt.u.request.opnum); + if (!NT_STATUS_IS_OK(nt_status)) { + TALLOC_FREE(creds); + ZERO_STRUCTP(return_authenticator); + return nt_status; + } + + *creds_out = creds; + return NT_STATUS_OK; +} diff --git a/librpc/rpc/server/netlogon/schannel_util.h b/librpc/rpc/server/netlogon/schannel_util.h new file mode 100644 index 0000000..561e256 --- /dev/null +++ b/librpc/rpc/server/netlogon/schannel_util.h @@ -0,0 +1,54 @@ +/* + Unix SMB/CIFS implementation. + + netlogon schannel utility functions + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2008 + Copyright (C) Stefan Metzmacher <metze@samba.org> 2005 + Copyright (C) Matthias Dieter Wallnöfer 2009-2010 + + 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/>. +*/ + +#ifndef __LIBRPC_RPC_SERVER_NETLOGON_SCHANNEL_UTIL_H__ +#define __LIBRPC_RPC_SERVER_NETLOGON_SCHANNEL_UTIL_H__ + +#include "replace.h" +#include <talloc.h> +#include "libcli/util/ntstatus.h" + +#define NETLOGON_SERVER_PIPE_STATE_MAGIC 0x4f555358 + +struct dcesrv_call_state; +struct netlogon_creds_CredentialState; +struct netr_Authenticator; +enum dcerpc_AuthType; +enum dcerpc_AuthLevel; + +NTSTATUS dcesrv_netr_check_schannel( + struct dcesrv_call_state *dce_call, + const struct netlogon_creds_CredentialState *creds, + enum dcerpc_AuthType auth_type, + enum dcerpc_AuthLevel auth_level, + uint16_t opnum); + +NTSTATUS dcesrv_netr_creds_server_step_check( + struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + const char *computer_name, + struct netr_Authenticator *received_authenticator, + struct netr_Authenticator *return_authenticator, + struct netlogon_creds_CredentialState **creds_out); + +#endif /* __LIBRPC_RPC_SERVER_NETLOGON_SCHANNEL_UTIL_H__ */ |