s4:auth/gensec/spnego: add support for fragmented spnego messages
[samba.git] / source4 / auth / gensec / spnego.c
index bbcba8dc5f14211f347ece1bc6178507e072824d..fa20c45a46a02758d426265ba31ae05306c7846f 100644 (file)
 #include "auth/credentials/credentials.h"
 #include "auth/gensec/gensec.h"
 #include "auth/gensec/gensec_proto.h"
+#include "auth/gensec/gensec_toplevel_proto.h"
 #include "param/param.h"
+#include "lib/util/asn1.h"
+
+_PUBLIC_ NTSTATUS gensec_spnego_init(void);
 
 enum spnego_state_position {
        SPNEGO_SERVER_START,
@@ -48,6 +52,16 @@ struct spnego_state {
        const char *neg_oid;
 
        DATA_BLOB mech_types;
+
+       /*
+        * The following is used to implement
+        * the update token fragmentation
+        */
+       size_t in_needed;
+       DATA_BLOB in_frag;
+       size_t out_max_length;
+       DATA_BLOB out_frag;
+       NTSTATUS out_status;
 };
 
 
@@ -55,7 +69,7 @@ static NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_securi
 {
        struct spnego_state *spnego_state;
 
-       spnego_state = talloc(gensec_security, struct spnego_state);
+       spnego_state = talloc_zero(gensec_security, struct spnego_state);
        if (!spnego_state) {
                return NT_STATUS_NO_MEMORY;
        }
@@ -65,6 +79,8 @@ static NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_securi
        spnego_state->sub_sec_security = NULL;
        spnego_state->no_response_expected = false;
        spnego_state->mech_types = data_blob(NULL, 0);
+       spnego_state->out_max_length = gensec_max_update_size(gensec_security);
+       spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
 
        gensec_security->private_data = spnego_state;
        return NT_STATUS_OK;
@@ -74,7 +90,7 @@ static NTSTATUS gensec_spnego_server_start(struct gensec_security *gensec_securi
 {
        struct spnego_state *spnego_state;
 
-       spnego_state = talloc(gensec_security, struct spnego_state);            
+       spnego_state = talloc_zero(gensec_security, struct spnego_state);
        if (!spnego_state) {
                return NT_STATUS_NO_MEMORY;
        }
@@ -84,6 +100,8 @@ static NTSTATUS gensec_spnego_server_start(struct gensec_security *gensec_securi
        spnego_state->sub_sec_security = NULL;
        spnego_state->no_response_expected = false;
        spnego_state->mech_types = data_blob(NULL, 0);
+       spnego_state->out_max_length = gensec_max_update_size(gensec_security);
+       spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
 
        gensec_security->private_data = spnego_state;
        return NT_STATUS_OK;
@@ -93,7 +111,6 @@ static NTSTATUS gensec_spnego_server_start(struct gensec_security *gensec_securi
   wrappers for the spnego_*() functions
 */
 static NTSTATUS gensec_spnego_unseal_packet(struct gensec_security *gensec_security, 
-                                           TALLOC_CTX *mem_ctx, 
                                            uint8_t *data, size_t length, 
                                            const uint8_t *whole_pdu, size_t pdu_length, 
                                            const DATA_BLOB *sig)
@@ -106,14 +123,12 @@ static NTSTATUS gensec_spnego_unseal_packet(struct gensec_security *gensec_secur
        }
        
        return gensec_unseal_packet(spnego_state->sub_sec_security, 
-                                   mem_ctx, 
                                    data, length, 
                                    whole_pdu, pdu_length,
                                    sig); 
 }
 
 static NTSTATUS gensec_spnego_check_packet(struct gensec_security *gensec_security, 
-                                          TALLOC_CTX *mem_ctx, 
                                           const uint8_t *data, size_t length, 
                                           const uint8_t *whole_pdu, size_t pdu_length, 
                                           const DATA_BLOB *sig)
@@ -126,7 +141,6 @@ static NTSTATUS gensec_spnego_check_packet(struct gensec_security *gensec_securi
        }
        
        return gensec_check_packet(spnego_state->sub_sec_security, 
-                                  mem_ctx, 
                                   data, length, 
                                   whole_pdu, pdu_length,
                                   sig);
@@ -296,6 +310,7 @@ static size_t gensec_spnego_max_wrapped_size(struct gensec_security *gensec_secu
 }
 
 static NTSTATUS gensec_spnego_session_key(struct gensec_security *gensec_security, 
+                                         TALLOC_CTX *mem_ctx,
                                          DATA_BLOB *session_key)
 {
        struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
@@ -304,11 +319,13 @@ static NTSTATUS gensec_spnego_session_key(struct gensec_security *gensec_securit
        }
        
        return gensec_session_key(spnego_state->sub_sec_security, 
+                                 mem_ctx,
                                  session_key);
 }
 
 static NTSTATUS gensec_spnego_session_info(struct gensec_security *gensec_security,
-                                                                     struct auth_session_info **session_info) 
+                                          TALLOC_CTX *mem_ctx,
+                                          struct auth_session_info **session_info)
 {
        struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
        if (!spnego_state->sub_sec_security) {
@@ -316,6 +333,7 @@ static NTSTATUS gensec_spnego_session_info(struct gensec_security *gensec_securi
        }
        
        return gensec_session_info(spnego_state->sub_sec_security, 
+                                  mem_ctx,
                                   session_info);
 }
 
@@ -328,6 +346,7 @@ static NTSTATUS gensec_spnego_session_info(struct gensec_security *gensec_securi
 
 static NTSTATUS gensec_spnego_server_try_fallback(struct gensec_security *gensec_security, 
                                                  struct spnego_state *spnego_state,
+                                                 struct tevent_context *ev,
                                                  TALLOC_CTX *out_mem_ctx, 
                                                  const DATA_BLOB in, DATA_BLOB *out) 
 {
@@ -381,7 +400,7 @@ static NTSTATUS gensec_spnego_server_try_fallback(struct gensec_security *gensec
                        return nt_status;
                }
                nt_status = gensec_update(spnego_state->sub_sec_security,
-                                         out_mem_ctx, in, out);
+                                         ev, out_mem_ctx, in, out);
                return nt_status;
        }
        DEBUG(1, ("Failed to parse SPNEGO request\n"));
@@ -397,6 +416,7 @@ static NTSTATUS gensec_spnego_server_try_fallback(struct gensec_security *gensec
 static NTSTATUS gensec_spnego_parse_negTokenInit(struct gensec_security *gensec_security,
                                                 struct spnego_state *spnego_state, 
                                                 TALLOC_CTX *out_mem_ctx, 
+                                                struct tevent_context *ev,
                                                 const char **mechType,
                                                 const DATA_BLOB unwrapped_in, DATA_BLOB *unwrapped_out) 
 {
@@ -420,9 +440,13 @@ static NTSTATUS gensec_spnego_parse_negTokenInit(struct gensec_security *gensec_
        }
 
        if (spnego_state->state_position == SPNEGO_SERVER_START) {
-               for (i=0; all_sec && all_sec[i].op; i++) {
-                       /* optomisitic token */
-                       if (strcmp(all_sec[i].oid, mechType[0]) == 0) {
+               uint32_t j;
+               for (j=0; mechType && mechType[j]; j++) {
+                       for (i=0; all_sec && all_sec[i].op; i++) {
+                               if (strcmp(mechType[j], all_sec[i].oid) != 0) {
+                                       continue;
+                               }
+
                                nt_status = gensec_subcontext_start(spnego_state,
                                                                    gensec_security,
                                                                    &spnego_state->sub_sec_security);
@@ -437,9 +461,18 @@ static NTSTATUS gensec_spnego_parse_negTokenInit(struct gensec_security *gensec_
                                        spnego_state->sub_sec_security = NULL;
                                        break;
                                }
-                               
+
+                               if (j > 0) {
+                                       /* no optimistic token */
+                                       spnego_state->neg_oid = all_sec[i].oid;
+                                       *unwrapped_out = data_blob_null;
+                                       nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+                                       break;
+                               }
+
                                nt_status = gensec_update(spnego_state->sub_sec_security,
                                                          out_mem_ctx, 
+                                                         ev,
                                                          unwrapped_in,
                                                          unwrapped_out);
                                if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_PARAMETER) || 
@@ -456,10 +489,18 @@ static NTSTATUS gensec_spnego_parse_negTokenInit(struct gensec_security *gensec_
                                spnego_state->neg_oid = all_sec[i].oid;
                                break;
                        }
+                       if (spnego_state->sub_sec_security) {
+                               break;
+                       }
+               }
+
+               if (!spnego_state->sub_sec_security) {
+                       DEBUG(1, ("SPNEGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n"));
+                       return NT_STATUS_INVALID_PARAMETER;
                }
        }
        
-       /* Having tried any optomisitc token from the client (if we
+       /* Having tried any optimistic token from the client (if we
         * were the server), if we didn't get anywhere, walk our list
         * in our preference order */
        
@@ -485,6 +526,7 @@ static NTSTATUS gensec_spnego_parse_negTokenInit(struct gensec_security *gensec_
                        /* only get the helping start blob for the first OID */
                        nt_status = gensec_update(spnego_state->sub_sec_security,
                                                  out_mem_ctx, 
+                                                 ev,
                                                  null_data_blob, 
                                                  unwrapped_out);
 
@@ -495,10 +537,12 @@ static NTSTATUS gensec_spnego_parse_negTokenInit(struct gensec_security *gensec_
                         * of this mech */
                        if (spnego_state->state_position != SPNEGO_SERVER_START) {
                                if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_PARAMETER) || 
+                                   NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_LOGON_SERVERS) ||
+                                   NT_STATUS_EQUAL(nt_status, NT_STATUS_TIME_DIFFERENCE_AT_DC) ||
                                    NT_STATUS_EQUAL(nt_status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) {
                                        /* Pretend we never started it (lets the first run find some incompatible demand) */
                                        
-                                       DEBUG(1, ("SPNEGO(%s) NEG_TOKEN_INIT failed to parse: %s\n", 
+                                       DEBUG(3, ("SPNEGO(%s) NEG_TOKEN_INIT failed: %s\n",
                                                  spnego_state->sub_sec_security->ops->name, nt_errstr(nt_status)));
                                        talloc_free(spnego_state->sub_sec_security);
                                        spnego_state->sub_sec_security = NULL;
@@ -558,6 +602,7 @@ static NTSTATUS gensec_spnego_parse_negTokenInit(struct gensec_security *gensec_
 static NTSTATUS gensec_spnego_create_negTokenInit(struct gensec_security *gensec_security, 
                                                  struct spnego_state *spnego_state,
                                                  TALLOC_CTX *out_mem_ctx, 
+                                                 struct tevent_context *ev,
                                                  const DATA_BLOB in, DATA_BLOB *out) 
 {
        int i;
@@ -566,7 +611,6 @@ static NTSTATUS gensec_spnego_create_negTokenInit(struct gensec_security *gensec
        const char **mechTypes = NULL;
        DATA_BLOB unwrapped_out = data_blob(NULL, 0);
        const struct gensec_security_ops_wrapper *all_sec;
-       const char *principal = NULL;
 
        mechTypes = gensec_security_oids(gensec_security, 
                                         out_mem_ctx, GENSEC_OID_SPNEGO);
@@ -599,6 +643,7 @@ static NTSTATUS gensec_spnego_create_negTokenInit(struct gensec_security *gensec
                if (spnego_state->state_position == SPNEGO_CLIENT_START) {
                        nt_status = gensec_update(spnego_state->sub_sec_security,
                                                  out_mem_ctx, 
+                                                 ev,
                                                  null_data_blob,
                                                  &unwrapped_out);
                        
@@ -633,15 +678,8 @@ static NTSTATUS gensec_spnego_create_negTokenInit(struct gensec_security *gensec
                spnego_out.negTokenInit.reqFlagsPadding = 0;
                
                if (spnego_state->state_position == SPNEGO_SERVER_START) {
-                       /* server credentials */
-                       struct cli_credentials *creds = gensec_get_credentials(gensec_security);
-                       if (creds) {
-                               principal = cli_credentials_get_principal(creds, out_mem_ctx);
-                       }
-               }
-               if (principal) {
                        spnego_out.negTokenInit.mechListMIC
-                               = data_blob_string_const(principal);
+                               = data_blob_string_const(ADS_IGNORE_PRINCIPAL);
                } else {
                        spnego_out.negTokenInit.mechListMIC = null_data_blob;
                }
@@ -721,6 +759,7 @@ static NTSTATUS gensec_spnego_server_negTokenTarg(struct gensec_security *gensec
 
 
 static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx, 
+                                    struct tevent_context *ev,
                                     const DATA_BLOB in, DATA_BLOB *out) 
 {
        struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
@@ -742,7 +781,7 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
 
        switch (spnego_state->state_position) {
        case SPNEGO_FALLBACK:
-               return gensec_update(spnego_state->sub_sec_security,
+               return gensec_update(spnego_state->sub_sec_security, ev,
                                     out_mem_ctx, in, out);
        case SPNEGO_SERVER_START:
        {
@@ -751,8 +790,8 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
 
                        len = spnego_read_data(gensec_security, in, &spnego);
                        if (len == -1) {
-                               return gensec_spnego_server_try_fallback(gensec_security, spnego_state, 
-                                                                        out_mem_ctx, in, out);
+                               return gensec_spnego_server_try_fallback(gensec_security, spnego_state,
+                                                                        out_mem_ctx, ev, in, out);
                        }
                        /* client sent NegTargetInit, we send NegTokenTarg */
 
@@ -768,6 +807,7 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
                        nt_status = gensec_spnego_parse_negTokenInit(gensec_security,
                                                                     spnego_state,
                                                                     out_mem_ctx, 
+                                                                    ev,
                                                                     spnego.negTokenInit.mechTypes,
                                                                     spnego.negTokenInit.mechToken, 
                                                                     &unwrapped_out);
@@ -785,7 +825,7 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
                        return nt_status;
                } else {
                        nt_status = gensec_spnego_create_negTokenInit(gensec_security, spnego_state, 
-                                                                     out_mem_ctx, in, out);
+                                                                     out_mem_ctx, ev, in, out);
                        spnego_state->state_position = SPNEGO_SERVER_START;
                        spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT;
                        return nt_status;
@@ -802,7 +842,7 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
                if (!in.length) {
                        /* client to produce negTokenInit */
                        nt_status = gensec_spnego_create_negTokenInit(gensec_security, spnego_state, 
-                                                                out_mem_ctx, in, out);
+                                                                     out_mem_ctx, ev, in, out);
                        spnego_state->state_position = SPNEGO_CLIENT_TARG;
                        spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
                        return nt_status;
@@ -825,9 +865,10 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
                        return NT_STATUS_INVALID_PARAMETER;
                }
 
-               if (spnego.negTokenInit.targetPrincipal) {
+               if (spnego.negTokenInit.targetPrincipal
+                   && strcmp(spnego.negTokenInit.targetPrincipal, ADS_IGNORE_PRINCIPAL) != 0) {
                        DEBUG(5, ("Server claims it's principal name is %s\n", spnego.negTokenInit.targetPrincipal));
-                       if (lp_client_use_spnego_principal(gensec_security->settings->lp_ctx)) {
+                       if (lpcfg_client_use_spnego_principal(gensec_security->settings->lp_ctx)) {
                                gensec_set_target_principal(gensec_security, spnego.negTokenInit.targetPrincipal);
                        }
                }
@@ -835,6 +876,7 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
                nt_status = gensec_spnego_parse_negTokenInit(gensec_security,
                                                             spnego_state,
                                                             out_mem_ctx, 
+                                                            ev,
                                                             spnego.negTokenInit.mechTypes,
                                                             spnego.negTokenInit.mechToken, 
                                                             &unwrapped_out);
@@ -902,13 +944,12 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
                }
 
                nt_status = gensec_update(spnego_state->sub_sec_security,
-                                         out_mem_ctx, 
+                                         out_mem_ctx, ev,
                                          spnego.negTokenTarg.responseToken,
                                          &unwrapped_out);
                if (NT_STATUS_IS_OK(nt_status) && spnego.negTokenTarg.mechListMIC.length > 0) {
                        new_spnego = true;
                        nt_status = gensec_check_packet(spnego_state->sub_sec_security,
-                                                       out_mem_ctx,
                                                        spnego_state->mech_types.data,
                                                        spnego_state->mech_types.length,
                                                        spnego_state->mech_types.data,
@@ -999,7 +1040,7 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
                        }
 
                        nt_status = gensec_update(spnego_state->sub_sec_security,
-                                                 out_mem_ctx, 
+                                                 out_mem_ctx, ev,
                                                  spnego.negTokenTarg.responseToken,
                                                  &unwrapped_out);
                        spnego_state->neg_oid = talloc_strdup(spnego_state, spnego.negTokenTarg.supportedMech);
@@ -1015,7 +1056,6 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
                        }
                        if (NT_STATUS_IS_OK(nt_status) && spnego.negTokenTarg.mechListMIC.length > 0) {
                                nt_status = gensec_check_packet(spnego_state->sub_sec_security,
-                                                               out_mem_ctx,
                                                                spnego_state->mech_types.data,
                                                                spnego_state->mech_types.length,
                                                                spnego_state->mech_types.data,
@@ -1030,7 +1070,7 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
                        bool new_spnego = false;
 
                        nt_status = gensec_update(spnego_state->sub_sec_security,
-                                                 out_mem_ctx, 
+                                                 out_mem_ctx, ev,
                                                  spnego.negTokenTarg.responseToken, 
                                                  &unwrapped_out);
 
@@ -1105,6 +1145,193 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
        return NT_STATUS_INVALID_PARAMETER;
 }
 
+static NTSTATUS gensec_spnego_update_in(struct gensec_security *gensec_security,
+                                       const DATA_BLOB in, DATA_BLOB *full_in)
+{
+       struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
+       size_t expected;
+       uint8_t *buf;
+       NTSTATUS status;
+       bool ok;
+
+       *full_in = data_blob_null;
+
+       if (spnego_state->in_needed == 0) {
+               size_t size = 0;
+
+               /*
+                * try to work out the size of the full
+                * input token, it might be fragmented
+                */
+               status = asn1_peek_full_tag(in,  ASN1_APPLICATION(0), &size);
+               if (!NT_STATUS_IS_OK(status) &&
+                   !NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+                       status = asn1_peek_full_tag(in, ASN1_CONTEXT(1), &size);
+               }
+
+               if (NT_STATUS_IS_OK(status) ||
+                   NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+                       spnego_state->in_needed = size;
+               } else {
+                       /*
+                        * If it is not an asn1 message
+                        * just call the next layer.
+                        */
+                       spnego_state->in_needed = in.length;
+               }
+       }
+
+       if (spnego_state->in_needed > UINT16_MAX) {
+               /*
+                * limit the incoming message to 0xFFFF
+                * to avoid DoS attacks.
+                */
+               return NT_STATUS_INVALID_BUFFER_SIZE;
+       }
+
+       if ((spnego_state->in_needed > 0) && (in.length == 0)) {
+               /*
+                * If we reach this, we know we got at least
+                * part of an asn1 message, getting 0 means
+                * the remote peer wants us to spin.
+                */
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       expected = spnego_state->in_needed - spnego_state->in_frag.length;
+       if (in.length > expected) {
+               /*
+                * we got more than expected
+                */
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       if (in.length == spnego_state->in_needed) {
+               /*
+                * if the in.length contains the full blob
+                * we are done.
+                *
+                * Note: this implies spnego_state->in_frag.length == 0,
+                *       but we do not need to check this explicitly
+                *       because we already know that we did not get
+                *       more than expected.
+                */
+               *full_in = in;
+               return NT_STATUS_OK;
+       }
+
+       ok = data_blob_append(spnego_state, &spnego_state->in_frag,
+                             in.data, in.length);
+       if (!ok) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       if (spnego_state->in_needed > spnego_state->in_frag.length) {
+               return NT_STATUS_MORE_PROCESSING_REQUIRED;
+       }
+
+       *full_in = spnego_state->in_frag;
+       return NT_STATUS_OK;
+}
+
+static NTSTATUS gensec_spnego_update_out(struct gensec_security *gensec_security,
+                                        TALLOC_CTX *out_mem_ctx,
+                                        DATA_BLOB *_out)
+{
+       struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
+       size_t new_length;
+       uint8_t *buf;
+       DATA_BLOB out = data_blob_null;
+
+       *_out = data_blob_null;
+
+       if (spnego_state->out_frag.length == 0) {
+               return spnego_state->out_status;
+       }
+
+       /*
+        * There is still more data to be delivered
+        * to the remote peer.
+        */
+
+       if (spnego_state->out_frag.length <= spnego_state->out_max_length) {
+               /*
+                * Fast path, we can deliver everything
+                */
+
+               *_out = spnego_state->out_frag;
+               talloc_steal(out_mem_ctx, _out->data);
+               spnego_state->out_frag = data_blob_null;
+               return spnego_state->out_status;
+       }
+
+       out = spnego_state->out_frag;
+
+       /*
+        * copy the remaining bytes
+        */
+       spnego_state->out_frag = data_blob_talloc(spnego_state,
+                                       out.data + spnego_state->out_max_length,
+                                       out.length - spnego_state->out_max_length);
+       if (spnego_state->out_frag.data == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       /*
+        * truncate the buffer
+        */
+       data_blob_realloc(spnego_state, &out, spnego_state->out_max_length);
+
+       talloc_steal(out_mem_ctx, out.data);
+       *_out = out;
+       return NT_STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+static NTSTATUS gensec_spnego_update_wrapper(struct gensec_security *gensec_security,
+                                            TALLOC_CTX *out_mem_ctx,
+                                            struct tevent_context *ev,
+                                            const DATA_BLOB in, DATA_BLOB *out)
+{
+       struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
+       DATA_BLOB full_in = data_blob_null;
+       NTSTATUS status;
+
+       *out = data_blob_null;
+
+       if (spnego_state->out_frag.length > 0) {
+               if (in.length > 0) {
+                       return NT_STATUS_INVALID_PARAMETER;
+               }
+
+               return gensec_spnego_update_out(gensec_security,
+                                               out_mem_ctx,
+                                               out);
+       }
+
+       status = gensec_spnego_update_in(gensec_security,
+                                        in, &full_in);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       status = gensec_spnego_update(gensec_security,
+                                     spnego_state, ev,
+                                     full_in,
+                                     &spnego_state->out_frag);
+       data_blob_free(&spnego_state->in_frag);
+       spnego_state->in_needed = 0;
+       if (!NT_STATUS_IS_OK(status) &&
+           !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+               return status;
+       }
+
+       spnego_state->out_status = status;
+
+       return gensec_spnego_update_out(gensec_security,
+                                       out_mem_ctx,
+                                       out);
+}
+
 static void gensec_spnego_want_feature(struct gensec_security *gensec_security,
                                       uint32_t feature)
 {
@@ -1143,7 +1370,7 @@ static const struct gensec_security_ops gensec_spnego_security_ops = {
        .oid              = gensec_spnego_oids,
        .client_start     = gensec_spnego_client_start,
        .server_start     = gensec_spnego_server_start,
-       .update           = gensec_spnego_update,
+       .update           = gensec_spnego_update_wrapper,
        .seal_packet      = gensec_spnego_seal_packet,
        .sign_packet      = gensec_spnego_sign_packet,
        .sig_size         = gensec_spnego_sig_size,