summaryrefslogtreecommitdiff
path: root/source4/auth/gensec/spnego.c
diff options
context:
space:
mode:
authorAndrew Bartlett <abartlet@samba.org>2005-03-29 08:24:03 +0000
committerGerald (Jerry) Carter <jerry@samba.org>2007-10-10 13:11:20 -0500
commit7cabdeb7ec84c7c0b3e9b907e19f4e240b7fc4ca (patch)
tree39fb9a70a787facf6bc48e58dbc821c7e5b2de84 /source4/auth/gensec/spnego.c
parentd15845eb813a2667dfc857d68dc3bf6b262ff496 (diff)
downloadsamba-7cabdeb7ec84c7c0b3e9b907e19f4e240b7fc4ca.tar.gz
samba-7cabdeb7ec84c7c0b3e9b907e19f4e240b7fc4ca.tar.bz2
samba-7cabdeb7ec84c7c0b3e9b907e19f4e240b7fc4ca.zip
r6113: Move GENSEC and the kerberos code out of libcli/auth, and into
auth/gensec and auth/kerberos. This also pulls the kerberos configure code out of libads (which is otherwise dead), and into auth/kerberos/kerberos.m4 Andrew Bartlett (This used to be commit e074d63f3dcf4f84239a10879112ebaf1cfa6c4f)
Diffstat (limited to 'source4/auth/gensec/spnego.c')
-rw-r--r--source4/auth/gensec/spnego.c884
1 files changed, 884 insertions, 0 deletions
diff --git a/source4/auth/gensec/spnego.c b/source4/auth/gensec/spnego.c
new file mode 100644
index 0000000000..f5a091cd78
--- /dev/null
+++ b/source4/auth/gensec/spnego.c
@@ -0,0 +1,884 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ RFC2478 Compliant SPNEGO implementation
+
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "includes.h"
+#include "auth/auth.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+enum spnego_state_position {
+ SPNEGO_SERVER_START,
+ SPNEGO_CLIENT_START,
+ SPNEGO_SERVER_TARG,
+ SPNEGO_CLIENT_TARG,
+ SPNEGO_FALLBACK,
+ SPNEGO_DONE
+};
+
+struct spnego_state {
+ uint_t ref_count;
+ enum spnego_message_type expected_packet;
+ enum spnego_state_position state_position;
+ struct gensec_security *sub_sec_security;
+ BOOL no_response_expected;
+};
+
+
+static NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_security)
+{
+ struct spnego_state *spnego_state;
+
+ spnego_state = talloc(gensec_security, struct spnego_state);
+ if (!spnego_state) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ 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;
+
+ gensec_security->private_data = spnego_state;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS gensec_spnego_server_start(struct gensec_security *gensec_security)
+{
+ struct spnego_state *spnego_state;
+
+ spnego_state = talloc(gensec_security, struct spnego_state);
+ if (!spnego_state) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ 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;
+
+ gensec_security->private_data = spnego_state;
+ return NT_STATUS_OK;
+}
+
+/*
+ wrappers for the spnego_*() functions
+*/
+static NTSTATUS gensec_spnego_unseal_packet(struct gensec_security *gensec_security,
+ TALLOC_CTX *mem_ctx,
+ uint8_t *data, size_t length,
+ const uint8_t *whole_pdu, size_t pdu_length,
+ DATA_BLOB *sig)
+{
+ struct spnego_state *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,
+ mem_ctx,
+ data, length,
+ whole_pdu, pdu_length,
+ sig);
+}
+
+static NTSTATUS gensec_spnego_check_packet(struct gensec_security *gensec_security,
+ TALLOC_CTX *mem_ctx,
+ const uint8_t *data, size_t length,
+ const uint8_t *whole_pdu, size_t pdu_length,
+ const DATA_BLOB *sig)
+{
+ struct spnego_state *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,
+ mem_ctx,
+ 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 = 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 = 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 = 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 = 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)
+{
+ struct spnego_state *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);
+}
+
+static NTSTATUS gensec_spnego_session_key(struct gensec_security *gensec_security,
+ DATA_BLOB *session_key)
+{
+ struct spnego_state *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,
+ session_key);
+}
+
+static NTSTATUS gensec_spnego_session_info(struct gensec_security *gensec_security,
+ struct auth_session_info **session_info)
+{
+ struct spnego_state *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,
+ 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
+ * try all the other options (and hope they all have a magic string
+ * they check)
+*/
+
+static NTSTATUS gensec_spnego_server_try_fallback(struct gensec_security *gensec_security,
+ struct spnego_state *spnego_state,
+ TALLOC_CTX *out_mem_ctx,
+ const DATA_BLOB in, DATA_BLOB *out)
+{
+ int i;
+ int num_ops;
+ const struct gensec_security_ops **all_ops = gensec_security_all(&num_ops);
+ for (i=0; i < num_ops; i++) {
+ NTSTATUS nt_status;
+ if (!all_ops[i]->oid) {
+ continue;
+ }
+ if (strcasecmp(GENSEC_OID_SPNEGO,all_ops[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_oid(spnego_state->sub_sec_security,
+ all_ops[i]->oid);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(spnego_state->sub_sec_security);
+ spnego_state->sub_sec_security = NULL;
+ continue;
+ }
+ nt_status = gensec_update(spnego_state->sub_sec_security,
+ out_mem_ctx, in, out);
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ spnego_state->state_position = SPNEGO_FALLBACK;
+ return nt_status;
+ }
+ talloc_free(spnego_state->sub_sec_security);
+ spnego_state->sub_sec_security = NULL;
+ }
+ DEBUG(1, ("Failed to parse SPNEGO request\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+
+}
+
+/*
+ Parse the netTokenInit from the client, to the server.
+
+
+*/
+
+static NTSTATUS gensec_spnego_server_parse_negTokenInit(struct gensec_security *gensec_security,
+ struct spnego_state *spnego_state,
+ TALLOC_CTX *out_mem_ctx,
+ const char **mechType,
+ const DATA_BLOB unwrapped_in, DATA_BLOB *unwrapped_out)
+{
+ NTSTATUS nt_status;
+
+ if (!mechType || !mechType[0]) {
+ DEBUG(1, ("SPNEGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ 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_oid(spnego_state->sub_sec_security,
+ mechType[0]);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(spnego_state->sub_sec_security);
+ spnego_state->sub_sec_security = NULL;
+ return nt_status;
+ }
+
+ if (!unwrapped_in.length) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ nt_status = gensec_update(spnego_state->sub_sec_security,
+ out_mem_ctx,
+ unwrapped_in,
+ unwrapped_out);
+
+ if (!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;
+ }
+ return nt_status;
+}
+
+static NTSTATUS gensec_spnego_client_parse_negTokenInit(struct gensec_security *gensec_security,
+ struct spnego_state *spnego_state,
+ TALLOC_CTX *out_mem_ctx,
+ const char **mechType,
+ const DATA_BLOB unwrapped_in, DATA_BLOB *unwrapped_out)
+{
+ int i;
+ NTSTATUS nt_status;
+ DATA_BLOB null_data_blob = data_blob(NULL,0);
+
+ for (i=0; mechType && mechType[i]; i++) {
+ nt_status = gensec_subcontext_start(spnego_state,
+ gensec_security,
+ &spnego_state->sub_sec_security);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ break;
+ }
+ /* select the sub context */
+ nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
+ mechType[i]);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(spnego_state->sub_sec_security);
+ spnego_state->sub_sec_security = NULL;
+ continue;
+ }
+
+ if (i == 0) {
+ nt_status = gensec_update(spnego_state->sub_sec_security,
+ out_mem_ctx,
+ unwrapped_in,
+ unwrapped_out);
+ } else {
+ /* only get the helping start blob for the first OID */
+ nt_status = gensec_update(spnego_state->sub_sec_security,
+ out_mem_ctx,
+ null_data_blob,
+ unwrapped_out);
+ }
+ if (!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;
+ }
+ return nt_status;
+ }
+ if (!mechType || !mechType[i]) {
+ DEBUG(1, ("SPNEGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n"));
+ }
+ return NT_STATUS_INVALID_PARAMETER;
+}
+
+/** create a client negTokenInit
+ *
+ * This is the case, where the client is the first one who sends data
+*/
+
+static NTSTATUS gensec_spnego_client_negTokenInit(struct gensec_security *gensec_security,
+ struct spnego_state *spnego_state,
+ TALLOC_CTX *out_mem_ctx,
+ const DATA_BLOB in, DATA_BLOB *out)
+{
+ DATA_BLOB null_data_blob = data_blob(NULL,0);
+ NTSTATUS nt_status;
+ const char **mechTypes = NULL;
+ DATA_BLOB unwrapped_out = data_blob(NULL,0);
+
+ mechTypes = gensec_security_oids(out_mem_ctx, GENSEC_OID_SPNEGO);
+
+ if (!mechTypes) {
+ DEBUG(1, ("no GENSEC OID backends available\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ 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 our preferred mech */
+ nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
+ mechTypes[0]);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(spnego_state->sub_sec_security);
+ spnego_state->sub_sec_security = NULL;
+ return nt_status;
+ }
+ nt_status = gensec_update(spnego_state->sub_sec_security,
+ out_mem_ctx, in, &unwrapped_out);
+ if (NT_STATUS_IS_OK(nt_status) || NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ struct spnego_data spnego_out;
+ spnego_out.type = SPNEGO_NEG_TOKEN_INIT;
+ spnego_out.negTokenInit.mechTypes = mechTypes;
+ spnego_out.negTokenInit.reqFlags = 0;
+ spnego_out.negTokenInit.mechListMIC = null_data_blob;
+ spnego_out.negTokenInit.mechToken = unwrapped_out;
+
+ 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;
+ }
+
+ /* set next state */
+ spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
+ spnego_state->state_position = SPNEGO_CLIENT_TARG;
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ spnego_state->no_response_expected = True;
+ }
+
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+ }
+ talloc_free(spnego_state->sub_sec_security);
+ spnego_state->sub_sec_security = NULL;
+
+ DEBUG(1, ("Failed to setup SPNEGO negTokenInit request: %s\n", nt_errstr(nt_status)));
+ return NT_STATUS_INVALID_PARAMETER;
+}
+
+
+/** create a client negTokenTarg
+ *
+ * This is the case, where the client is the first one who sends data
+*/
+
+static NTSTATUS gensec_spnego_server_negTokenTarg(struct gensec_security *gensec_security,
+ struct spnego_state *spnego_state,
+ TALLOC_CTX *out_mem_ctx,
+ NTSTATUS nt_status,
+ const DATA_BLOB unwrapped_out, DATA_BLOB *out)
+{
+ struct spnego_data spnego_out;
+ DATA_BLOB null_data_blob = data_blob(NULL, 0);
+
+ /* compose reply */
+ spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
+ spnego_out.negTokenTarg.responseToken = unwrapped_out;
+ spnego_out.negTokenTarg.mechListMIC = null_data_blob;
+ spnego_out.negTokenTarg.supportedMech = NULL;
+
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ spnego_out.negTokenTarg.supportedMech
+ = spnego_state->sub_sec_security->ops->oid;
+ 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->sub_sec_security->ops->oid;
+ }
+ spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_COMPLETED;
+ spnego_state->state_position = SPNEGO_DONE;
+ } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_PARAMETER)) {
+ if (spnego_state->sub_sec_security) {
+ /* we have a mech, but we just didn't get the input parameter */
+ spnego_out.negTokenTarg.supportedMech
+ = spnego_state->sub_sec_security->ops->oid;
+ } else {
+ const char **mechTypes = gensec_security_oids(out_mem_ctx, GENSEC_OID_SPNEGO);
+ if (!mechTypes) {
+ DEBUG(1, ("no GENSEC OID backends available\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ 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 our preferred mech */
+ nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
+ mechTypes[0]);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(spnego_state->sub_sec_security);
+ spnego_state->sub_sec_security = NULL;
+ return nt_status;
+ }
+
+ /* we should be sending the whole list here */
+ spnego_out.negTokenTarg.supportedMech = mechTypes[0];
+ }
+
+ spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE;
+ spnego_state->state_position = SPNEGO_SERVER_TARG;
+ nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ } else {
+ spnego_out.negTokenTarg.negResult = SPNEGO_REJECT;
+ 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"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
+
+ return nt_status;
+}
+
+
+static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx,
+ const DATA_BLOB in, DATA_BLOB *out)
+{
+ struct spnego_state *spnego_state = gensec_security->private_data;
+ DATA_BLOB null_data_blob = data_blob(NULL, 0);
+ DATA_BLOB unwrapped_out = data_blob(NULL, 0);
+ struct spnego_data spnego_out;
+ struct spnego_data spnego;
+
+ ssize_t len;
+
+ *out = data_blob(NULL, 0);
+
+ if (!out_mem_ctx) {
+ out_mem_ctx = spnego_state;
+ }
+
+ /* and switch into the state machine */
+
+ switch (spnego_state->state_position) {
+ case SPNEGO_FALLBACK:
+ return gensec_update(spnego_state->sub_sec_security,
+ out_mem_ctx, in, out);
+ case SPNEGO_SERVER_START:
+ {
+ if (in.length) {
+ NTSTATUS nt_status;
+
+ len = spnego_read_data(in, &spnego);
+ if (len == -1) {
+ return gensec_spnego_server_try_fallback(gensec_security, spnego_state, 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;
+ }
+
+ nt_status = gensec_spnego_server_parse_negTokenInit(gensec_security,
+ spnego_state,
+ out_mem_ctx,
+ spnego.negTokenInit.mechTypes,
+ spnego.negTokenInit.mechToken,
+ &unwrapped_out);
+
+ nt_status = gensec_spnego_server_negTokenTarg(gensec_security,
+ spnego_state,
+ out_mem_ctx,
+ nt_status,
+ unwrapped_out,
+ out);
+
+ spnego_free_data(&spnego);
+
+ return nt_status;
+ } else {
+ const char **mechlist = gensec_security_oids(out_mem_ctx, GENSEC_OID_SPNEGO);
+
+ spnego_out.type = SPNEGO_NEG_TOKEN_INIT;
+ spnego_out.negTokenInit.mechTypes = mechlist;
+ spnego_out.negTokenInit.reqFlags = 0;
+ spnego_out.negTokenInit.mechListMIC
+ = data_blob_string_const(talloc_asprintf(out_mem_ctx, "%s$@%s", lp_netbios_name(), lp_realm()));
+ spnego_out.negTokenInit.mechToken = unwrapped_out;
+
+ 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;
+ }
+
+ /* set next state */
+ spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
+ spnego_state->state_position = SPNEGO_SERVER_TARG;
+
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+ }
+ }
+
+ case SPNEGO_CLIENT_START:
+ {
+ /* The server offers a list of mechanisms */
+
+ const char *my_mechs[] = {NULL, NULL};
+ NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER;
+
+ if (!in.length) {
+ /* client to produce negTokenInit */
+ return gensec_spnego_client_negTokenInit(gensec_security, spnego_state,
+ out_mem_ctx, in, out);
+ }
+
+ len = spnego_read_data(in, &spnego);
+
+ if (len == -1) {
+ DEBUG(1, ("Invalid SPNEGO request:\n"));
+ dump_data(1, in.data, in.length);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* 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 (spnego.negTokenInit.targetPrincipal) {
+ DEBUG(5, ("Server claims it's principal name is %s (ignored)\n", spnego.negTokenInit.targetPrincipal));
+ }
+
+ nt_status = gensec_spnego_client_parse_negTokenInit(gensec_security,
+ spnego_state,
+ out_mem_ctx,
+ spnego.negTokenInit.mechTypes,
+ spnego.negTokenInit.mechToken,
+ &unwrapped_out);
+
+ if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) {
+ spnego_free_data(&spnego);
+ return nt_status;
+ }
+
+ /* compose reply */
+ my_mechs[0] = spnego_state->sub_sec_security->ops->oid;
+
+ spnego_out.type = SPNEGO_NEG_TOKEN_INIT;
+ spnego_out.negTokenInit.mechTypes = my_mechs;
+ spnego_out.negTokenInit.reqFlags = 0;
+ spnego_out.negTokenInit.mechListMIC = null_data_blob;
+ spnego_out.negTokenInit.mechToken = unwrapped_out;
+
+ 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;
+ }
+
+ /* set next state */
+ spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
+ spnego_state->state_position = SPNEGO_CLIENT_TARG;
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ spnego_state->no_response_expected = True;
+ }
+
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+ }
+ case SPNEGO_SERVER_TARG:
+ {
+ NTSTATUS nt_status;
+ if (!in.length) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ len = spnego_read_data(in, &spnego);
+
+ if (len == -1) {
+ DEBUG(1, ("Invalid SPNEGO request:\n"));
+ dump_data(1, in.data, in.length);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* 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 (!spnego_state->sub_sec_security) {
+ DEBUG(1, ("SPNEGO: Did not setup a mech in NEG_TOKEN_INIT\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ nt_status = gensec_update(spnego_state->sub_sec_security,
+ out_mem_ctx,
+ spnego.negTokenTarg.responseToken,
+ &unwrapped_out);
+
+ nt_status = gensec_spnego_server_negTokenTarg(gensec_security,
+ spnego_state,
+ out_mem_ctx,
+ nt_status,
+ unwrapped_out,
+ out);
+
+ spnego_free_data(&spnego);
+
+ return nt_status;
+ }
+ case SPNEGO_CLIENT_TARG:
+ {
+ NTSTATUS nt_status;
+ if (!in.length) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ len = spnego_read_data(in, &spnego);
+
+ if (len == -1) {
+ DEBUG(1, ("Invalid SPNEGO request:\n"));
+ dump_data(1, in.data, in.length);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* 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 (spnego.negTokenTarg.negResult == SPNEGO_REJECT) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (spnego_state->no_response_expected) {
+ if (spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED) {
+ DEBUG(3,("GENSEC SPNEGO: client GENSEC accepted, but server rejected (bad password?)\n"));
+ nt_status = NT_STATUS_INVALID_PARAMETER;
+ } else if (spnego.negTokenTarg.responseToken.length) {
+ DEBUG(2,("GENSEC SPNEGO: client GENSEC accepted, but server continued negotiation!\n"));
+ nt_status = NT_STATUS_INVALID_PARAMETER;
+ } else {
+ nt_status = NT_STATUS_OK;
+ }
+ } else {
+ nt_status = gensec_update(spnego_state->sub_sec_security,
+ out_mem_ctx,
+ spnego.negTokenTarg.responseToken,
+ &unwrapped_out);
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ spnego_state->no_response_expected = True;
+ }
+ }
+
+ 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;
+ }
+
+ if (unwrapped_out.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 = null_data_blob;
+
+ 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->state_position = SPNEGO_CLIENT_TARG;
+ nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ } else {
+
+ /* all done - server has accepted, and we agree */
+ *out = null_data_blob;
+
+ 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;
+ }
+ }
+
+ spnego_state->state_position = SPNEGO_DONE;
+
+ return nt_status;
+ }
+ case SPNEGO_DONE:
+ return NT_STATUS_OK;
+ }
+ return NT_STATUS_INVALID_PARAMETER;
+}
+
+static BOOL gensec_spnego_have_feature(struct gensec_security *gensec_security,
+ uint32_t feature)
+{
+ struct spnego_state *spnego_state = gensec_security->private_data;
+ if (!spnego_state->sub_sec_security) {
+ return False;
+ }
+
+ return gensec_have_feature(spnego_state->sub_sec_security,
+ feature);
+}
+
+static const struct gensec_security_ops gensec_spnego_security_ops = {
+ .name = "spnego",
+ .sasl_name = "GSS-SPNEGO",
+ .auth_type = DCERPC_AUTH_TYPE_SPNEGO,
+ .oid = GENSEC_OID_SPNEGO,
+ .client_start = gensec_spnego_client_start,
+ .server_start = gensec_spnego_server_start,
+ .update = gensec_spnego_update,
+ .seal_packet = gensec_spnego_seal_packet,
+ .sign_packet = gensec_spnego_sign_packet,
+ .sig_size = gensec_spnego_sig_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,
+ .have_feature = gensec_spnego_have_feature,
+ .enabled = True
+};
+
+NTSTATUS gensec_spnego_init(void)
+{
+ NTSTATUS ret;
+ ret = gensec_register(&gensec_spnego_security_ops);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(0,("Failed to register '%s' gensec backend!\n",
+ gensec_spnego_security_ops.name));
+ return ret;
+ }
+
+ return ret;
+}