diff --git a/ngx_http_auth_ldap_module.c b/ngx_http_auth_ldap_module.c index 080af26..c70aa31 100644 --- a/ngx_http_auth_ldap_module.c +++ b/ngx_http_auth_ldap_module.c @@ -27,6 +27,7 @@ #include #include #include +#include #include typedef struct { @@ -49,6 +50,9 @@ typedef struct { typedef struct { ngx_array_t *servers; /* array of ngx_http_auth_ldap_server_t */ + ngx_flag_t cache_enabled; + ngx_msec_t cache_expiration_time; + size_t cache_size; } ngx_http_auth_ldap_main_conf_t; typedef struct { @@ -56,7 +60,29 @@ typedef struct { ngx_array_t *servers; /* array of ngx_http_auth_ldap_server_t* */ } ngx_http_auth_ldap_loc_conf_t; +typedef struct { + uint32_t small_hash; /* murmur2 hash of username ^ &server */ + uint32_t outcome; /* 0 = authentication failed, 1 = succeeded */ + ngx_msec_t time; /* ngx_current_msec when created */ + u_char big_hash[16]; /* md5 hash of (username, server, password) */ +} ngx_http_auth_ldap_cache_elt_t; + +typedef struct { + ngx_http_auth_ldap_cache_elt_t *buckets; + ngx_uint_t num_buckets; + ngx_uint_t elts_per_bucket; + ngx_msec_t expiration_time; +} ngx_http_auth_ldap_cache_t; + +typedef struct { + ngx_http_auth_ldap_cache_elt_t *cache_bucket; + u_char cache_big_hash[16]; + uint32_t cache_small_hash; +} ngx_http_auth_ldap_ctx_t; + static void * ngx_http_auth_ldap_create_main_conf(ngx_conf_t *cf); +static char * ngx_http_auth_ldap_init_main_conf(ngx_conf_t *cf, void *parent); +static ngx_int_t ngx_http_auth_ldap_init_worker(ngx_cycle_t *cycle); static char * ngx_http_auth_ldap_ldap_server_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char * ngx_http_auth_ldap_parse_url(ngx_conf_t *cf, ngx_http_auth_ldap_server_t *server); static char * ngx_http_auth_ldap_parse_require(ngx_conf_t *cf, ngx_http_auth_ldap_server_t *server); @@ -69,10 +95,13 @@ static char * ngx_http_auth_ldap_merge_loc_conf(ngx_conf_t *, void *, void *); static ngx_int_t ngx_http_auth_ldap_authenticate_against_server(ngx_http_request_t *r, ngx_http_auth_ldap_server_t *server, ngx_http_auth_ldap_loc_conf_t *conf); static ngx_int_t ngx_http_auth_ldap_set_realm(ngx_http_request_t *r, ngx_str_t *realm); -static ngx_int_t ngx_http_auth_ldap_authenticate(ngx_http_request_t *r, ngx_http_auth_ldap_loc_conf_t *conf); +static ngx_int_t ngx_http_auth_ldap_authenticate(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx, + ngx_http_auth_ldap_loc_conf_t *conf); static char * ngx_http_auth_ldap(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char * ngx_http_auth_ldap_servers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +ngx_http_auth_ldap_cache_t ngx_http_auth_ldap_cache; + static ngx_command_t ngx_http_auth_ldap_commands[] = { { ngx_string("ldap_server"), @@ -82,6 +111,30 @@ static ngx_command_t ngx_http_auth_ldap_commands[] = { 0, NULL }, + { + ngx_string("auth_ldap_cache_enabled"), + NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_HTTP_MAIN_CONF_OFFSET, + offsetof(ngx_http_auth_ldap_main_conf_t, cache_enabled), + NULL + }, + { + ngx_string("auth_ldap_cache_expiration_time"), + NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_MAIN_CONF_OFFSET, + offsetof(ngx_http_auth_ldap_main_conf_t, cache_expiration_time), + NULL + }, + { + ngx_string("auth_ldap_cache_size"), + NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_MAIN_CONF_OFFSET, + offsetof(ngx_http_auth_ldap_main_conf_t, cache_size), + NULL + }, { ngx_string("auth_ldap"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_TAKE1, @@ -102,29 +155,29 @@ static ngx_command_t ngx_http_auth_ldap_commands[] = { }; static ngx_http_module_t ngx_http_auth_ldap_module_ctx = { - NULL, /* preconfiguration */ - ngx_http_auth_ldap_init, /* postconfiguration */ + NULL, /* preconfiguration */ + ngx_http_auth_ldap_init, /* postconfiguration */ ngx_http_auth_ldap_create_main_conf, /* create main configuration */ - NULL, /* init main configuration */ - NULL, //ngx_http_auth_ldap_create_server_conf, /* create server configuration */ - NULL, //ngx_http_auth_ldap_merge_server_conf, /* merge server configuration */ - ngx_http_auth_ldap_create_loc_conf, /* create location configuration */ - ngx_http_auth_ldap_merge_loc_conf /* merge location configuration */ + ngx_http_auth_ldap_init_main_conf, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_http_auth_ldap_create_loc_conf, /* create location configuration */ + ngx_http_auth_ldap_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_auth_ldap_module = { NGX_MODULE_V1, - &ngx_http_auth_ldap_module_ctx, /* module context */ - ngx_http_auth_ldap_commands, /* module directives */ - NGX_HTTP_MODULE, /* module type */ - NULL, /* init master */ - NULL, /* init module */ - NULL, /* init process */ - NULL, /* init thread */ - NULL, /* exit thread */ - NULL, /* exit process */ - NULL, /* exit master */ - NGX_MODULE_V1_PADDING /**/ + &ngx_http_auth_ldap_module_ctx, /* module context */ + ngx_http_auth_ldap_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_http_auth_ldap_init_worker, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING }; @@ -456,9 +509,144 @@ ngx_http_auth_ldap_create_main_conf(ngx_conf_t *cf) return NULL; } + conf->cache_enabled = NGX_CONF_UNSET; + conf->cache_expiration_time = NGX_CONF_UNSET_MSEC; + conf->cache_size = NGX_CONF_UNSET_SIZE; + return conf; } +static char * +ngx_http_auth_ldap_init_main_conf(ngx_conf_t *cf, void *parent) +{ + ngx_http_auth_ldap_main_conf_t *conf = parent; + + if (conf->cache_enabled == NGX_CONF_UNSET) { + conf->cache_enabled = 0; + } + if (conf->cache_enabled == 0) { + return NGX_CONF_OK; + } + + if (conf->cache_size == NGX_CONF_UNSET_SIZE) { + conf->cache_size = 100; + } + if (conf->cache_size < 100) { + ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "auth_ldap_cache_size cannot be smaller than 100 entries."); + return NGX_CONF_ERROR; + } + + if (conf->cache_expiration_time == NGX_CONF_UNSET_MSEC) { + conf->cache_expiration_time = 10000; + } + if (conf->cache_expiration_time < 1000) { + ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "auth_ldap_cache_expiration_time cannot be smaller than 1000 ms."); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + +static ngx_int_t +ngx_http_auth_ldap_init_worker(ngx_cycle_t *cycle) +{ + ngx_http_auth_ldap_main_conf_t *conf; + ngx_uint_t want, count, i; + ngx_http_auth_ldap_cache_t *cache; + static const uint16_t primes[] = { + 13, 53, 101, 151, 199, 263, 317, 383, 443, 503, + 577, 641, 701, 769, 839, 911, 983, 1049, 1109 + }; + + + if (ngx_process != NGX_PROCESS_SINGLE && ngx_process != NGX_PROCESS_WORKER) { + return NGX_OK; + } + + + conf = (ngx_http_auth_ldap_main_conf_t *) ngx_http_cycle_get_module_main_conf(cycle, ngx_http_auth_ldap_module); + if (conf == NULL || !conf->cache_enabled) { + return NGX_OK; + } + + want = (conf->cache_size + 7) / 8; + for (i = 0; i < sizeof(primes)/sizeof(primes[0]); i++) { + count = primes[i]; + if (count >= want) { + break; + } + } + + cache = &ngx_http_auth_ldap_cache; + cache->expiration_time = conf->cache_expiration_time; + cache->num_buckets = count; + cache->elts_per_bucket = 8; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, cycle->log, 0, "http_auth_ldap: Allocating %ud bytes of LDAP cache.", + cache->num_buckets * cache->elts_per_bucket * sizeof(ngx_http_auth_ldap_cache_elt_t)); + + cache->buckets = (ngx_http_auth_ldap_cache_elt_t *) ngx_calloc(count * 8 * sizeof(ngx_http_auth_ldap_cache_elt_t), cycle->log); + if (cache->buckets == NULL) { + ngx_log_error(NGX_LOG_ERR, cycle->log, 0, "http_auth_ldap: Unable to allocate memory for LDAP cache."); + return NGX_ERROR; + } + + return NGX_OK; +} + +static ngx_int_t +ngx_http_auth_ldap_check_cache(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx, + ngx_http_auth_ldap_cache_t *cache, ngx_http_auth_ldap_server_t *server) +{ + ngx_http_auth_ldap_cache_elt_t *elt; + ngx_md5_t md5ctx; + ngx_msec_t time_limit; + ngx_uint_t i; + + ctx->cache_small_hash = ngx_murmur_hash2(r->headers_in.user.data, r->headers_in.user.len) ^ (uint32_t)(ngx_uint_t)server; + + ngx_md5_init(&md5ctx); + ngx_md5_update(&md5ctx, r->headers_in.user.data, r->headers_in.user.len); + ngx_md5_update(&md5ctx, server, sizeof(*server)); + ngx_md5_update(&md5ctx, r->headers_in.passwd.data, r->headers_in.passwd.len); + ngx_md5_final(ctx->cache_big_hash, &md5ctx); + + ctx->cache_bucket = &cache->buckets[ctx->cache_small_hash % cache->num_buckets]; + + elt = ctx->cache_bucket; + time_limit = ngx_current_msec - cache->expiration_time; + for (i = 0; i < cache->elts_per_bucket; i++, elt++) { + if (elt->small_hash == ctx->cache_small_hash && + elt->time > time_limit && + memcmp(elt->big_hash, ctx->cache_big_hash, 16) == 0) { + return elt->outcome; + } + } + + return -1; +} + +static void +ngx_http_auth_ldap_update_cache(ngx_http_auth_ldap_ctx_t *ctx, + ngx_http_auth_ldap_cache_t *cache, ngx_flag_t outcome) +{ + ngx_http_auth_ldap_cache_elt_t *elt, *oldest_elt; + ngx_uint_t i; + + elt = ctx->cache_bucket; + oldest_elt = elt; + for (i = 1; i < cache->elts_per_bucket; i++, elt++) { + if (elt->time < oldest_elt->time) { + oldest_elt = elt; + } + } + + oldest_elt->time = ngx_current_msec; + oldest_elt->outcome = outcome; + oldest_elt->small_hash = ctx->cache_small_hash; + ngx_memcpy(oldest_elt->big_hash, ctx->cache_big_hash, 16); +} + /** * Create location conf */ @@ -494,29 +682,40 @@ ngx_http_auth_ldap_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { * LDAP Authentication handler */ static ngx_int_t ngx_http_auth_ldap_handler(ngx_http_request_t *r) { - int rc; ngx_http_auth_ldap_loc_conf_t *alcf; + ngx_http_auth_ldap_ctx_t *ctx; + int rc; alcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_ldap_module); if (alcf->realm.len == 0) { return NGX_DECLINED; } - rc = ngx_http_auth_basic_user(r); - if (rc == NGX_DECLINED) { - return ngx_http_auth_ldap_set_realm(r, &alcf->realm); - } - if (rc == NGX_ERROR) { - return NGX_HTTP_INTERNAL_SERVER_ERROR; + ctx = ngx_http_get_module_ctx(r, ngx_http_auth_ldap_module); + if (ctx == NULL) { + rc = ngx_http_auth_basic_user(r); + if (rc == NGX_DECLINED) { + return ngx_http_auth_ldap_set_realm(r, &alcf->realm); + } + if (rc == NGX_ERROR) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_auth_ldap_ctx_t)); + if (ctx == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_http_set_ctx(r, ctx, ngx_http_auth_ldap_module); } - return ngx_http_auth_ldap_authenticate(r, alcf); + return ngx_http_auth_ldap_authenticate(r, ctx, alcf); } /** * Read user credentials from request, set LDAP parameters and call authentication against required servers */ -static ngx_int_t ngx_http_auth_ldap_authenticate(ngx_http_request_t *r, ngx_http_auth_ldap_loc_conf_t *conf) { +static ngx_int_t ngx_http_auth_ldap_authenticate(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx, + ngx_http_auth_ldap_loc_conf_t *conf) { ngx_http_auth_ldap_server_t *server; int rc; @@ -525,7 +724,6 @@ static ngx_int_t ngx_http_auth_ldap_authenticate(ngx_http_request_t *r, ngx_http int version = LDAP_VERSION3; int reqcert = LDAP_OPT_X_TLS_ALLOW; struct timeval timeOut = { 10, 0 }; - ngx_flag_t pass = NGX_CONF_UNSET; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "LDAP username: %V", &r->headers_in.user); @@ -546,10 +744,28 @@ static ngx_int_t ngx_http_auth_ldap_authenticate(ngx_http_request_t *r, ngx_http for (i = 0; i < conf->servers->nelts; i++) { server = ((ngx_http_auth_ldap_server_t **) conf->servers->elts)[i]; - pass = ngx_http_auth_ldap_authenticate_against_server(r, server, conf); - if (pass == 1) { + + if (ngx_http_auth_ldap_cache.buckets != NULL) { + rc = ngx_http_auth_ldap_check_cache(r, ctx, &ngx_http_auth_ldap_cache, server); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "LDAP: Cached outcome %d", rc); + if (rc == 0) { + continue; + } + if (rc == 1) { + return NGX_OK; + } + } + + rc = ngx_http_auth_ldap_authenticate_against_server(r, server, conf); + + if ((rc == 0 || rc == 1) && ngx_http_auth_ldap_cache.buckets != NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "LDAP: Caching outcome %d", rc); + ngx_http_auth_ldap_update_cache(ctx, &ngx_http_auth_ldap_cache, rc); + } + + if (rc == 1) { return NGX_OK; - } else if (pass == NGX_HTTP_INTERNAL_SERVER_ERROR) { + } else if (rc == NGX_HTTP_INTERNAL_SERVER_ERROR) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } }