From 7730b658a10c60e0c30c0d16aa4ff2da0a99ac10 Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Tue, 29 Jul 2003 15:00:38 +0000 Subject: This adds gss-spnego to ntlm_auth. It contains some new spnego support from Jim McDonough. It is to enable cyrus sasl to provide the gss-spnego support. For a preliminary patch to cyrus sasl see http://samba.sernet.de/cyrus-gss-spnego.diff Volker (This used to be commit 45cef8f66e46abe4a25fd2b803a7d1051c1c6602) --- source3/Makefile.in | 3 +- source3/include/includes.h | 2 + source3/include/spnego.h | 65 ++++++++++ source3/libsmb/spnego.c | 292 +++++++++++++++++++++++++++++++++++++++++++++ source3/utils/ntlm_auth.c | 226 ++++++++++++++++++++++++++++++++++- 5 files changed, 586 insertions(+), 2 deletions(-) create mode 100644 source3/include/spnego.h create mode 100644 source3/libsmb/spnego.c (limited to 'source3') diff --git a/source3/Makefile.in b/source3/Makefile.in index 56ca81fd5c..c4d1b306a8 100644 --- a/source3/Makefile.in +++ b/source3/Makefile.in @@ -615,7 +615,8 @@ POPT_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \ TDBBACKUP_OBJ = tdb/tdbbackup.o tdb/tdbback.o $(TDBBASE_OBJ) -NTLM_AUTH_OBJ = utils/ntlm_auth.o $(LIBSAMBA_OBJ) $(POPT_LIB_OBJ) +NTLM_AUTH_OBJ = utils/ntlm_auth.o $(LIBSAMBA_OBJ) $(POPT_LIB_OBJ) \ + libsmb/asn1.o libsmb/spnego.o ###################################################################### # now the rules... diff --git a/source3/include/includes.h b/source3/include/includes.h index d900d7feb9..eb7f73b9d3 100644 --- a/source3/include/includes.h +++ b/source3/include/includes.h @@ -835,6 +835,8 @@ extern int errno; #include "nsswitch/winbind_client.h" +#include "spnego.h" + /* * Type for wide character dirent structure. * Only d_name is defined by POSIX. diff --git a/source3/include/spnego.h b/source3/include/spnego.h new file mode 100644 index 0000000000..8bb13bd354 --- /dev/null +++ b/source3/include/spnego.h @@ -0,0 +1,65 @@ +/* + Unix SMB/CIFS implementation. + + RFC2478 Compliant SPNEGO implementation + + Copyright (C) Jim McDonough 2003 + + 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. +*/ + +#ifndef SAMBA_SPNEGO_H +#define SAMBA_SPNEGO_H + +#define SPNEGO_DELEG_FLAG 0x01 +#define SPNEGO_MUTUAL_FLAG 0x02 +#define SPNEGO_REPLAY_FLAG 0x04 +#define SPNEGO_SEQUENCE_FLAG 0x08 +#define SPNEGO_ANON_FLAG 0x10 +#define SPNEGO_CONF_FLAG 0x20 +#define SPNEGO_INTEG_FLAG 0x40 +#define SPNEGO_REQ_FLAG 0x80 + +#define SPNEGO_NEG_TOKEN_INIT 0 +#define SPNEGO_NEG_TOKEN_TARG 1 + +typedef enum _spnego_negResult { + SPNEGO_ACCEPT_COMPLETED = 0, + SPNEGO_ACCEPT_INCOMPLETE = 1, + SPNEGO_REJECT = 2 +} negResult_t; + +typedef struct spnego_negTokenInit { + char **mechTypes; + int reqFlags; + DATA_BLOB mechToken; + DATA_BLOB mechListMIC; +} negTokenInit_t; + +typedef struct spnego_negTokenTarg { + uint8 negResult; + char *supportedMech; + DATA_BLOB responseToken; + DATA_BLOB mechListMIC; +} negTokenTarg_t; + +typedef struct spnego_spnego { + int type; + negTokenInit_t negTokenInit; + negTokenTarg_t negTokenTarg; +} SPNEGO_DATA; + +#endif diff --git a/source3/libsmb/spnego.c b/source3/libsmb/spnego.c new file mode 100644 index 0000000000..078191ffba --- /dev/null +++ b/source3/libsmb/spnego.c @@ -0,0 +1,292 @@ +/* + Unix SMB/CIFS implementation. + + RFC2478 Compliant SPNEGO implementation + + Copyright (C) Jim McDonough 2003 + + 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" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +static BOOL read_negTokenInit(ASN1_DATA *asn1, negTokenInit_t *token) +{ + ZERO_STRUCTP(token); + + asn1_start_tag(asn1, ASN1_CONTEXT(0)); + asn1_start_tag(asn1, ASN1_SEQUENCE(0)); + + while (!asn1->has_error && 0 < asn1_tag_remaining(asn1)) { + int i; + + switch (asn1->data[asn1->ofs]) { + /* Read mechTypes */ + case ASN1_CONTEXT(0): + asn1_start_tag(asn1, ASN1_CONTEXT(0)); + asn1_start_tag(asn1, ASN1_SEQUENCE(0)); + + token->mechTypes = malloc(sizeof(*token->mechTypes)); + for (i = 0; !asn1->has_error && + 0 < asn1_tag_remaining(asn1); i++) { + token->mechTypes = + realloc(token->mechTypes, (i + 1) * + sizeof(*token->mechTypes)); + asn1_read_OID(asn1, token->mechTypes + i); + } + token->mechTypes[i] = NULL; + + asn1_end_tag(asn1); + asn1_end_tag(asn1); + break; + /* Read reqFlags */ + case ASN1_CONTEXT(1): + asn1_start_tag(asn1, ASN1_CONTEXT(1)); + asn1_read_Integer(asn1, &token->reqFlags); + token->reqFlags |= SPNEGO_REQ_FLAG; + asn1_end_tag(asn1); + break; + /* Read mechToken */ + case ASN1_CONTEXT(2): + asn1_start_tag(asn1, ASN1_CONTEXT(2)); + asn1_read_OctetString(asn1, &token->mechToken); + asn1_end_tag(asn1); + break; + /* Read mecListMIC */ + case ASN1_CONTEXT(3): + asn1_start_tag(asn1, ASN1_CONTEXT(3)); + if (!asn1_read_OctetString(asn1, &token->mechListMIC)) { + char *mechListMIC; + asn1_push_tag(asn1, ASN1_SEQUENCE(0)); + asn1_push_tag(asn1, ASN1_CONTEXT(0)); + asn1_read_GeneralString(asn1, &mechListMIC); + asn1_pop_tag(asn1); + asn1_pop_tag(asn1); + + token->mechListMIC = + data_blob(mechListMIC, strlen(mechListMIC)); + SAFE_FREE(mechListMIC); + } + asn1_end_tag(asn1); + break; + default: + asn1->has_error = True; + break; + } + } + + asn1_end_tag(asn1); + asn1_end_tag(asn1); + + return !asn1->has_error; +} + +static BOOL write_negTokenInit(ASN1_DATA *asn1, negTokenInit_t *token) +{ + asn1_push_tag(asn1, ASN1_CONTEXT(0)); + asn1_push_tag(asn1, ASN1_SEQUENCE(0)); + + /* Write mechTypes */ + if (token->mechTypes && *token->mechTypes) { + int i; + + asn1_push_tag(asn1, ASN1_CONTEXT(0)); + asn1_push_tag(asn1, ASN1_SEQUENCE(0)); + for (i = 0; token->mechTypes[i]; i++) { + asn1_write_OID(asn1, token->mechTypes[i]); + } + asn1_pop_tag(asn1); + asn1_pop_tag(asn1); + } + + /* write reqFlags */ + if (token->reqFlags & SPNEGO_REQ_FLAG) { + int flags = token->reqFlags & ~SPNEGO_REQ_FLAG; + + asn1_push_tag(asn1, ASN1_CONTEXT(1)); + asn1_write_Integer(asn1, flags); + asn1_pop_tag(asn1); + } + + /* write mechToken */ + if (token->mechToken.data) { + asn1_push_tag(asn1, ASN1_CONTEXT(2)); + asn1_write_OctetString(asn1, token->mechToken.data, + token->mechToken.length); + asn1_pop_tag(asn1); + } + + /* write mechListMIC */ + if (token->mechListMIC.data) { + asn1_push_tag(asn1, ASN1_CONTEXT(3)); + asn1_write_OctetString(asn1, token->mechListMIC.data, + token->mechListMIC.length); + asn1_pop_tag(asn1); + } + + asn1_pop_tag(asn1); + asn1_pop_tag(asn1); + + return !asn1->has_error; +} + +static BOOL read_negTokenTarg(ASN1_DATA *asn1, negTokenTarg_t *token) +{ + ZERO_STRUCTP(token); + + asn1_start_tag(asn1, ASN1_CONTEXT(1)); + asn1_start_tag(asn1, ASN1_SEQUENCE(0)); + + while (!asn1->has_error && 0 < asn1_tag_remaining(asn1)) { + switch (asn1->data[asn1->ofs]) { + case ASN1_CONTEXT(0): + /* this is listed as being non-optional by RFC2478 but + Windows doesn't always send it... */ + asn1_start_tag(asn1, ASN1_CONTEXT(0)); + asn1_start_tag(asn1, ASN1_ENUMERATED); + asn1_read_uint8(asn1, &token->negResult); + asn1_end_tag(asn1); + asn1_end_tag(asn1); + break; + case ASN1_CONTEXT(1): + asn1_start_tag(asn1, ASN1_CONTEXT(1)); + asn1_read_OID(asn1, &token->supportedMech); + asn1_end_tag(asn1); + break; + case ASN1_CONTEXT(2): + asn1_start_tag(asn1, ASN1_CONTEXT(2)); + asn1_read_OctetString(asn1, &token->responseToken); + asn1_end_tag(asn1); + break; + case ASN1_CONTEXT(3): + asn1_start_tag(asn1, ASN1_CONTEXT(3)); + asn1_read_OctetString(asn1, &token->mechListMIC); + asn1_end_tag(asn1); + break; + default: + asn1->has_error = True; + break; + } + } + + asn1_end_tag(asn1); + asn1_end_tag(asn1); + + return !asn1->has_error; +} + +static BOOL write_negTokenTarg(ASN1_DATA *asn1, negTokenTarg_t *token) +{ + asn1_push_tag(asn1, ASN1_CONTEXT(1)); + asn1_push_tag(asn1, ASN1_SEQUENCE(0)); + + asn1_push_tag(asn1, ASN1_CONTEXT(0)); + asn1_write_enumerated(asn1, token->negResult); + asn1_pop_tag(asn1); + + if (token->supportedMech) { + asn1_push_tag(asn1, ASN1_CONTEXT(1)); + asn1_write_OID(asn1, token->supportedMech); + asn1_pop_tag(asn1); + } + + if (token->responseToken.data) { + asn1_push_tag(asn1, ASN1_CONTEXT(2)); + asn1_write_OctetString(asn1, token->responseToken.data, + token->responseToken.length); + asn1_pop_tag(asn1); + } + + if (token->mechListMIC.data) { + asn1_push_tag(asn1, ASN1_CONTEXT(3)); + asn1_write_OctetString(asn1, token->mechListMIC.data, + token->mechListMIC.length); + asn1_pop_tag(asn1); + } + + asn1_pop_tag(asn1); + asn1_pop_tag(asn1); + + return !asn1->has_error; +} + +ssize_t read_spnego_data(DATA_BLOB data, SPNEGO_DATA *token) +{ + ASN1_DATA asn1; + ssize_t ret = -1; + + ZERO_STRUCTP(token); + ZERO_STRUCT(asn1); + asn1_load(&asn1, data); + + switch (asn1.data[asn1.ofs]) { + case ASN1_APPLICATION(0): + asn1_start_tag(&asn1, ASN1_APPLICATION(0)); + asn1_check_OID(&asn1, OID_SPNEGO); + if (read_negTokenInit(&asn1, &token->negTokenInit)) { + token->type = SPNEGO_NEG_TOKEN_INIT; + } + asn1_end_tag(&asn1); + break; + case ASN1_CONTEXT(1): + if (read_negTokenTarg(&asn1, &token->negTokenTarg)) { + token->type = SPNEGO_NEG_TOKEN_TARG; + } + break; + default: + break; + } + + if (!asn1.has_error) ret = asn1.ofs; + asn1_free(&asn1); + + return ret; +} + +ssize_t write_spnego_data(DATA_BLOB *blob, SPNEGO_DATA *spnego) +{ + ASN1_DATA asn1; + ssize_t ret = -1; + + ZERO_STRUCT(asn1); + + switch (spnego->type) { + case SPNEGO_NEG_TOKEN_INIT: + asn1_push_tag(&asn1, ASN1_APPLICATION(0)); + asn1_write_OID(&asn1, OID_SPNEGO); + write_negTokenInit(&asn1, &spnego->negTokenInit); + asn1_pop_tag(&asn1); + break; + case SPNEGO_NEG_TOKEN_TARG: + write_negTokenTarg(&asn1, &spnego->negTokenTarg); + break; + default: + asn1.has_error = True; + break; + } + + if (!asn1.has_error) { + *blob = data_blob(asn1.data, asn1.length); + ret = asn1.ofs; + } + asn1_free(&asn1); + + return ret; +} + diff --git a/source3/utils/ntlm_auth.c b/source3/utils/ntlm_auth.c index a84f9b30f5..fcb2cb4335 100644 --- a/source3/utils/ntlm_auth.c +++ b/source3/utils/ntlm_auth.c @@ -32,7 +32,8 @@ enum squid_mode { SQUID_2_4_BASIC, SQUID_2_5_BASIC, - SQUID_2_5_NTLMSSP + SQUID_2_5_NTLMSSP, + GSS_SPNEGO }; @@ -342,6 +343,225 @@ static void manage_squid_basic_request(enum squid_mode squid_mode, } } +static void offer_gss_spnego_mechs(void) { + + DATA_BLOB token; + ASN1_DATA asn1; + SPNEGO_DATA spnego; + const char *OIDs[] = {OID_NTLMSSP, NULL}; + ssize_t len; + char *reply_base64; + + /* Server negTokenInit (mech offerings) */ + ZERO_STRUCT(spnego); + spnego.type = SPNEGO_NEG_TOKEN_INIT; + spnego.negTokenInit.mechTypes = OIDs; + + ZERO_STRUCT(asn1); + asn1_push_tag(&asn1, ASN1_SEQUENCE(0)); + asn1_push_tag(&asn1, ASN1_CONTEXT(0)); + asn1_write_GeneralString(&asn1, "NONE"); + asn1_pop_tag(&asn1); + asn1_pop_tag(&asn1); + spnego.negTokenInit.mechListMIC = data_blob(asn1.data, asn1.length); + asn1_free(&asn1); + + len = write_spnego_data(&token, &spnego); + data_blob_free(&spnego.negTokenInit.mechListMIC); + + if (len == -1) { + DEBUG(1, ("Could not write SPNEGO data blob\n")); + x_fprintf(x_stdout, "BH\n"); + return; + } + + reply_base64 = base64_encode_data_blob(token); + x_fprintf(x_stdout, "TT %s\n", reply_base64); + + SAFE_FREE(reply_base64); + data_blob_free(&token); + DEBUG(10, ("sent SPNEGO negTokenInit\n")); + return; +} + +static void manage_gss_spnego_request(enum squid_mode squid_mode, + char *buf, int length) +{ + static NTLMSSP_STATE *ntlmssp_state = NULL; + SPNEGO_DATA spnego; + DATA_BLOB request, token; + NTSTATUS status; + char *reply_base64; + pstring reply; + ssize_t len; + + if (strlen(buf) < 2) { + + if (ntlmssp_state != NULL) { + DEBUG(1, ("Request for initial SPNEGO request where " + "we already have a state\n")); + x_fprintf(x_stdout, "BH\n"); + return; + } + + DEBUG(1, ("NTLMSSP query [%s] invalid", buf)); + x_fprintf(x_stdout, "BH\n"); + return; + } + + if ( (strlen(buf) == 2) && (strcmp(buf, "YR") == 0) ) { + + /* Initial request, get the negTokenInit offering + mechanisms */ + + offer_gss_spnego_mechs(); + return; + } + + /* All subsequent requests are "KK" (Knock, Knock ;)) and have + a blob. This might be negTokenInit or negTokenTarg */ + + if ( (strlen(buf) <= 3) || (strncmp(buf, "KK", 2) != 0) ) { + DEBUG(1, ("GSS-SPNEGO query [%s] invalid\n", buf)); + x_fprintf(x_stdout, "BH\n"); + return; + } + + request = base64_decode_data_blob(buf + 3); + + len = read_spnego_data(request, &spnego); + + if (len == -1) { + DEBUG(1, ("GSS-SPNEGO query [%s] invalid", buf)); + x_fprintf(x_stdout, "BH\n"); + return; + } + + if ( (spnego.type != SPNEGO_NEG_TOKEN_INIT) && + (spnego.type != SPNEGO_NEG_TOKEN_TARG) ) { + + DEBUG(1, ("Got an invalid SPNEGO token!\n")); + x_fprintf(x_stdout, "BH\n"); + return; + } + + if (spnego.type == SPNEGO_NEG_TOKEN_INIT) { + + /* Second request from Client. This is where the + client offers its mechanism to use. We currently + only support NTLMSSP, the decision for Kerberos + would be taken here. */ + + if ( (spnego.negTokenInit.mechTypes == NULL) || + (spnego.negTokenInit.mechTypes[0] == NULL) ) { + DEBUG(1, ("Client did not offer any mechanism")); + x_fprintf(x_stdout, "BH\n"); + return; + } + + if ( strcmp(spnego.negTokenInit.mechTypes[0], OID_NTLMSSP) != 0 ) { + DEBUG(1, ("Client did not choose NTLMSSP but %s\n", + spnego.negTokenInit.mechTypes[0])); + x_fprintf(x_stdout, "BH\n"); + return; + } + + if ( spnego.negTokenInit.mechToken.data == NULL ) { + DEBUG(1, ("Client did not provide NTLMSSP data\n")); + x_fprintf(x_stdout, "BH\n"); + return; + } + + if ( ntlmssp_state != NULL ) { + DEBUG(1, ("Client wants a new NTLMSSP challenge, but " + "already got one\n")); + x_fprintf(x_stdout, "BH\n"); + + ntlmssp_server_end(&ntlmssp_state); + return; + } + + ntlmssp_server_start(&ntlmssp_state); + ntlmssp_state->check_password = winbind_pw_check; + ntlmssp_state->get_domain = get_winbind_domain; + ntlmssp_state->get_global_myname = get_winbind_netbios_name; + + DEBUG(10, ("got NTLMSSP packet:\n")); + dump_data(10, spnego.negTokenInit.mechToken.data, + spnego.negTokenInit.mechToken.length); + + ZERO_STRUCT(spnego); + spnego.type = SPNEGO_NEG_TOKEN_TARG; + spnego.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE; + spnego.negTokenTarg.supportedMech = OID_NTLMSSP; + + status = ntlmssp_server_update(ntlmssp_state, + spnego.negTokenInit.mechToken, + &spnego.negTokenTarg.responseToken); + + } else { + + /* spnego.type == SPNEGO_NEG_TOKEN_TARG */ + + DATA_BLOB response; + + if (spnego.negTokenTarg.responseToken.data == NULL) { + DEBUG(1, ("Got a negTokenArg without a responseToken!\n")); + x_fprintf(x_stdout, "BH\n"); + return; + } + + status = ntlmssp_server_update(ntlmssp_state, + spnego.negTokenTarg.responseToken, + &response); + + data_blob_free(&spnego.negTokenTarg.responseToken); + + spnego.negTokenTarg.responseToken = response; + + } + + if ( !NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) ) { + + /* Neither ok nor more work to do, so reject */ + + x_fprintf(x_stdout, "NA %s\n", nt_errstr(status)); + DEBUG(10, ("NTLMSSP %s\n", nt_errstr(status))); + return; + } + + pstr_sprintf(reply, "TT"); + + if (NT_STATUS_IS_OK(status)) { + pstr_sprintf(reply, "AF %s\\%s", + ntlmssp_state->domain, ntlmssp_state->user); + + spnego.negTokenTarg.negResult = SPNEGO_ACCEPT_COMPLETED; + + DEBUG(10, ("NTLMSSP OK!\n")); + } + + len = write_spnego_data(&token, &spnego); + + if (len == -1) { + DEBUG(1, ("Could not write SPNEGO data blob\n")); + x_fprintf(x_stdout, "BH\n"); + return; + } + + reply_base64 = base64_encode_data_blob(token); + x_fprintf(x_stdout, "%s %s\n", reply, reply_base64); + SAFE_FREE(reply_base64); + data_blob_free(&token); + + if (NT_STATUS_IS_OK(status)) { + ntlmssp_server_end(&ntlmssp_state); + } + + return; +} + static void manage_squid_request(enum squid_mode squid_mode) { char buf[SQUID_BUFFER_SIZE+1]; @@ -383,6 +603,8 @@ static void manage_squid_request(enum squid_mode squid_mode) manage_squid_basic_request(squid_mode, buf, length); } else if (squid_mode == SQUID_2_5_NTLMSSP) { manage_squid_ntlmssp_request(squid_mode, buf, length); + } else if (squid_mode == GSS_SPNEGO) { + manage_gss_spnego_request(squid_mode, buf, length); } } @@ -1334,6 +1556,8 @@ enum { squid_stream(SQUID_2_5_BASIC); } else if (strcmp(helper_protocol, "squid-2.4-basic")== 0) { squid_stream(SQUID_2_4_BASIC); + } else if (strcmp(helper_protocol, "gss-spnego")== 0) { + squid_stream(GSS_SPNEGO); } else { x_fprintf(x_stderr, "unknown helper protocol [%s]\n", helper_protocol); exit(1); -- cgit