summaryrefslogtreecommitdiff
path: root/daemon/ntlm.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemon/ntlm.c')
-rw-r--r--daemon/ntlm.c703
1 files changed, 703 insertions, 0 deletions
diff --git a/daemon/ntlm.c b/daemon/ntlm.c
new file mode 100644
index 0000000..880ae9e
--- /dev/null
+++ b/daemon/ntlm.c
@@ -0,0 +1,703 @@
+
+/* TODO: Include attribution for ideas, and code from mod_ntlm */
+
+#include "usuals.h"
+#include "httpauthd.h"
+#include "hash.h"
+#include "defaults.h"
+
+#include <syslog.h>
+
+/* The NTLM headers */
+#include "ntlmssp.h"
+
+/* -------------------------------------------------------------------------------
+ * Defaults and Constants
+ */
+
+/* This needs to be the same as an MD5 hash length */
+#define NTLM_HASH_KEY_LEN 16
+#define NTLM_ESTABLISHED (void*)1
+
+
+/* -------------------------------------------------------------------------------
+ * Structures and Globals
+ */
+
+/* A pending connection */
+typedef struct ntlm_connection
+{
+ void* handle;
+ char nonce[NONCE_LEN];
+ unsigned int flags;
+}
+ntlm_connection_t;
+
+
+/* The main context */
+typedef struct ntlm_context
+{
+ /* Settings ---------------------------------------------------------- */
+ 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) */
+ const char* basic_realm; /* The realm for basic authentication */
+
+ /* Context ----------------------------------------------------------- */
+ hash_t* pending; /* Pending connections */
+ hash_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, 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* getpending(ntlm_context_t* ctx, const void* key)
+{
+ ntlm_connection_t* ret;
+
+ ha_lock(NULL);
+
+ if(ret = (ntlm_connection_t*)hash_get(ctx->pending, key))
+ hash_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;
+
+ ha_lock(NULL);
+
+ if(hash_get(ctx->pending, key))
+ {
+ if(!hash_set(ctx->pending, key, (void*)conn))
+ {
+ ha_messagex(LOG_ERR, "out of memory");
+ r = -1;
+ }
+ }
+
+ ha_unlock(NULL);
+
+ return r;
+}
+
+static ntlm_connection_t* makeconnection(ntlm_context_t* ctx)
+{
+ ntlm_connection_t* conn;
+
+ conn = (ntlm_connection_t*)malloc(sizeof(ntlm_connection_t));
+ if(!conn)
+ {
+ ha_messagex(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, conn->nonce);
+ if(!conn->handle)
+ {
+ ha_messagex(LOG_ERR, "couldn't connect to the domain server %s (backup: %s)",
+ ctx->server, ctx->backup ? ctx->backup : "none");
+ free(conn);
+ return NULL;
+ }
+}
+
+static void freeconnection(ntlm_context_t* ctx, ntlm_connection_t* conn)
+{
+ if(conn->handle)
+ {
+ ntlmssp_disconnect(conn->handle);
+ conn->handle = NULL;
+ }
+
+ free(conn);
+}
+
+int ntlm_auth_basic(ntlm_context_t* ctx, char* key, const char* header,
+ ha_response_t* resp, ha_buffer_t* buf)
+{
+ ntlm_connection_t* conn;
+ char* t;
+ ha_basic_header_t basic;
+ const char* domain = NULL;
+
+ /*
+ * 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)
+ freeconnection(ctx, conn);
+
+ if(ha_parsebasic(header, buf, &basic) == HA_ERROR)
+ return HA_ERROR;
+
+ /* Check and see if this connection is in the cache */
+ ha_lock(NULL);
+
+ if(hash_get(ctx->established, basic.key) == BASIC_ESTABLISHED)
+ found = 1;
+
+ ha_unlock(NULL);
+
+
+ /* 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(!found && basic.user && basic.user[0] && basic.password &&
+ basic.password[0] && domain && domain[0])
+ {
+ /* 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 */
+ resp->code = HA_SERVER_ACCEPT;
+ }
+
+ ha_unlock(&g_smblib_mutex);
+ }
+
+ if(resp->code = HA_SERVER_ACCEPT)
+ {
+ resp->detail = basic.user;
+
+ /* We put this connection into the successful connections */
+ if(!hash_set(ctx->established, basic.key, BASIC_ESTABLISHED))
+ {
+ ha_messagex(LOG_CRIT, "out of memory");
+ return HA_ERROR;
+ }
+
+ return HA_OK;
+ }
+
+ return HA_FALSE;
+}
+
+int ntlm_auth_ntlm(ntlm_context_t* ctx, void* key, const char* header,
+ ha_response_t* resp, ha_buffer_t* buf)
+{
+ ntlmssp_info_rec ntlmssp;
+ ntlm_connection_t* conn = NULL;
+ unsigned int flags = 0;
+ int ret = HA_FALSE;
+ int r;
+
+ /*
+ * 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.
+ */
+
+ ha_bufnext(buf);
+ ha_bufdec64(buf, header);
+ header = ha_bufdata(buf);
+
+ if(ha_buferr(buf))
+ goto finally;
+
+ r = ntlmssp_decode_msg(&ntlmssp, ha_bufdata(buf), ha_buflen(buf), &flags);
+ if(r != 0)
+ {
+ ha_messagex(LOG_ERR, "decoding NTLM message failed (error %d)", r);
+ resp->code = HA_SERVER_BADREQ;
+ goto finally;
+ }
+
+
+ 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.
+ */
+ if(putpending(ctx, key, conn) != -1)
+ conn = NULL;
+
+ ha_messagex(LOG_ERR, "received out of order NTLM request from client");
+ resp->code = HA_SERVER_BADREQ;
+ goto finally;
+ }
+
+
+ /*
+ * 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);
+ r = (hash_count(ctx->pending) >= ctx->pending_max);
+ ha_unlock(NULL);
+
+ if(r)
+ {
+ resp->code = HA_SERVER_BUSY;
+ goto finally;
+ }
+ }
+
+
+ /*
+ * 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(ctx);
+
+ if(!conn)
+ {
+ resp->code = HA_SERVER_ERROR;
+ goto finally;
+ }
+
+ /* Save away any flags given us by ntlm_decode_msg */
+ conn->flags = flags;
+
+ /* Start building the header */
+ ha_bufnext(buf);
+ ha_bufcat(buf, NTLM_PREFIX);
+
+ if(win9x)
+ {
+ struct ntlm_msg2_win9x msg_win9x;
+ ntlmssp_encode_msg2_win9x(conn->nonce, &msg_win9x, (char*)ctx->domain, flags);
+ ha_bufenc64(buf, (unsigned char*)&msg_win9x, sizeof(msg_win9x));
+ }
+ else
+ {
+ struct ntlm_msg2 msg;
+ ntlmssp_encode_msg2(conn->nonce, &msg);
+ ha_bufenc64(buf, (unsigned char*)&msg, sizeof(msg));
+ }
+
+ if(ha_buferr(buf))
+ goto finally;
+
+ /*
+ * TODO: Our callers need to be able to keep alive
+ * connections that have authentication going on.
+ */
+
+ /* Cache this connection in our pending set ... */
+ if(putpending(ctx, key, conn) == -1)
+ {
+ resp->code = HA_SERVER_ERROR;
+ goto finally;
+ }
+
+ /*
+ * By marking this as null, the cleanup code
+ * won't free the connection since it's been
+ * cached above.
+ */
+ conn = NULL;
+
+ ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf));
+ resp->code = HA_SERVER_DECLINE;
+ goto finally;
+ }
+
+ /* 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(LOG_ERR, "received out of order NTLM response from client");
+ resp->code = HA_SERVER_BADREQ;
+ goto finally;
+ }
+
+ if(!ntlmssp.user)
+ {
+ ha_messagex(LOG_ERR, "received NTLM response without user name");
+ resp->code = HA_SERVER_BADREQ;
+ goto finally;
+ }
+
+ /* We have to lock while going into smblib */
+ ha_lock(&g_smblib_mutex);
+
+ /* Now authenticate them against the DC */
+ r = ntlmssp_auth(conn->handle, ntlmssp.user, ntlmssp.nt, 1,
+ ntlmssp.domain[0] ? (char*)ntlmssp.domain : (char*)ctx->domain);
+
+ 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(LOG_ERR, "failed NTLM logon for user '%s'", ntlmssp.user);
+ ret = HA_FALSE;
+ }
+
+ /* A successful login ends here */
+ else
+ {
+ resp->code = HA_SERVER_ACCEPT;
+
+ /* We put this connection into the successful connections */
+ if(!hash_set(ctx->established, key, NTLM_ESTABLISHED))
+ {
+ ha_messagex(LOG_CRIT, "out of memory");
+ ret = HA_ERROR;
+ }
+ }
+
+ goto finally;
+ }
+
+ default:
+ ha_messagex(LOG_ERR, "received invalid NTLM message (type %d)", ntlmssp.msg_type);
+ resp->code = HA_SERVER_BADREQ;
+ goto finally;
+ };
+
+
+finally:
+ if(ha_buferr(buf))
+ ret = HA_ERROR;
+
+ if(conn)
+ freeconnection(ctx, 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->data);
+
+ 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));
+ }
+
+ else if(strcmp(name, "realm") == 0)
+ {
+ ctx->basic_realm = value;
+ return HA_OK;
+ }
+
+ return HA_FALSE;
+}
+
+int ntlm_init(ha_context_t* context)
+{
+ /* Per context initialization */
+ if(context)
+ {
+ ntlm_context_t* ctx = (ntlm_context_t*)(context->data);
+
+ /* Make sure there are some types of authentication we can do */
+ if(!(context->types & (HA_TYPE_BASIC | HA_TYPE_NTLM)))
+ {
+ ha_messagex(LOG_ERR, "NTLM module configured, but does not implement any "
+ "configured authentication type.");
+ return HA_ERROR;
+ }
+
+ /* Check for mandatory configuration */
+ if(!(ctx->server) || !(ctx->domain))
+ {
+ ha_messagex(LOG_ERR, "NTLM configuration incomplete. "
+ "Must have NTLMServer and NTLMDomain configured.");
+ return HA_ERROR;
+ }
+
+ /* Initialize our tables */
+ if(!(ctx->pending = hash_create(NTLM_HASH_KEY_LEN)) ||
+ !(ctx->established = hash_create(NTLM_HASH_KEY_LEN)))
+ {
+ ha_messagex(LOG_CRIT, "out of memory");
+ return HA_ERROR;
+ }
+ }
+
+ /* 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_mutex, &g_smblib_mutexattr) != 0)
+ {
+ ha_messagex(LOG_CRIT, "threading problem. can't create mutex");
+ return HA_ERROR;
+ }
+ }
+
+ return HA_OK;
+}
+
+void ntlm_destroy(ha_context_t* context)
+{
+ /* Per context destroy */
+ if(context)
+ {
+ ntlm_context_t* ctx = (ntlm_context_t*)(context->data);
+
+ /* Note: We don't need to be thread safe here anymore */
+ hash_free(ctx->pending);
+ hash_free(ctx->established);
+ }
+
+ /* Global Destroy */
+ else
+ {
+ /* Close the mutex */
+ pthread_mutex_destroy(&g_smblib_mutex);
+ pthread_mutexattr_destroy(&g_smblib_mutexattr);
+ }
+}
+
+int ntlm_process(ha_context_t* context, ha_request_t* req,
+ ha_response_t* resp, ha_buffer_t* buf)
+{
+ ntlm_context_t* ctx = (ntlm_context_t*)(context->data);
+ void* ntlm_connection_t = NULL;
+ unsigned char key[NTLM_HASH_KEY_LEN];
+ const char* header = NULL;
+ time_t t = time(NULL);
+ int ret;
+
+ resp->code = -1;
+
+ /* Hash the unique key */
+ ha_md5string(req->args[1], key);
+
+
+ ha_lock(NULL);
+
+ XXX: These connections aren't being destroyed properly
+
+ /*
+ * Purge out stale connection stuff. This includes
+ * authenticated connections which have expired as
+ * well as half open connections which expire.
+ */
+ hash_purge(ctx->pending, t - ctx->pending_timeout);
+ hash_purge(ctx->established, t - context->timeout);
+
+ ha_unlock(NULL);
+
+
+ /* Look for a NTLM header */
+ if(context->types & HA_TYPES_NTLM)
+ {
+ header = ha_getheader(req, "Authorization", NTLM_PREFIX);
+ if(header)
+ {
+ /* Trim off for decoding */
+ while(*header && isspace(*header))
+ header++;
+
+ ret = ntlm_auth_ntlm(ctx, key, header, resp, buf);
+ if(ret == HA_ERROR)
+ return ret;
+ }
+ }
+
+ /* If basic is enabled, and no NTLM */
+ if(!header && context->types & HA_TYPE_BASIC)
+ {
+ /* Look for a Basic header */
+ header = ha_getheader(req, "Authorization", BASIC_PREFIX);
+ if(header)
+ {
+ /* Trim off for decoding */
+ while(*header && isspace(*header))
+ header++;
+
+ ret = ntlm_auth_basic(ctx, key, header, resp, buf);
+ if(ret == HA_ERROR)
+ 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(hash_get(ctx->established, key) == NTLM_ESTABLISHED)
+ {
+ hash_touch(ctx->established, key);
+ resp->code = HA_SERVER_ACCEPT;
+ }
+
+ ha_unlock(NULL);
+ }
+
+
+ /* If nobody's set any other response then... */
+ if(resp->code != -1)
+ {
+ /* If authentication failed tell the browser about it */
+ resp->code = HA_SERVER_DECLINE;
+
+ if(context->types & HA_TYPE_NTLM)
+ ha_addheader(resp, "WWW-Authenticate", NTLM_PREFIX);
+
+ if(context->types & HA_TYPE_BASIC)
+ {
+ ha_bufnext(buf);
+ ha_bufcat(buf, BASIC_PREFIX, "realm=\"",
+ ctx->basic_realm ? ctx->basic_realm : "", "\"", NULL);
+
+ ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf));
+ }
+ }
+
+ 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)
+};
+