Ken Murchison
2003-Dec-31 17:49 UTC
[Samba] Re: [PATCH] Add winbind-backed NTLMSSP support to Cyrus-SASL
Andrew Bartlett wrote:> Windows authentication extends far beyond the CIFS protocol the Samba > implements, but it only very recently that work has been done to catch > up to Microsoft's extensions in this area. This has caused many > administrators pain and toil that their MS counterparts simply don't > have. For them, authentication 'just works', with single-sign-on and > the lot. > > I have worked, for over a year, with the Squid development team, in > extending NTLMSSP authentication to HTTP. The squid team made a very > good start (as I see Cyrus-SASL now has) in including a basic NTLMSSP > implementation, and even providing a proxy-mechanism to authenticate > against a Windows DC. I extended on this base, providing the > ntlm_auth tool, which allows them to perform this against winbind, and > without having to understand NTLMSSP as anything more than BASE64 strings. > > This provides a much more reliable interface, as winbind is not only faster, > we can also prevent man-in-the-middle attacks. > > The attached patch provides this for Cyrus-SASL. In the same was that > Squid now uses Winbind, all Cyrus-SASL enabled applications can use > Winbind (via ntlm_auth) to authenticate their users. This provides > the most current NTLMSSP implementation in the Open Source arena, as > it is the one that we must maintain for Samba's internal use. > > The plugin is designed to use ntlm_auth over a stdio interface, > because as part of Samba, it is GPL'ed. The plugin provides a client, > and an server implementation, but can only proxy it's server-side (I > can provide a mode that allows for local passwords if it is required). > > Current Samba 3.0 CVS is required to find the NTLMSSP client code exposed.Here is my opinion, Rob's *may* differ: Having support for all of the latest NTLMSSP stuff is a great idea, but I don't think we want to have yet another dependency for Cyrus SASL, especially unreleased Samba code. I also think that being able to use passwords that are stored in an auxprop plugin is mandatory as there might be sites which want to support MS clients but don't have an MS server to proxy to. Can you point me to any references to Winbind, so I at least know what we are missing?> Patch against current SASL CVS, but my testing was actually with 2.1.15I wanted to take a look at your code, but this patch does not apply cleanly to CVS -- only 1 of 7 hunks succeeds. -- Kenneth Murchison Oceana Matrix Ltd. Software Engineer 21 Princeton Place 716-662-8973 x26 Orchard Park, NY 14127 --PGP Public Key-- http://www.oceana.com/~ken/ksm.pgp
Andrew Bartlett
2004-Jan-01 08:07 UTC
[Samba] [PATCH] Add winbind-backed NTLMSSP support to Cyrus-SASL
Windows authentication extends far beyond the CIFS protocol the Samba implements, but it only very recently that work has been done to catch up to Microsoft's extensions in this area. This has caused many administrators pain and toil that their MS counterparts simply don't have. For them, authentication 'just works', with single-sign-on and the lot. I have worked, for over a year, with the Squid development team, in extending NTLMSSP authentication to HTTP. The squid team made a very good start (as I see Cyrus-SASL now has) in including a basic NTLMSSP implementation, and even providing a proxy-mechanism to authenticate against a Windows DC. I extended on this base, providing the ntlm_auth tool, which allows them to perform this against winbind, and without having to understand NTLMSSP as anything more than BASE64 strings. This provides a much more reliable interface, as winbind is not only faster, we can also prevent man-in-the-middle attacks. The attached patch provides this for Cyrus-SASL. In the same was that Squid now uses Winbind, all Cyrus-SASL enabled applications can use Winbind (via ntlm_auth) to authenticate their users. This provides the most current NTLMSSP implementation in the Open Source arena, as it is the one that we must maintain for Samba's internal use. The plugin is designed to use ntlm_auth over a stdio interface, because as part of Samba, it is GPL'ed. The plugin provides a client, and an server implementation, but can only proxy it's server-side (I can provide a mode that allows for local passwords if it is required). Current Samba 3.0 CVS is required to find the NTLMSSP client code exposed. This work is based on the hard work of many persons, but in particular Volker Lendecke for his work on the GSSSPNEGO plugin (also attached). Patch against current SASL CVS, but my testing was actually with 2.1.15 Andrew Bartlett -------------- next part -------------- diff -ur cyrus-sasl-2.1.15/acconfig.h cyrus-sasl-2.1.15-2/acconfig.h --- cyrus-sasl-2.1.15/acconfig.h 2003-07-02 23:13:34.000000000 +1000 +++ cyrus-sasl-2.1.15-2/acconfig.h 2003-11-12 15:38:37.000000000 +1100 @@ -70,6 +70,7 @@ #undef STATIC_LOGIN #undef STATIC_MYSQL #undef STATIC_NTLM +#undef STATIC_GSSSPNEGO #undef STATIC_OTP #undef STATIC_PLAIN #undef STATIC_SASLDB diff -ur cyrus-sasl-2.1.15/configure.in cyrus-sasl-2.1.15-2/configure.in --- cyrus-sasl-2.1.15/configure.in 2003-07-16 01:39:21.000000000 +1000 +++ cyrus-sasl-2.1.15-2/configure.in 2003-11-12 15:48:29.000000000 +1100 @@ -563,6 +563,26 @@ fi +dnl GSSSPNEGO +AC_ARG_ENABLE(gssspnego, [ --enable-gssspnego enable unsupported GSSSPNEGO authentication [no] ], + gssspnego=$enableval, + gssspnego=no) + +AC_MSG_CHECKING(GSSSPNEGO) +if test "$gssspengo" != no; then + AC_MSG_RESULT(enabled) + AC_SUBST(GSSSPNEGO_LIBS) + + SASL_MECHS="$SASL_MECHS libgssspnego.la" + if test "$enable_static" = yes; then + SASL_STATIC_OBJS="$SASL_STATIC_OBJS ../plugins/gssspengo.o" + AC_DEFINE(STATIC_GSSSPNEGO) + fi +else + AC_MSG_RESULT(disabled) +fi + + # make the option show up so people don't whine that it is only in the # saslauthd configure script --help AC_ARG_WITH(ldap, [ --with-ldap=DIR use LDAP (in DIR) for saslauthd (experimental) [no] ],,) diff -ur cyrus-sasl-2.1.15/plugins/makeinit.sh cyrus-sasl-2.1.15-2/plugins/makeinit.sh --- cyrus-sasl-2.1.15/plugins/makeinit.sh 2003-02-19 05:28:49.000000000 +1100 +++ cyrus-sasl-2.1.15-2/plugins/makeinit.sh 2003-11-12 15:09:49.000000000 +1100 @@ -1,4 +1,4 @@ -for mech in anonymous crammd5 digestmd5 gssapiv2 kerberos4 login ntlm otp plain srp; do +for mech in anonymous crammd5 digestmd5 gssapiv2 kerberos4 login ntlm gssspnego otp plain srp; do echo " #include <config.h> diff -ur cyrus-sasl-2.1.15/plugins/ntlm.c cyrus-sasl-2.1.15-2/plugins/ntlm.c --- cyrus-sasl-2.1.15/plugins/ntlm.c 2003-02-14 06:56:04.000000000 +1100 +++ cyrus-sasl-2.1.15-2/plugins/ntlm.c 2003-12-30 23:20:25.000000000 +1100 @@ -1,11 +1,5 @@ -/* NTLM SASL plugin - * Ken Murchison - * $Id: ntlm.c,v 1.6 2003/02/13 19:56:04 rjs3 Exp $ - * - * References: - * http://www.innovation.ch/java/ntlm.html - * http://www.opengroup.org/comsource/techref2/NCH1222X.HTM - * http://www.ubiqx.org/cifs/rfc-draft/draft-leach-cifs-v1-spec-02.html +/* NTLM (via ntlm_auth) plugin + * Volker Lendecke, Andrew Bartlett */ /* * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved. @@ -49,584 +43,300 @@ #include <config.h> #include <stdio.h> -#include <stdlib.h> -#include <ctype.h> - -#include <openssl/des.h> -#include <openssl/md4.h> - +#include <string.h> #include <sasl.h> #include <saslplug.h> +#include <syslog.h> -#include "plugin_common.h" +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> -/***************************** Common Section *****************************/ - -static const char plugin_id[] = "$Id: ntlm.c,v 1.6 2003/02/13 19:56:04 rjs3 Exp $"; +#include "plugin_common.h" -#define NTLM_SIGNATURE "NTLMSSP" +#ifdef macintosh +#include <sasl_ntlm_plugin_decl.h> +#endif -#define NTLM_USE_UNICODE 0x00001 -#define NTLM_USE_ASCII 0x00002 -#define NTLM_ASK_TARGET 0x00004 -#define NTLM_AUTH_NTLM 0x00200 -#define NTLM_ALWAYS_SIGN 0x08000 -#define NTLM_TARGET_IS_DOMAIN 0x10000 -#define NTLM_TARGET_IS_SERVER 0x20000 -#define NTLM_FLAGS_MASK 0x0ffff - -#define NTLM_NONCE_LENGTH 8 -#define NTLM_HASH_LENGTH 21 -#define NTLM_RESP_LENGTH 24 -#define NTLM_SESSKEY_LENGTH 16 - -typedef unsigned short uint16; -typedef unsigned int uint32; - -typedef enum { - NTLM_REQUEST = 1, - NTLM_CHALLENGE = 2, - NTLM_RESPONSE = 3 -} ntlm_type_t; - -typedef struct { - uint16 len; - uint16 maxlen; - uint32 offset; -} ntlm_buffer_t; - -typedef struct { - u_char sig[sizeof(NTLM_SIGNATURE)]; - uint32 type; - uint32 flags; - ntlm_buffer_t domain; - ntlm_buffer_t wkstn; - /* buffer data follows */ -} ntlm_request_t; - -typedef struct { - u_char sig[sizeof(NTLM_SIGNATURE)]; - uint32 type; - ntlm_buffer_t domain; - uint32 flags; - u_char nonce[NTLM_NONCE_LENGTH]; - u_char reserved[8]; - ntlm_buffer_t empty; - /* buffer data follows */ -} ntlm_challenge_t; - -typedef struct ntlm_response_s { - u_char sig[sizeof(NTLM_SIGNATURE)]; - uint32 type; - ntlm_buffer_t lm_resp; - ntlm_buffer_t nt_resp; - ntlm_buffer_t domain; - ntlm_buffer_t user; - ntlm_buffer_t wkstn; - ntlm_buffer_t key; - uint32 flags; - /* buffer data follows */ -} ntlm_response_t; - -/* return the length of a string (even if it is NULL) */ -#define xstrlen(s) (s ? strlen(s) : 0) - -/* machine-independent routines to convert to/from Intel byte-order */ -#define UINT16_TO_INTEL(x, i) \ - i = ((unsigned char *) &x)[0] | (((unsigned char *) &x)[1] << 8) - -#define UINT16_FROM_INTEL(i, x) \ - ((unsigned char *) &x)[0] = i & 0xff; \ - ((unsigned char *) &x)[1] = (i >> 8) - -#define UINT32_TO_INTEL(x, i) \ - i = ((unsigned char *) &x)[0] | (((unsigned char *) &x)[1] << 8) | \ - (((unsigned char *) &x)[2] << 16) | (((unsigned char *) &x)[3] << 24) - -#define UINT32_FROM_INTEL(i, x) \ - ((unsigned char *) &x)[0] = i & 0xff; \ - ((unsigned char *) &x)[1] = (i >> 8) & 0xff; \ - ((unsigned char *) &x)[2] = (i >> 16) & 0xff; \ - ((unsigned char *) &x)[3] = (i >> 24) +/***************************** Common Section *****************************/ -/* convert string to all upper case */ -static const char *ucase(const char *str, unsigned len) -{ - char *cp = (char *) str; +static const char plugin_id[] = "$Id: ntlm.c,v 1.61 2003/03/26 17:18:04 rjs3 Exp $"; - if (!len) len = xstrlen(str); - - while (len && cp && *cp) { - *cp = toupper((int) *cp); - cp++; - len--; - } +/***************************** Server Section *****************************/ - return (str); -} +struct ntlm_context { + pid_t child_pid; + FILE *pipe_in; + FILE *pipe_out; + int first; + sasl_secret_t *password; /* user password */ + unsigned int free_password; /* set if we need to free password */ + unsigned int sent_password; /* set if we have told ntlm_auth the password already */ + const char *authid; + const char *domain; +}; -/* copy src to dst as unicode (in Intel byte-order) */ -static void to_unicode(u_char *dst, const char *src, int len) -{ - for (; len; len--) { - *dst++ = *src++; - *dst++ = 0; - } -} +static int fork_child(struct ntlm_context *context, + const char *prog, + char * const argv[], + const sasl_utils_t *utils) -/* copy unicode src (in Intel byte-order) to dst */ -static void from_unicode(char *dst, u_char *src, int len) { - for (; len; len--) { - *dst++ = *src & 0x7f; - src += 2; - } -} - -/* load a string into an NTLM buffer */ -static void load_buffer(ntlm_buffer_t *buf, const u_char *str, uint16 len, - int unicode, u_char *base, uint32 *offset) -{ - if (len) { - if (unicode) { - to_unicode(base + *offset, str, len); - len *= 2; - } - else { - memcpy(base + *offset, str, len); + int pipe_in[2]; + int pipe_out[2]; + + if ( (pipe(pipe_in) < 0) || (pipe(pipe_out) < 0) ) { + syslog(LOG_DEBUG, "fork_child: could not open pipes\n"); + utils->seterror(utils->conn, 0, "Could not allocate pipe\n"); + return SASL_FAIL; } - } - - UINT16_TO_INTEL(len, buf->len); - buf->maxlen = buf->len; - UINT32_TO_INTEL(*offset, buf->offset); - *offset += len; -} - -/* unload a string from an NTLM buffer */ -static int unload_buffer(const sasl_utils_t *utils, ntlm_buffer_t *buf, - u_char **str, unsigned *outlen, - int unicode, u_char *base, unsigned msglen) -{ - uint16 len = 0; - - UINT16_FROM_INTEL(buf->len, len); - if (len) { - uint32 offset = 0; + context->child_pid = fork(); - *str = utils->malloc(len + 1); /* add 1 for NUL */ - if (*str == NULL) { - MEMERROR(utils); - return SASL_NOMEM; + if (context->child_pid == -1) { + syslog(LOG_DEBUG, "fork_child: Could not fork\n"); + utils->seterror(utils->conn, 0, "Could not fork\n"); + return SASL_FAIL; } - UINT32_FROM_INTEL(buf->offset, offset); + if (context->child_pid == 0) { - /* sanity check */ - if ((offset + len) > msglen) return SASL_BADPROT; + /* Set up the pipes correctly */ - if (unicode) { - len /= 2; - from_unicode((char *) *str, base + offset, len); + close(0); + close(1); + dup2(pipe_out[0], 0); + close(pipe_out[0]); + close(pipe_out[1]); + dup2(pipe_in[1], 1); + close(pipe_in[0]); + close(pipe_in[1]); + + execvp(prog, argv); + + /* We should never get here ... */ + exit(1); + + } else { + + context->pipe_in = fdopen(pipe_in[0], "r"); + close(pipe_in[1]); + context->pipe_out = fdopen(pipe_out[1], "w"); + close(pipe_out[0]); } - else - memcpy(*str, base + offset, len); - (*str)[len] = '\0'; /* add NUL */ - } - else { - *str = NULL; - } - - if (outlen) *outlen = len; - - return SASL_OK; + return SASL_OK; } -/* - * NTLM encryption/authentication routines per section 2.10 of - * draft-leach-cifs-v1-spec-02 - */ -static void E(unsigned char *out, unsigned char *K, unsigned Klen, - unsigned char *D, unsigned Dlen) - -{ - unsigned k, d; - des_cblock K64; - des_key_schedule ks; - unsigned char *Dp; -#define KEY_SIZE 7 -#define BLOCK_SIZE 8 - - for (k = 0; k < Klen; k += KEY_SIZE, K += KEY_SIZE) { - /* convert 56-bit key to 64-bit */ - K64[0] = K[0]; - K64[1] = ((K[0] << 7) & 0xFF) | (K[1] >> 1); - K64[2] = ((K[1] << 6) & 0xFF) | (K[2] >> 2); - K64[3] = ((K[2] << 5) & 0xFF) | (K[3] >> 3); - K64[4] = ((K[3] << 4) & 0xFF) | (K[4] >> 4); - K64[5] = ((K[4] << 3) & 0xFF) | (K[5] >> 5); - K64[6] = ((K[5] << 2) & 0xFF) | (K[6] >> 6); - K64[7] = (K[6] << 1) & 0xFF; - - des_set_odd_parity(&K64); /* XXX is this necessary? */ - des_set_key(&K64, ks); - - for (d = 0, Dp = D; d < Dlen; - d += BLOCK_SIZE, Dp += BLOCK_SIZE, out += BLOCK_SIZE) { - des_ecb_encrypt((void *) Dp, (void *) out, ks, DES_ENCRYPT); - } - } -} - -static unsigned char *P16_lm(unsigned char *P16, const char *passwd) +static void ntlm_kill_helper(struct ntlm_context *context) { - char P14[14]; - unsigned char S8[] = { 0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 }; - - strncpy(P14, passwd, sizeof(P14)); - ucase(P14, sizeof(P14)); - - E(P16, P14, sizeof(P14), S8, sizeof(S8)); - return P16; -} + int status; + + if ((context == NULL) || (context->child_pid == 0)) + return; -static unsigned char *P16_nt(unsigned char *P16, const char *passwd) -{ - char U_PN[1024]; + fclose(context->pipe_out); + fclose(context->pipe_in); - to_unicode(U_PN, passwd, strlen(passwd)); - MD4(U_PN, 2 * strlen(passwd), P16); - return P16; -} + waitpid(context->child_pid, &status, 0); + syslog(LOG_DEBUG, "Child died with status %d\n", status); -static unsigned char *P21(unsigned char *P21, const char *passwd, - unsigned char* (*P16)(unsigned char *, const char *)) -{ - memset(P16(P21, passwd) + 16, 0, 5); - return P21; + context->child_pid = 0; } -static unsigned char *P24(unsigned char *P24, unsigned char *P21, - unsigned char *C8) - +static int ntlm_child_interact(struct ntlm_context *context, + unsigned char *childbuf, + unsigned max_childbuflen, + unsigned *childbuflen) { - E(P24, P21, NTLM_HASH_LENGTH, C8, NTLM_NONCE_LENGTH); - return P24; -} - -/***************************** Server Section *****************************/ + syslog(LOG_DEBUG, "Sending %d bytes do child: %s\n", + *childbuflen, childbuf); -typedef struct server_context { - int state; + fprintf(context->pipe_out, "%s\n", childbuf); + fflush(context->pipe_out); - uint32 flags; - unsigned char nonce[NTLM_NONCE_LENGTH]; - - /* per-step mem management */ - char *out_buf; - unsigned out_buf_len; - -} server_context_t; - -static int create_challenge(const sasl_utils_t *utils, - server_context_t *text, - const char *domain, - uint32 flags, - const u_char *nonce, - unsigned *outlen) -{ - ntlm_challenge_t *chal; - uint32 type = NTLM_CHALLENGE; - uint32 offset = sizeof(ntlm_challenge_t); + if (fgets(childbuf, max_childbuflen-1, context->pipe_in) == NULL) { + syslog(LOG_DEBUG, "Did not get a response from child\n"); + return SASL_FAIL; + } - if (!nonce) { - SETERROR(utils, "need nonce for NTLM challenge"); - return SASL_FAIL; - } + *childbuflen = strlen(childbuf)-1; - *outlen = sizeof(ntlm_challenge_t) + 2*xstrlen(domain); + syslog(LOG_DEBUG, "Got %d bytes from child: %s\n", + *childbuflen, childbuf); - if (_plug_buf_alloc(utils, &text->out_buf, &text->out_buf_len, - *outlen) != SASL_OK) { - SETERROR(utils, "cannot allocate NTLM challenge"); - return SASL_NOMEM; - } - - chal = (ntlm_challenge_t *) text->out_buf; - memcpy(chal->sig, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)); - UINT32_TO_INTEL(type, chal->type); - - load_buffer(&chal->domain, ucase(domain, 0), xstrlen(domain), - flags & NTLM_USE_UNICODE, (u_char *) chal, &offset); - UINT32_TO_INTEL(flags, chal->flags); - memcpy(chal->nonce, nonce, NTLM_NONCE_LENGTH); + if ( *childbuflen < 2 ) { + syslog(LOG_DEBUG, "Got invalid response from child\n"); + return SASL_FAIL; + } - return SASL_OK; + if ((*childbuflen <= 3) && (strncmp(childbuf, "BH", 2) == 0)) { + syslog(LOG_DEBUG, "Broke Helper somehow...\n"); + return SASL_FAIL; + } + return SASL_OK; } static int ntlm_server_mech_new(void *glob_context __attribute__((unused)), - sasl_server_params_t *sparams, - const char *challenge __attribute__((unused)), - unsigned challen __attribute__((unused)), - void **conn_context) -{ - server_context_t *text; - - /* holds state are in */ - text = sparams->utils->malloc(sizeof(server_context_t)); - if (text == NULL) { - MEMERROR( sparams->utils ); - return SASL_NOMEM; - } - - memset(text, 0, sizeof(server_context_t)); - - text->state = 1; - - *conn_context = text; - - return SASL_OK; -} - -static int ntlm_server_mech_step1(server_context_t *text, - sasl_server_params_t *sparams, - const char *clientin, - unsigned clientinlen, - const char **serverout, - unsigned *serveroutlen, - sasl_out_params_t *oparams __attribute__((unused))) -{ - ntlm_request_t *request = (ntlm_request_t *) clientin; - char *domain = NULL; - int result; - - if (!request || clientinlen < sizeof(ntlm_request_t)) { - SETERROR(sparams->utils, "client didn't issue valid NTLM request"); - return SASL_BADPROT; - } - - UINT32_FROM_INTEL(request->flags, text->flags); - text->flags &= NTLM_FLAGS_MASK; - - /* if client can do Unicode, turn off ASCII */ - if (text->flags & NTLM_USE_UNICODE) text->flags &= ~NTLM_USE_ASCII; - - /* if client asked for target, use FQDN as server target */ - if (text->flags & NTLM_ASK_TARGET) { - result = _plug_strdup(sparams->utils, sparams->serverFQDN, - &domain, NULL); - if (result != SASL_OK) return result; - - text->flags |= NTLM_TARGET_IS_SERVER; - } - - /* generate a nonce */ - sparams->utils->rand(sparams->utils->rpool, - (char *) text->nonce, NTLM_NONCE_LENGTH); - - result = create_challenge(sparams->utils, text, domain, text->flags, - text->nonce, serveroutlen); - if (result != SASL_OK) goto cleanup; - - *serverout = text->out_buf; + sasl_server_params_t *sparams, + const char *challenge __attribute__((unused)), + unsigned challen __attribute__((unused)), + void **conn_context) +{ + char * argv[] = { "ntlm_auth", + "--helper-protocol=squid-2.5-ntlmssp", + NULL }; + + struct ntlm_context *context + malloc(sizeof(struct ntlm_context)); + + if (context == NULL) { + syslog(LOG_DEBUG, "ntlm_server_mech_new: No memory\n"); + MEMERROR(sparams->utils); + return SASL_NOMEM; + } - text->state = 2; - - result = SASL_CONTINUE; + *conn_context = context; - cleanup: - if (domain) sparams->utils->free(domain); + memset(context, '\0', sizeof(*context)); + + context->first = 1; - return result; + return fork_child(context, "ntlm_auth", + argv, sparams->utils); } -static int ntlm_server_mech_step2(server_context_t *text, - sasl_server_params_t *sparams, - const char *clientin, - unsigned clientinlen, - const char **serverout __attribute__((unused)), - unsigned *serveroutlen __attribute__((unused)), - sasl_out_params_t *oparams) -{ - ntlm_response_t *response = (ntlm_response_t *) clientin; - unsigned char *lm_resp_c = NULL, *nt_resp_c = NULL; - char *domain = NULL, *authid = NULL; - sasl_secret_t *password = NULL; - unsigned lm_resp_len, nt_resp_len, domain_len, authid_len, pass_len; - int result; - const char *password_request[] = { SASL_AUX_PASSWORD, - NULL }; - struct propval auxprop_values[2]; - unsigned char hash[NTLM_HASH_LENGTH]; - unsigned char lm_resp_s[NTLM_RESP_LENGTH], nt_resp_s[NTLM_RESP_LENGTH]; - - if (!response || clientinlen < sizeof(ntlm_response_t)) { - SETERROR(sparams->utils, "client didn't issue valid NTLM response"); - return SASL_BADPROT; - } - - result = unload_buffer(sparams->utils, &response->lm_resp, - (u_char **) &lm_resp_c, &lm_resp_len, 0, - (u_char *) response, clientinlen); - if (result != SASL_OK) goto cleanup; - - result = unload_buffer(sparams->utils, &response->nt_resp, - (u_char **) &nt_resp_c, &nt_resp_len, 0, - (u_char *) response, clientinlen); - if (result != SASL_OK) goto cleanup; - - result = unload_buffer(sparams->utils, &response->domain, - (u_char **) &domain, &domain_len, - text->flags & NTLM_USE_UNICODE, - (u_char *) response, clientinlen); - if (result != SASL_OK) goto cleanup; - - result = unload_buffer(sparams->utils, &response->user, - (u_char **) &authid, &authid_len, - text->flags & NTLM_USE_UNICODE, - (u_char *) response, clientinlen); - if (result != SASL_OK) goto cleanup; - - /* require at least one response and an authid */ - if ((!lm_resp_c && !nt_resp_c) || - (lm_resp_c && lm_resp_len < NTLM_RESP_LENGTH) || - (nt_resp_c && nt_resp_len < NTLM_RESP_LENGTH) || - !authid) { - SETERROR(sparams->utils, "client issued incorrect/nonexistent responses"); - result = SASL_BADPROT; - goto cleanup; - } +static int ntlm_server_mech_step(void *conn_context, + sasl_server_params_t *params, + const char *clientin, + unsigned clientinlen, + const char **serverout, + unsigned *serveroutlen, + sasl_out_params_t *oparams) +{ + struct ntlm_context *context + (struct ntlm_context *)conn_context; + + unsigned char childbuf[1025]; + unsigned childbuflen; + + unsigned base64len; + + static unsigned char bin[1025]; + + int result; + + if (context->first) { + strncpy(childbuf, "YR ", sizeof(childbuf)-1); + context->first = 0; + } else { + strncpy(childbuf, "KK ", sizeof(childbuf)-1); + } - /* fetch user's password */ - result = sparams->utils->prop_request(sparams->propctx, password_request); - if (result != SASL_OK) goto cleanup; - - /* this will trigger the getting of the aux properties */ - result = sparams->canon_user(sparams->utils->conn, authid, authid_len, - SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams); - if (result != SASL_OK) goto cleanup; - - result = sparams->utils->prop_getnames(sparams->propctx, - password_request, - auxprop_values); - if (result < 0 || - (!auxprop_values[0].name || !auxprop_values[0].values)) { - /* We didn't find this username */ - SETERROR(sparams->utils, "no secret in database"); - result = SASL_NOUSER; - goto cleanup; - } - - pass_len = strlen(auxprop_values[0].values[0]); - if (pass_len == 0) { - SETERROR(sparams->utils, "empty secret"); - result = SASL_FAIL; - goto cleanup; - } + if (params->utils->encode64(clientin, clientinlen, + childbuf+3, sizeof(childbuf)-4, + &childbuflen) != SASL_OK) { + syslog(LOG_DEBUG, + "Could not convert clientin to base64\n"); + return SASL_FAIL; + } - password = sparams->utils->malloc(sizeof(sasl_secret_t) + pass_len); - if (!password) { - result = SASL_NOMEM; - goto cleanup; - } + if ((result = ntlm_child_interact(context, + childbuf, + sizeof(childbuf), + &childbuflen)) != SASL_OK) + return result; - password->len = pass_len; - strncpy(password->data, auxprop_values[0].values[0], pass_len + 1); + /* The child's reply contains 3 parts: + - The code: TT, AF or NA + - The blob to send to the client, coded in base64 + - or for AF it's domain\\user + for NA it's the NT error code + */ + + if (strlen(childbuf) < 3) { + syslog(LOG_DEBUG, "Got invalid response from child (parse of ntlm_auth output failure)\n"); + return SASL_FAIL; + } - /* calculate our own responses */ - P24(lm_resp_s, P21(hash, password->data, P16_lm), text->nonce); - P24(nt_resp_s, P21(hash, password->data, P16_nt), text->nonce); - - /* compare client's responses with ours */ - if ((lm_resp_c && memcmp(lm_resp_c, lm_resp_s, NTLM_RESP_LENGTH)) || - (nt_resp_c && memcmp(nt_resp_c, nt_resp_s, NTLM_RESP_LENGTH))) { - SETERROR(sparams->utils, "incorrect NTLM responses"); - result = SASL_BADAUTH; - goto cleanup; - } + base64len = strlen(childbuf) - 3; - /* set oparams */ - oparams->doneflag = 1; - oparams->mech_ssf = 0; - oparams->maxoutbuf = 0; - oparams->encode_context = NULL; - oparams->encode = NULL; - oparams->decode_context = NULL; - oparams->decode = NULL; - oparams->param_version = 0; - - result = SASL_OK; - - cleanup: - if (lm_resp_c) sparams->utils->free(lm_resp_c); - if (nt_resp_c) sparams->utils->free(nt_resp_c); - if (domain) sparams->utils->free(domain); - if (authid) sparams->utils->free(authid); - if (password) _plug_free_secret(sparams->utils, &password); + if ( strncmp(childbuf, "TT ", 3) == 0) { + if (params->utils->decode64(childbuf+3, base64len, + bin, sizeof(bin)-1, + serveroutlen) != SASL_OK) { + syslog(LOG_DEBUG, "Could not decode child's base64\n"); + return SASL_FAIL; + } + *serverout = bin; - return result; -} + /* Next round */ + return SASL_CONTINUE; + } + if (strncmp(childbuf, "NA ", 3) == 0) { + /* Not Authenticated */ + ntlm_kill_helper(context); + return SASL_BADAUTH; + } -static int ntlm_server_mech_step(void *conn_context, - sasl_server_params_t *sparams, - const char *clientin, - unsigned clientinlen, - const char **serverout, - unsigned *serveroutlen, - sasl_out_params_t *oparams) -{ - server_context_t *text = (server_context_t *) conn_context; - - *serverout = NULL; - *serveroutlen = 0; - - sparams->utils->log(NULL, SASL_LOG_DEBUG, - "NTLM server step %d\n", text->state); + if (strncmp(childbuf, "AF ", 3) == 0) { + /* Authentication Fine */ + char *user = childbuf + 3; + int status; + + ntlm_kill_helper(context); + + if ( (status = params->canon_user(params->utils->conn, + user, 0, SASL_CU_AUTHID, + oparams)) != SASL_OK ) { + syslog(LOG_DEBUG, "canon_user for AUTHID [%s] failed\n", user); + return status; + } + + if ( (status = params->canon_user(params->utils->conn, + user, 0, SASL_CU_AUTHZID, + oparams)) != SASL_OK ) { + syslog(LOG_DEBUG, "canon_user for AUTHZID [%s] failed\n", user); + return status; + } + + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; - switch (text->state) { - - case 1: - return ntlm_server_mech_step1(text, sparams, clientin, clientinlen, - serverout, serveroutlen, oparams); - - case 2: - return ntlm_server_mech_step2(text, sparams, clientin, clientinlen, - serverout, serveroutlen, oparams); - - default: - sparams->utils->log(NULL, SASL_LOG_ERR, - "Invalid NTLM server step %d\n", text->state); + return SASL_OK; + } + + syslog(LOG_DEBUG, "Child's response unknown\n"); return SASL_FAIL; - } - - return SASL_FAIL; /* should never get here */ } static void ntlm_server_mech_dispose(void *conn_context, - const sasl_utils_t *utils) + const sasl_utils_t *utils __attribute__((unused))) { - server_context_t *text = (server_context_t *) conn_context; - - if (!text) return; - - if (text->out_buf) utils->free(text->out_buf); + struct ntlm_context *context + (struct ntlm_context *)conn_context; + + ntlm_kill_helper(context); - utils->free(text); + return; } static sasl_server_plug_t ntlm_server_plugins[] = { { - "NTLM", /* mech_name */ + "NTLM", /* mech_name */ 0, /* max_ssf */ - SASL_SEC_NOPLAINTEXT - | SASL_SEC_NOANONYMOUS, /* security_flags */ - SASL_FEAT_WANT_CLIENT_FIRST, /* features */ + SASL_SEC_NOANONYMOUS + | SASL_SEC_NOPLAINTEXT, /* security_flags */ + SASL_FEAT_WANT_CLIENT_FIRST, /* features */ NULL, /* glob_context */ &ntlm_server_mech_new, /* mech_new */ - &ntlm_server_mech_step, /* mech_step */ + &ntlm_server_mech_step, /* mech_step */ &ntlm_server_mech_dispose, /* mech_dispose */ NULL, /* mech_free */ NULL, /* setpass */ @@ -637,11 +347,11 @@ } }; -int ntlm_server_plug_init(sasl_utils_t *utils, - int maxversion, - int *out_version, - sasl_server_plug_t **pluglist, - int *plugcount) +int ntlm_server_plug_init(const sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_server_plug_t **pluglist, + int *plugcount) { if (maxversion < SASL_SERVER_PLUG_VERSION) { SETERROR(utils, "NTLM version mismatch"); @@ -650,157 +360,135 @@ *out_version = SASL_SERVER_PLUG_VERSION; *pluglist = ntlm_server_plugins; - *plugcount = 1; + *plugcount = 1; return SASL_OK; } /***************************** Client Section *****************************/ -typedef struct client_context { - int state; +static int ntlm_client_mech_new(void *glob_context __attribute__((unused)), + sasl_client_params_t *params, + void **conn_context) +{ + struct ntlm_context *context + malloc(sizeof(struct ntlm_context)); - /* per-step mem management */ - char *out_buf; - unsigned out_buf_len; - -} client_context_t; - -static int create_request(const sasl_utils_t *utils, - client_context_t *text, - const char *domain, const char *wkstn, - unsigned *outlen) -{ - ntlm_request_t *req; - uint32 type = NTLM_REQUEST; - uint32 flags = NTLM_USE_UNICODE | NTLM_USE_ASCII | - NTLM_ASK_TARGET | NTLM_AUTH_NTLM | NTLM_ALWAYS_SIGN; - uint32 offset = sizeof(ntlm_request_t); - - *outlen = sizeof(ntlm_request_t) + xstrlen(domain) + xstrlen(wkstn); - if (_plug_buf_alloc(utils, &text->out_buf, &text->out_buf_len, - *outlen) != SASL_OK) { - SETERROR(utils, "cannot allocate NTLM request"); - return SASL_NOMEM; - } - - req = (ntlm_request_t *) text->out_buf; - memcpy(req->sig, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)); - UINT32_TO_INTEL(type, req->type); - UINT32_TO_INTEL(flags, req->flags); - - load_buffer(&req->domain, domain, xstrlen(domain), 0, - (u_char *) req, &offset); - load_buffer(&req->wkstn, wkstn, xstrlen(wkstn), 0, - (u_char *) req, &offset); + if (context == NULL) { + MEMERROR(params->utils); + return SASL_NOMEM; + } - return SASL_OK; + memset(context, 0, sizeof(struct ntlm_context)); + + *conn_context = context; + memset(context, '\0', sizeof(*context)); + context->first = 1; + + return SASL_OK; } -static int create_response(const sasl_utils_t *utils, - client_context_t *text, - const u_char *lm_resp, - const u_char *nt_resp, - const char *domain, const char *user, - const char *wkstn, const u_char *key, - uint32 flags, - unsigned *outlen) -{ - ntlm_response_t *resp; - uint32 type = NTLM_RESPONSE; - uint32 offset = sizeof(ntlm_response_t); +static int ntlm_new_client_helper(struct ntlm_context *conn_context, + sasl_client_params_t *params, + sasl_interact_t **prompt_need, + sasl_out_params_t *oparams) +{ + struct ntlm_context *context + (struct ntlm_context *)conn_context; + + int auth_result = SASL_OK; + int domain_result = SASL_OK; - if (!lm_resp || !nt_resp) { - SETERROR(utils, "need NTLM responses"); - return SASL_FAIL; - } + int pass_result = SASL_OK; + int result; - *outlen = sizeof(ntlm_response_t) + 2*NTLM_RESP_LENGTH + - 2*xstrlen(domain) + 2*xstrlen(user) + 2*xstrlen(wkstn); - if (key) *outlen += NTLM_SESSKEY_LENGTH; - - if (_plug_buf_alloc(utils, &text->out_buf, &text->out_buf_len, - *outlen) != SASL_OK) { - SETERROR(utils, "cannot allocate NTLM response"); - return SASL_NOMEM; - } - - resp = (ntlm_response_t *) text->out_buf; - memcpy(resp->sig, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)); - UINT32_TO_INTEL(type, resp->type); - - load_buffer(&resp->lm_resp, lm_resp, NTLM_RESP_LENGTH, 0, - (u_char *) resp, &offset); - load_buffer(&resp->nt_resp, nt_resp, NTLM_RESP_LENGTH, 0, - (u_char *) resp, &offset); - load_buffer(&resp->domain, ucase(domain, 0), xstrlen(domain), - flags & NTLM_USE_UNICODE, - (u_char *) resp, &offset); - load_buffer(&resp->user, user, xstrlen(user), - flags & NTLM_USE_UNICODE, - (u_char *) resp, &offset); - load_buffer(&resp->wkstn, ucase(wkstn, 0), xstrlen(wkstn), - flags & NTLM_USE_UNICODE, - (u_char *) resp, &offset); - load_buffer(&resp->key, key, key ? NTLM_SESSKEY_LENGTH : 0, 0, - (u_char *) resp, &offset); + char child_user_arg[512]; + char child_domain_arg[512]; - UINT32_TO_INTEL(flags, resp->flags); + const char *domains[] = { + "", + NULL + }; + + char *argv[] = { "ntlm_auth", + "--helper-protocol=ntlmssp-client-1", + child_user_arg, + child_domain_arg, + NULL }; + + if (context->authid == NULL) { + auth_result = _plug_get_authid(params->utils, + &context->authid, prompt_need); + + if ( (auth_result != SASL_OK) && + (auth_result != SASL_INTERACT) && + (context->authid == NULL) ) + return auth_result; + } - return SASL_OK; -} + if (context->domain == NULL) { + domain_result = _plug_get_realm(params->utils, domains, + &context->domain, prompt_need); + if ( (domain_result != SASL_OK) && + (domain_result != SASL_INTERACT) && + (context->domain == NULL) ) + return domain_result; + } + + if (context->password == NULL) { + pass_result = _plug_get_password(params->utils, &context->password, + &context->free_password, prompt_need); + + if ( (pass_result != SASL_OK) && + (pass_result != SASL_INTERACT) + && (context->password == NULL)) + return pass_result; + } + + /* free prompts we got */ + if (prompt_need && *prompt_need) { + params->utils->free(*prompt_need); + *prompt_need = NULL; + } -static int ntlm_client_mech_new(void *glob_context __attribute__((unused)), - sasl_client_params_t *params, - void **conn_context) -{ - client_context_t *text; - - /* holds state are in */ - text = params->utils->malloc(sizeof(client_context_t)); - if (text == NULL) { - MEMERROR( params->utils ); - return SASL_NOMEM; - } - - memset(text, 0, sizeof(client_context_t)); - - text->state = 1; - - *conn_context = text; - - return SASL_OK; -} + if ( (auth_result == SASL_INTERACT) || + (domain_result == SASL_INTERACT) ) { + result = _plug_make_prompts(params->utils, prompt_need, + NULL, NULL, + (auth_result == SASL_INTERACT) ? + "Username" : NULL, NULL, + pass_result == SASL_INTERACT ? + "Password" : NULL, NULL, + NULL, NULL, NULL, + NULL, + (domain_result == SASL_INTERACT) ? + "Realm (Domain)" : NULL, NULL); + + if (result != SASL_OK) + return result; + return SASL_INTERACT; + } -static int ntlm_client_mech_step1(client_context_t *text, - sasl_client_params_t *params, - const char *serverin __attribute__((unused)), - unsigned serverinlen __attribute__((unused)), - sasl_interact_t **prompt_need __attribute__((unused)), - const char **clientout, - unsigned *clientoutlen, - sasl_out_params_t *oparams __attribute__((unused))) -{ - int result; - - /* check if sec layer strong enough */ - if (params->props.min_ssf > params->external_ssf) { - SETERROR(params->utils, "SSF requested of NTLM plugin"); - return SASL_TOOWEAK; - } + if (!context->password) { + PARAMERROR(params->utils); + return SASL_BADPARAM; + } - /* we don't care about domain or wkstn */ - result = create_request(params->utils, text, NULL, NULL, clientoutlen); - if (result != SASL_OK) return result; + oparams->user = strdup(context->authid); + oparams->ulen = strlen(context->authid); + oparams->authid = strdup(context->authid); + oparams->alen = oparams->ulen; + + snprintf(child_user_arg, sizeof(child_user_arg)-1, + "--username=%s", context->authid); + snprintf(child_domain_arg, sizeof(child_domain_arg)-1, + "--domain=%s", context->domain); - *clientout = text->out_buf; - - text->state = 2; - - return SASL_CONTINUE; + return fork_child(context, "ntlm_auth", + argv, params->utils); } -static int ntlm_client_mech_step2(client_context_t *text, +static int ntlm_client_mech_step(void *conn_context, sasl_client_params_t *params, const char *serverin, unsigned serverinlen, @@ -809,171 +497,156 @@ unsigned *clientoutlen, sasl_out_params_t *oparams) { - ntlm_challenge_t *challenge = (ntlm_challenge_t *) serverin; - const char *authid = NULL; - sasl_secret_t *password = NULL; - unsigned int free_password; /* set if we need to free password */ - char *domain = NULL; - int auth_result = SASL_OK; - int pass_result = SASL_OK; - uint32 flags = 0; - unsigned char hash[NTLM_HASH_LENGTH]; - unsigned char lm_resp[NTLM_RESP_LENGTH], nt_resp[NTLM_RESP_LENGTH]; - int result; - - if (!challenge || serverinlen < sizeof(ntlm_challenge_t)) { - SETERROR(params->utils, "server didn't issue valid NTLM challenge"); - return SASL_BADPROT; - } + struct ntlm_context *context + (struct ntlm_context *)conn_context; - /* try to get the authid */ - if (oparams->authid == NULL) { - auth_result = _plug_get_authid(params->utils, &authid, prompt_need); - - if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT)) - return auth_result; - } - - /* try to get the password */ - if (password == NULL) { - pass_result = _plug_get_password(params->utils, &password, - &free_password, prompt_need); - - if ((pass_result != SASL_OK) && (pass_result != SASL_INTERACT)) - return pass_result; - } + unsigned char childbuf[1025]; + unsigned childbuflen; - /* free prompts we got */ - if (prompt_need && *prompt_need) { - params->utils->free(*prompt_need); - *prompt_need = NULL; - } - - /* if there are prompts not filled in */ - if ((auth_result == SASL_INTERACT) || (pass_result == SASL_INTERACT)) { - /* make the prompt list */ - result - _plug_make_prompts(params->utils, prompt_need, - NULL, NULL, - auth_result == SASL_INTERACT ? - "Please enter your authentication name" : NULL, - NULL, - pass_result == SASL_INTERACT ? - "Please enter your password" : NULL, NULL, - NULL, NULL, NULL, - NULL, NULL, NULL); - if (result != SASL_OK) goto cleanup; - - return SASL_INTERACT; - } - - result = params->canon_user(params->utils->conn, authid, 0, - SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams); - if (result != SASL_OK) goto cleanup; - - UINT32_FROM_INTEL(challenge->flags, flags); - flags &= NTLM_FLAGS_MASK; - - result = unload_buffer(params->utils, &challenge->domain, - (u_char **) &domain, NULL, - flags & NTLM_USE_UNICODE, - (u_char *) challenge, serverinlen); - if (result != SASL_OK) goto cleanup; - - P24(lm_resp, P21(hash, password->data, P16_lm), challenge->nonce); - P24(nt_resp, P21(hash, password->data, P16_nt), challenge->nonce); - - /* we don't care about wkstn or session key */ - result = create_response(params->utils, text, lm_resp, nt_resp, - domain, oparams->authid, - NULL, NULL, flags, clientoutlen); - if (result != SASL_OK) goto cleanup; - - *clientout = text->out_buf; - - /* set oparams */ - oparams->doneflag = 1; - oparams->mech_ssf = 0; - oparams->maxoutbuf = 0; - oparams->encode_context = NULL; - oparams->encode = NULL; - oparams->decode_context = NULL; - oparams->decode = NULL; - oparams->param_version = 0; - - result = SASL_OK; + int result; - cleanup: - if (domain) params->utils->free(domain); - if (free_password) _plug_free_secret(params->utils, &password); + if (context->child_pid == 0) { + result = ntlm_new_client_helper(context, params, + prompt_need, oparams); - return result; -} + if (result != SASL_OK) + return result; + } -static int ntlm_client_mech_step(void *conn_context, - sasl_client_params_t *params, - const char *serverin, - unsigned serverinlen, - sasl_interact_t **prompt_need, - const char **clientout, - unsigned *clientoutlen, - sasl_out_params_t *oparams) -{ - client_context_t *text = (client_context_t *) conn_context; - - *clientout = NULL; - *clientoutlen = 0; - - params->utils->log(NULL, SASL_LOG_DEBUG, - "NTLM client step %d\n", text->state); + if (!context->sent_password) { + + strncpy(childbuf, "PW ", sizeof(childbuf)-1); + + if (params->utils->encode64(context->password->data, context->password->len, + childbuf+3, sizeof(childbuf)-3, + &childbuflen) != SASL_OK) { + syslog(LOG_DEBUG, + "Could not convert password to base64\n"); + return SASL_FAIL; + } + + if ((result = ntlm_child_interact(context, + childbuf, + sizeof(childbuf), + &childbuflen)) != SASL_OK) + return result; + + if (strncmp(childbuf, "OK", 2) != 0) { + syslog(LOG_DEBUG, "Child did not accept our password\n"); + return SASL_FAIL; + } + context->sent_password = 1; + } + + if (context->first) { + strncpy(childbuf, "YR", sizeof(childbuf)-1); + context->first = 0; + } else { + strncpy(childbuf, "TT", sizeof(childbuf)-1); + } + + if (serverinlen) { + strncpy(childbuf+2, " ", sizeof(childbuf)-3); + if (params->utils->encode64(serverin, serverinlen, + childbuf+3, sizeof(childbuf)-4, + &childbuflen) != SASL_OK) { + syslog(LOG_DEBUG, + "Could not convert serverin to base64\n"); + return SASL_FAIL; + } + } + + if ((result = ntlm_child_interact(context, + childbuf, + sizeof(childbuf), + &childbuflen)) != SASL_OK) + return result; + + /* The child's reply can be: + "PW" -> It wants a password (but we premptivly deal with that above) + "YR <base64>" -> Send (initial) <base64> to the server + "KK <base64>" -> Send <base64> to the server + "AF" -> Authenticated + "NA" -> Not Authenticated + */ + + if ((strncmp(childbuf, "YR ", 3) == 0) || (strncmp(childbuf, "KK ", 3) == 0)) { + static char bin[2048]; + + if (params->utils->decode64(childbuf+3, childbuflen-3, + bin, sizeof(bin)-1, + clientoutlen) != SASL_OK) { + syslog(LOG_DEBUG, "Could not decode child's base64\n"); + return SASL_FAIL; + } + *clientout = bin; + return SASL_CONTINUE; + } + + if ( (strncmp(childbuf, "AF", 2) == 0) || + (strncmp(childbuf, "NA", 2) == 0) ) { + int status; + + fclose(context->pipe_in); + fclose(context->pipe_out); + + waitpid(context->child_pid, &status, 0); + + *clientout = NULL; + *clientoutlen = 0; + + /* set oparams */ + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + + return (strncmp(childbuf, "AF", 2) == 0) ? + SASL_OK : SASL_BADAUTH; + } - switch (text->state) { - - case 1: - return ntlm_client_mech_step1(text, params, serverin, serverinlen, - prompt_need, clientout, clientoutlen, - oparams); - - case 2: - return ntlm_client_mech_step2(text, params, serverin, serverinlen, - prompt_need, clientout, clientoutlen, - oparams); - - default: - params->utils->log(NULL, SASL_LOG_ERR, - "Invalid NTLM client step %d\n", text->state); return SASL_FAIL; - } - - return SASL_FAIL; /* should never get here */ } static void ntlm_client_mech_dispose(void *conn_context, - const sasl_utils_t *utils) + const sasl_utils_t *utils __attribute__((unused))) { - client_context_t *text = (client_context_t *) conn_context; - - if (!text) return; - - if (text->out_buf) utils->free(text->out_buf); - - utils->free(text); + struct ntlm_context *context + (struct ntlm_context *)conn_context; + + ntlm_kill_helper(context); + + return; +} + +static void ntlm_client_mech_free(void *conn_context, + const sasl_utils_t *utils __attribute__((unused))) +{ + struct ntlm_context *context + (struct ntlm_context *)conn_context; + + if (context->free_password) + _plug_free_secret(utils, &context->password); + + return; } static sasl_client_plug_t ntlm_client_plugins[] = { { - "NTLM", /* mech_name */ + "NTLM", /* mech_name */ 0, /* max_ssf */ - SASL_SEC_NOPLAINTEXT - | SASL_SEC_NOANONYMOUS, /* security_flags */ - SASL_FEAT_WANT_CLIENT_FIRST, /* features */ + SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT, /* security_flags */ + SASL_FEAT_WANT_CLIENT_FIRST, /* features */ NULL, /* required_prompts */ NULL, /* glob_context */ - &ntlm_client_mech_new, /* mech_new */ - &ntlm_client_mech_step, /* mech_step */ + &ntlm_client_mech_new, /* mech_new */ + &ntlm_client_mech_step, /* mech_step */ &ntlm_client_mech_dispose, /* mech_dispose */ - NULL, /* mech_free */ + &ntlm_client_mech_free, /* mech_free */ NULL, /* idle */ NULL, /* spare */ NULL /* spare */ @@ -981,11 +654,11 @@ }; int ntlm_client_plug_init(sasl_utils_t *utils, - int maxversion, - int *out_version, - sasl_client_plug_t **pluglist, - int *plugcount) + int maxversion, + int *out_version, + sasl_client_plug_t **pluglist, + int *plugcount) { if (maxversion < SASL_CLIENT_PLUG_VERSION) { SETERROR(utils, "NTLM version mismatch"); --- /dev/null 2003-09-15 23:40:47.000000000 +1000 +++ cyrus-sasl-2.1.15-2/plugins/gssspnego_init.c 2003-11-12 15:47:06.000000000 +1100 @@ -0,0 +1,43 @@ + +#include <config.h> + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#ifndef macintosh +#include <sys/stat.h> +#endif +#include <fcntl.h> +#include <assert.h> + +#include <sasl.h> +#include <saslplug.h> +#include <saslutil.h> + +#include "plugin_common.h" + +#ifdef macintosh +#include <sasl_gssspnego_plugin_decl.h> +#endif + +#ifdef WIN32 +BOOL APIENTRY DllMain( HANDLE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} +#endif + +SASL_CLIENT_PLUG_INIT( gssspnego ) +SASL_SERVER_PLUG_INIT( gssspnego ) + --- /dev/null 2003-09-15 23:40:47.000000000 +1000 +++ cyrus-sasl-2.1.15-2/plugins/gssspnego.c 2003-11-12 15:51:50.000000000 +1100 @@ -0,0 +1,661 @@ +/* GSS SPNEGO plugin + * Volker Lendecke + */ +/* + * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any other legal + * details, please contact + * Office of Technology Transfer + * Carnegie Mellon University + * 5000 Forbes Avenue + * Pittsburgh, PA 15213-3890 + * (412) 268-4387, fax: (412) 268-7395 + * tech-transfer@andrew.cmu.edu + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <config.h> +#include <stdio.h> +#include <string.h> +#include <sasl.h> +#include <saslplug.h> +#include <syslog.h> + +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "plugin_common.h" + +#ifdef macintosh +#include <sasl_gssspnego_plugin_decl.h> +#endif + +/***************************** Common Section *****************************/ + +static const char plugin_id[] = "$Id: gssspnego.c,v 1.61 2003/03/26 17:18:04 rjs3 Exp $"; + +/***************************** Server Section *****************************/ + +struct gssspnego_context { + pid_t child_pid; + FILE *pipe_in; + FILE *pipe_out; +}; + +static int fork_child(struct gssspnego_context *context, + const char *prog, + char * const argv[], + const sasl_utils_t *utils) + +{ + int pipe_in[2]; + int pipe_out[2]; + + if ( (pipe(pipe_in) < 0) || (pipe(pipe_out) < 0) ) { + syslog(LOG_DEBUG, "fork_child: could not open pipes\n"); + utils->seterror(utils->conn, 0, "Could not allocate pipe\n"); + return SASL_FAIL; + } + + context->child_pid = fork(); + + if (context->child_pid == -1) { + syslog(LOG_DEBUG, "fork_child: Could not fork\n"); + utils->seterror(utils->conn, 0, "Could not fork\n"); + return SASL_FAIL; + } + + if (context->child_pid == 0) { + + /* Set up the pipes correctly */ + + close(0); + close(1); + dup2(pipe_out[0], 0); + close(pipe_out[0]); + close(pipe_out[1]); + dup2(pipe_in[1], 1); + close(pipe_in[0]); + close(pipe_in[1]); + + execvp(prog, argv); + + /* We should never get here ... */ + exit(1); + + } else { + + context->pipe_in = fdopen(pipe_in[0], "r"); + close(pipe_in[1]); + context->pipe_out = fdopen(pipe_out[1], "w"); + close(pipe_out[0]); + } + + return SASL_OK; +} + +static void gssspnego_kill_helper(struct gssspnego_context *context) +{ + int status; + + if ((context == NULL) || (context->child_pid == 0)) + return; + + fclose(context->pipe_out); + fclose(context->pipe_in); + + waitpid(context->child_pid, &status, 0); + syslog(LOG_DEBUG, "Child died with status %d\n", status); + + context->child_pid = 0; +} + +static int gssspnego_child_interact(struct gssspnego_context *context, + unsigned char *childbuf, + unsigned max_childbuflen, + unsigned *childbuflen) +{ + syslog(LOG_DEBUG, "Sending %d bytes do child:\n%s\n", + *childbuflen, childbuf); + + fprintf(context->pipe_out, "%s\n", childbuf); + fflush(context->pipe_out); + + if (fgets(childbuf, max_childbuflen-1, context->pipe_in) == NULL) { + syslog(LOG_DEBUG, "Did not get a response from child\n"); + return SASL_FAIL; + } + + *childbuflen = strlen(childbuf)-1; + + syslog(LOG_DEBUG, "Got %d bytes from child:\n%s\n", + *childbuflen, childbuf); + + if ( *childbuflen < 2 ) { + syslog(LOG_DEBUG, "Got invalid response from child\n"); + return SASL_FAIL; + } + + if ((*childbuflen <= 3) && (strncmp(childbuf, "BH", 2) == 0)) { + syslog(LOG_DEBUG, "Broke Helper somehow...\n"); + return SASL_FAIL; + } + return SASL_OK; +} + +static int gssspnego_server_mech_new(void *glob_context __attribute__((unused)), + sasl_server_params_t *sparams, + const char *challenge __attribute__((unused)), + unsigned challen __attribute__((unused)), + void **conn_context) +{ + char * argv[] = { "ntlm_auth", + "--helper-protocol=gss-spnego", + NULL }; + + struct gssspnego_context *context + malloc(sizeof(struct gssspnego_context)); + + if (context == NULL) { + syslog(LOG_DEBUG, "gssspnego_server_mech_new: No memory\n"); + MEMERROR(sparams->utils); + return SASL_NOMEM; + } + + *conn_context = context; + + return fork_child(context, "ntlm_auth", + argv, sparams->utils); +} + +static int gssspnego_server_mech_step(void *conn_context, + sasl_server_params_t *params, + const char *clientin, + unsigned clientinlen, + const char **serverout, + unsigned *serveroutlen, + sasl_out_params_t *oparams) +{ + struct gssspnego_context *context + (struct gssspnego_context *)conn_context; + + unsigned char childbuf[1025]; + unsigned childbuflen; + + unsigned char *childarg; + unsigned base64len; + + static unsigned char bin[1025]; + + int result; + + if (clientinlen == 0) { + + /* This should only happen on the first request, we + need the initial mechanism offer from the child. */ + + strncpy(childbuf, "YR", sizeof(childbuf)-1); + childbuflen = strlen(childbuf); + + } else { + + strncpy(childbuf, "KK ", sizeof(childbuf)-1); + + if (params->utils->encode64(clientin, clientinlen, + childbuf+3, sizeof(childbuf)-4, + &childbuflen) != SASL_OK) { + syslog(LOG_DEBUG, + "Could not convert clientin to base64\n"); + return SASL_FAIL; + } + } + + if ((result = gssspnego_child_interact(context, + childbuf, + sizeof(childbuf), + &childbuflen)) != SASL_OK) + return result; + + /* The child's reply contains 3 parts: + - The code: TT, AF or NA + - The blob to send to the client, coded in base64 + - The argument: + For TT it's a dummy '*' + For AF it's domain\\user + For NA it's the NT error code + */ + + childarg = strchr(childbuf+3, ' '); + + if (childarg == NULL) { + syslog(LOG_DEBUG, "Got invalid response from child\n"); + return SASL_FAIL; + } + + base64len = (childarg - childbuf) - 3; + + if (params->utils->decode64(childbuf+3, base64len, + bin, sizeof(bin)-1, + serveroutlen) != SASL_OK) { + syslog(LOG_DEBUG, "Could not decode child's base64\n"); + return SASL_FAIL; + } + *serverout = bin; + childarg++; + + if ( strncmp(childbuf, "TT ", 3) == 0) { + /* Next round */ + return SASL_CONTINUE; + } + if (strncmp(childbuf, "NA ", 3) == 0) { + /* Not Authenticated */ + gssspnego_kill_helper(context); + return SASL_BADAUTH; + } + + if (strncmp(childbuf, "AF ", 3) == 0) { + /* Authentication Fine */ + char *domain = childarg; + char *user = strchr(domain, '\\'); + int status; + + gssspnego_kill_helper(context); + + if (user == NULL) { + syslog(LOG_DEBUG, + "Could not find user/domain in AF\n"); + return SASL_FAIL; + } + *user++ = 0; + if ( (status = params->canon_user(params->utils->conn, + user, 0, SASL_CU_AUTHID, + oparams)) != SASL_OK ) { + syslog(LOG_DEBUG, "canon_user for AUTHID failed\n"); + return status; + } + + if ( (status = params->canon_user(params->utils->conn, + user, 0, SASL_CU_AUTHZID, + oparams)) != SASL_OK ) { + syslog(LOG_DEBUG, "canon_user for AUTHZID failed\n"); + return status; + } + + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + + return SASL_OK; + } + + syslog(LOG_DEBUG, "Child's response unknown\n"); + return SASL_FAIL; +} + +static void gssspnego_server_mech_dispose(void *conn_context, + const sasl_utils_t *utils __attribute__((unused))) +{ + struct gssspnego_context *context + (struct gssspnego_context *)conn_context; + + gssspnego_kill_helper(context); + + return; +} + +static sasl_server_plug_t gssspnego_server_plugins[] = +{ + { + "GSS-SPNEGO", /* mech_name */ + 0, /* max_ssf */ + SASL_SEC_NOANONYMOUS + | SASL_SEC_NOPLAINTEXT, /* security_flags */ + SASL_FEAT_SERVER_FIRST, /* features */ + NULL, /* glob_context */ + &gssspnego_server_mech_new, /* mech_new */ + &gssspnego_server_mech_step, /* mech_step */ + &gssspnego_server_mech_dispose, /* mech_dispose */ + NULL, /* mech_free */ + NULL, /* setpass */ + NULL, /* user_query */ + NULL, /* idle */ + NULL, /* mech_avail */ + NULL /* spare */ + } +}; + +int gssspnego_server_plug_init(const sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_server_plug_t **pluglist, + int *plugcount) +{ + if (maxversion < SASL_SERVER_PLUG_VERSION) { + SETERROR(utils, "GSSSPNEGO version mismatch"); + return SASL_BADVERS; + } + + *out_version = SASL_SERVER_PLUG_VERSION; + *pluglist = gssspnego_server_plugins; + *plugcount = 1; + + return SASL_OK; +} + +/***************************** Client Section *****************************/ + +static int gssspnego_client_mech_new(void *glob_context __attribute__((unused)), + sasl_client_params_t *params, + void **conn_context) +{ + struct gssspnego_context *context + malloc(sizeof(struct gssspnego_context)); + + if (context == NULL) { + MEMERROR(params->utils); + return SASL_NOMEM; + } + + memset(context, 0, sizeof(struct gssspnego_context)); + + *conn_context = context; + + return SASL_OK; +} + +static int gssspnego_new_client_helper(struct gssspnego_context *context, + sasl_client_params_t *params, + sasl_interact_t **prompt_need, + sasl_out_params_t *oparams) +{ + int auth_result = SASL_OK; + int domain_result = SASL_OK; + static const char *authid = NULL; + static const char *domain = NULL; + + char child_user_arg[512]; + char child_domain_arg[512]; + + int result; + + char *argv[] = { "ntlm_auth", + "--helper-protocol=gss-spnego-client", + child_user_arg, + child_domain_arg, + NULL }; + + if (authid == NULL) { + auth_result = _plug_get_authid(params->utils, + &authid, prompt_need); + + if ( (auth_result != SASL_OK) && + (auth_result != SASL_INTERACT) && + (authid == NULL) ) + return auth_result; + } + + if (domain == NULL) { + domain_result = _plug_get_realm(params->utils, NULL, + &domain, prompt_need); + if ( (domain_result != SASL_OK) && + (domain_result != SASL_INTERACT) && + (domain == NULL) ) + return domain_result; + } + + /* free prompts we got */ + if (prompt_need && *prompt_need) { + params->utils->free(*prompt_need); + *prompt_need = NULL; + } + + if ( (auth_result == SASL_INTERACT) || + (domain_result == SASL_INTERACT) ) { + result = _plug_make_prompts(params->utils, prompt_need, + NULL, NULL, + (auth_result == SASL_INTERACT) ? + "Username" : NULL, NULL, + NULL, NULL, + NULL, NULL, NULL, + NULL, + (domain_result == SASL_INTERACT) ? + "Realm (Domain)" : NULL, NULL); + + if (result != SASL_OK) + return result; + return SASL_INTERACT; + } + + oparams->user = strdup(authid); + oparams->ulen = strlen(authid); + oparams->authid = strdup(authid); + oparams->alen = oparams->ulen; + + snprintf(child_user_arg, sizeof(child_user_arg)-1, + "--username=%s", authid); + snprintf(child_domain_arg, sizeof(child_domain_arg)-1, + "--domain=%s", domain); + + return fork_child(context, "ntlm_auth", + argv, params->utils); +} + +static int gssspnego_client_mech_step(void *conn_context, + sasl_client_params_t *params, + const char *serverin, + unsigned serverinlen, + sasl_interact_t **prompt_need, + const char **clientout, + unsigned *clientoutlen, + sasl_out_params_t *oparams) +{ + struct gssspnego_context *context + (struct gssspnego_context *)conn_context; + + unsigned char childbuf[1025]; + unsigned childbuflen; + + int result; + + static sasl_secret_t *password; + static int must_free_password; + static int must_send_password = 0; + + if (context->child_pid == 0) { + result = gssspnego_new_client_helper(context, params, + prompt_need, oparams); + + if (result != SASL_OK) + return result; + } + + if (must_send_password != 0) { + + must_send_password = 0; + + send_password: + + strncpy(childbuf, "PW ", sizeof(childbuf)-1); + + if (params->utils->encode64(password->data, password->len, + childbuf+3, sizeof(childbuf)-3, + &childbuflen) != SASL_OK) { + syslog(LOG_DEBUG, + "Could not convert password to base64\n"); + return SASL_FAIL; + } + + if ((result = gssspnego_child_interact(context, + childbuf, + sizeof(childbuf), + &childbuflen)) != SASL_OK) + return result; + + if (strncmp(childbuf, "OK", 2) != 0) { + syslog(LOG_DEBUG, "Child did not accept our password\n"); + return SASL_FAIL; + } + } + + strncpy(childbuf, "TT ", sizeof(childbuf)-1); + + if (params->utils->encode64(serverin, serverinlen, + childbuf+3, sizeof(childbuf)-4, + &childbuflen) != SASL_OK) { + syslog(LOG_DEBUG, + "Could not convert serverin to base64\n"); + return SASL_FAIL; + } + + if ((result = gssspnego_child_interact(context, + childbuf, + sizeof(childbuf), + &childbuflen)) != SASL_OK) + return result; + + /* The child's reply can be: + "PW" -> It wants a password + "KK <base64>" -> Send <base64> to the server + "AF" -> Authenticated + "NA" -> Not Authenticated + */ + + if (strncmp(childbuf, "PW", 2) == 0) { + + result = _plug_get_password(params->utils, &password, + &must_free_password, prompt_need); + + if ( (result != SASL_OK) && + (result != SASL_INTERACT) ) + return result; + + /* free prompts we got */ + if (prompt_need && *prompt_need) { + params->utils->free(*prompt_need); + *prompt_need = NULL; + } + + if (result == SASL_OK) + goto send_password; + + result = _plug_make_prompts(params->utils, prompt_need, + NULL, NULL, + NULL, NULL, + "Password", NULL, + NULL, NULL, NULL, + NULL, NULL, NULL); + if (result != SASL_OK) + return result; + return SASL_INTERACT; + + } + + if (strncmp(childbuf, "KK ", 3) == 0) { + static char bin[2048]; + + if (params->utils->decode64(childbuf+3, childbuflen-3, + bin, sizeof(bin)-1, + clientoutlen) != SASL_OK) { + syslog(LOG_DEBUG, "Could not decode child's base64\n"); + return SASL_FAIL; + } + *clientout = bin; + return SASL_CONTINUE; + } + + if ( (strncmp(childbuf, "AF", 2) == 0) || + (strncmp(childbuf, "NA", 2) == 0) ) { + int status; + + fclose(context->pipe_in); + fclose(context->pipe_out); + + waitpid(context->child_pid, &status, 0); + + *clientout = NULL; + *clientoutlen = 0; + + /* set oparams */ + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + + return (strncmp(childbuf, "AF", 2) == 0) ? + SASL_OK : SASL_BADAUTH; + } + + return SASL_FAIL; +} + +static sasl_client_plug_t gssspnego_client_plugins[] = +{ + { + "GSS-SPNEGO", /* mech_name */ + 0, /* max_ssf */ + SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT, /* security_flags */ + SASL_FEAT_SERVER_FIRST, /* features */ + NULL, /* required_prompts */ + NULL, /* glob_context */ + &gssspnego_client_mech_new, /* mech_new */ + &gssspnego_client_mech_step, /* mech_step */ + NULL, /* mech_dispose */ + NULL, /* mech_free */ + NULL, /* idle */ + NULL, /* spare */ + NULL /* spare */ + } +}; + +int gssspnego_client_plug_init(sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_client_plug_t **pluglist, + int *plugcount) +{ + if (maxversion < SASL_CLIENT_PLUG_VERSION) { + SETERROR(utils, "GSSSPNEGO version mismatch"); + return SASL_BADVERS; + } + + *out_version = SASL_CLIENT_PLUG_VERSION; + *pluglist = gssspnego_client_plugins; + *plugcount = 1; + + return SASL_OK; +}