s4:rpc_server: implement security context multiplexing
authorStefan Metzmacher <metze@samba.org>
Thu, 8 Nov 2018 13:59:58 +0000 (14:59 +0100)
committerJeremy Allison <jra@samba.org>
Sat, 12 Jan 2019 02:13:41 +0000 (03:13 +0100)
There're some systems like Cisco ISE use security multiplexing
without checking (via bind time feature negotiation)
the server supports it.

Others like VMWare View, fallback to NT4 style netlogon
connections without using netlogon secure channel,
which then triggers an error, with "server schannel = yes",
see https://bugzilla.samba.org/show_bug.cgi?id=13464.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=7113
BUG: https://bugzilla.samba.org/show_bug.cgi?id=11892

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
selftest/knownfail.d/security_context_multiplexing [deleted file]
source4/rpc_server/dcerpc_server.c
source4/rpc_server/dcerpc_server.h
source4/rpc_server/dcesrv_auth.c

diff --git a/selftest/knownfail.d/security_context_multiplexing b/selftest/knownfail.d/security_context_multiplexing
deleted file mode 100644 (file)
index b5b9658..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-^samba.tests.dcerpc.raw_protocol.*.TestDCERPC_BIND.test_lsa_multi_auth.*
-^samba.tests.dcerpc.raw_protocol.*.TestDCERPC_BIND.test_spnego_multiple_auth_hdr_signing
-^samba.tests.dcerpc.raw_protocol.*.TestDCERPC_BIND.test_multiple_auth_limit
-^samba.tests.dcerpc.raw_protocol.*.TestDCERPC_BIND.test_no_auth_bind_time_sec_ctx_ignore_additional
index 75e1dfe..9c5bb4f 100644 (file)
@@ -643,6 +643,11 @@ static NTSTATUS dcesrv_endpoint_connect(struct dcesrv_context *dce_ctx,
                                                 "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) {
@@ -707,6 +712,8 @@ static void dcesrv_call_set_list(struct dcesrv_call_state *call,
 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;
        }
@@ -716,6 +723,10 @@ static void dcesrv_call_disconnect_after(struct dcesrv_call_state *call,
 
        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__;
@@ -1126,7 +1137,10 @@ static NTSTATUS dcesrv_bind(struct dcesrv_call_state *call)
                a->result = DCERPC_BIND_ACK_RESULT_NEGOTIATE_ACK;
                a->reason.negotiate = 0;
                if (features & DCERPC_BIND_TIME_SECURITY_CONTEXT_MULTIPLEXING) {
-                       /* not supported yet */
+                       if (call->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 |=
@@ -2018,6 +2032,10 @@ static NTSTATUS dcesrv_process_ncacn_packet(struct dcesrv_connection *dce_conn,
        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;
 
        call = talloc_zero(dce_conn, struct dcesrv_call_state);
        if (!call) {
@@ -2036,7 +2054,73 @@ static NTSTATUS dcesrv_process_ncacn_packet(struct dcesrv_connection *dce_conn,
        talloc_steal(call, blob.data);
        call->pkt = *pkt;
 
-       call->auth_state = dce_conn->default_auth_state;
+       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;
+
+               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) {
+                       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_type != auth_type) {
+                               continue;
+                       }
+                       if (a->auth_finished && a->auth_level != auth_level) {
+                               continue;
+                       }
+                       if (a->auth_context_id != auth_context_id) {
+                               continue;
+                       }
+
+                       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.
+                        */
+                       a->auth_invalid = true;
+               }
+               call->auth_state = a;
+       }
 
        talloc_set_destructor(call, dcesrv_call_dequeue);
 
@@ -2051,7 +2135,10 @@ static NTSTATUS dcesrv_process_ncacn_packet(struct dcesrv_connection *dce_conn,
        /* we have to check the signing here, before combining the
           pdus */
        if (call->pkt.ptype == DCERPC_PKT_REQUEST) {
-               if (!call->auth_state->auth_finished) {
+               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);
                }
@@ -2479,6 +2566,7 @@ const struct dcesrv_critical_sizes *dcerpc_module_version(void)
 static void dcesrv_terminate_connection(struct dcesrv_connection *dce_conn, const char *reason)
 {
        struct dcesrv_context *dce_ctx = dce_conn->dce_ctx;
+       struct dcesrv_auth *a = NULL;
        struct stream_connection *srv_conn;
        srv_conn = talloc_get_type(dce_conn->transport.private_data,
                                   struct stream_connection);
@@ -2492,6 +2580,10 @@ static void dcesrv_terminate_connection(struct dcesrv_connection *dce_conn, cons
 
        dce_conn->default_auth_state->auth_invalid = true;
 
+       for (a = dce_conn->auth_states; a != NULL; a = a->next) {
+               a->auth_invalid = true;
+       }
+
        if (dce_conn->pending_call_list == NULL) {
                char *full_reason = talloc_asprintf(dce_conn, "dcesrv: %s", reason);
 
index 25a3aed..50b0746 100644 (file)
@@ -197,6 +197,7 @@ struct dcesrv_handle {
 
 /* 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;
@@ -205,6 +206,7 @@ struct dcesrv_auth {
        NTSTATUS (*session_key_fn)(struct dcesrv_auth *, DATA_BLOB *session_key);
        bool auth_started;
        bool auth_finished;
+       bool auth_audited;
        bool auth_invalid;
 };
 
@@ -288,6 +290,10 @@ struct dcesrv_connection {
 
        /* 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;
index 192d90b..c71e486 100644 (file)
@@ -36,7 +36,7 @@ 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 *auth = call->auth_state;
+       struct dcesrv_auth *a = NULL;
 
        if (!(call->pkt.pfc_flags & DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN)) {
                return NT_STATUS_OK;
@@ -60,12 +60,20 @@ static NTSTATUS dcesrv_auth_negotiate_hdr_signing(struct dcesrv_call_state *call
                pkt->pfc_flags |= DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN;
        }
 
-       if (auth->gensec_security == NULL) {
-               return NT_STATUS_OK;
+       a = call->conn->default_auth_state;
+       if (a->gensec_security != NULL) {
+               gensec_want_feature(a->gensec_security,
+                                   GENSEC_FEATURE_SIGN_PKT_HEADER);
        }
 
-       gensec_want_feature(auth->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;
 }
@@ -250,6 +258,68 @@ static void log_successful_dcesrv_authz_event(struct dcesrv_call_state *call)
                                   auth_type,
                                   transport_protection,
                                   auth->session_info);
+
+       auth->auth_audited = 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;
+
+       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;
+       }
+
+       log_successful_dcesrv_authz_event(call);
 }
 
 /*
@@ -338,6 +408,12 @@ NTSTATUS dcesrv_auth_complete(struct dcesrv_call_state *call, NTSTATUS 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;
        }
@@ -367,6 +443,7 @@ NTSTATUS dcesrv_auth_prepare_bind_ack(struct dcesrv_call_state *call, struct nca
        }
 
        dce_conn->allow_alter = true;
+       dcesrv_default_auth_state_finish_bind(call);
 
        if (call->pkt.auth_length == 0) {
                auth->auth_finished = true;
@@ -467,11 +544,6 @@ bool dcesrv_auth_alter(struct dcesrv_call_state *call)
                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.alter.auth_info,
                                          &call->in_auth_info, NULL, true);
        if (!NT_STATUS_IS_OK(status)) {
@@ -479,6 +551,18 @@ bool dcesrv_auth_alter(struct dcesrv_call_state *call)
                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;
@@ -552,6 +636,10 @@ bool dcesrv_auth_pkt_pull(struct dcesrv_call_state *call,
        };
        NTSTATUS status;
 
+       if (!auth->auth_started) {
+               return false;
+       }
+
        if (!auth->auth_finished) {
                call->fault_code = DCERPC_NCA_S_PROTO_ERROR;
                return false;