summaryrefslogtreecommitdiffstats
path: root/librpc/rpc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--librpc/rpc/binding.c1502
-rw-r--r--librpc/rpc/binding_handle.c568
-rw-r--r--librpc/rpc/dcerpc_error.c153
-rw-r--r--librpc/rpc/dcerpc_helper.c132
-rw-r--r--librpc/rpc/dcerpc_helper.h26
-rw-r--r--librpc/rpc/dcerpc_pkt_auth.c500
-rw-r--r--librpc/rpc/dcerpc_pkt_auth.h59
-rw-r--r--librpc/rpc/dcerpc_samr.h42
-rw-r--r--librpc/rpc/dcerpc_util.c1140
-rw-r--r--librpc/rpc/dcerpc_util.h85
-rw-r--r--librpc/rpc/dcesrv_auth.c703
-rw-r--r--librpc/rpc/dcesrv_core.c3369
-rw-r--r--librpc/rpc/dcesrv_core.h715
-rw-r--r--librpc/rpc/dcesrv_handles.c372
-rw-r--r--librpc/rpc/dcesrv_mgmt.c169
-rw-r--r--librpc/rpc/dcesrv_reply.c309
-rw-r--r--librpc/rpc/rpc_common.h412
-rw-r--r--librpc/rpc/server/netlogon/schannel_util.c570
-rw-r--r--librpc/rpc/server/netlogon/schannel_util.h54
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__ */