s4:librpc: fix netlogon connections against servers without AES support
[metze/samba/wip.git] / source4 / librpc / rpc / dcerpc_schannel.c
index 68e211afae735a5d6c149a93c624322b4f074858..130ebebd9092aa2a5161a9b0b4f6426969160723 100644 (file)
 */
 
 #include "includes.h"
+#include <tevent.h>
 #include "auth/auth.h"
 #include "libcli/composite/composite.h"
 #include "libcli/auth/libcli_auth.h"
 #include "librpc/gen_ndr/ndr_netlogon.h"
 #include "librpc/gen_ndr/ndr_netlogon_c.h"
 #include "auth/credentials/credentials.h"
+#include "librpc/rpc/dcerpc_proto.h"
+#include "param/param.h"
 
 struct schannel_key_state {
        struct dcerpc_pipe *pipe;
        struct dcerpc_pipe *pipe2;
        struct dcerpc_binding *binding;
+       bool dcerpc_schannel_auto;
        struct cli_credentials *credentials;
-       struct creds_CredentialState *creds;
-       uint32_t negotiate_flags;
+       struct netlogon_creds_CredentialState *creds;
+       uint32_t local_negotiate_flags;
+       uint32_t remote_negotiate_flags;
        struct netr_Credential credentials1;
        struct netr_Credential credentials2;
        struct netr_Credential credentials3;
@@ -47,8 +52,9 @@ struct schannel_key_state {
 
 static void continue_secondary_connection(struct composite_context *ctx);
 static void continue_bind_auth_none(struct composite_context *ctx);
-static void continue_srv_challenge(struct rpc_request *req);
-static void continue_srv_auth2(struct rpc_request *req);
+static void continue_srv_challenge(struct tevent_req *subreq);
+static void continue_srv_auth2(struct tevent_req *subreq);
+static void continue_get_capabilities(struct tevent_req *subreq);
 
 
 /*
@@ -117,7 +123,7 @@ static void continue_bind_auth_none(struct composite_context *ctx)
 {
        struct composite_context *c;
        struct schannel_key_state *s;
-       struct rpc_request *srv_challenge_req;
+       struct tevent_req *subreq;
 
        c = talloc_get_type(ctx->async.private_data, struct composite_context);
        s = talloc_get_type(c->private_data, struct schannel_key_state);
@@ -131,17 +137,19 @@ static void continue_bind_auth_none(struct composite_context *ctx)
        if (composite_nomem(s->r.in.server_name, c)) return;
        s->r.in.computer_name = cli_credentials_get_workstation(s->credentials);
        s->r.in.credentials   = &s->credentials1;
-       s->r.out.credentials  = &s->credentials2;
+       s->r.out.return_credentials  = &s->credentials2;
        
        generate_random_buffer(s->credentials1.data, sizeof(s->credentials1.data));
 
        /*
          request a netlogon challenge - a rpc request over opened secondary pipe
        */
-       srv_challenge_req = dcerpc_netr_ServerReqChallenge_send(s->pipe2, c, &s->r);
-       if (composite_nomem(srv_challenge_req, c)) return;
+       subreq = dcerpc_netr_ServerReqChallenge_r_send(s, c->event_ctx,
+                                                      s->pipe2->binding_handle,
+                                                      &s->r);
+       if (composite_nomem(subreq, c)) return;
 
-       composite_continue_rpc(c, srv_challenge_req, continue_srv_challenge, c);
+       tevent_req_set_callback(subreq, continue_srv_challenge, c);
 }
 
 
@@ -149,43 +157,52 @@ static void continue_bind_auth_none(struct composite_context *ctx)
   Stage 5 of schannel_key: Receive a challenge and perform authentication
   on the netlogon pipe
 */
-static void continue_srv_challenge(struct rpc_request *req)
+static void continue_srv_challenge(struct tevent_req *subreq)
 {
        struct composite_context *c;
        struct schannel_key_state *s;
-       struct rpc_request *srv_auth2_req;
 
-       c = talloc_get_type(req->async.private_data, struct composite_context);
+       c = tevent_req_callback_data(subreq, struct composite_context);
        s = talloc_get_type(c->private_data, struct schannel_key_state);
 
        /* receive rpc request result - netlogon challenge */
-       c->status = dcerpc_ndr_request_recv(req);
+       c->status = dcerpc_netr_ServerReqChallenge_r_recv(subreq, s);
+       TALLOC_FREE(subreq);
        if (!composite_is_ok(c)) return;
 
        /* prepare credentials for auth2 request */
        s->mach_pwd = cli_credentials_get_nt_hash(s->credentials, c);
 
-       creds_client_init(s->creds, &s->credentials1, &s->credentials2,
-                         s->mach_pwd, &s->credentials3, s->negotiate_flags);
-
        /* auth2 request arguments */
        s->a.in.server_name      = s->r.in.server_name;
        s->a.in.account_name     = cli_credentials_get_username(s->credentials);
        s->a.in.secure_channel_type =
                cli_credentials_get_secure_channel_type(s->credentials);
        s->a.in.computer_name    = cli_credentials_get_workstation(s->credentials);
-       s->a.in.negotiate_flags  = &s->negotiate_flags;
+       s->a.in.negotiate_flags  = &s->local_negotiate_flags;
        s->a.in.credentials      = &s->credentials3;
-       s->a.out.negotiate_flags = &s->negotiate_flags;
-       s->a.out.credentials     = &s->credentials3;
-
+       s->a.out.negotiate_flags = &s->remote_negotiate_flags;
+       s->a.out.return_credentials     = &s->credentials3;
+
+       s->creds = netlogon_creds_client_init(s, 
+                                             s->a.in.account_name, 
+                                             s->a.in.computer_name,
+                                             s->a.in.secure_channel_type,
+                                             &s->credentials1, &s->credentials2,
+                                             s->mach_pwd, &s->credentials3,
+                                             s->local_negotiate_flags);
+       if (composite_nomem(s->creds, c)) {
+               return;
+       }
        /*
          authenticate on the netlogon pipe - a rpc request over secondary pipe
        */
-       srv_auth2_req = dcerpc_netr_ServerAuthenticate2_send(s->pipe2, c, &s->a);
-       if (composite_nomem(srv_auth2_req, c)) return;
+       subreq = dcerpc_netr_ServerAuthenticate2_r_send(s, c->event_ctx,
+                                                       s->pipe2->binding_handle,
+                                                       &s->a);
+       if (composite_nomem(subreq, c)) return;
 
-       composite_continue_rpc(c, srv_auth2_req, continue_srv_auth2, c);
+       tevent_req_set_callback(subreq, continue_srv_auth2, c);
 }
 
 
@@ -193,20 +210,88 @@ static void continue_srv_challenge(struct rpc_request *req)
   Stage 6 of schannel_key: Receive authentication request result and verify
   received credentials
 */
-static void continue_srv_auth2(struct rpc_request *req)
+static void continue_srv_auth2(struct tevent_req *subreq)
 {
        struct composite_context *c;
        struct schannel_key_state *s;
 
-       c = talloc_get_type(req->async.private_data, struct composite_context);
+       c = tevent_req_callback_data(subreq, struct composite_context);
        s = talloc_get_type(c->private_data, struct schannel_key_state);
 
        /* receive rpc request result - auth2 credentials */ 
-       c->status = dcerpc_ndr_request_recv(req);
+       c->status = dcerpc_netr_ServerAuthenticate2_r_recv(subreq, s);
+       TALLOC_FREE(subreq);
        if (!composite_is_ok(c)) return;
 
+       if (!NT_STATUS_EQUAL(s->a.out.result, NT_STATUS_ACCESS_DENIED) &&
+           !NT_STATUS_IS_OK(s->a.out.result)) {
+               composite_error(c, s->a.out.result);
+               return;
+       }
+
+       /*
+        * Strong keys could be unsupported (NT4) or disables. So retry with the
+        * flags returned by the server. - asn
+        */
+       if (NT_STATUS_EQUAL(s->a.out.result, NT_STATUS_ACCESS_DENIED)) {
+               uint32_t lf = s->local_negotiate_flags;
+               const char *ln = NULL;
+               uint32_t rf = s->remote_negotiate_flags;
+               const char *rn = NULL;
+
+               if (!s->dcerpc_schannel_auto) {
+                       composite_error(c, s->a.out.result);
+                       return;
+               }
+               s->dcerpc_schannel_auto = false;
+
+               if (lf & NETLOGON_NEG_SUPPORTS_AES)  {
+                       ln = "aes";
+                       if (rf & NETLOGON_NEG_SUPPORTS_AES) {
+                               composite_error(c, s->a.out.result);
+                               return;
+                       }
+               } else if (lf & NETLOGON_NEG_STRONG_KEYS) {
+                       ln = "strong";
+                       if (rf & NETLOGON_NEG_STRONG_KEYS) {
+                               composite_error(c, s->a.out.result);
+                               return;
+                       }
+               } else {
+                       ln = "des";
+               }
+
+               if (rf & NETLOGON_NEG_SUPPORTS_AES)  {
+                       rn = "aes";
+               } else if (rf & NETLOGON_NEG_STRONG_KEYS) {
+                       rn = "strong";
+               } else {
+                       rn = "des";
+               }
+
+               DEBUG(3, ("Server doesn't support %s keys, downgrade to %s"
+                         "and retry! local[0x%08X] remote[0x%08X]\n",
+                         ln, rn, lf, rf));
+
+               s->local_negotiate_flags = s->remote_negotiate_flags;
+
+               generate_random_buffer(s->credentials1.data,
+                                      sizeof(s->credentials1.data));
+
+               subreq = dcerpc_netr_ServerReqChallenge_r_send(s,
+                                                              c->event_ctx,
+                                                              s->pipe2->binding_handle,
+                                                              &s->r);
+               if (composite_nomem(subreq, c)) return;
+
+               tevent_req_set_callback(subreq, continue_srv_challenge, c);
+               return;
+       }
+
+       s->creds->negotiate_flags = s->remote_negotiate_flags;
+
        /* verify credentials */
-       if (!creds_client_check(s->creds, s->a.out.credentials)) {
+       if (!netlogon_creds_client_check(s->creds, s->a.out.return_credentials)) {
                composite_error(c, NT_STATUS_UNSUCCESSFUL);
                return;
        }
@@ -217,7 +302,6 @@ static void continue_srv_auth2(struct rpc_request *req)
        composite_done(c);
 }
 
-
 /*
   Initiate establishing a schannel key using netlogon challenge
   on a secondary pipe
@@ -230,6 +314,7 @@ struct composite_context *dcerpc_schannel_key_send(TALLOC_CTX *mem_ctx,
        struct composite_context *c;
        struct schannel_key_state *s;
        struct composite_context *epm_map_req;
+       enum netr_SchannelType schannel_type = cli_credentials_get_secure_channel_type(credentials);
        
        /* composite context allocation and setup */
        c = composite_create(mem_ctx, p->conn->event_ctx);
@@ -242,20 +327,29 @@ struct composite_context *dcerpc_schannel_key_send(TALLOC_CTX *mem_ctx,
        /* store parameters in the state structure */
        s->pipe        = p;
        s->credentials = credentials;
+       s->local_negotiate_flags = NETLOGON_NEG_AUTH2_FLAGS;
 
        /* allocate credentials */
-       s->creds = talloc(c, struct creds_CredentialState);
-       if (composite_nomem(s->creds, c)) return c;
+       if (s->pipe->conn->flags & DCERPC_SCHANNEL_128) {
+               s->local_negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS;
+       }
+       if (s->pipe->conn->flags & DCERPC_SCHANNEL_AES) {
+               s->local_negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS;
+               s->local_negotiate_flags |= NETLOGON_NEG_SUPPORTS_AES;
+       }
+       if (s->pipe->conn->flags & DCERPC_SCHANNEL_AUTO) {
+               s->local_negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS;
+               s->local_negotiate_flags |= NETLOGON_NEG_SUPPORTS_AES;
+               s->dcerpc_schannel_auto = true;
+       }
 
        /* type of authentication depends on schannel type */
-       if (s->pipe->conn->flags & DCERPC_SCHANNEL_128) {
-               s->negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS;
-       } else {
-               s->negotiate_flags = NETLOGON_NEG_AUTH2_FLAGS;
+       if (schannel_type == SEC_CHAN_RODC) {
+               s->local_negotiate_flags |= NETLOGON_NEG_RODC_PASSTHROUGH;
        }
 
        /* allocate binding structure */
-       s->binding = talloc(c, struct dcerpc_binding);
+       s->binding = talloc_zero(c, struct dcerpc_binding);
        if (composite_nomem(s->binding, c)) return c;
 
        *s->binding = *s->pipe->binding;
@@ -290,6 +384,12 @@ struct auth_schannel_state {
        const struct ndr_interface_table *table;
        struct loadparm_context *lp_ctx;
        uint8_t auth_level;
+       struct netlogon_creds_CredentialState *creds_state;
+       struct netlogon_creds_CredentialState save_creds_state;
+       struct netr_Authenticator auth;
+       struct netr_Authenticator return_auth;
+       union netr_Capabilities capabilities;
+       struct netr_LogonGetCapabilities c;
 };
 
 
@@ -307,18 +407,18 @@ static void continue_schannel_key(struct composite_context *ctx)
                                                      struct composite_context);
        struct auth_schannel_state *s = talloc_get_type(c->private_data,
                                                        struct auth_schannel_state);
+       NTSTATUS status;
 
        /* receive schannel key */
-       c->status = dcerpc_schannel_key_recv(ctx);
+       status = c->status = dcerpc_schannel_key_recv(ctx);
        if (!composite_is_ok(c)) {
-               DEBUG(1, ("Failed to setup credentials for account %s: %s\n",
-                         cli_credentials_get_username(s->credentials), nt_errstr(c->status)));
+               DEBUG(1, ("Failed to setup credentials: %s\n", nt_errstr(status)));
                return;
        }
 
        /* send bind auth request with received creds */
        auth_req = dcerpc_bind_auth_send(c, s->pipe, s->table, s->credentials, 
-                                        s->lp_ctx,
+                                        lpcfg_gensec_settings(c, s->lp_ctx),
                                         DCERPC_AUTH_TYPE_SCHANNEL, s->auth_level,
                                         NULL);
        if (composite_nomem(auth_req, c)) return;
@@ -335,10 +435,117 @@ static void continue_bind_auth(struct composite_context *ctx)
 {
        struct composite_context *c = talloc_get_type(ctx->async.private_data,
                                                      struct composite_context);
+       struct auth_schannel_state *s = talloc_get_type(c->private_data,
+                                                       struct auth_schannel_state);
+       struct tevent_req *subreq;
 
        c->status = dcerpc_bind_auth_recv(ctx);
        if (!composite_is_ok(c)) return;
 
+       /* if we have a AES encrypted connection, verify the capabilities */
+       if (ndr_syntax_id_equal(&s->table->syntax_id,
+                               &ndr_table_netlogon.syntax_id)) {
+               ZERO_STRUCT(s->return_auth);
+
+               s->creds_state = cli_credentials_get_netlogon_creds(s->credentials);
+               if (composite_nomem(s->creds_state, c)) return;
+
+               s->save_creds_state = *s->creds_state;
+               netlogon_creds_client_authenticator(&s->save_creds_state, &s->auth);
+
+               s->c.in.server_name = talloc_asprintf(c,
+                                                     "\\\\%s",
+                                                     dcerpc_server_name(s->pipe));
+               if (composite_nomem(s->c.in.server_name, c)) return;
+               s->c.in.computer_name         = cli_credentials_get_workstation(s->credentials);
+               s->c.in.credential            = &s->auth;
+               s->c.in.return_authenticator  = &s->return_auth;
+               s->c.in.query_level           = 1;
+
+               s->c.out.capabilities         = &s->capabilities;
+               s->c.out.return_authenticator = &s->return_auth;
+
+               DEBUG(5, ("We established a AES connection, verifying logon "
+                         "capabilities\n"));
+
+               subreq = dcerpc_netr_LogonGetCapabilities_r_send(s,
+                                                                c->event_ctx,
+                                                                s->pipe->binding_handle,
+                                                                &s->c);
+               if (composite_nomem(subreq, c)) return;
+
+               tevent_req_set_callback(subreq, continue_get_capabilities, c);
+               return;
+       }
+
+       composite_done(c);
+}
+
+/*
+  Stage 4 of auth_schannel: Get the Logon Capablities and verify them.
+*/
+static void continue_get_capabilities(struct tevent_req *subreq)
+{
+       struct composite_context *c;
+       struct auth_schannel_state *s;
+
+       c = tevent_req_callback_data(subreq, struct composite_context);
+       s = talloc_get_type(c->private_data, struct auth_schannel_state);
+
+       /* receive rpc request result */
+       c->status = dcerpc_netr_LogonGetCapabilities_r_recv(subreq, s);
+       TALLOC_FREE(subreq);
+       if (NT_STATUS_EQUAL(c->status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
+               if (s->creds_state->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) {
+                       composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE);
+                       return;
+               } else {
+                       /* This is probably NT */
+                       composite_done(c);
+                       return;
+               }
+       } else if (!composite_is_ok(c)) {
+               return;
+       }
+
+       if (NT_STATUS_EQUAL(s->c.out.result, NT_STATUS_NOT_IMPLEMENTED)) {
+               if (s->creds_state->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) {
+                       /* This means AES isn't supported. */
+                       composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE);
+                       return;
+               }
+
+               /* This is probably an old Samba version */
+               composite_done(c);
+               return;
+       }
+
+       /* verify credentials */
+       if (!netlogon_creds_client_check(&s->save_creds_state,
+                                        &s->c.out.return_authenticator->cred)) {
+               composite_error(c, NT_STATUS_UNSUCCESSFUL);
+               return;
+       }
+
+       *s->creds_state = s->save_creds_state;
+
+       if (!NT_STATUS_IS_OK(s->c.out.result)) {
+               composite_error(c, s->c.out.result);
+               return;
+       }
+
+       /* compare capabilities */
+       if (s->creds_state->negotiate_flags != s->capabilities.server_capabilities) {
+               DEBUG(2, ("The client capabilities don't match the server "
+                         "capabilities: local[0x%08X] remote[0x%08X]\n",
+                         s->creds_state->negotiate_flags,
+                         s->capabilities.server_capabilities));
+               composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE);
+               return;
+       }
+
+       /* TODO: Add downgrade dectection. */
+
        composite_done(c);
 }
 
@@ -396,7 +603,7 @@ NTSTATUS dcerpc_bind_auth_schannel_recv(struct composite_context *c)
 /*
   Perform schannel authenticated bind - sync version
  */
-NTSTATUS dcerpc_bind_auth_schannel(TALLOC_CTX *tmp_ctx, 
+_PUBLIC_ NTSTATUS dcerpc_bind_auth_schannel(TALLOC_CTX *tmp_ctx, 
                                   struct dcerpc_pipe *p,
                                   const struct ndr_interface_table *table,
                                   struct cli_credentials *credentials,