/* TODO: Include attribution for ideas, and code from mod_ntlm */ #include "usuals.h" #include "httpauthd.h" #include "hash.h" #include "defaults.h" #include /* 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) };