X-Git-Url: http://git.samba.org/samba.git/?a=blobdiff_plain;f=auth%2Fgensec%2Fspnego.c;h=0b3fbdce7acd1b44a85f1109d4e2961e0d5668e0;hb=490756a8401189550aa549d2eb1600c30e1c5d30;hp=484f49175fca486ea415e24987bf72d3b82593d4;hpb=3cfa58de1256de94461e9e303984030fa6483568;p=nivanova%2Fsamba-autobuild%2F.git diff --git a/auth/gensec/spnego.c b/auth/gensec/spnego.c index 484f49175fc..0b3fbdce7ac 100644 --- a/auth/gensec/spnego.c +++ b/auth/gensec/spnego.c @@ -23,6 +23,8 @@ */ #include "includes.h" +#include +#include "lib/util/tevent_ntstatus.h" #include "../libcli/auth/spnego.h" #include "librpc/gen_ndr/ndr_dcerpc.h" #include "auth/credentials/credentials.h" @@ -32,6 +34,9 @@ #include "lib/util/asn1.h" #include "lib/util/base64.h" +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + #undef strcasecmp _PUBLIC_ NTSTATUS gensec_spnego_init(TALLOC_CTX *ctx); @@ -45,11 +50,79 @@ enum spnego_state_position { SPNEGO_DONE }; +struct spnego_state; +struct spnego_neg_ops; +struct spnego_neg_state; + +struct spnego_neg_state { + const struct spnego_neg_ops *ops; + const struct gensec_security_ops_wrapper *all_sec; + size_t all_idx; + const char * const *mech_types; + size_t mech_idx; +}; + +struct spnego_neg_ops { + const char *name; + /* + * The start hook does the initial processing on the incoming paket and + * may starts the first possible subcontext. It indicates that + * gensec_update() is required on the subcontext by returning + * NT_STATUS_MORE_PROCESSING_REQUIRED and return something useful in + * 'in_next'. Note that 'in_mem_ctx' is just passed as a hint, the + * caller should treat 'in_next' as const and don't attempt to free the + * content. NT_STATUS_OK indicates the finish hook should be invoked + * directly withing the need of gensec_update() on the subcontext. + * Every other error indicates an error that's returned to the caller. + */ + NTSTATUS (*start_fn)(struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next); + /* + * The step hook processes the result of a failed gensec_update() and + * can decide to ignore a failure and continue the negotiation by + * setting up the next possible subcontext. It indicates that + * gensec_update() is required on the subcontext by returning + * NT_STATUS_MORE_PROCESSING_REQUIRED and return something useful in + * 'in_next'. Note that 'in_mem_ctx' is just passed as a hint, the + * caller should treat 'in_next' as const and don't attempt to free the + * content. NT_STATUS_OK indicates the finish hook should be invoked + * directly withing the need of gensec_update() on the subcontext. + * Every other error indicates an error that's returned to the caller. + */ + NTSTATUS (*step_fn)(struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next); + /* + * The finish hook processes the result of a successful gensec_update() + * (NT_STATUS_OK or NT_STATUS_MORE_PROCESSING_REQUIRED). It forms the + * response pdu that will be returned from the toplevel gensec_update() + * together with NT_STATUS_OK or NT_STATUS_MORE_PROCESSING_REQUIRED. It + * may also alter the state machine to prepare receiving the next pdu + * from the peer. + */ + NTSTATUS (*finish_fn)(struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out); +}; + struct spnego_state { enum spnego_message_type expected_packet; enum spnego_state_position state_position; struct gensec_security *sub_sec_security; - bool no_response_expected; + bool sub_sec_ready; const char *neg_oid; @@ -75,6 +148,25 @@ struct spnego_state { NTSTATUS out_status; }; +static struct spnego_neg_state *gensec_spnego_neg_state(TALLOC_CTX *mem_ctx, + const struct spnego_neg_ops *ops) +{ + struct spnego_neg_state *n = NULL; + + n = talloc_zero(mem_ctx, struct spnego_neg_state); + if (n == NULL) { + return NULL; + } + n->ops = ops; + + return n; +} + +static void gensec_spnego_reset_sub_sec(struct spnego_state *spnego_state) +{ + spnego_state->sub_sec_ready = false; + TALLOC_FREE(spnego_state->sub_sec_security); +} static NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_security) { @@ -88,8 +180,8 @@ static NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_securi spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT; spnego_state->state_position = SPNEGO_CLIENT_START; spnego_state->sub_sec_security = NULL; - spnego_state->no_response_expected = false; - spnego_state->mech_types = data_blob(NULL, 0); + spnego_state->sub_sec_ready = false; + spnego_state->mech_types = data_blob_null; spnego_state->out_max_length = gensec_max_update_size(gensec_security); spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED; @@ -112,8 +204,8 @@ static NTSTATUS gensec_spnego_server_start(struct gensec_security *gensec_securi spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT; spnego_state->state_position = SPNEGO_SERVER_START; spnego_state->sub_sec_security = NULL; - spnego_state->no_response_expected = false; - spnego_state->mech_types = data_blob(NULL, 0); + spnego_state->sub_sec_ready = false; + spnego_state->mech_types = data_blob_null; spnego_state->out_max_length = gensec_max_update_size(gensec_security); spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED; @@ -124,183 +216,6 @@ static NTSTATUS gensec_spnego_server_start(struct gensec_security *gensec_securi return NT_STATUS_OK; } -/* - wrappers for the spnego_*() functions -*/ -static NTSTATUS gensec_spnego_unseal_packet(struct gensec_security *gensec_security, - uint8_t *data, size_t length, - const uint8_t *whole_pdu, size_t pdu_length, - const DATA_BLOB *sig) -{ - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; - - if (spnego_state->state_position != SPNEGO_DONE - && spnego_state->state_position != SPNEGO_FALLBACK) { - return NT_STATUS_INVALID_PARAMETER; - } - - return gensec_unseal_packet(spnego_state->sub_sec_security, - data, length, - whole_pdu, pdu_length, - sig); -} - -static NTSTATUS gensec_spnego_check_packet(struct gensec_security *gensec_security, - const uint8_t *data, size_t length, - const uint8_t *whole_pdu, size_t pdu_length, - const DATA_BLOB *sig) -{ - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; - - if (spnego_state->state_position != SPNEGO_DONE - && spnego_state->state_position != SPNEGO_FALLBACK) { - return NT_STATUS_INVALID_PARAMETER; - } - - return gensec_check_packet(spnego_state->sub_sec_security, - data, length, - whole_pdu, pdu_length, - sig); -} - -static NTSTATUS gensec_spnego_seal_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, - DATA_BLOB *sig) -{ - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; - - if (spnego_state->state_position != SPNEGO_DONE - && spnego_state->state_position != SPNEGO_FALLBACK) { - return NT_STATUS_INVALID_PARAMETER; - } - - return gensec_seal_packet(spnego_state->sub_sec_security, - mem_ctx, - data, length, - whole_pdu, pdu_length, - sig); -} - -static NTSTATUS gensec_spnego_sign_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, - DATA_BLOB *sig) -{ - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; - - if (spnego_state->state_position != SPNEGO_DONE - && spnego_state->state_position != SPNEGO_FALLBACK) { - return NT_STATUS_INVALID_PARAMETER; - } - - return gensec_sign_packet(spnego_state->sub_sec_security, - mem_ctx, - data, length, - whole_pdu, pdu_length, - sig); -} - -static NTSTATUS gensec_spnego_wrap(struct gensec_security *gensec_security, - TALLOC_CTX *mem_ctx, - const DATA_BLOB *in, - DATA_BLOB *out) -{ - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; - - if (spnego_state->state_position != SPNEGO_DONE - && spnego_state->state_position != SPNEGO_FALLBACK) { - DEBUG(1, ("gensec_spnego_wrap: wrong state for wrap\n")); - return NT_STATUS_INVALID_PARAMETER; - } - - return gensec_wrap(spnego_state->sub_sec_security, - mem_ctx, in, out); -} - -static NTSTATUS gensec_spnego_unwrap(struct gensec_security *gensec_security, - TALLOC_CTX *mem_ctx, - const DATA_BLOB *in, - DATA_BLOB *out) -{ - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; - - if (spnego_state->state_position != SPNEGO_DONE - && spnego_state->state_position != SPNEGO_FALLBACK) { - DEBUG(1, ("gensec_spnego_unwrap: wrong state for unwrap\n")); - return NT_STATUS_INVALID_PARAMETER; - } - - return gensec_unwrap(spnego_state->sub_sec_security, - mem_ctx, in, out); -} - -static size_t gensec_spnego_sig_size(struct gensec_security *gensec_security, size_t data_size) -{ - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; - - if (spnego_state->state_position != SPNEGO_DONE - && spnego_state->state_position != SPNEGO_FALLBACK) { - return 0; - } - - return gensec_sig_size(spnego_state->sub_sec_security, data_size); -} - -static size_t gensec_spnego_max_input_size(struct gensec_security *gensec_security) -{ - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; - - if (spnego_state->state_position != SPNEGO_DONE - && spnego_state->state_position != SPNEGO_FALLBACK) { - return 0; - } - - return gensec_max_input_size(spnego_state->sub_sec_security); -} - -static size_t gensec_spnego_max_wrapped_size(struct gensec_security *gensec_security) -{ - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; - - if (spnego_state->state_position != SPNEGO_DONE - && spnego_state->state_position != SPNEGO_FALLBACK) { - return 0; - } - - return gensec_max_wrapped_size(spnego_state->sub_sec_security); -} - -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; - if (!spnego_state->sub_sec_security) { - return NT_STATUS_INVALID_PARAMETER; - } - - return gensec_session_key(spnego_state->sub_sec_security, - mem_ctx, - session_key); -} - -static NTSTATUS gensec_spnego_session_info(struct gensec_security *gensec_security, - 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) { - return NT_STATUS_INVALID_PARAMETER; - } - - return gensec_session_info(spnego_state->sub_sec_security, - mem_ctx, - session_info); -} - /** Fallback to another GENSEC mechanism, based on magic strings * * This is the 'fallback' case, where we don't get SPNEGO, and have to @@ -310,22 +225,23 @@ 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) + TALLOC_CTX *mem_ctx, + const DATA_BLOB in) { int i,j; const struct gensec_security_ops **all_ops; - all_ops = gensec_security_mechs(gensec_security, out_mem_ctx); + all_ops = gensec_security_mechs(gensec_security, mem_ctx); for (i=0; all_ops && all_ops[i]; i++) { bool is_spnego; NTSTATUS nt_status; - if (gensec_security != NULL && - !gensec_security_ops_enabled(all_ops[i], gensec_security)) - continue; + if (gensec_security != NULL && + !gensec_security_ops_enabled(all_ops[i], gensec_security)) + { + continue; + } if (!all_ops[i]->oid) { continue; @@ -365,660 +281,640 @@ static NTSTATUS gensec_spnego_server_try_fallback(struct gensec_security *gensec if (!NT_STATUS_IS_OK(nt_status)) { return nt_status; } - nt_status = gensec_update_ev(spnego_state->sub_sec_security, - ev, out_mem_ctx, in, out); - return nt_status; + + return NT_STATUS_OK; } DEBUG(1, ("Failed to parse SPNEGO request\n")); return NT_STATUS_INVALID_PARAMETER; } -/* - Parse the netTokenInit, either from the client, to the server, or - from the server to the client. -*/ - -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 * const *mechType, - const DATA_BLOB unwrapped_in, DATA_BLOB *unwrapped_out) +static NTSTATUS gensec_spnego_create_negTokenInit_start( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) { - int i; - NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER; - DATA_BLOB null_data_blob = data_blob(NULL,0); - bool ok; - - const struct gensec_security_ops_wrapper *all_sec - = gensec_security_by_oid_list(gensec_security, - out_mem_ctx, - mechType, - GENSEC_OID_SPNEGO); - - ok = spnego_write_mech_types(spnego_state, - mechType, - &spnego_state->mech_types); - if (!ok) { - DEBUG(1, ("SPNEGO: Failed to write mechTypes\n")); + n->mech_idx = 0; + n->mech_types = gensec_security_oids(gensec_security, n, + GENSEC_OID_SPNEGO); + if (n->mech_types == NULL) { + DBG_WARNING("gensec_security_oids() failed\n"); return NT_STATUS_NO_MEMORY; } - if (spnego_state->state_position == SPNEGO_SERVER_START) { - 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); - if (!NT_STATUS_IS_OK(nt_status)) { - return nt_status; - } - /* select the sub context */ - nt_status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, - all_sec[i].op); - if (!NT_STATUS_IS_OK(nt_status)) { - talloc_free(spnego_state->sub_sec_security); - 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; - /* - * Indicate the downgrade and request a - * mic. - */ - spnego_state->downgraded = true; - spnego_state->mic_requested = true; - break; - } - - nt_status = gensec_update_ev(spnego_state->sub_sec_security, - out_mem_ctx, - ev, - unwrapped_in, - unwrapped_out); - if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_PARAMETER) || - 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 contents: %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; - break; - } - - 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; - } + n->all_idx = 0; + n->all_sec = gensec_security_by_oid_list(gensec_security, + n, n->mech_types, + GENSEC_OID_SPNEGO); + if (n->all_sec == NULL) { + DBG_WARNING("gensec_security_by_oid_list() failed\n"); + return NT_STATUS_NO_MEMORY; } - /* 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 */ - - if (!spnego_state->sub_sec_security) { - for (i=0; all_sec && all_sec[i].op; i++) { - nt_status = gensec_subcontext_start(spnego_state, - gensec_security, - &spnego_state->sub_sec_security); - if (!NT_STATUS_IS_OK(nt_status)) { - return nt_status; - } - /* select the sub context */ - nt_status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, - all_sec[i].op); - if (!NT_STATUS_IS_OK(nt_status)) { - talloc_free(spnego_state->sub_sec_security); - spnego_state->sub_sec_security = NULL; - continue; - } - - spnego_state->neg_oid = all_sec[i].oid; - - /* only get the helping start blob for the first OID */ - nt_status = gensec_update_ev(spnego_state->sub_sec_security, - out_mem_ctx, - ev, - null_data_blob, - unwrapped_out); - - /* it is likely that a NULL input token will - * not be liked by most server mechs, but if - * we are in the client, we want the first - * update packet to be able to abort the use - * 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)) { - const char *next = NULL; - const char *principal = NULL; - int dbg_level = DBGLVL_WARNING; - - if (all_sec[i+1].op != NULL) { - next = all_sec[i+1].op->name; - dbg_level = DBGLVL_NOTICE; - } - - if (gensec_security->target.principal != NULL) { - principal = gensec_security->target.principal; - } else if (gensec_security->target.service != NULL && - gensec_security->target.hostname != NULL) - { - principal = talloc_asprintf(spnego_state->sub_sec_security, - "%s/%s", - gensec_security->target.service, - gensec_security->target.hostname); - } else { - principal = gensec_security->target.hostname; - } - - DEBUG(dbg_level, ("SPNEGO(%s) creating NEG_TOKEN_INIT for %s failed (next[%s]): %s\n", - spnego_state->sub_sec_security->ops->name, - principal, - next, nt_errstr(nt_status))); - - /* Pretend we never started it (lets the first run find some incompatible demand) */ - talloc_free(spnego_state->sub_sec_security); - spnego_state->sub_sec_security = NULL; - continue; - } - } + return n->ops->step_fn(gensec_security, spnego_state, n, + spnego_in, NT_STATUS_OK, in_mem_ctx, in_next); +} - break; +static NTSTATUS gensec_spnego_create_negTokenInit_step( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + if (!NT_STATUS_IS_OK(last_status)) { + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + const struct gensec_security_ops_wrapper *next_sec = NULL; + const char *next = NULL; + const char *principal = NULL; + int dbg_level = DBGLVL_WARNING; + NTSTATUS status = last_status; + + if (cur_sec[1].op != NULL) { + next_sec = &cur_sec[1]; } - } - - if (spnego_state->sub_sec_security) { - /* it is likely that a NULL input token will - * not be liked by most server mechs, but this - * does the right thing in the CIFS client. - * just push us along the merry-go-round - * again, and hope for better luck next - * time */ - if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_PARAMETER)) { - *unwrapped_out = data_blob(NULL, 0); - nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED; + if (next_sec != NULL) { + next = next_sec->op->name; + dbg_level = DBGLVL_NOTICE; } - if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_PARAMETER) - && !NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) - && !NT_STATUS_IS_OK(nt_status)) { - DEBUG(1, ("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; - - /* We started the mech correctly, and the - * input from the other side was valid. - * Return the error (say bad password, invalid - * ticket) */ - return nt_status; + if (gensec_security->target.principal != NULL) { + principal = gensec_security->target.principal; + } else if (gensec_security->target.service != NULL && + gensec_security->target.hostname != NULL) + { + principal = talloc_asprintf(spnego_state->sub_sec_security, + "%s/%s", + gensec_security->target.service, + gensec_security->target.hostname); + } else { + principal = gensec_security->target.hostname; } - return nt_status; /* OK, INVALID_PARAMETER ore MORE PROCESSING */ - } - - DEBUG(1, ("SPNEGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n")); - /* we could re-negotiate here, but it would only work - * if the client or server lied about what it could - * support the first time. Lets keep this code to - * reality */ + DBG_PREFIX(dbg_level, ( + "%s: creating NEG_TOKEN_INIT for %s failed " + "(next[%s]): %s\n", cur_sec->op->name, + principal, next, nt_errstr(status))); - return nt_status; -} + if (next == NULL) { + /* + * A hard error without a possible fallback. + */ + return status; + } -/** create a negTokenInit - * - * This is the same packet, no matter if the client or server sends it first, but it is always the first packet -*/ -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; - NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER; - DATA_BLOB null_data_blob = data_blob(NULL,0); - const char **mechTypes = NULL; - DATA_BLOB unwrapped_out = data_blob(NULL, 0); - const struct gensec_security_ops_wrapper *all_sec; + /* + * Pretend we never started it + */ + gensec_spnego_reset_sub_sec(spnego_state); - mechTypes = gensec_security_oids(gensec_security, - out_mem_ctx, GENSEC_OID_SPNEGO); + /* + * And try the next one... + */ + n->all_idx += 1; + } - all_sec = gensec_security_by_oid_list(gensec_security, - out_mem_ctx, - mechTypes, - GENSEC_OID_SPNEGO); - for (i=0; all_sec && all_sec[i].op; i++) { - struct spnego_data spnego_out; - const char **send_mech_types; - bool ok; + for (; n->all_sec[n->all_idx].op != NULL; n->all_idx++) { + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + NTSTATUS status; - nt_status = gensec_subcontext_start(spnego_state, - gensec_security, - &spnego_state->sub_sec_security); - if (!NT_STATUS_IS_OK(nt_status)) { - return nt_status; + status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; } + /* select the sub context */ - nt_status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, - all_sec[i].op); - if (!NT_STATUS_IS_OK(nt_status)) { - talloc_free(spnego_state->sub_sec_security); - spnego_state->sub_sec_security = NULL; + status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, + cur_sec->op); + if (!NT_STATUS_IS_OK(status)) { + gensec_spnego_reset_sub_sec(spnego_state); continue; } /* In the client, try and produce the first (optimistic) packet */ if (spnego_state->state_position == SPNEGO_CLIENT_START) { - nt_status = gensec_update_ev(spnego_state->sub_sec_security, - out_mem_ctx, - ev, - null_data_blob, - &unwrapped_out); - - if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) - && !NT_STATUS_IS_OK(nt_status)) { - const char *next = NULL; - const char *principal = NULL; - int dbg_level = DBGLVL_WARNING; - - if (all_sec[i+1].op != NULL) { - next = all_sec[i+1].op->name; - dbg_level = DBGLVL_NOTICE; - } - - if (gensec_security->target.principal != NULL) { - principal = gensec_security->target.principal; - } else if (gensec_security->target.service != NULL && - gensec_security->target.hostname != NULL) - { - principal = talloc_asprintf(spnego_state->sub_sec_security, - "%s/%s", - gensec_security->target.service, - gensec_security->target.hostname); - } else { - principal = gensec_security->target.hostname; - } - - DEBUG(dbg_level, ("SPNEGO(%s) creating NEG_TOKEN_INIT for %s failed (next[%s]): %s\n", - spnego_state->sub_sec_security->ops->name, - principal, - next, nt_errstr(nt_status))); - talloc_free(spnego_state->sub_sec_security); - spnego_state->sub_sec_security = NULL; - /* Pretend we never started it (lets the first run find some incompatible demand) */ - - continue; - } + *in_next = data_blob_null; + return NT_STATUS_MORE_PROCESSING_REQUIRED; } - spnego_out.type = SPNEGO_NEG_TOKEN_INIT; + *in_next = data_blob_null; + return NT_STATUS_OK; + } - send_mech_types = gensec_security_oids_from_ops_wrapped(out_mem_ctx, - &all_sec[i]); + DBG_WARNING("Failed to setup SPNEGO negTokenInit request\n"); + return NT_STATUS_INVALID_PARAMETER; +} - ok = spnego_write_mech_types(spnego_state, - send_mech_types, - &spnego_state->mech_types); - if (!ok) { - DEBUG(1, ("SPNEGO: Failed to write mechTypes\n")); - return NT_STATUS_NO_MEMORY; - } +static NTSTATUS gensec_spnego_create_negTokenInit_finish( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + struct spnego_data spnego_out; + bool ok; - /* List the remaining mechs as options */ - spnego_out.negTokenInit.mechTypes = send_mech_types; - spnego_out.negTokenInit.reqFlags = null_data_blob; - spnego_out.negTokenInit.reqFlagsPadding = 0; + spnego_out.type = SPNEGO_NEG_TOKEN_INIT; - if (spnego_state->state_position == SPNEGO_SERVER_START) { - spnego_out.negTokenInit.mechListMIC - = data_blob_string_const(ADS_IGNORE_PRINCIPAL); - } else { - spnego_out.negTokenInit.mechListMIC = null_data_blob; - } + n->mech_types = gensec_security_oids_from_ops_wrapped(n, cur_sec); + if (n->mech_types == NULL) { + DBG_WARNING("gensec_security_oids_from_ops_wrapped() failed\n"); + return NT_STATUS_NO_MEMORY; + } - spnego_out.negTokenInit.mechToken = unwrapped_out; + ok = spnego_write_mech_types(spnego_state, + n->mech_types, + &spnego_state->mech_types); + if (!ok) { + DBG_ERR("Failed to write mechTypes\n"); + return NT_STATUS_NO_MEMORY; + } - if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { - DEBUG(1, ("Failed to write NEG_TOKEN_INIT\n")); - return NT_STATUS_INVALID_PARAMETER; - } + /* List the remaining mechs as options */ + spnego_out.negTokenInit.mechTypes = n->mech_types; + spnego_out.negTokenInit.reqFlags = data_blob_null; + spnego_out.negTokenInit.reqFlagsPadding = 0; - /* set next state */ - spnego_state->neg_oid = all_sec[i].oid; + if (spnego_state->state_position == SPNEGO_SERVER_START) { + spnego_out.negTokenInit.mechListMIC + = data_blob_string_const(ADS_IGNORE_PRINCIPAL); + } else { + spnego_out.negTokenInit.mechListMIC = data_blob_null; + } - if (NT_STATUS_IS_OK(nt_status)) { - spnego_state->no_response_expected = true; - } + spnego_out.negTokenInit.mechToken = sub_out; - return NT_STATUS_MORE_PROCESSING_REQUIRED; - } - talloc_free(spnego_state->sub_sec_security); - spnego_state->sub_sec_security = NULL; + if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DBG_ERR("Failed to write NEG_TOKEN_INIT\n"); + return NT_STATUS_INVALID_PARAMETER; + } - DEBUG(10, ("Failed to setup SPNEGO negTokenInit request: %s\n", nt_errstr(nt_status))); - return nt_status; -} + /* + * Note that 'cur_sec' is temporary memory, but + * cur_sec->oid points to a const string in the + * backends gensec_security_ops structure. + */ + spnego_state->neg_oid = cur_sec->oid; + /* set next state */ + if (spnego_state->state_position == SPNEGO_SERVER_START) { + spnego_state->state_position = SPNEGO_SERVER_START; + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT; + } else { + spnego_state->state_position = SPNEGO_CLIENT_TARG; + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + } -/** create a server negTokenTarg - * - * This is the case, where the client is the first one who sends data -*/ + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +static const struct spnego_neg_ops gensec_spnego_create_negTokenInit_ops = { + .name = "create_negTokenInit", + .start_fn = gensec_spnego_create_negTokenInit_start, + .step_fn = gensec_spnego_create_negTokenInit_step, + .finish_fn = gensec_spnego_create_negTokenInit_finish, +}; -static NTSTATUS gensec_spnego_server_negTokenTarg(struct spnego_state *spnego_state, - TALLOC_CTX *out_mem_ctx, - NTSTATUS nt_status, - const DATA_BLOB unwrapped_out, - DATA_BLOB mech_list_mic, - DATA_BLOB *out) +static NTSTATUS gensec_spnego_client_negTokenInit_start( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) { - struct spnego_data spnego_out; - DATA_BLOB null_data_blob = data_blob(NULL, 0); + const char *tp = NULL; - /* compose reply */ - spnego_out.type = SPNEGO_NEG_TOKEN_TARG; - spnego_out.negTokenTarg.responseToken = unwrapped_out; - spnego_out.negTokenTarg.mechListMIC = mech_list_mic; - spnego_out.negTokenTarg.supportedMech = NULL; + /* The server offers a list of mechanisms */ - if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { - spnego_out.negTokenTarg.supportedMech = spnego_state->neg_oid; - if (spnego_state->mic_requested) { - spnego_out.negTokenTarg.negResult = SPNEGO_REQUEST_MIC; - spnego_state->mic_requested = false; - } else { - spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE; - } - spnego_state->state_position = SPNEGO_SERVER_TARG; - } else if (NT_STATUS_IS_OK(nt_status)) { - if (unwrapped_out.data) { - spnego_out.negTokenTarg.supportedMech = spnego_state->neg_oid; + tp = spnego_in->negTokenInit.targetPrincipal; + if (tp != NULL && strcmp(tp, ADS_IGNORE_PRINCIPAL) != 0) { + DBG_INFO("Server claims it's principal name is %s\n", tp); + if (lpcfg_client_use_spnego_principal(gensec_security->settings->lp_ctx)) { + gensec_set_target_principal(gensec_security, tp); } - spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_COMPLETED; - spnego_state->state_position = SPNEGO_DONE; - } else { - spnego_out.negTokenTarg.negResult = SPNEGO_REJECT; - spnego_out.negTokenTarg.mechListMIC = null_data_blob; - DEBUG(2, ("SPNEGO login failed: %s\n", nt_errstr(nt_status))); - spnego_state->state_position = SPNEGO_DONE; } - if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { - DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n")); + n->mech_idx = 0; + n->mech_types = spnego_in->negTokenInit.mechTypes; + if (n->mech_types == NULL) { return NT_STATUS_INVALID_PARAMETER; } - spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; - spnego_state->num_targs++; + n->all_idx = 0; + n->all_sec = gensec_security_by_oid_list(gensec_security, + n, n->mech_types, + GENSEC_OID_SPNEGO); + if (n->all_sec == NULL) { + DBG_WARNING("gensec_security_by_oid_list() failed\n"); + return NT_STATUS_INVALID_PARAMETER; + } - return nt_status; + return n->ops->step_fn(gensec_security, spnego_state, n, + spnego_in, NT_STATUS_OK, in_mem_ctx, in_next); } - -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) +static NTSTATUS gensec_spnego_client_negTokenInit_step( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) { - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; - DATA_BLOB null_data_blob = data_blob(NULL, 0); - DATA_BLOB mech_list_mic = data_blob(NULL, 0); - DATA_BLOB unwrapped_out = data_blob(NULL, 0); - struct spnego_data spnego_out; - struct spnego_data spnego; - - ssize_t len; + if (!NT_STATUS_IS_OK(last_status)) { + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + const struct gensec_security_ops_wrapper *next_sec = NULL; + const char *next = NULL; + const char *principal = NULL; + int dbg_level = DBGLVL_WARNING; + bool allow_fallback = false; + NTSTATUS status = last_status; + + if (cur_sec[1].op != NULL) { + next_sec = &cur_sec[1]; + } - *out = data_blob(NULL, 0); + /* + * it is likely that a NULL input token will + * not be liked by most server mechs, but if + * we are in the client, we want the first + * update packet to be able to abort the use + * of this mech + */ + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER) || + NT_STATUS_EQUAL(status, NT_STATUS_NO_LOGON_SERVERS) || + NT_STATUS_EQUAL(status, NT_STATUS_TIME_DIFFERENCE_AT_DC) || + NT_STATUS_EQUAL(status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) + { + allow_fallback = true; + } - if (!out_mem_ctx) { - out_mem_ctx = spnego_state; - } + if (allow_fallback && next_sec != NULL) { + next = next_sec->op->name; + dbg_level = DBGLVL_NOTICE; + } - /* and switch into the state machine */ + if (gensec_security->target.principal != NULL) { + principal = gensec_security->target.principal; + } else if (gensec_security->target.service != NULL && + gensec_security->target.hostname != NULL) + { + principal = talloc_asprintf(spnego_state->sub_sec_security, + "%s/%s", + gensec_security->target.service, + gensec_security->target.hostname); + } else { + principal = gensec_security->target.hostname; + } - switch (spnego_state->state_position) { - case SPNEGO_FALLBACK: - return gensec_update_ev(spnego_state->sub_sec_security, ev, - out_mem_ctx, in, out); - case SPNEGO_SERVER_START: - { - NTSTATUS nt_status; - if (in.length) { + DBG_PREFIX(dbg_level, ( + "%s: creating NEG_TOKEN_INIT for %s failed " + "(next[%s]): %s\n", cur_sec->op->name, + principal, next, nt_errstr(status))); - len = spnego_read_data(gensec_security, in, &spnego); - if (len == -1) { - return gensec_spnego_server_try_fallback(gensec_security, spnego_state, - ev, out_mem_ctx, in, out); - } - /* client sent NegTargetInit, we send NegTokenTarg */ - - /* OK, so it's real SPNEGO, check the packet's the one we expect */ - if (spnego.type != spnego_state->expected_packet) { - DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type, - spnego_state->expected_packet)); - dump_data(1, in.data, in.length); - spnego_free_data(&spnego); - return NT_STATUS_INVALID_PARAMETER; - } + if (next == NULL) { + /* + * A hard error without a possible fallback. + */ + return status; + } - nt_status = gensec_spnego_parse_negTokenInit(gensec_security, - spnego_state, - out_mem_ctx, - ev, - spnego.negTokenInit.mechTypes, - spnego.negTokenInit.mechToken, - &unwrapped_out); + /* + * Pretend we never started it. + */ + gensec_spnego_reset_sub_sec(spnego_state); - if (spnego_state->simulate_w2k) { - /* - * Windows 2000 returns the unwrapped token - * also in the mech_list_mic field. - * - * In order to verify our client code, - * we need a way to have a server with this - * broken behaviour - */ - mech_list_mic = unwrapped_out; - } + /* + * And try the next one... + */ + n->all_idx += 1; + } - nt_status = gensec_spnego_server_negTokenTarg(spnego_state, - out_mem_ctx, - nt_status, - unwrapped_out, - mech_list_mic, - out); + for (; n->all_sec[n->all_idx].op != NULL; n->all_idx++) { + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + NTSTATUS status; - spnego_free_data(&spnego); + status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } - return nt_status; - } else { - nt_status = gensec_spnego_create_negTokenInit(gensec_security, spnego_state, - out_mem_ctx, ev, in, out); - spnego_state->state_position = SPNEGO_SERVER_START; - spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT; - return nt_status; + /* select the sub context */ + status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, + cur_sec->op); + if (!NT_STATUS_IS_OK(status)) { + gensec_spnego_reset_sub_sec(spnego_state); + continue; } + + /* + * Note that 'cur_sec' is temporary memory, but + * cur_sec->oid points to a const string in the + * backends gensec_security_ops structure. + */ + spnego_state->neg_oid = cur_sec->oid; + + /* + * As client we don't use an optimistic token from the server. + * But try to produce one for the server. + */ + *in_next = data_blob_null; + return NT_STATUS_MORE_PROCESSING_REQUIRED; } - case SPNEGO_CLIENT_START: - { - /* The server offers a list of mechanisms */ + DBG_WARNING("Could not find a suitable mechtype in NEG_TOKEN_INIT\n"); + return NT_STATUS_INVALID_PARAMETER; +} - const char *my_mechs[] = {NULL, NULL}; - NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER; - bool ok; +static NTSTATUS gensec_spnego_client_negTokenInit_finish( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct spnego_data spnego_out; + const char *my_mechs[] = {NULL, NULL}; + bool ok; - if (!in.length) { - /* client to produce negTokenInit */ - nt_status = gensec_spnego_create_negTokenInit(gensec_security, spnego_state, - out_mem_ctx, ev, in, out); - spnego_state->state_position = SPNEGO_CLIENT_TARG; - spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; - return nt_status; - } + my_mechs[0] = spnego_state->neg_oid; + /* compose reply */ + spnego_out.type = SPNEGO_NEG_TOKEN_INIT; + spnego_out.negTokenInit.mechTypes = my_mechs; + spnego_out.negTokenInit.reqFlags = data_blob_null; + spnego_out.negTokenInit.reqFlagsPadding = 0; + spnego_out.negTokenInit.mechListMIC = data_blob_null; + spnego_out.negTokenInit.mechToken = sub_out; - len = spnego_read_data(gensec_security, in, &spnego); + if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DBG_ERR("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n"); + return NT_STATUS_INVALID_PARAMETER; + } - if (len == -1) { - DEBUG(1, ("Invalid SPNEGO request:\n")); - dump_data(1, in.data, in.length); - return NT_STATUS_INVALID_PARAMETER; - } + ok = spnego_write_mech_types(spnego_state, + my_mechs, + &spnego_state->mech_types); + if (!ok) { + DBG_ERR("failed to write mechTypes\n"); + return NT_STATUS_NO_MEMORY; + } - /* OK, so it's real SPNEGO, check the packet's the one we expect */ - if (spnego.type != spnego_state->expected_packet) { - DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type, - spnego_state->expected_packet)); - dump_data(1, in.data, in.length); - spnego_free_data(&spnego); - return NT_STATUS_INVALID_PARAMETER; - } + /* set next state */ + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + spnego_state->state_position = SPNEGO_CLIENT_TARG; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +static const struct spnego_neg_ops gensec_spnego_client_negTokenInit_ops = { + .name = "client_negTokenInit", + .start_fn = gensec_spnego_client_negTokenInit_start, + .step_fn = gensec_spnego_client_negTokenInit_step, + .finish_fn = gensec_spnego_client_negTokenInit_finish, +}; + +static NTSTATUS gensec_spnego_client_negTokenTarg_start( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + struct spnego_negTokenTarg *ta = &spnego_in->negTokenTarg; + NTSTATUS status; - 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 (lpcfg_client_use_spnego_principal(gensec_security->settings->lp_ctx)) { - gensec_set_target_principal(gensec_security, spnego.negTokenInit.targetPrincipal); + spnego_state->num_targs++; + + if (ta->negResult == SPNEGO_REJECT) { + return NT_STATUS_LOGON_FAILURE; + } + + if (ta->negResult == SPNEGO_REQUEST_MIC) { + spnego_state->mic_requested = true; + } + + if (ta->mechListMIC.length > 0) { + DATA_BLOB *m = &ta->mechListMIC; + const DATA_BLOB *r = &ta->responseToken; + + /* + * Windows 2000 has a bug, it repeats the + * responseToken in the mechListMIC field. + */ + if (m->length == r->length) { + int cmp; + + cmp = memcmp(m->data, r->data, m->length); + if (cmp == 0) { + data_blob_free(m); } } + } - nt_status = gensec_spnego_parse_negTokenInit(gensec_security, - spnego_state, - out_mem_ctx, - ev, - spnego.negTokenInit.mechTypes, - spnego.negTokenInit.mechToken, - &unwrapped_out); + /* Server didn't like our choice of mech, and chose something else */ + if (((ta->negResult == SPNEGO_ACCEPT_INCOMPLETE) || + (ta->negResult == SPNEGO_REQUEST_MIC)) && + ta->supportedMech != NULL && + strcmp(ta->supportedMech, spnego_state->neg_oid) != 0) + { + const char *client_mech = NULL; + const char *client_oid = NULL; + const char *server_mech = NULL; + const char *server_oid = NULL; + + client_mech = gensec_get_name_by_oid(gensec_security, + spnego_state->neg_oid); + client_oid = spnego_state->neg_oid; + server_mech = gensec_get_name_by_oid(gensec_security, + ta->supportedMech); + server_oid = ta->supportedMech; + + DBG_NOTICE("client preferred mech (%s[%s]) not accepted, " + "server wants: %s[%s]\n", + client_mech, client_oid, server_mech, server_oid); + + spnego_state->downgraded = true; + gensec_spnego_reset_sub_sec(spnego_state); + + status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } - if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) { - spnego_free_data(&spnego); - return nt_status; + /* select the sub context */ + status = gensec_start_mech_by_oid(spnego_state->sub_sec_security, + ta->supportedMech); + if (!NT_STATUS_IS_OK(status)) { + return status; } - my_mechs[0] = spnego_state->neg_oid; - /* compose reply */ - spnego_out.type = SPNEGO_NEG_TOKEN_INIT; - spnego_out.negTokenInit.mechTypes = my_mechs; - spnego_out.negTokenInit.reqFlags = null_data_blob; - spnego_out.negTokenInit.reqFlagsPadding = 0; - spnego_out.negTokenInit.mechListMIC = null_data_blob; - spnego_out.negTokenInit.mechToken = unwrapped_out; + spnego_state->neg_oid = talloc_strdup(spnego_state, + ta->supportedMech); + if (spnego_state->neg_oid == NULL) { + return NT_STATUS_NO_MEMORY; + } + } - if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { - DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n")); - return NT_STATUS_INVALID_PARAMETER; + if (ta->mechListMIC.length > 0) { + if (spnego_state->sub_sec_ready) { + spnego_state->needs_mic_check = true; } + } - ok = spnego_write_mech_types(spnego_state, - my_mechs, - &spnego_state->mech_types); - if (!ok) { - DEBUG(1, ("SPNEGO: Failed to write mechTypes\n")); - return NT_STATUS_NO_MEMORY; + if (spnego_state->needs_mic_check) { + if (ta->responseToken.length != 0) { + DBG_WARNING("non empty response token not expected\n"); + return NT_STATUS_INVALID_PARAMETER; } - /* set next state */ - spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; - spnego_state->state_position = SPNEGO_CLIENT_TARG; + if (ta->mechListMIC.length == 0 + && spnego_state->may_skip_mic_check) { + /* + * In this case we don't require + * a mechListMIC from the server. + * + * This works around bugs in the Azure + * and Apple spnego implementations. + * + * See + * https://bugzilla.samba.org/show_bug.cgi?id=11994 + */ + spnego_state->needs_mic_check = false; + return NT_STATUS_OK; + } - if (NT_STATUS_IS_OK(nt_status)) { - spnego_state->no_response_expected = true; + status = gensec_check_packet(spnego_state->sub_sec_security, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + &ta->mechListMIC); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to verify mechListMIC: %s\n", + nt_errstr(status)); + return status; } + spnego_state->needs_mic_check = false; + spnego_state->done_mic_check = true; + return NT_STATUS_OK; + } - spnego_free_data(&spnego); + if (!spnego_state->sub_sec_ready) { + *in_next = ta->responseToken; return NT_STATUS_MORE_PROCESSING_REQUIRED; } - case SPNEGO_SERVER_TARG: - { - NTSTATUS nt_status; - bool have_sign = true; - bool new_spnego = false; - if (!in.length) { - return NT_STATUS_INVALID_PARAMETER; - } + return NT_STATUS_OK; +} - len = spnego_read_data(gensec_security, in, &spnego); +static NTSTATUS gensec_spnego_client_negTokenTarg_step( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + if (GENSEC_UPDATE_IS_NTERROR(last_status)) { + DBG_WARNING("SPNEGO(%s) login failed: %s\n", + spnego_state->sub_sec_security->ops->name, + nt_errstr(last_status)); + return last_status; + } - if (len == -1) { - DEBUG(1, ("Invalid SPNEGO request:\n")); - dump_data(1, in.data, in.length); - return NT_STATUS_INVALID_PARAMETER; - } + /* + * This should never be reached! + * The step function is only called on errors! + */ + smb_panic(__location__); + return NT_STATUS_INTERNAL_ERROR; +} - /* OK, so it's real SPNEGO, check the packet's the one we expect */ - if (spnego.type != spnego_state->expected_packet) { - DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type, - spnego_state->expected_packet)); - dump_data(1, in.data, in.length); - spnego_free_data(&spnego); - return NT_STATUS_INVALID_PARAMETER; - } +static NTSTATUS gensec_spnego_client_negTokenTarg_finish( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + const struct spnego_negTokenTarg *ta = + &spnego_in->negTokenTarg; + DATA_BLOB mech_list_mic = data_blob_null; + NTSTATUS status; + struct spnego_data spnego_out; - spnego_state->num_targs++; + status = sub_status; - if (!spnego_state->sub_sec_security) { - DEBUG(1, ("SPNEGO: Did not setup a mech in NEG_TOKEN_INIT\n")); - spnego_free_data(&spnego); - return NT_STATUS_INVALID_PARAMETER; - } + if (!spnego_state->sub_sec_ready) { + /* + * We're not yet ready to deal with signatures. + */ + goto client_response; + } - if (spnego_state->needs_mic_check) { - if (spnego.negTokenTarg.responseToken.length != 0) { - DEBUG(1, ("SPNEGO: Did not setup a mech in NEG_TOKEN_INIT\n")); - spnego_free_data(&spnego); - return NT_STATUS_INVALID_PARAMETER; - } + if (spnego_state->done_mic_check) { + /* + * We already checked the mic, + * either the in last round here + * in gensec_spnego_client_negTokenTarg_finish() + * or during this round in + * gensec_spnego_client_negTokenTarg_start(). + * + * Both cases we're sure we don't have to + * call gensec_sign_packet(). + */ + goto client_response; + } - nt_status = gensec_check_packet(spnego_state->sub_sec_security, - spnego_state->mech_types.data, - spnego_state->mech_types.length, - spnego_state->mech_types.data, - spnego_state->mech_types.length, - &spnego.negTokenTarg.mechListMIC); - if (NT_STATUS_IS_OK(nt_status)) { - spnego_state->needs_mic_check = false; - spnego_state->done_mic_check = true; - } else { - DEBUG(2,("GENSEC SPNEGO: failed to verify mechListMIC: %s\n", - nt_errstr(nt_status))); - } - goto server_response; - } + if (spnego_state->may_skip_mic_check) { + /* + * This can only be set during + * the last round here in + * gensec_spnego_client_negTokenTarg_finish() + * below. And during this round + * we already passed the checks in + * gensec_spnego_client_negTokenTarg_start(). + * + * So we need to skip to deal with + * any signatures now. + */ + goto client_response; + } - nt_status = gensec_update_ev(spnego_state->sub_sec_security, - out_mem_ctx, ev, - spnego.negTokenTarg.responseToken, - &unwrapped_out); - if (!NT_STATUS_IS_OK(nt_status)) { - goto server_response; - } + if (!spnego_state->done_mic_check) { + bool have_sign = true; + bool new_spnego = false; have_sign = gensec_have_feature(spnego_state->sub_sec_security, GENSEC_FEATURE_SIGN); @@ -1027,405 +923,852 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA } new_spnego = gensec_have_feature(spnego_state->sub_sec_security, GENSEC_FEATURE_NEW_SPNEGO); - if (spnego.negTokenTarg.mechListMIC.length > 0) { - new_spnego = true; + + switch (ta->negResult) { + case SPNEGO_ACCEPT_COMPLETED: + case SPNEGO_NONE_RESULT: + if (spnego_state->num_targs == 1) { + /* + * the first exchange doesn't require + * verification + */ + new_spnego = false; + } + + break; + + case SPNEGO_ACCEPT_INCOMPLETE: + if (ta->mechListMIC.length > 0) { + new_spnego = true; + break; + } + + if (spnego_state->downgraded) { + /* + * A downgrade should be protected if + * supported + */ + break; + } + + /* + * The caller may just asked for + * GENSEC_FEATURE_SESSION_KEY, this + * is only reflected in the want_features. + * + * As it will imply + * gensec_have_features(GENSEC_FEATURE_SIGN) + * to return true. + */ + if (gensec_security->want_features & GENSEC_FEATURE_SIGN) { + break; + } + if (gensec_security->want_features & GENSEC_FEATURE_SEAL) { + break; + } + /* + * Here we're sure our preferred mech was + * selected by the server and our caller doesn't + * need GENSEC_FEATURE_SIGN nor + * GENSEC_FEATURE_SEAL support. + * + * In this case we don't require + * a mechListMIC from the server. + * + * This works around bugs in the Azure + * and Apple spnego implementations. + * + * See + * https://bugzilla.samba.org/show_bug.cgi?id=11994 + */ + spnego_state->may_skip_mic_check = true; + break; + + case SPNEGO_REQUEST_MIC: + if (ta->mechListMIC.length > 0) { + new_spnego = true; + } + break; + default: + break; + } + + if (spnego_state->mic_requested) { + if (have_sign) { + new_spnego = true; + } } if (have_sign && new_spnego) { spnego_state->needs_mic_check = true; spnego_state->needs_mic_sign = true; } + } - if (have_sign && spnego.negTokenTarg.mechListMIC.length > 0) { - nt_status = gensec_check_packet(spnego_state->sub_sec_security, - spnego_state->mech_types.data, - spnego_state->mech_types.length, - spnego_state->mech_types.data, - spnego_state->mech_types.length, - &spnego.negTokenTarg.mechListMIC); - if (!NT_STATUS_IS_OK(nt_status)) { - DEBUG(2,("GENSEC SPNEGO: failed to verify mechListMIC: %s\n", - nt_errstr(nt_status))); - goto server_response; - } + if (ta->mechListMIC.length > 0) { + status = gensec_check_packet(spnego_state->sub_sec_security, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + &ta->mechListMIC); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to verify mechListMIC: %s\n", + nt_errstr(status)); + return status; + } + spnego_state->needs_mic_check = false; + spnego_state->done_mic_check = true; + } - spnego_state->needs_mic_check = false; - spnego_state->done_mic_check = true; - } - - if (spnego_state->needs_mic_sign) { - nt_status = gensec_sign_packet(spnego_state->sub_sec_security, - out_mem_ctx, - spnego_state->mech_types.data, - spnego_state->mech_types.length, - spnego_state->mech_types.data, - spnego_state->mech_types.length, - &mech_list_mic); - if (!NT_STATUS_IS_OK(nt_status)) { - DEBUG(2,("GENSEC SPNEGO: failed to sign mechListMIC: %s\n", - nt_errstr(nt_status))); - goto server_response; + if (spnego_state->needs_mic_sign) { + status = gensec_sign_packet(spnego_state->sub_sec_security, + n, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + &mech_list_mic); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to sign mechListMIC: %s\n", + nt_errstr(status)); + return status; + } + spnego_state->needs_mic_sign = false; + } + + client_response: + if (sub_out.length == 0 && mech_list_mic.length == 0) { + *out = data_blob_null; + + if (!spnego_state->sub_sec_ready) { + /* somethings wrong here... */ + DBG_ERR("gensec_update not ready without output\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + if (ta->negResult != SPNEGO_ACCEPT_COMPLETED) { + /* unless of course it did not accept */ + DBG_WARNING("gensec_update ok but not accepted\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!spnego_state->needs_mic_check) { + spnego_state->state_position = SPNEGO_DONE; + return NT_STATUS_OK; + } + } + + /* compose reply */ + spnego_out.type = SPNEGO_NEG_TOKEN_TARG; + spnego_out.negTokenTarg.negResult = SPNEGO_NONE_RESULT; + spnego_out.negTokenTarg.supportedMech = NULL; + spnego_out.negTokenTarg.responseToken = sub_out; + spnego_out.negTokenTarg.mechListMIC = mech_list_mic; + + if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DBG_WARNING("Failed to write NEG_TOKEN_TARG\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + spnego_state->num_targs++; + + /* set next state */ + spnego_state->state_position = SPNEGO_CLIENT_TARG; + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +static const struct spnego_neg_ops gensec_spnego_client_negTokenTarg_ops = { + .name = "client_negTokenTarg", + .start_fn = gensec_spnego_client_negTokenTarg_start, + .step_fn = gensec_spnego_client_negTokenTarg_step, + .finish_fn = gensec_spnego_client_negTokenTarg_finish, +}; + +/** create a server negTokenTarg + * + * This is the case, where the client is the first one who sends data +*/ + +static NTSTATUS gensec_spnego_server_response(struct spnego_state *spnego_state, + TALLOC_CTX *out_mem_ctx, + NTSTATUS nt_status, + const DATA_BLOB unwrapped_out, + DATA_BLOB mech_list_mic, + DATA_BLOB *out) +{ + struct spnego_data spnego_out; + + /* compose reply */ + spnego_out.type = SPNEGO_NEG_TOKEN_TARG; + spnego_out.negTokenTarg.responseToken = unwrapped_out; + spnego_out.negTokenTarg.mechListMIC = mech_list_mic; + spnego_out.negTokenTarg.supportedMech = NULL; + + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + spnego_out.negTokenTarg.supportedMech = spnego_state->neg_oid; + if (spnego_state->mic_requested) { + spnego_out.negTokenTarg.negResult = SPNEGO_REQUEST_MIC; + spnego_state->mic_requested = false; + } else { + spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE; + } + spnego_state->state_position = SPNEGO_SERVER_TARG; + } else if (NT_STATUS_IS_OK(nt_status)) { + if (unwrapped_out.data) { + spnego_out.negTokenTarg.supportedMech = spnego_state->neg_oid; + } + spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_COMPLETED; + spnego_state->state_position = SPNEGO_DONE; + } + + if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + spnego_state->num_targs++; + + return nt_status; +} + +static NTSTATUS gensec_spnego_server_negTokenInit_start( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + bool ok; + + n->mech_idx = 0; + n->mech_types = spnego_in->negTokenInit.mechTypes; + if (n->mech_types == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + n->all_idx = 0; + n->all_sec = gensec_security_by_oid_list(gensec_security, + n, n->mech_types, + GENSEC_OID_SPNEGO); + if (n->all_sec == NULL) { + DBG_WARNING("gensec_security_by_oid_list() failed\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + ok = spnego_write_mech_types(spnego_state, + n->mech_types, + &spnego_state->mech_types); + if (!ok) { + DBG_ERR("Failed to write mechTypes\n"); + return NT_STATUS_NO_MEMORY; + } + + return n->ops->step_fn(gensec_security, spnego_state, n, + spnego_in, NT_STATUS_OK, in_mem_ctx, in_next); +} + +static NTSTATUS gensec_spnego_server_negTokenInit_step( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + if (!NT_STATUS_IS_OK(last_status)) { + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + const char *next_mech = n->mech_types[n->mech_idx+1]; + const struct gensec_security_ops_wrapper *next_sec = NULL; + const char *next = NULL; + int dbg_level = DBGLVL_WARNING; + bool allow_fallback = false; + NTSTATUS status = last_status; + size_t i; + + for (i = 0; next_mech != NULL && n->all_sec[i].op != NULL; i++) { + if (strcmp(next_mech, n->all_sec[i].oid) != 0) { + continue; } - spnego_state->needs_mic_sign = false; + + next_sec = &n->all_sec[i]; + break; } - if (spnego_state->needs_mic_check) { - nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED; + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER) || + NT_STATUS_EQUAL(status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) + { + allow_fallback = true; } - server_response: - nt_status = gensec_spnego_server_negTokenTarg(spnego_state, - out_mem_ctx, - nt_status, - unwrapped_out, - mech_list_mic, - out); + if (allow_fallback && next_sec != NULL) { + next = next_sec->op->name; + dbg_level = DBGLVL_NOTICE; + } - spnego_free_data(&spnego); + DBG_PREFIX(dbg_level, ( + "%s: parsing NEG_TOKEN_INIT content failed " + "(next[%s]): %s\n", cur_sec->op->name, + next, nt_errstr(status))); - return nt_status; + if (next == NULL) { + /* + * A hard error without a possible fallback. + */ + return status; + } + + /* + * Pretend we never started it + */ + gensec_spnego_reset_sub_sec(spnego_state); + + /* + * And try the next one, based on the clients + * mech type list... + */ + n->mech_idx += 1; } - case SPNEGO_CLIENT_TARG: - { - NTSTATUS nt_status = NT_STATUS_INTERNAL_ERROR; - if (!in.length) { - return NT_STATUS_INVALID_PARAMETER; + /* + * we always reset all_idx here, as the negotiation is + * done via mech_idx! + */ + n->all_idx = 0; + + for (; n->mech_types[n->mech_idx] != NULL; n->mech_idx++) { + const char *cur_mech = n->mech_types[n->mech_idx]; + const struct gensec_security_ops_wrapper *cur_sec = NULL; + NTSTATUS status; + DATA_BLOB sub_in = data_blob_null; + size_t i; + + for (i = 0; n->all_sec[i].op != NULL; i++) { + if (strcmp(cur_mech, n->all_sec[i].oid) != 0) { + continue; + } + + cur_sec = &n->all_sec[i]; + n->all_idx = i; + break; } - len = spnego_read_data(gensec_security, in, &spnego); + if (cur_sec == NULL) { + continue; + } - if (len == -1) { - DEBUG(1, ("Invalid SPNEGO request:\n")); - dump_data(1, in.data, in.length); - return NT_STATUS_INVALID_PARAMETER; + status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; } - /* OK, so it's real SPNEGO, check the packet's the one we expect */ - if (spnego.type != spnego_state->expected_packet) { - DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type, - spnego_state->expected_packet)); - dump_data(1, in.data, in.length); - spnego_free_data(&spnego); + /* select the sub context */ + status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, + cur_sec->op); + if (!NT_STATUS_IS_OK(status)) { + /* + * Pretend we never started it + */ + gensec_spnego_reset_sub_sec(spnego_state); + continue; + } + + if (n->mech_idx == 0) { + /* + * We can use the optimistic token. + */ + sub_in = spnego_in->negTokenInit.mechToken; + } else { + /* + * Indicate the downgrade and request a + * mic. + */ + spnego_state->downgraded = true; + spnego_state->mic_requested = true; + } + + /* + * Note that 'cur_sec' is temporary memory, but + * cur_sec->oid points to a const string in the + * backends gensec_security_ops structure. + */ + spnego_state->neg_oid = cur_sec->oid; + + /* we need some content from the mech */ + *in_next = sub_in; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + DBG_WARNING("Could not find a suitable mechtype in NEG_TOKEN_INIT\n"); + return NT_STATUS_INVALID_PARAMETER; +} + +static NTSTATUS gensec_spnego_server_negTokenInit_finish( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + DATA_BLOB mech_list_mic = data_blob_null; + + if (spnego_state->simulate_w2k) { + /* + * Windows 2000 returns the unwrapped token + * also in the mech_list_mic field. + * + * In order to verify our client code, + * we need a way to have a server with this + * broken behaviour + */ + mech_list_mic = sub_out; + } + + return gensec_spnego_server_response(spnego_state, + out_mem_ctx, + sub_status, + sub_out, + mech_list_mic, + out); +} + +static const struct spnego_neg_ops gensec_spnego_server_negTokenInit_ops = { + .name = "server_negTokenInit", + .start_fn = gensec_spnego_server_negTokenInit_start, + .step_fn = gensec_spnego_server_negTokenInit_step, + .finish_fn = gensec_spnego_server_negTokenInit_finish, +}; + +static NTSTATUS gensec_spnego_server_negTokenTarg_start( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + const struct spnego_negTokenTarg *ta = &spnego_in->negTokenTarg; + NTSTATUS status; + + spnego_state->num_targs++; + + if (spnego_state->sub_sec_security == NULL) { + DBG_ERR("SPNEGO: Did not setup a mech in NEG_TOKEN_INIT\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + if (spnego_state->needs_mic_check) { + if (ta->responseToken.length != 0) { + DBG_WARNING("non empty response token not expected\n"); return NT_STATUS_INVALID_PARAMETER; } - spnego_state->num_targs++; + status = gensec_check_packet(spnego_state->sub_sec_security, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + &ta->mechListMIC); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to verify mechListMIC: %s\n", + nt_errstr(status)); + return status; + } + + spnego_state->needs_mic_check = false; + spnego_state->done_mic_check = true; + return NT_STATUS_OK; + } + + if (!spnego_state->sub_sec_ready) { + *in_next = ta->responseToken; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_spnego_server_negTokenTarg_step( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + if (GENSEC_UPDATE_IS_NTERROR(last_status)) { + DBG_NOTICE("SPNEGO(%s) login failed: %s\n", + spnego_state->sub_sec_security->ops->name, + nt_errstr(last_status)); + return last_status; + } + + /* + * This should never be reached! + * The step function is only called on errors! + */ + smb_panic(__location__); + return NT_STATUS_INTERNAL_ERROR; +} + +static NTSTATUS gensec_spnego_server_negTokenTarg_finish( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + const struct spnego_negTokenTarg *ta = &spnego_in->negTokenTarg; + DATA_BLOB mech_list_mic = data_blob_null; + NTSTATUS status; + bool have_sign = true; + bool new_spnego = false; - if (spnego.negTokenTarg.negResult == SPNEGO_REJECT) { - spnego_free_data(&spnego); - return NT_STATUS_LOGON_FAILURE; + status = sub_status; + + if (!spnego_state->sub_sec_ready) { + /* + * We're not yet ready to deal with signatures. + */ + goto server_response; + } + + if (spnego_state->done_mic_check) { + /* + * We already checked the mic, + * either the in last round here + * in gensec_spnego_server_negTokenTarg_finish() + * or during this round in + * gensec_spnego_server_negTokenTarg_start(). + * + * Both cases we're sure we don't have to + * call gensec_sign_packet(). + */ + goto server_response; + } + + have_sign = gensec_have_feature(spnego_state->sub_sec_security, + GENSEC_FEATURE_SIGN); + if (spnego_state->simulate_w2k) { + have_sign = false; + } + new_spnego = gensec_have_feature(spnego_state->sub_sec_security, + GENSEC_FEATURE_NEW_SPNEGO); + if (ta->mechListMIC.length > 0) { + new_spnego = true; + } + + if (have_sign && new_spnego) { + spnego_state->needs_mic_check = true; + spnego_state->needs_mic_sign = true; + } + + if (have_sign && ta->mechListMIC.length > 0) { + status = gensec_check_packet(spnego_state->sub_sec_security, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + &ta->mechListMIC); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to verify mechListMIC: %s\n", + nt_errstr(status)); + return status; } - if (spnego.negTokenTarg.negResult == SPNEGO_REQUEST_MIC) { - spnego_state->mic_requested = true; + spnego_state->needs_mic_check = false; + spnego_state->done_mic_check = true; + } + + if (spnego_state->needs_mic_sign) { + status = gensec_sign_packet(spnego_state->sub_sec_security, + n, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + &mech_list_mic); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to sign mechListMIC: %s\n", + nt_errstr(status)); + return status; } + spnego_state->needs_mic_sign = false; + } + + if (spnego_state->needs_mic_check) { + status = NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + server_response: + return gensec_spnego_server_response(spnego_state, + out_mem_ctx, + status, + sub_out, + mech_list_mic, + out); +} - /* Server didn't like our choice of mech, and chose something else */ - if (((spnego.negTokenTarg.negResult == SPNEGO_ACCEPT_INCOMPLETE) || - (spnego.negTokenTarg.negResult == SPNEGO_REQUEST_MIC)) && - spnego.negTokenTarg.supportedMech && - strcmp(spnego.negTokenTarg.supportedMech, spnego_state->neg_oid) != 0) { - DEBUG(3,("GENSEC SPNEGO: client preferred mech (%s) not accepted, server wants: %s\n", - gensec_get_name_by_oid(gensec_security, spnego_state->neg_oid), - gensec_get_name_by_oid(gensec_security, spnego.negTokenTarg.supportedMech))); - spnego_state->downgraded = true; - spnego_state->no_response_expected = false; - talloc_free(spnego_state->sub_sec_security); - nt_status = gensec_subcontext_start(spnego_state, - gensec_security, - &spnego_state->sub_sec_security); - if (!NT_STATUS_IS_OK(nt_status)) { - spnego_free_data(&spnego); - return nt_status; - } - /* select the sub context */ - nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security, - spnego.negTokenTarg.supportedMech); - if (!NT_STATUS_IS_OK(nt_status)) { - spnego_free_data(&spnego); - return nt_status; - } +static const struct spnego_neg_ops gensec_spnego_server_negTokenTarg_ops = { + .name = "server_negTokenTarg", + .start_fn = gensec_spnego_server_negTokenTarg_start, + .step_fn = gensec_spnego_server_negTokenTarg_step, + .finish_fn = gensec_spnego_server_negTokenTarg_finish, +}; - spnego_state->neg_oid = talloc_strdup(spnego_state, - spnego.negTokenTarg.supportedMech); - if (spnego_state->neg_oid == NULL) { - spnego_free_data(&spnego); - return NT_STATUS_NO_MEMORY; - }; - } +struct gensec_spnego_update_state { + struct tevent_context *ev; + struct gensec_security *gensec; + struct spnego_state *spnego; - if (spnego.negTokenTarg.mechListMIC.length > 0) { - DATA_BLOB *m = &spnego.negTokenTarg.mechListMIC; - const DATA_BLOB *r = &spnego.negTokenTarg.responseToken; + DATA_BLOB full_in; + struct spnego_data _spnego_in; + struct spnego_data *spnego_in; - /* - * Windows 2000 has a bug, it repeats the - * responseToken in the mechListMIC field. - */ - if (m->length == r->length) { - int cmp; + struct { + bool needed; + DATA_BLOB in; + NTSTATUS status; + DATA_BLOB out; + } sub; - cmp = memcmp(m->data, r->data, m->length); - if (cmp == 0) { - data_blob_free(m); - } - } - } + struct spnego_neg_state *n; - if (spnego.negTokenTarg.mechListMIC.length > 0) { - if (spnego_state->no_response_expected) { - spnego_state->needs_mic_check = true; - } - } + NTSTATUS status; + DATA_BLOB out; +}; - if (spnego_state->needs_mic_check) { - if (spnego.negTokenTarg.responseToken.length != 0) { - DEBUG(1, ("SPNEGO: Did not setup a mech in NEG_TOKEN_INIT\n")); - spnego_free_data(&spnego); - return NT_STATUS_INVALID_PARAMETER; - } +static void gensec_spnego_update_cleanup(struct tevent_req *req, + enum tevent_req_state req_state) +{ + struct gensec_spnego_update_state *state = + tevent_req_data(req, + struct gensec_spnego_update_state); + + switch (req_state) { + case TEVENT_REQ_USER_ERROR: + case TEVENT_REQ_TIMED_OUT: + case TEVENT_REQ_NO_MEMORY: + /* + * A fatal error, further updates are not allowed. + */ + state->spnego->state_position = SPNEGO_DONE; + break; + default: + break; + } +} - if (spnego.negTokenTarg.mechListMIC.length == 0 - && spnego_state->may_skip_mic_check) { - /* - * In this case we don't require - * a mechListMIC from the server. - * - * This works around bugs in the Azure - * and Apple spnego implementations. - * - * See - * https://bugzilla.samba.org/show_bug.cgi?id=11994 - */ - spnego_state->needs_mic_check = false; - nt_status = NT_STATUS_OK; - goto client_response; - } +static NTSTATUS gensec_spnego_update_in(struct gensec_security *gensec_security, + const DATA_BLOB in, TALLOC_CTX *mem_ctx, + DATA_BLOB *full_in); +static void gensec_spnego_update_pre(struct tevent_req *req); +static void gensec_spnego_update_done(struct tevent_req *subreq); +static void gensec_spnego_update_post(struct tevent_req *req); +static NTSTATUS gensec_spnego_update_out(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *_out); - nt_status = gensec_check_packet(spnego_state->sub_sec_security, - spnego_state->mech_types.data, - spnego_state->mech_types.length, - spnego_state->mech_types.data, - spnego_state->mech_types.length, - &spnego.negTokenTarg.mechListMIC); - if (!NT_STATUS_IS_OK(nt_status)) { - DEBUG(2,("GENSEC SPNEGO: failed to verify mechListMIC: %s\n", - nt_errstr(nt_status))); - spnego_free_data(&spnego); - return nt_status; - } - spnego_state->needs_mic_check = false; - spnego_state->done_mic_check = true; - goto client_response; - } +static struct tevent_req *gensec_spnego_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct spnego_state *spnego_state = + talloc_get_type_abort(gensec_security->private_data, + struct spnego_state); + struct tevent_req *req = NULL; + struct gensec_spnego_update_state *state = NULL; + NTSTATUS status; + ssize_t len; - if (!spnego_state->no_response_expected) { - nt_status = gensec_update_ev(spnego_state->sub_sec_security, - out_mem_ctx, ev, - spnego.negTokenTarg.responseToken, - &unwrapped_out); - if (!NT_STATUS_IS_OK(nt_status)) { - goto client_response; - } + req = tevent_req_create(mem_ctx, &state, + struct gensec_spnego_update_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->gensec = gensec_security; + state->spnego = spnego_state; + tevent_req_set_cleanup_fn(req, gensec_spnego_update_cleanup); - spnego_state->no_response_expected = true; - } else { - nt_status = NT_STATUS_OK; + if (spnego_state->out_frag.length > 0) { + if (in.length > 0) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); } - if (spnego_state->no_response_expected && - !spnego_state->done_mic_check) - { - bool have_sign = true; - bool new_spnego = false; - - have_sign = gensec_have_feature(spnego_state->sub_sec_security, - GENSEC_FEATURE_SIGN); - if (spnego_state->simulate_w2k) { - have_sign = false; - } - new_spnego = gensec_have_feature(spnego_state->sub_sec_security, - GENSEC_FEATURE_NEW_SPNEGO); - - switch (spnego.negTokenTarg.negResult) { - case SPNEGO_ACCEPT_COMPLETED: - case SPNEGO_NONE_RESULT: - if (spnego_state->num_targs == 1) { - /* - * the first exchange doesn't require - * verification - */ - new_spnego = false; - } + status = gensec_spnego_update_out(gensec_security, + state, &state->out); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } - break; + state->status = status; + tevent_req_done(req); + return tevent_req_post(req, ev); + } - case SPNEGO_ACCEPT_INCOMPLETE: - if (spnego.negTokenTarg.mechListMIC.length > 0) { - new_spnego = true; - break; - } + status = gensec_spnego_update_in(gensec_security, in, + state, &state->full_in); + state->status = status; + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } - if (spnego_state->downgraded) { - /* - * A downgrade should be protected if - * supported - */ - break; - } + /* Check if we got a valid SPNEGO blob... */ - /* - * The caller may just asked for - * GENSEC_FEATURE_SESSION_KEY, this - * is only reflected in the want_features. - * - * As it will imply - * gensec_have_features(GENSEC_FEATURE_SIGN) - * to return true. - */ - if (gensec_security->want_features & GENSEC_FEATURE_SIGN) { - break; - } - if (gensec_security->want_features & GENSEC_FEATURE_SEAL) { - break; - } - /* - * Here we're sure our preferred mech was - * selected by the server and our caller doesn't - * need GENSEC_FEATURE_SIGN nor - * GENSEC_FEATURE_SEAL support. - * - * In this case we don't require - * a mechListMIC from the server. - * - * This works around bugs in the Azure - * and Apple spnego implementations. - * - * See - * https://bugzilla.samba.org/show_bug.cgi?id=11994 - */ - spnego_state->may_skip_mic_check = true; - break; + switch (spnego_state->state_position) { + case SPNEGO_FALLBACK: + break; - case SPNEGO_REQUEST_MIC: - if (spnego.negTokenTarg.mechListMIC.length > 0) { - new_spnego = true; - } - break; - default: - break; - } + case SPNEGO_CLIENT_TARG: + case SPNEGO_SERVER_TARG: + if (state->full_in.length == 0) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } - if (spnego_state->mic_requested) { - if (have_sign) { - new_spnego = true; - } - } + FALL_THROUGH; + case SPNEGO_CLIENT_START: + case SPNEGO_SERVER_START: - if (have_sign && new_spnego) { - spnego_state->needs_mic_check = true; - spnego_state->needs_mic_sign = true; - } + if (state->full_in.length == 0) { + /* create_negTokenInit later */ + break; } - if (spnego.negTokenTarg.mechListMIC.length > 0) { - nt_status = gensec_check_packet(spnego_state->sub_sec_security, - spnego_state->mech_types.data, - spnego_state->mech_types.length, - spnego_state->mech_types.data, - spnego_state->mech_types.length, - &spnego.negTokenTarg.mechListMIC); - if (!NT_STATUS_IS_OK(nt_status)) { - DEBUG(2,("GENSEC SPNEGO: failed to verify mechListMIC: %s\n", - nt_errstr(nt_status))); - spnego_free_data(&spnego); - return nt_status; + len = spnego_read_data(state, + state->full_in, + &state->_spnego_in); + if (len == -1) { + if (spnego_state->state_position != SPNEGO_SERVER_START) { + DEBUG(1, ("Invalid SPNEGO request:\n")); + dump_data(1, state->full_in.data, + state->full_in.length); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); } - spnego_state->needs_mic_check = false; - spnego_state->done_mic_check = true; - } - - if (spnego_state->needs_mic_sign) { - nt_status = gensec_sign_packet(spnego_state->sub_sec_security, - out_mem_ctx, - spnego_state->mech_types.data, - spnego_state->mech_types.length, - spnego_state->mech_types.data, - spnego_state->mech_types.length, - &mech_list_mic); - if (!NT_STATUS_IS_OK(nt_status)) { - DEBUG(2,("GENSEC SPNEGO: failed to sign mechListMIC: %s\n", - nt_errstr(nt_status))); - spnego_free_data(&spnego); - return nt_status; + + /* + * This is the 'fallback' case, where we don't get + * SPNEGO, and have to try all the other options (and + * hope they all have a magic string they check) + */ + status = gensec_spnego_server_try_fallback(gensec_security, + spnego_state, + state, + state->full_in); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); } - spnego_state->needs_mic_sign = false; - } - if (spnego_state->needs_mic_check) { - nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED; + /* + * We'll continue with SPNEGO_FALLBACK below... + */ + break; } + state->spnego_in = &state->_spnego_in; - client_response: - spnego_free_data(&spnego); - - if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) - && !NT_STATUS_IS_OK(nt_status)) { - DEBUG(1, ("SPNEGO(%s) login failed: %s\n", - spnego_state->sub_sec_security->ops->name, - nt_errstr(nt_status))); - return nt_status; + /* OK, so it's real SPNEGO, check the packet's the one we expect */ + if (state->spnego_in->type != spnego_state->expected_packet) { + DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", + state->spnego_in->type, + spnego_state->expected_packet)); + dump_data(1, state->full_in.data, + state->full_in.length); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); } - if (unwrapped_out.length || mech_list_mic.length) { - /* compose reply */ - spnego_out.type = SPNEGO_NEG_TOKEN_TARG; - spnego_out.negTokenTarg.negResult = SPNEGO_NONE_RESULT; - spnego_out.negTokenTarg.supportedMech = NULL; - spnego_out.negTokenTarg.responseToken = unwrapped_out; - spnego_out.negTokenTarg.mechListMIC = mech_list_mic; - - if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { - DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n")); - return NT_STATUS_INVALID_PARAMETER; - } + break; - spnego_state->num_targs++; - spnego_state->state_position = SPNEGO_CLIENT_TARG; - nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED; - } else { + default: + smb_panic(__location__); + return NULL; + } - /* all done - server has accepted, and we agree */ - *out = null_data_blob; + gensec_spnego_update_pre(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } - if (spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED) { - /* unless of course it did not accept */ - DEBUG(1,("gensec_update ok but not accepted\n")); - nt_status = NT_STATUS_INVALID_PARAMETER; - } + if (state->sub.needed) { + struct tevent_req *subreq = NULL; - spnego_state->state_position = SPNEGO_DONE; + /* + * We may need one more roundtrip... + */ + subreq = gensec_update_send(state, state->ev, + spnego_state->sub_sec_security, + state->sub.in); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); } - - return nt_status; + tevent_req_set_callback(subreq, + gensec_spnego_update_done, + req); + state->sub.needed = false; + return req; } - case SPNEGO_DONE: - /* We should not be called after we are 'done' */ - return NT_STATUS_INVALID_PARAMETER; + + gensec_spnego_update_post(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); } - return NT_STATUS_INVALID_PARAMETER; + + return req; } static NTSTATUS gensec_spnego_update_in(struct gensec_security *gensec_security, - const DATA_BLOB in, DATA_BLOB *full_in) + const DATA_BLOB in, TALLOC_CTX *mem_ctx, + DATA_BLOB *full_in) { - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; + struct spnego_state *spnego_state = + talloc_get_type_abort(gensec_security->private_data, + struct spnego_state); size_t expected; bool ok; *full_in = data_blob_null; + switch (spnego_state->state_position) { + case SPNEGO_FALLBACK: + *full_in = in; + spnego_state->in_needed = 0; + return NT_STATUS_OK; + + case SPNEGO_CLIENT_START: + case SPNEGO_CLIENT_TARG: + case SPNEGO_SERVER_START: + case SPNEGO_SERVER_TARG: + break; + + case SPNEGO_DONE: + default: + return NT_STATUS_INVALID_PARAMETER; + } + if (spnego_state->in_needed == 0) { size_t size = 0; int ret; @@ -1486,6 +1829,7 @@ static NTSTATUS gensec_spnego_update_in(struct gensec_security *gensec_security, * more than expected. */ *full_in = in; + spnego_state->in_needed = 0; return NT_STATUS_OK; } @@ -1500,166 +1844,315 @@ static NTSTATUS gensec_spnego_update_in(struct gensec_security *gensec_security, } *full_in = spnego_state->in_frag; + talloc_steal(mem_ctx, full_in->data); + spnego_state->in_frag = data_blob_null; + spnego_state->in_needed = 0; return NT_STATUS_OK; } -static NTSTATUS gensec_spnego_update_out(struct gensec_security *gensec_security, - TALLOC_CTX *out_mem_ctx, - DATA_BLOB *_out) +static void gensec_spnego_update_pre(struct tevent_req *req) { - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; - DATA_BLOB out = data_blob_null; - bool ok; + struct gensec_spnego_update_state *state = + tevent_req_data(req, + struct gensec_spnego_update_state); + struct spnego_state *spnego_state = state->spnego; + const struct spnego_neg_ops *ops = NULL; + NTSTATUS status; - *_out = data_blob_null; + state->sub.needed = false; + state->sub.in = data_blob_null; + state->sub.status = NT_STATUS_INTERNAL_ERROR; + state->sub.out = data_blob_null; - if (spnego_state->out_frag.length == 0) { - return spnego_state->out_status; + if (spnego_state->state_position == SPNEGO_FALLBACK) { + state->sub.in = state->full_in; + state->full_in = data_blob_null; + state->sub.needed = true; + return; } - /* - * There is still more data to be delivered - * to the remote peer. - */ + switch (spnego_state->state_position) { + case SPNEGO_CLIENT_START: + if (state->spnego_in == NULL) { + /* client to produce negTokenInit */ + ops = &gensec_spnego_create_negTokenInit_ops; + break; + } - if (spnego_state->out_frag.length <= spnego_state->out_max_length) { - /* - * Fast path, we can deliver everything - */ + ops = &gensec_spnego_client_negTokenInit_ops; + break; - *_out = spnego_state->out_frag; - talloc_steal(out_mem_ctx, _out->data); - spnego_state->out_frag = data_blob_null; - return spnego_state->out_status; + case SPNEGO_CLIENT_TARG: + ops = &gensec_spnego_client_negTokenTarg_ops; + break; + + case SPNEGO_SERVER_START: + if (state->spnego_in == NULL) { + /* server to produce negTokenInit */ + ops = &gensec_spnego_create_negTokenInit_ops; + break; + } + + ops = &gensec_spnego_server_negTokenInit_ops; + break; + + case SPNEGO_SERVER_TARG: + ops = &gensec_spnego_server_negTokenTarg_ops; + break; + + default: + smb_panic(__location__); + return; } - out = spnego_state->out_frag; + state->n = gensec_spnego_neg_state(state, ops); + if (tevent_req_nomem(state->n, req)) { + return; + } - /* - * 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; + status = ops->start_fn(state->gensec, spnego_state, state->n, + state->spnego_in, state, &state->sub.in); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return; } - /* - * truncate the buffer - */ - ok = data_blob_realloc(spnego_state, &out, - spnego_state->out_max_length); - if (!ok) { - return NT_STATUS_NO_MEMORY; + if (NT_STATUS_IS_OK(status)) { + /* + * Call finish_fn() with an empty + * blob and NT_STATUS_OK. + */ + state->sub.status = NT_STATUS_OK; + } else { + /* + * MORE_PROCESSING_REQUIRED => + * we need to call gensec_update_send(). + */ + state->sub.needed = true; } +} - talloc_steal(out_mem_ctx, out.data); - *_out = out; - return NT_STATUS_MORE_PROCESSING_REQUIRED; +static void gensec_spnego_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct gensec_spnego_update_state *state = + tevent_req_data(req, + struct gensec_spnego_update_state); + struct spnego_state *spnego_state = state->spnego; + + state->sub.status = gensec_update_recv(subreq, state, &state->sub.out); + TALLOC_FREE(subreq); + if (NT_STATUS_IS_OK(state->sub.status)) { + spnego_state->sub_sec_ready = true; + } + + gensec_spnego_update_post(req); } -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) +static void gensec_spnego_update_post(struct tevent_req *req) { - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; - DATA_BLOB full_in = data_blob_null; + struct gensec_spnego_update_state *state = + tevent_req_data(req, + struct gensec_spnego_update_state); + struct spnego_state *spnego_state = state->spnego; + const struct spnego_neg_ops *ops = NULL; NTSTATUS status; - *out = data_blob_null; + state->sub.in = data_blob_null; + state->sub.needed = false; - if (spnego_state->out_frag.length > 0) { - if (in.length > 0) { - return NT_STATUS_INVALID_PARAMETER; + if (spnego_state->state_position == SPNEGO_FALLBACK) { + status = state->sub.status; + spnego_state->out_frag = state->sub.out; + talloc_steal(spnego_state, spnego_state->out_frag.data); + state->sub.out = data_blob_null; + goto respond; + } + + ops = state->n->ops; + + if (GENSEC_UPDATE_IS_NTERROR(state->sub.status)) { + + + /* + * gensec_update_recv() returned an error, + * let's see if the step_fn() want to + * handle it and negotiate something else. + */ + + status = ops->step_fn(state->gensec, + spnego_state, + state->n, + state->spnego_in, + state->sub.status, + state, + &state->sub.in); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return; } - return gensec_spnego_update_out(gensec_security, - out_mem_ctx, - out); + state->sub.out = data_blob_null; + state->sub.status = NT_STATUS_INTERNAL_ERROR; + + if (NT_STATUS_IS_OK(status)) { + /* + * Call finish_fn() with an empty + * blob and NT_STATUS_OK. + */ + state->sub.status = NT_STATUS_OK; + } else { + /* + * MORE_PROCESSING_REQUIRED... + */ + state->sub.needed = true; + } } - status = gensec_spnego_update_in(gensec_security, - in, &full_in); - if (!NT_STATUS_IS_OK(status)) { - return status; + if (state->sub.needed) { + struct tevent_req *subreq = NULL; + + /* + * We may need one more roundtrip... + */ + subreq = gensec_update_send(state, state->ev, + spnego_state->sub_sec_security, + state->sub.in); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + gensec_spnego_update_done, + req); + state->sub.needed = false; + return; + } + + status = ops->finish_fn(state->gensec, + spnego_state, + state->n, + state->spnego_in, + state->sub.status, + state->sub.out, + spnego_state, + &spnego_state->out_frag); + TALLOC_FREE(state->n); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return; } - 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)) { bool reset_full = true; - gensec_security->child_security = spnego_state->sub_sec_security; - reset_full = !spnego_state->done_mic_check; status = gensec_may_reset_crypto(spnego_state->sub_sec_security, reset_full); - } - if (!NT_STATUS_IS_OK(status) && - !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { - return status; + if (tevent_req_nterror(req, status)) { + return; + } } +respond: 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) -{ - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; - - gensec_security->want_features |= feature; - if (!spnego_state || !spnego_state->sub_sec_security) { + status = gensec_spnego_update_out(state->gensec, + state, &state->out); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); return; } - gensec_want_feature(spnego_state->sub_sec_security, - feature); + state->status = status; + tevent_req_done(req); + return; } -static bool gensec_spnego_have_feature(struct gensec_security *gensec_security, - uint32_t feature) +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; - if (!spnego_state->sub_sec_security) { - return false; + struct spnego_state *spnego_state = + talloc_get_type_abort(gensec_security->private_data, + struct spnego_state); + DATA_BLOB out = data_blob_null; + bool ok; + + *_out = data_blob_null; + + if (spnego_state->out_frag.length <= spnego_state->out_max_length) { + /* + * Fast path, we can deliver everything + */ + + *_out = spnego_state->out_frag; + if (spnego_state->out_frag.length > 0) { + talloc_steal(out_mem_ctx, _out->data); + spnego_state->out_frag = data_blob_null; + } + + if (!NT_STATUS_IS_OK(spnego_state->out_status)) { + return spnego_state->out_status; + } + + /* + * We're completely done, further updates are not allowed. + */ + spnego_state->state_position = SPNEGO_DONE; + return gensec_child_ready(gensec_security, + spnego_state->sub_sec_security); } - return gensec_have_feature(spnego_state->sub_sec_security, - feature); -} + out = spnego_state->out_frag; -static NTTIME gensec_spnego_expire_time(struct gensec_security *gensec_security) -{ - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; + /* + * 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; + } - if (!spnego_state->sub_sec_security) { - return GENSEC_EXPIRE_TIME_INFINITY; + /* + * truncate the buffer + */ + ok = data_blob_realloc(spnego_state, &out, + spnego_state->out_max_length); + if (!ok) { + return NT_STATUS_NO_MEMORY; } - return gensec_expire_time(spnego_state->sub_sec_security); + talloc_steal(out_mem_ctx, out.data); + *_out = out; + return NT_STATUS_MORE_PROCESSING_REQUIRED; } -static const char *gensec_spnego_final_auth_type(struct gensec_security *gensec_security) +static NTSTATUS gensec_spnego_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) { - struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; + struct gensec_spnego_update_state *state = + tevent_req_data(req, + struct gensec_spnego_update_state); + NTSTATUS status; - if (!spnego_state->sub_sec_security) { - return "NONE"; - } else { - return gensec_final_auth_type(spnego_state->sub_sec_security); + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; } + + *out = state->out; + talloc_steal(out_mem_ctx, state->out.data); + status = state->status; + tevent_req_received(req); + return status; } static const char *gensec_spnego_oids[] = { @@ -1674,24 +2167,26 @@ 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_wrapper, - .seal_packet = gensec_spnego_seal_packet, - .sign_packet = gensec_spnego_sign_packet, - .sig_size = gensec_spnego_sig_size, - .max_wrapped_size = gensec_spnego_max_wrapped_size, - .max_input_size = gensec_spnego_max_input_size, - .check_packet = gensec_spnego_check_packet, - .unseal_packet = gensec_spnego_unseal_packet, - .wrap = gensec_spnego_wrap, - .unwrap = gensec_spnego_unwrap, - .session_key = gensec_spnego_session_key, - .session_info = gensec_spnego_session_info, - .want_feature = gensec_spnego_want_feature, - .have_feature = gensec_spnego_have_feature, - .expire_time = gensec_spnego_expire_time, - .final_auth_type = gensec_spnego_final_auth_type, + .update_send = gensec_spnego_update_send, + .update_recv = gensec_spnego_update_recv, + .seal_packet = gensec_child_seal_packet, + .sign_packet = gensec_child_sign_packet, + .sig_size = gensec_child_sig_size, + .max_wrapped_size = gensec_child_max_wrapped_size, + .max_input_size = gensec_child_max_input_size, + .check_packet = gensec_child_check_packet, + .unseal_packet = gensec_child_unseal_packet, + .wrap = gensec_child_wrap, + .unwrap = gensec_child_unwrap, + .session_key = gensec_child_session_key, + .session_info = gensec_child_session_info, + .want_feature = gensec_child_want_feature, + .have_feature = gensec_child_have_feature, + .expire_time = gensec_child_expire_time, + .final_auth_type = gensec_child_final_auth_type, .enabled = true, - .priority = GENSEC_SPNEGO + .priority = GENSEC_SPNEGO, + .glue = true, }; _PUBLIC_ NTSTATUS gensec_spnego_init(TALLOC_CTX *ctx)