diff options
Diffstat (limited to 'daemon/ntlm.c')
-rw-r--r-- | daemon/ntlm.c | 825 |
1 files changed, 825 insertions, 0 deletions
diff --git a/daemon/ntlm.c b/daemon/ntlm.c new file mode 100644 index 0000000..dc22f7c --- /dev/null +++ b/daemon/ntlm.c @@ -0,0 +1,825 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * 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. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen <nielsen@memberwebs.com> + * + */ + +#include "usuals.h" +#include "httpauthd.h" +#include "hash.h" +#include "defaults.h" +#include "md5.h" +#include "basic.h" +#include "stringx.h" + +#include <syslog.h> + +/* The NTLM headers */ +#include "ntlmssp.h" + +/* ------------------------------------------------------------------------------- + * Defaults and Constants + */ + +#define NTLM_HASH_KEY_LEN MD5_LEN +#define NTLM_ESTABLISHED (void*)1 + + +/* ------------------------------------------------------------------------------- + * Structures and Globals + */ + +/* A pending connection */ +typedef struct ntlm_connection +{ + void* handle; + unsigned char nonce[NONCE_LEN]; + unsigned int flags; +} +ntlm_connection_t; + + +/* The main context */ +typedef struct ntlm_context +{ + /* Read Only --------------------------------------------------------- */ + const char* server; /* Server to authenticate against */ + const char* domain; /* NTLM domain to authenticate against */ + const char* backup; /* Backup server if primary is down */ + int pending_max; /* Maximum number of connections at once */ + int pending_timeout; /* Timeout for authentication (in seconds) */ + + /* Require Locking --------------------------------------------------- */ + hsh_t* pending; /* Pending connections */ + hsh_t* established; /* Established connections */ +} +ntlm_context_t; + + +/* The default context settings */ +static const ntlm_context_t ntlm_defaults = +{ + NULL, NULL, NULL, DEFAULT_PENDING_MAX, DEFAULT_PENDING_TIMEOUT, + NULL, NULL +}; + + +/* Mutexes for accessing non-thread-safe smblib */ +static pthread_mutex_t g_smblib_mutex; +static pthread_mutexattr_t g_smblib_mutexattr; + + +/* ------------------------------------------------------------------------------- + * Internal Functions + */ + +static ntlm_connection_t* makeconnection(ha_request_t* rq, ntlm_context_t* ctx) +{ + ntlm_connection_t* conn; + + ASSERT(ctx); + + conn = (ntlm_connection_t*)malloc(sizeof(ntlm_connection_t)); + if(!conn) + { + ha_messagex(NULL, LOG_CRIT, "out of memory"); + return NULL; + } + + memset(conn, 0, sizeof(*conn)); + + /* + * Open a connection to to the domain controller. I don't think + * we can cache these connections or use them again as opening + * a connection here results in an nonce being generated. + */ + conn->handle = ntlmssp_connect(ctx->server, ctx->backup, + ctx->domain, (char*)conn->nonce); + if(!conn->handle) + { + ha_messagex(rq, LOG_ERR, "couldn't connect to the domain server %s (backup: %s)", + ctx->server, ctx->backup ? ctx->backup : "none"); + free(conn); + return NULL; + } + + ha_messagex(rq, LOG_INFO, "established connection to server"); + return conn; +} + +static void freeconnection(ha_request_t* rq, ntlm_connection_t* conn) +{ + ASSERT(conn); + + if(conn->handle) + { + ha_messagex(rq, LOG_DEBUG, "disconnected from server"); + ntlmssp_disconnect(conn->handle); + conn->handle = NULL; + } + + free(conn); +} + +static void free_hash_object(void* arg, void* val) +{ + if(val) + { + ASSERT(val != NTLM_ESTABLISHED); + freeconnection(NULL, (ntlm_connection_t*)val); + } +} + +static ntlm_connection_t* getpending(ntlm_context_t* ctx, const void* key) +{ + ntlm_connection_t* ret; + + ASSERT(ctx && key); + + ha_lock(NULL); + + ret = (ntlm_connection_t*)hsh_rem(ctx->pending, key); + + ha_unlock(NULL); + + return ret; +} + +static int putpending(ntlm_context_t* ctx, const void* key, ntlm_connection_t* conn) +{ + int r = 0; + + ASSERT(ctx && key && conn); + ASSERT(conn->handle); + + ha_lock(NULL); + + if(!hsh_get(ctx->pending, key)) + { + if(!hsh_set(ctx->pending, key, (void*)conn)) + { + free_hash_object(NULL, conn); + ha_messagex(NULL, LOG_ERR, "out of memory"); + r = -1; + } + } + + ha_unlock(NULL); + + return r; +} + +int ntlm_auth_basic(ha_request_t* rq, ntlm_context_t* ctx, unsigned char* key, + const char* header) +{ + ntlm_connection_t* conn; + char* t; + basic_header_t basic; + const char* domain = NULL; + int found = 0; + int r; + + ASSERT(ctx && key && header && rq); + + /* + * We're doing basic authentication on the connection + * which invalidates any NTLM authentication we've started + * or done on this connection. + */ + conn = getpending(ctx, key); + if(conn) + { + ha_messagex(rq, LOG_WARNING, "basic auth killed a pending ntlm auth in progress"); + freeconnection(rq, conn); + } + + if((r = basic_parse(header, rq->buf, &basic)) < 0) + return r; + + /* Check and see if this connection is in the cache */ + ha_lock(NULL); + + if(hsh_get(ctx->established, basic.key) == NTLM_ESTABLISHED) + found = 1; + + ha_unlock(NULL); + + if(found) + ha_messagex(rq, LOG_NOTICE, "validated basic user against cache: %s", basic.user); + + else + { + /* Try to find a domain in the user */ + if((t = strchr(basic.user, '\\')) != NULL || + (t = strchr(basic.user, '/')) != NULL) + { + /* Break at the domain */ + domain = basic.user; + basic.user = t + 1; + *t = 0; + + /* Make sure this is our domain */ + if(strcasecmp(domain, ctx->domain) != 0) + domain = NULL; + } + + if(!domain) + { + /* Use the default domain if none specified */ + domain = ctx->domain; + } + + /* Make sure above did not fail */ + if(basic.user && basic.user[0] && basic.password && + domain && domain[0]) + { + ha_messagex(rq, LOG_DEBUG, "checking user against server: %s", basic.user); + + /* We need to lock to go into smblib */ + ha_lock(&g_smblib_mutex); + + /* Found in smbval/valid.h */ + if(ntlmssp_validuser(basic.user, basic.password, ctx->server, + ctx->backup, domain) == NTV_NO_ERROR) + { + /* If valid then we return */ + found = 1; + } + + ha_unlock(&g_smblib_mutex); + } + + if(found) + ha_messagex(rq, LOG_NOTICE, "validated basic user against server: %s", basic.user); + } + + if(found) + { + int r; + rq->resp_code = HA_SERVER_OK; + rq->resp_detail = basic.user; + + ha_lock(NULL); + + /* We put this connection into the successful connections */ + r = hsh_set(ctx->established, basic.key, NTLM_ESTABLISHED); + + ha_unlock(NULL); + + if(!r) + { + ha_messagex(NULL, LOG_CRIT, "out of memory"); + return HA_CRITERROR; + } + + return HA_OK; + } + + return HA_FALSE; +} + +int ntlm_auth_ntlm(ha_request_t* rq, ntlm_context_t* ctx, void* key, + const char* header) +{ + ntlmssp_info_rec ntlmssp; + ntlm_connection_t* conn = NULL; + unsigned int flags = 0; + int ret = HA_FALSE; + size_t len = 0; + void* d; + int r; + + ASSERT(ctx && key && header && rq); + + /* + * Retrieve and remove the connection from the pending bag. + * We add it back again below if that's necessary. + */ + conn = getpending(ctx, key); + + /* + * We use the flags from an already established connection + * if we've been pending and stuff + */ + + if(conn && conn->flags) + flags = conn->flags; + + /* + * First we figure out what kind of message the client + * is sending us. + */ + + d = ha_bufdec64(rq->buf, header, &len); + + if(!d || len == 0) + RETURN(HA_FALSE); + + r = ntlmssp_decode_msg(&ntlmssp, d, len, &flags); + if(r != 0) + { + ha_messagex(rq, LOG_WARNING, "decoding NTLMSSP message failed (error %d)", r); + rq->resp_code = HA_SERVER_BADREQ; + RETURN(HA_FALSE); + } + + + switch(ntlmssp.msg_type) + { + + /* An initial NTLM request? */ + case 1: + { + /* Win9x doesn't seem to send a domain or host */ + int win9x = !ntlmssp.host[0] && !ntlmssp.domain[0]; + + /* + * If we already have a connection to the domain controller + * then we're in trouble. Basically this is the second + * type 1 message we've received over this connection. + * + * TODO: Eventually what we want to do here is wait for the + * other authentication request to complete, or something + * like that. + */ + if(conn) + { + /* + * In this case we also add the connection back into the + * pending stack so that the correct request will complete + * properly when it comes through. + */ + r = putpending(ctx, key, conn); + conn = NULL; + + if(r < 0) + { + RETURN(HA_CRITERROR); + } + else + { + ha_messagex(rq, LOG_ERR, "received out of order NTLM request from client"); + rq->resp_code = HA_SERVER_BADREQ; + RETURN(HA_FALSE); + } + } + + + /* + * Check how many connections we have to the domain controller + * and if too many then cut off here. + */ + if(ctx->pending_max != -1) + { + ha_lock(NULL); + + if(hsh_count(ctx->pending) >= ctx->pending_max) + hsh_bump(ctx->pending); + + ha_unlock(NULL); + } + + + /* + * Open a connection to to the domain controller. I don't think + * we can cache these connections or use them again as opening + * a connection here results in an nonce being generated. + */ + conn = makeconnection(rq, ctx); + + if(!conn) + RETURN(HA_FAILED); + + /* Save away any flags given us by ntlm_decode_msg */ + conn->flags = flags; + + /* Start building the header */ + ha_bufcpy(rq->buf, HA_PREFIX_NTLM); + + if(win9x) + { + struct ntlm_msg2_win9x msg_win9x; + ntlmssp_encode_msg2_win9x(conn->nonce, &msg_win9x, (char*)ctx->domain, flags); + ha_bufjoin(rq->buf); + ha_bufenc64(rq->buf, (unsigned char*)&msg_win9x, sizeof(msg_win9x)); + } + else + { + struct ntlm_msg2 msg; + ntlmssp_encode_msg2(conn->nonce, &msg); + ha_bufjoin(rq->buf); + ha_bufenc64(rq->buf, (unsigned char*)&msg, sizeof(msg)); + } + + if(CHECK_RBUF(rq)) + RETURN(HA_CRITERROR); + + + /* Cache this connection in our pending set ... */ + r = putpending(ctx, key, conn); + + /* + * By marking this as null, the cleanup code + * won't free the connection since it's been + * cached above. + */ + conn = NULL; + + if(r < 0) + { + RETURN(HA_CRITERROR); + } + else + { + ha_messagex(rq, LOG_DEBUG, "sending ntlm challenge"); + ha_addheader(rq, "WWW-Authenticate", ha_bufdata(rq->buf)); + rq->resp_code = HA_SERVER_DECLINE; + RETURN(HA_FALSE); + } + } + + /* A response to a challenge */ + case 3: + { + /* + * We need to have a connection at this point or this whole thing + * has come in in the wrong order. Actually it's a client error + * for stuff to come in wrong. But since some web servers also + * kill keep-alives and stuff, we forgive and just ask the client + * for the authentication info again. + */ + if(!conn || !conn->handle) + { + ha_messagex(rq, LOG_WARNING, "received out of order NTLM response from client"); + rq->resp_code = HA_SERVER_BADREQ; + RETURN(HA_FALSE); + } + + if(!ntlmssp.user) + { + ha_messagex(rq, LOG_WARNING, "received NTLM response without user name"); + rq->resp_code = HA_SERVER_BADREQ; + RETURN(HA_FALSE); + } + + /* We have to lock while going into smblib */ + ha_lock(&g_smblib_mutex); + + /* Now authenticate them against the DC */ +fprintf(stderr, "AUTHENTICATING: %s / %s / %s / %s\n", (const char*)ntlmssp.user, (const char*)ntlmssp.nt, + (char*)ntlmssp.domain , (char*)ctx->domain); + r = ntlmssp_auth(conn->handle, (const char*)ntlmssp.user, (const char*)ntlmssp.nt, 1); + + ha_unlock(&g_smblib_mutex); + + /* The connection gets disconnected below */ + + if(r == NTV_LOGON_ERROR) + { + /* + * Note that we don't set a code here. This causes our + * caller to put in all the proper headers for us. + */ + ha_messagex(rq, LOG_WARNING, "failed NTLM logon for user '%s'", ntlmssp.user); + RETURN(HA_FALSE); + } + + /* A successful login ends here */ + else + { + int r; + rq->resp_detail = (const char*)ntlmssp.user; + ha_messagex(rq, LOG_NOTICE, "validated ntlm user against server", ntlmssp.user); + + ha_lock(NULL); + + /* We put this connection into the successful connections */ + r = hsh_set(ctx->established, key, NTLM_ESTABLISHED); + + ha_unlock(NULL); + + if(!r) + { + ha_messagex(NULL, LOG_CRIT, "out of memory"); + RETURN(HA_CRITERROR); + } + + RETURN(HA_OK); + } + } + break; + + default: + ha_messagex(rq, LOG_WARNING, "received invalid NTLM message (type %d)", ntlmssp.msg_type); + rq->resp_code = HA_SERVER_BADREQ; + RETURN(HA_FALSE); + }; + + +finally: + if(CHECK_RBUF(rq)) + ret = HA_CRITERROR; + + if(conn) + freeconnection(rq, conn); + + return ret; +} + + +/* ------------------------------------------------------------------------------- + * Handler Functions + */ + +int ntlm_config(ha_context_t* context, const char* name, const char* value) +{ + ntlm_context_t* ctx = (ntlm_context_t*)(context->ctx_data); + + ASSERT(name && value && value[0]); + + if(strcmp(name, "ntlmserver") == 0) + { + ctx->server = value; + return HA_OK; + } + + else if(strcmp(name, "ntlmbackup") == 0) + { + ctx->backup = value; + return HA_OK; + } + + else if(strcmp(name, "ntlmdomain") == 0) + { + ctx->domain = value; + return HA_OK; + } + + else if(strcmp(name, "pendingmax") == 0) + { + return ha_confint(name, value, 1, 256, &(ctx->pending_max)); + } + + else if(strcmp(name, "pendingtimeout") == 0) + { + return ha_confint(name, value, 1, 86400, &(ctx->pending_timeout)); + } + + return HA_FALSE; +} + +int ntlm_init(ha_context_t* context) +{ + /* Per context initialization */ + if(context) + { + ntlm_context_t* ctx = (ntlm_context_t*)(context->ctx_data); + hsh_table_calls_t htc; + + ASSERT(ctx); + + /* Make sure there are some types of authentication we can do */ + if(!(context->allowed_types & (HA_TYPE_BASIC | HA_TYPE_NTLM))) + { + ha_messagex(NULL, LOG_ERR, "NTLM module configured, but does not implement any " + "configured authentication type."); + return HA_FAILED; + } + + /* Check for mandatory configuration */ + if(!(ctx->server) || !(ctx->domain)) + { + ha_messagex(NULL, LOG_ERR, "NTLM configuration incomplete. " + "Must have NTLMServer and NTLMDomain configured."); + return HA_FAILED; + } + + ASSERT(!ctx->pending); + ASSERT(!ctx->established); + + /* Initialize our tables */ + if(!(ctx->pending = hsh_create(NTLM_HASH_KEY_LEN)) || + !(ctx->established = hsh_create(NTLM_HASH_KEY_LEN))) + { + ha_messagex(NULL, LOG_CRIT, "out of memory"); + return HA_CRITERROR; + } + + htc.f_freeval = free_hash_object; + htc.arg = NULL; + hsh_set_table_calls(ctx->pending, &htc); + + ha_messagex(NULL, LOG_INFO, "initialized ntlm handler"); + } + + /* Global Initialization */ + else + { + /* Create the smblib mutex */ + if(pthread_mutexattr_init(&g_smblib_mutexattr) != 0 || + pthread_mutexattr_settype(&g_smblib_mutexattr, HA_MUTEX_TYPE) || + pthread_mutex_init(&g_smblib_mutex, &g_smblib_mutexattr) != 0) + { + ha_messagex(NULL, LOG_CRIT, "threading problem. can't create mutex"); + return HA_CRITERROR; + } + } + + return HA_OK; +} + +void ntlm_destroy(ha_context_t* context) +{ + /* Per context destroy */ + if(context) + { + /* Note: We don't need to be thread safe here anymore */ + ntlm_context_t* ctx = (ntlm_context_t*)(context->ctx_data); + + if(ctx->pending) + hsh_free(ctx->pending); + + if(ctx->established) + hsh_free(ctx->established); + + ha_messagex(NULL, LOG_INFO, "uninitialized handler"); + } + + /* Global Destroy */ + else + { + /* Close the mutex */ + pthread_mutex_destroy(&g_smblib_mutex); + pthread_mutexattr_destroy(&g_smblib_mutexattr); + } +} + +int ntlm_process(ha_request_t* rq) +{ + ntlm_context_t* ctx = (ntlm_context_t*)(rq->context->ctx_data); + void* ntlm_connection_t = NULL; + unsigned char key[NTLM_HASH_KEY_LEN]; + const char* header = NULL; + time_t t = time(NULL); + int ret = 0, r; + + ASSERT(rq); + ASSERT(rq->req_args[AUTH_ARG_CONN]); + + rq->resp_code = -1; + + /* Hash the unique key */ + md5_string(key, rq->req_args[AUTH_ARG_CONN]); + + + ha_lock(NULL); + + /* + * Purge out stale connection stuff. This includes + * authenticated connections which have expired as + * well as half open connections which expire. + */ + r = hsh_purge(ctx->pending, t - ctx->pending_timeout); + r += hsh_purge(ctx->established, t - rq->context->cache_timeout); + + ha_unlock(NULL); + + if(r > 0) + ha_messagex(rq, LOG_DEBUG, "purged info from cache: %d", r); + + /* Look for a NTLM header */ + if(rq->context->allowed_types & HA_TYPE_NTLM) + { + header = ha_getheader(rq, "Authorization", HA_PREFIX_NTLM); + if(header) + { + /* Trim off for decoding */ + header = trim_start(header); + + ha_messagex(rq, LOG_DEBUG, "processing ntlm auth header"); + ret = ntlm_auth_ntlm(rq, ctx, key, header); + if(ret < 0) + return ret; + } + } + + /* If basic is enabled, and no NTLM */ + if(!header && rq->context->allowed_types & HA_TYPE_BASIC) + { + /* Look for a Basic header */ + header = ha_getheader(rq, "Authorization", HA_PREFIX_BASIC); + if(header) + { + /* Trim off for decoding */ + header = trim_start(header); + + ha_messagex(rq, LOG_DEBUG, "processing basic auth header"); + ret = ntlm_auth_basic(rq, ctx, key, header); + if(ret < 0) + return ret; + } + } + + /* The authorization header was not found */ + else + { + ha_lock(NULL); + + /* + * NTLM trusts a connection after it's been authenticated + * so just pass success for those. Note that we do this + * in the absence of a authorization header so that we + * allow connections to be re-authenticated. + */ + + if(hsh_get(ctx->established, key) == NTLM_ESTABLISHED) + { + hsh_touch(ctx->established, key); + rq->resp_code = HA_SERVER_OK; + } + + ha_unlock(NULL); + + if(rq->resp_code == HA_SERVER_OK) + ha_messagex(rq, LOG_NOTICE, "validated user against connection cache"); + + /* TODO: We need to be able to retrieve the user here somehow */ + } + + + /* If nobody's set any other response then... */ + if(rq->resp_code == -1) + { + /* If authentication failed tell the browser about it */ + rq->resp_code = HA_SERVER_DECLINE; + + if(rq->context->allowed_types & HA_TYPE_NTLM) + { + ha_addheader(rq, "WWW-Authenticate", HA_PREFIX_NTLM); + ha_messagex(rq, LOG_DEBUG, "sent ntlm auth request"); + } + + if(rq->context->allowed_types & HA_TYPE_BASIC) + { + ha_bufmcat(rq->buf, HA_PREFIX_BASIC, "realm=\"", rq->context->realm, "\"", NULL); + + if(CHECK_RBUF(rq)) + return HA_CRITERROR; + + ha_addheader(rq, "WWW-Authenticate", ha_bufdata(rq->buf)); + ha_messagex(rq, LOG_DEBUG, "sent basic auth request"); + } + } + + return ret; +} + + + +/* ------------------------------------------------------------------------------- + * Handler Definition + */ + +ha_handler_t ntlm_handler = +{ + "NTLM", /* The type */ + ntlm_init, /* Initialization function */ + ntlm_destroy, /* Uninitialization routine */ + ntlm_config, /* Config routine */ + ntlm_process, /* Processing routine */ + &ntlm_defaults, /* Default settings */ + sizeof(ntlm_context_t) +}; + |