diff --git a/README.md b/README.md index 2f040e2..a12d988 100644 --- a/README.md +++ b/README.md @@ -84,11 +84,18 @@ prove -r t ## nginx Version Compatibility +This module is now **v2** and intentionally targets only newer nginx versions. + | nginx version | Notes | |---------------|-------| -| < 1.9.1 | Not supported — the upstream peer API used by this module was introduced in nginx 1.9.1. | -| 1.9.1 – 1.24.x | Supported. | -| ≥ 1.25.x | Supported. Versions in the 1.25/1.26/1.27/1.28 series (e.g. 1.28.3) changed internal assumptions about `ngx_connection_t->data` in the upstream event handler (`ngx_http_upstream_handler` now expects `c->data` to hold the request pointer). Earlier releases of this module reused `c->data` to store the NTLM cache item on idle connections, which caused segfaults with these nginx versions. This was fixed in the module — see [PR #4](https://github.com/Securepoint/nginx-ntlm-module/pull/4). Use a module build from the current `main` branch when running nginx ≥ 1.25. | +| < 1.25.0 | Not supported by v2. | +| ≥ 1.25.0 | Supported. | + +### Migration notes (breaking change) + +- v2 enforces nginx >= 1.25.0 at compile time. +- If you need older nginx support, keep using an older release/branch of this module. +- Directive names and behavior remain the same: `ntlm`, `ntlm_timeout`, `ntlm_time`, `ntlm_requests`. ## Acknowledgments diff --git a/ngx_http_upstream_ntlm_module.c b/ngx_http_upstream_ntlm_module.c index 64c262f..3724235 100644 --- a/ngx_http_upstream_ntlm_module.c +++ b/ngx_http_upstream_ntlm_module.c @@ -2,40 +2,19 @@ #include #include -static ngx_int_t -ngx_http_upstream_init_ntlm_peer(ngx_http_request_t *r, - ngx_http_upstream_srv_conf_t *us); -static ngx_int_t ngx_http_upstream_get_ntlm_peer(ngx_peer_connection_t *pc, - void *data); -static void ngx_http_upstream_free_ntlm_peer(ngx_peer_connection_t *pc, - void *data, ngx_uint_t state); - -static void ngx_http_upstream_ntlm_dummy_handler(ngx_event_t *ev); -static void ngx_http_upstream_ntlm_close_handler(ngx_event_t *ev); -static void ngx_http_upstream_ntlm_close(ngx_connection_t *c); -static void ngx_http_upstream_ntlm_notify_peer(ngx_peer_connection_t *pc, - void *data, ngx_uint_t type); - -#if (NGX_HTTP_SSL) -static ngx_int_t ngx_http_upstream_ntlm_set_session(ngx_peer_connection_t *pc, - void *data); -static void ngx_http_upstream_ntlm_save_session(ngx_peer_connection_t *pc, - void *data); +#if (nginx_version < 1025000) +#error "ngx_http_upstream_ntlm_module v2 requires nginx >= 1.25.0" #endif -static void *ngx_http_upstream_ntlm_create_conf(ngx_conf_t *cf); -static char *ngx_http_upstream_ntlm(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf); - -static void ngx_http_upstream_client_conn_cleanup(void *data); - typedef struct { ngx_uint_t max_cached; ngx_uint_t max_requests; ngx_msec_t time; ngx_msec_t timeout; + ngx_queue_t free; ngx_queue_t cache; + ngx_http_upstream_init_pt original_init_upstream; ngx_http_upstream_init_peer_pt original_init_peer; } ngx_http_upstream_ntlm_srv_conf_t; @@ -45,15 +24,6 @@ typedef struct { ngx_queue_t queue; ngx_connection_t *peer_connection; ngx_connection_t *client_connection; - /* - * in_cache == 1 : item is in conf->cache and owns peer_connection. - * in_cache == 0 : item is in conf->free (idle slot, no connection). - * - * This flag is the authoritative ownership indicator. Every code path - * that moves an item out of conf->cache MUST set in_cache = 0 and NULL - * out peer_connection BEFORE doing anything else, so that a concurrently - * running cleanup or close handler can bail out early and safely. - */ unsigned in_cache : 1; } ngx_http_upstream_ntlm_cache_t; @@ -62,8 +32,10 @@ typedef struct { ngx_http_upstream_t *upstream; void *data; ngx_connection_t *client_connection; + unsigned cached : 1; unsigned ntlm_init : 1; + ngx_event_get_peer_pt original_get_peer; ngx_event_free_peer_pt original_free_peer; ngx_event_notify_peer_pt original_notify; @@ -71,74 +43,215 @@ typedef struct { ngx_event_set_peer_session_pt original_set_session; ngx_event_save_peer_session_pt original_save_session; #endif - } ngx_http_upstream_ntlm_peer_data_t; -static ngx_command_t ngx_http_upstream_ntlm_commands[] = { +static ngx_int_t ngx_http_upstream_init_ntlm(ngx_conf_t *cf, + ngx_http_upstream_srv_conf_t *us); +static ngx_int_t ngx_http_upstream_init_ntlm_peer(ngx_http_request_t *r, + ngx_http_upstream_srv_conf_t *us); +static ngx_int_t ngx_http_upstream_get_ntlm_peer(ngx_peer_connection_t *pc, + void *data); +static void ngx_http_upstream_free_ntlm_peer(ngx_peer_connection_t *pc, + void *data, ngx_uint_t state); +static void ngx_http_upstream_ntlm_notify_peer(ngx_peer_connection_t *pc, + void *data, ngx_uint_t type); - {ngx_string("ntlm"), NGX_HTTP_UPS_CONF | NGX_CONF_NOARGS | NGX_CONF_TAKE1, - ngx_http_upstream_ntlm, NGX_HTTP_SRV_CONF_OFFSET, 0, NULL}, +static void ngx_http_upstream_ntlm_dummy_handler(ngx_event_t *ev); +static void ngx_http_upstream_ntlm_close_handler(ngx_event_t *ev); +static void ngx_http_upstream_ntlm_close(ngx_connection_t *c); +static void ngx_http_upstream_client_conn_cleanup(void *data); - {ngx_string("ntlm_timeout"), NGX_HTTP_UPS_CONF | NGX_CONF_TAKE1, - ngx_conf_set_msec_slot, NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_upstream_ntlm_srv_conf_t, timeout), NULL}, +static ngx_int_t ngx_http_upstream_ntlm_header_is_auth(ngx_str_t *value); +static void ngx_http_upstream_ntlm_item_release_to_free( + ngx_http_upstream_ntlm_cache_t *item); +static void ngx_http_upstream_ntlm_force_release_to_free( + ngx_http_upstream_ntlm_cache_t *item); +static ngx_pool_cleanup_t *ngx_http_upstream_ntlm_pool_cleanup_add( + ngx_pool_t *p, size_t size); - {ngx_string("ntlm_time"), NGX_HTTP_UPS_CONF | NGX_CONF_TAKE1, - ngx_conf_set_msec_slot, NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_upstream_ntlm_srv_conf_t, time), NULL}, +#if (NGX_HTTP_SSL) +static ngx_int_t ngx_http_upstream_ntlm_set_session(ngx_peer_connection_t *pc, + void *data); +static void ngx_http_upstream_ntlm_save_session(ngx_peer_connection_t *pc, + void *data); +#endif - {ngx_string("ntlm_requests"), NGX_HTTP_UPS_CONF | NGX_CONF_TAKE1, - ngx_conf_set_num_slot, NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_upstream_ntlm_srv_conf_t, max_requests), NULL}, +static void *ngx_http_upstream_ntlm_create_conf(ngx_conf_t *cf); +static char *ngx_http_upstream_ntlm(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +static ngx_command_t ngx_http_upstream_ntlm_commands[] = { - ngx_null_command /* command termination */ + { ngx_string("ntlm"), + NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1, + ngx_http_upstream_ntlm, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("ntlm_timeout"), + NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_upstream_ntlm_srv_conf_t, timeout), + NULL }, + + { ngx_string("ntlm_time"), + NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_upstream_ntlm_srv_conf_t, time), + NULL }, + + { ngx_string("ntlm_requests"), + NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_upstream_ntlm_srv_conf_t, max_requests), + NULL }, + + ngx_null_command }; -/* The module context */ -static ngx_http_module_t ngx_http_upstream_ntlm_ctx = { - NULL, /* preconfiguration */ - NULL, /* postconfiguration */ +static ngx_http_module_t ngx_http_upstream_ntlm_module_ctx = { + NULL, + NULL, - NULL, /* create main configuration */ - NULL, /* init main configuration */ + NULL, + NULL, - ngx_http_upstream_ntlm_create_conf, /* create server configuration */ - NULL, /* merge server configuration */ + ngx_http_upstream_ntlm_create_conf, + NULL, - NULL, /* create location configuration */ - NULL /* merge location configuration */ + NULL, + NULL }; -/* The module definition */ ngx_module_t ngx_http_upstream_ntlm_module = { NGX_MODULE_V1, - &ngx_http_upstream_ntlm_ctx, /* module context */ - ngx_http_upstream_ntlm_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_upstream_ntlm_module_ctx, + ngx_http_upstream_ntlm_commands, + NGX_HTTP_MODULE, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NGX_MODULE_V1_PADDING +}; -static ngx_int_t ngx_http_upstream_init_ntlm(ngx_conf_t *cf, - ngx_http_upstream_srv_conf_t *us) { +static ngx_int_t +ngx_http_upstream_ntlm_header_is_auth(ngx_str_t *value) +{ + size_t token_len; + + if (value == NULL || value->data == NULL) { + return 0; + } + + token_len = sizeof("NTLM") - 1; + if (value->len >= token_len + && ngx_strncasecmp(value->data, (u_char *) "NTLM", token_len) == 0 + && (value->len == token_len || value->data[token_len] == ' ' + || value->data[token_len] == '\t')) + { + return 1; + } + + token_len = sizeof("Negotiate") - 1; + if (value->len >= token_len + && ngx_strncasecmp(value->data, (u_char *) "Negotiate", token_len) == 0 + && (value->len == token_len || value->data[token_len] == ' ' + || value->data[token_len] == '\t')) + { + return 1; + } + + return 0; +} + +static void +ngx_http_upstream_ntlm_item_release_to_free(ngx_http_upstream_ntlm_cache_t *item) +{ + if (item == NULL) { + return; + } + + if (!item->in_cache) { + return; + } + + item->in_cache = 0; + item->peer_connection = NULL; + item->client_connection = NULL; + + ngx_queue_remove(&item->queue); + ngx_queue_insert_head(&item->conf->free, &item->queue); +} + +static void +ngx_http_upstream_ntlm_force_release_to_free(ngx_http_upstream_ntlm_cache_t *item) +{ + if (item == NULL) { + return; + } + + item->in_cache = 0; + item->peer_connection = NULL; + item->client_connection = NULL; + + ngx_queue_remove(&item->queue); + ngx_queue_insert_head(&item->conf->free, &item->queue); +} + +static ngx_int_t +ngx_http_upstream_init_ntlm(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us) +{ ngx_uint_t i; ngx_http_upstream_ntlm_cache_t *cached; ngx_http_upstream_ntlm_srv_conf_t *hncf; - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, "ntlm init"); - hncf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_ntlm_module); ngx_conf_init_uint_value(hncf->max_cached, 100); - ngx_conf_init_msec_value(hncf->timeout, 60000); ngx_conf_init_uint_value(hncf->max_requests, 1000); + ngx_conf_init_msec_value(hncf->timeout, 60000); ngx_conf_init_msec_value(hncf->time, 3600000); + if (hncf->max_cached == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "ntlm: max_cached must be greater than 0"); + return NGX_ERROR; + } + + if (hncf->max_requests == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "ntlm: ntlm_requests must be greater than 0"); + return NGX_ERROR; + } + + if (hncf->timeout == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "ntlm: ntlm_timeout must be greater than 0"); + return NGX_ERROR; + } + + if (hncf->time == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "ntlm: ntlm_time must be greater than 0"); + return NGX_ERROR; + } + + if (hncf->max_cached > ((ngx_uint_t) -1) + / sizeof(ngx_http_upstream_ntlm_cache_t)) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "ntlm: max_cached is too large"); + return NGX_ERROR; + } + if (hncf->original_init_upstream(cf, us) != NGX_OK) { return NGX_ERROR; } @@ -146,8 +259,8 @@ static ngx_int_t ngx_http_upstream_init_ntlm(ngx_conf_t *cf, hncf->original_init_peer = us->peer.init; us->peer.init = ngx_http_upstream_init_ntlm_peer; - cached = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_ntlm_cache_t) * - hncf->max_cached); + cached = ngx_pcalloc(cf->pool, + sizeof(ngx_http_upstream_ntlm_cache_t) * hncf->max_cached); if (cached == NULL) { return NGX_ERROR; } @@ -156,8 +269,11 @@ static ngx_int_t ngx_http_upstream_init_ntlm(ngx_conf_t *cf, ngx_queue_init(&hncf->free); for (i = 0; i < hncf->max_cached; i++) { - ngx_queue_insert_head(&hncf->free, &cached[i].queue); cached[i].conf = hncf; + cached[i].in_cache = 0; + cached[i].peer_connection = NULL; + cached[i].client_connection = NULL; + ngx_queue_insert_head(&hncf->free, &cached[i].queue); } return NGX_OK; @@ -165,16 +281,14 @@ static ngx_int_t ngx_http_upstream_init_ntlm(ngx_conf_t *cf, static ngx_int_t ngx_http_upstream_init_ntlm_peer(ngx_http_request_t *r, - ngx_http_upstream_srv_conf_t *us) { + ngx_http_upstream_srv_conf_t *us) +{ ngx_http_upstream_ntlm_peer_data_t *hnpd; ngx_http_upstream_ntlm_srv_conf_t *hncf; - ngx_str_t auth_header_value; - // get the upstream configuration hncf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_ntlm_module); - // alocate memory for peer data - hnpd = ngx_palloc(r->pool, sizeof(ngx_http_upstream_ntlm_peer_data_t)); + hnpd = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_ntlm_peer_data_t)); if (hnpd == NULL) { return NGX_ERROR; } @@ -183,28 +297,19 @@ ngx_http_upstream_init_ntlm_peer(ngx_http_request_t *r, return NGX_ERROR; } - hnpd->ntlm_init = 0; - hnpd->cached = 0; - - if (r->headers_in.authorization != NULL) { - auth_header_value = r->headers_in.authorization->value; - - if ((auth_header_value.len >= sizeof("NTLM") - 1 && - ngx_strncasecmp(auth_header_value.data, (u_char *)"NTLM", - sizeof("NTLM") - 1) == 0) || - (auth_header_value.len >= sizeof("Negotiate") - 1 && - ngx_strncasecmp(auth_header_value.data, (u_char *)"Negotiate", - sizeof("Negotiate") - 1) == 0)) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "ntlm auth header found"); - hnpd->ntlm_init = 1; - } - } - hnpd->conf = hncf; hnpd->upstream = r->upstream; hnpd->data = r->upstream->peer.data; hnpd->client_connection = r->connection; + hnpd->cached = 0; + hnpd->ntlm_init = 0; + + if (r->headers_in.authorization != NULL + && ngx_http_upstream_ntlm_header_is_auth( + &r->headers_in.authorization->value)) + { + hnpd->ntlm_init = 1; + } hnpd->original_get_peer = r->upstream->peer.get; hnpd->original_free_peer = r->upstream->peer.free; @@ -230,133 +335,110 @@ ngx_http_upstream_init_ntlm_peer(ngx_http_request_t *r, return NGX_OK; } -static ngx_int_t ngx_http_upstream_get_ntlm_peer(ngx_peer_connection_t *pc, - void *data) { - ngx_http_upstream_ntlm_peer_data_t *hndp = data; - ngx_http_upstream_ntlm_cache_t *item; - +static ngx_int_t +ngx_http_upstream_get_ntlm_peer(ngx_peer_connection_t *pc, void *data) +{ ngx_int_t rc; - ngx_queue_t *q, *cache; + ngx_queue_t *q; + ngx_queue_t *cache; ngx_connection_t *c; + ngx_http_upstream_ntlm_cache_t *item; + ngx_http_upstream_ntlm_peer_data_t *hndp; - /* ask balancer */ + hndp = data; rc = hndp->original_get_peer(pc, hndp->data); - if (rc != NGX_OK) { return rc; } - /* search cache for suitable connection */ - cache = &hndp->conf->cache; - for (q = ngx_queue_head(cache); q != ngx_queue_sentinel(cache); - q = ngx_queue_next(q)) { + for (q = ngx_queue_head(cache); + q != ngx_queue_sentinel(cache); + q = ngx_queue_next(q)) + { item = ngx_queue_data(q, ngx_http_upstream_ntlm_cache_t, queue); - if (item->client_connection == hndp->client_connection) { - c = item->peer_connection; - - /* - * If the current request carries new NTLM/Negotiate credentials - * and the cached upstream connection has already served at least - * two requests (i.e. the NTLM three-way handshake is complete), - * this is a re-authentication attempt for a potentially different - * identity. Evict and close the old authenticated session rather - * than silently reusing it. - * - * When c->requests == 1 the handshake is still in progress - * (Type-1 sent, Type-3 is the next expected request); in that - * case reuse must be allowed so the exchange can complete on the - * same upstream TCP connection. - */ - if (hndp->ntlm_init && c->requests >= 2) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, - "ntlm: new credentials on established session, evicting upstream connection %p (requests=%ui)", - c, c->requests); - item->in_cache = 0; - item->peer_connection = NULL; - ngx_queue_remove(q); - ngx_queue_insert_head(&hndp->conf->free, q); - ngx_http_upstream_ntlm_close(c); - break; - } - - /* - * Clear ownership fields BEFORE touching the queues so that any - * handler that checks in_cache (close_handler, cleanup) sees the - * item as no longer active and does nothing. - */ - item->in_cache = 0; - item->peer_connection = NULL; - - ngx_queue_remove(q); - ngx_queue_insert_head(&hndp->conf->free, q); - hndp->cached = 1; - goto found; + if (item->client_connection != hndp->client_connection) { + continue; } - } - return NGX_OK; + c = item->peer_connection; + if (c == NULL || !item->in_cache) { + ngx_http_upstream_ntlm_force_release_to_free(item); + continue; + } -found: + if (hndp->ntlm_init && c->requests >= 2) { + ngx_http_upstream_ntlm_item_release_to_free(item); + ngx_http_upstream_ntlm_close(c); + break; + } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, - "ntlm peer using connection %p", c); + ngx_http_upstream_ntlm_item_release_to_free(item); - c->idle = 0; - c->sent = 0; - /* - * Restore c->read->data to the connection pointer. While cached, - * c->read->data held the ntlm cache item (see ngx_http_upstream_free_ntlm_peer). - * nginx upstream core expects ev->data == c (the connection), so we must - * reset this before handing the connection back to upstream. - */ - c->read->data = c; - c->data = NULL; - c->log = pc->log; - c->read->log = pc->log; - c->write->log = pc->log; - c->pool->log = pc->log; + c->idle = 0; + c->sent = 0; + c->read->data = c; + c->data = NULL; + c->log = pc->log; + c->read->log = pc->log; + c->write->log = pc->log; + c->pool->log = pc->log; - if (c->read->timer_set) { - ngx_del_timer(c->read); - } + if (c->read->timer_set) { + ngx_del_timer(c->read); + } - pc->connection = c; - pc->cached = 1; + pc->connection = c; + pc->cached = 1; + hndp->cached = 1; - return NGX_DONE; + return NGX_DONE; + } + + return NGX_OK; } -static void ngx_http_upstream_free_ntlm_peer(ngx_peer_connection_t *pc, - void *data, ngx_uint_t state) { - ngx_http_upstream_ntlm_peer_data_t *hndp = data; - ngx_http_upstream_ntlm_cache_t *item; +static ngx_pool_cleanup_t * +ngx_http_upstream_ntlm_pool_cleanup_add(ngx_pool_t *p, size_t size) +{ +#ifdef NGX_NTLM_TEST_CLEANUP_NULL + (void) p; + (void) size; + return NULL; +#else + return ngx_pool_cleanup_add(p, size); +#endif +} +static void +ngx_http_upstream_free_ntlm_peer(ngx_peer_connection_t *pc, void *data, + ngx_uint_t state) +{ ngx_queue_t *q; ngx_connection_t *c; - ngx_http_upstream_t *u; + ngx_connection_t *old; ngx_pool_cleanup_t *cln; - ngx_http_upstream_ntlm_cache_t *cleanup_item = NULL; + ngx_http_upstream_t *u; + ngx_http_upstream_ntlm_cache_t *item; + ngx_http_upstream_ntlm_cache_t *cleanup_item; + ngx_http_upstream_ntlm_peer_data_t *hndp; - /* cache valid connections */ + hndp = data; + cleanup_item = NULL; u = hndp->upstream; c = pc->connection; - if (state & NGX_PEER_FAILED || c == NULL || c->read->eof || - c->read->error || c->read->timedout || c->write->error || - c->write->timedout) { + if (state & NGX_PEER_FAILED || c == NULL || c->read->eof || c->read->error + || c->read->timedout || c->write->error || c->write->timedout) + { goto invalid; } - if (!u->keepalive) { - goto invalid; - } - - if (!u->request_body_sent) { + if (!u->keepalive || !u->request_body_sent) { goto invalid; } @@ -376,83 +458,53 @@ static void ngx_http_upstream_free_ntlm_peer(ngx_peer_connection_t *pc, goto invalid; } - if (hndp->ntlm_init == 0 && hndp->cached == 0) { + if (!hndp->ntlm_init && !hndp->cached) { goto invalid; } if (ngx_queue_empty(&hndp->conf->free)) { - ngx_connection_t *old_pc; - q = ngx_queue_last(&hndp->conf->cache); - ngx_queue_remove(q); - item = ngx_queue_data(q, ngx_http_upstream_ntlm_cache_t, queue); - /* - * Evict the oldest cached entry. Clear ownership before closing so - * that close_handler (if it fires for the evicted connection) finds - * in_cache == 0 and does nothing. - */ - old_pc = item->peer_connection; - item->in_cache = 0; - item->peer_connection = NULL; - - if (old_pc != NULL) { - ngx_http_upstream_ntlm_close(old_pc); + old = item->peer_connection; + ngx_http_upstream_ntlm_item_release_to_free(item); + + if (old != NULL) { + ngx_http_upstream_ntlm_close(old); } - } else { - q = ngx_queue_head(&hndp->conf->free); - ngx_queue_remove(q); - item = ngx_queue_data(q, ngx_http_upstream_ntlm_cache_t, queue); } + q = ngx_queue_head(&hndp->conf->free); + item = ngx_queue_data(q, ngx_http_upstream_ntlm_cache_t, queue); + ngx_queue_remove(q); ngx_queue_insert_head(&hndp->conf->cache, q); item->peer_connection = c; item->client_connection = hndp->client_connection; item->in_cache = 1; - ngx_log_debug2( - NGX_LOG_DEBUG_HTTP, pc->log, 0, - "ntlm free peer saving item client_connection %p, peer connection %p", - item->client_connection, c); - - /* - * Ensure the client connection has a cleanup handler that points to - * *this* item. If a handler was already registered (e.g. from a - * previous request on the same keep-alive client connection), update - * its data pointer so it always refers to the current item. - */ - for (cln = item->client_connection->pool->cleanup; cln; cln = cln->next) { + for (cln = item->client_connection->pool->cleanup; + cln; + cln = cln->next) + { if (cln->handler == ngx_http_upstream_client_conn_cleanup) { cln->data = item; cleanup_item = item; break; } } + if (cleanup_item == NULL) { - cln = ngx_pool_cleanup_add(item->client_connection->pool, 0); + cln = ngx_http_upstream_ntlm_pool_cleanup_add( + item->client_connection->pool, 0); if (cln == NULL) { ngx_log_error(NGX_LOG_ERR, pc->log, 0, "ntlm: failed to allocate cleanup handler," " not caching upstream connection"); - /* - * Without a cleanup handler we cannot close the upstream - * connection when the client disconnects. The cached item - * would hold a stale client_connection pointer that nginx may - * later recycle for a completely different client, allowing - * get_ntlm_peer to hand an already-authenticated upstream - * session to the wrong client (session hijack via pointer - * reuse). Undo the cache insertion and treat this connection - * as uncacheable. - */ - item->in_cache = 0; - item->peer_connection = NULL; - item->client_connection = NULL; - ngx_queue_remove(q); - ngx_queue_insert_head(&hndp->conf->free, q); + ngx_http_upstream_ntlm_item_release_to_free(item); goto invalid; } + cln->handler = ngx_http_upstream_client_conn_cleanup; cln->data = item; } @@ -468,19 +520,6 @@ static void ngx_http_upstream_free_ntlm_peer(ngx_peer_connection_t *pc, c->write->handler = ngx_http_upstream_ntlm_dummy_handler; c->read->handler = ngx_http_upstream_ntlm_close_handler; - - /* - * Store the cache item in c->read->data (the read event's data field) - * rather than in c->data. nginx's ngx_http_upstream_handler() assumes - * c->data is ngx_http_request_t*, so overwriting it with an ntlm cache - * item pointer causes a segfault if any posted or ready event reaches - * that handler while the connection is idle. Since we have replaced - * c->read->handler with our own ngx_http_upstream_ntlm_close_handler, - * only our handler runs on read events for this connection; it retrieves - * the item via ev->data (= c->read->data) without touching c->data at - * all. c->read->data is restored to c before the connection is returned - * to upstream core (see ngx_http_upstream_get_ntlm_peer). - */ c->read->data = item; c->data = NULL; c->idle = 1; @@ -497,45 +536,20 @@ static void ngx_http_upstream_free_ntlm_peer(ngx_peer_connection_t *pc, hndp->original_free_peer(pc, hndp->data, state); } -static void ngx_http_upstream_client_conn_cleanup(void *data) { - ngx_http_upstream_ntlm_cache_t *item = data; +static void +ngx_http_upstream_client_conn_cleanup(void *data) +{ ngx_connection_t *c; + ngx_http_upstream_ntlm_cache_t *item; - ngx_log_debug2( - NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, - "ntlm client connection closed %p, dropping peer connection %p", - item->client_connection, item->peer_connection); - - /* - * Only act if this item currently owns a cached upstream connection. - * in_cache == 0 means the connection was already consumed (reused by - * get_ntlm_peer) or closed (by close_handler / eviction). In that - * case the cleanup is a no-op; attempting to touch peer_connection or - * the queue would corrupt state. - * - * Note: we must NOT post an event here. A posted event fires - * asynchronously and by the time it runs the peer connection may have - * been reused for a new request, causing close_handler to close a - * live connection or corrupt the queue. Close synchronously instead. - */ - if (!item->in_cache) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, - "ntlm client connection %p/%p already gone", - item->client_connection, item->peer_connection); + item = data; + if (item == NULL || !item->in_cache) { return; } - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, - "ntlm client connection %p/%p doing cleanup", - item->client_connection, item->peer_connection); c = item->peer_connection; - /* Clear ownership before any queue or connection operations. */ - item->in_cache = 0; - item->peer_connection = NULL; - - ngx_queue_remove(&item->queue); - ngx_queue_insert_head(&item->conf->free, &item->queue); + ngx_http_upstream_ntlm_item_release_to_free(item); if (c == NULL) { return; @@ -548,53 +562,41 @@ static void ngx_http_upstream_client_conn_cleanup(void *data) { ngx_http_upstream_ntlm_close(c); } -static void ngx_http_upstream_ntlm_dummy_handler(ngx_event_t *ev) { +static void +ngx_http_upstream_ntlm_dummy_handler(ngx_event_t *ev) +{ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, "ntlm dummy handler"); } -static void ngx_http_upstream_ntlm_notify_peer(ngx_peer_connection_t *pc, - void *data, ngx_uint_t type) { - ngx_http_upstream_ntlm_peer_data_t *hndp = data; +static void +ngx_http_upstream_ntlm_notify_peer(ngx_peer_connection_t *pc, void *data, + ngx_uint_t type) +{ + ngx_http_upstream_ntlm_peer_data_t *hndp; + + hndp = data; if (hndp->original_notify) { hndp->original_notify(pc, hndp->data, type); } } -static void ngx_http_upstream_ntlm_close_handler(ngx_event_t *ev) { - ngx_http_upstream_ntlm_srv_conf_t *conf; - ngx_http_upstream_ntlm_cache_t *item; - +static void +ngx_http_upstream_ntlm_close_handler(ngx_event_t *ev) +{ int n; char buf[1]; ngx_connection_t *c; + ngx_http_upstream_ntlm_cache_t *item; - /* - * While a connection is idle in the NTLM cache its read event's data - * field (c->read->data, i.e. ev->data here) holds a pointer to the - * owning ngx_http_upstream_ntlm_cache_t item. We retrieve the item - * directly from ev->data rather than from c->data, because c->data must - * be left alone: nginx's ngx_http_upstream_handler() assumes c->data is - * ngx_http_request_t* and will segfault if it finds our cache item there - * via any posted or ready event. - */ item = ev->data; - - /* - * item should never be NULL here because we always set c->read->data = - * item before installing this handler, and c->read->data is restored to - * c only after the handler is replaced. The NULL guard is a belt-and- - * suspenders safety check. - */ if (item == NULL || !item->in_cache) { - /* Already reclaimed by cleanup or eviction; nothing to do. */ return; } c = item->peer_connection; - if (c == NULL) { - /* Ownership was cleared; nothing to do. */ + ngx_http_upstream_ntlm_item_release_to_free(item); return; } @@ -616,40 +618,18 @@ static void ngx_http_upstream_ntlm_close_handler(ngx_event_t *ev) { close: - /* - * Belt-and-suspenders guard for the close: path. Although the early - * checks above already verified in_cache and peer_connection before any - * blocking/yield point, this second check ensures correctness if, due to - * a hypothetical future code change, the close: label is reached by a - * path that did not pass the early guards. - */ if (!item->in_cache || item->peer_connection != c) { - /* Connection is no longer ours; nothing to do. */ return; } - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ev->log, 0, - "ntlm close peer connection %p, timeout %u, read %i", c, - c->read->timedout, n); - - conf = item->conf; - - /* Clear ownership before closing so the cleanup handler (if it fires - * for the associated client connection) finds in_cache == 0 and exits - * early, preventing any double-close or double-queue-remove. */ - item->in_cache = 0; - item->peer_connection = NULL; - + ngx_http_upstream_ntlm_item_release_to_free(item); ngx_http_upstream_ntlm_close(c); - - ngx_queue_remove(&item->queue); - ngx_queue_insert_head(&conf->free, &item->queue); } -static void ngx_http_upstream_ntlm_close(ngx_connection_t *c) { - +static void +ngx_http_upstream_ntlm_close(ngx_connection_t *c) +{ #if (NGX_HTTP_SSL) - if (c->ssl) { c->ssl->no_wait_shutdown = 1; c->ssl->no_send_shutdown = 1; @@ -659,37 +639,44 @@ static void ngx_http_upstream_ntlm_close(ngx_connection_t *c) { return; } } - #endif if (c->pool) { ngx_destroy_pool(c->pool); c->pool = NULL; } + ngx_close_connection(c); } #if (NGX_HTTP_SSL) -static ngx_int_t ngx_http_upstream_ntlm_set_session(ngx_peer_connection_t *pc, - void *data) { - ngx_http_upstream_ntlm_peer_data_t *hndp = data; +static ngx_int_t +ngx_http_upstream_ntlm_set_session(ngx_peer_connection_t *pc, void *data) +{ + ngx_http_upstream_ntlm_peer_data_t *hndp; + + hndp = data; return hndp->original_set_session(pc, hndp->data); } -static void ngx_http_upstream_ntlm_save_session(ngx_peer_connection_t *pc, - void *data) { - ngx_http_upstream_ntlm_peer_data_t *hndp = data; +static void +ngx_http_upstream_ntlm_save_session(ngx_peer_connection_t *pc, void *data) +{ + ngx_http_upstream_ntlm_peer_data_t *hndp; + hndp = data; hndp->original_save_session(pc, hndp->data); - return; } #endif -static void *ngx_http_upstream_ntlm_create_conf(ngx_conf_t *cf) { +static void * +ngx_http_upstream_ntlm_create_conf(ngx_conf_t *cf) +{ ngx_http_upstream_ntlm_srv_conf_t *conf; + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_ntlm_srv_conf_t)); if (conf == NULL) { return NULL; @@ -703,32 +690,35 @@ static void *ngx_http_upstream_ntlm_create_conf(ngx_conf_t *cf) { return conf; } -static char *ngx_http_upstream_ntlm(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf) { - ngx_http_upstream_srv_conf_t *uscf; - ngx_http_upstream_ntlm_srv_conf_t *hncf = conf; - +static char * +ngx_http_upstream_ntlm(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ ngx_int_t n; ngx_str_t *value; + ngx_http_upstream_srv_conf_t *uscf; + ngx_http_upstream_ntlm_srv_conf_t *hncf; + + hncf = conf; - /* read options */ if (cf->args->nelts == 2) { value = cf->args->elts; n = ngx_atoi(value[1].data, value[1].len); + if (n == NGX_ERROR || n == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "ntlm invalid value \"%V\" in \"%V\" directive", &value[1], &cmd->name); return NGX_CONF_ERROR; } - hncf->max_cached = n; + + hncf->max_cached = (ngx_uint_t) n; } uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module); hncf->original_init_upstream = uscf->peer.init_upstream - ? uscf->peer.init_upstream - : ngx_http_upstream_init_round_robin; + ? uscf->peer.init_upstream + : ngx_http_upstream_init_round_robin; uscf->peer.init_upstream = ngx_http_upstream_init_ntlm; diff --git a/t/001-sanity.t b/t/001-sanity.t index 991f33e..176f214 100644 --- a/t/001-sanity.t +++ b/t/001-sanity.t @@ -111,7 +111,33 @@ also [error] -=== TEST 4: The backend connection should die when client connection dies +=== TEST 4: Session must not be reused across different client connections +Authentication context is connection-bound and must not leak between clients. +--- http_config + upstream backend { + server localhost:19841; + server localhost:19842; + ntlm; + } +--- config + location /t { + proxy_pass http://backend; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } +--- request eval +["GET /t", "GET /t"] +--- more_headers eval +["Authorization: NTLM " . $::random_token . "\r\nConnection: close", "Connection: close"] +--- response_body eval +["OK", "OK"] +--- response_headers eval +["X-NGX-NTLM-AUTH: " . $::random_token, ""] +--- no_error_log +[error] + + +=== TEST 5: The backend connection should die when client connection dies When the authorization header contains Negotiate the token is saved on the server connection if the client drops the connection the backend connection must die also @@ -141,7 +167,7 @@ also [error] -=== TEST 5: New NTLM credentials on an established connection must evict the old session +=== TEST 6: New NTLM credentials on an established connection must evict the old session After the initial NTLM handshake completes (upstream connection has served at least 2 requests), presenting new credentials on the same keep-alive client connection must not reuse the old authenticated upstream session. The module @@ -169,3 +195,48 @@ credentials are negotiated independently. ["X-NGX-NTLM-AUTH: " . $::random_token, "X-NGX-NTLM-AUTH: " . $::random_token, "X-NGX-NTLM-AUTH: " . $::random_token2] --- no_error_log [error] + + +# TEST 7 exercises the OOM path in ngx_http_upstream_free_ntlm_peer where +# ngx_pool_cleanup_add returns NULL. Because this path can only be triggered +# by a build compiled with the fault-injection flag, the test is unconditionally +# skipped in normal CI. +# +# To run this test locally, build nginx with the module compiled with the extra +# CFLAGS flag: +# +# ./configure ... --add-module=/path/to/nginx-ntlm-module \ +# --with-cc-opt="-DNGX_NTLM_TEST_CLEANUP_NULL" +# make +# +# Then remove the `--- SKIP` line for this block and run: +# +# TEST_NGINX_BINARY=/path/to/objs/nginx prove -v t/001-sanity.t + +=== TEST 7: OOM in cleanup handler must not cache the upstream connection +When ngx_pool_cleanup_add returns NULL, the upstream session MUST NOT be +inserted into the cache. A subsequent request on the same client connection +must therefore NOT receive the old authenticated upstream session. +This test requires a build with -DNGX_NTLM_TEST_CLEANUP_NULL. +--- SKIP +--- http_config + upstream backend { + server 127.0.0.1:19841; + ntlm; + } +--- config + location /t { + proxy_pass http://backend; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } +--- pipelined_requests eval +["GET /t", "GET /t"] +--- more_headers eval +["Authorization: NTLM " . $::random_token, "Authorization: NTLM " . $::random_token] +--- response_body eval +["OK", "OK"] +--- error_log +ntlm: failed to allocate cleanup handler +--- response_headers eval +["X-NGX-NTLM-AUTH: " . $::random_token, "X-NGX-NTLM-AUTH: " . $::random_token] diff --git a/t/backend/package-lock.json b/t/backend/package-lock.json index 43e7627..5a74040 100644 --- a/t/backend/package-lock.json +++ b/t/backend/package-lock.json @@ -1,28 +1,39 @@ { "name": "nginx-upstream-backend-test", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "accepts": { + "packages": { + "": { + "name": "nginx-upstream-backend-test", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "express": "^4.17.1" + } + }, + "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "requires": { + "dependencies": { "mime-types": "~2.1.24", "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" } }, - "array-flatten": { + "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "body-parser": { + "node_modules/body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "requires": { + "dependencies": { "bytes": "3.1.0", "content-type": "~1.0.4", "debug": "2.6.9", @@ -33,79 +44,103 @@ "qs": "6.7.0", "raw-body": "2.4.0", "type-is": "~1.6.17" + }, + "engines": { + "node": ">= 0.8" } }, - "bytes": { + "node_modules/bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "engines": { + "node": ">= 0.8" + } }, - "content-disposition": { + "node_modules/content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "requires": { + "dependencies": { "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.6" } }, - "content-type": { + "node_modules/content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } }, - "cookie": { + "node_modules/cookie": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "engines": { + "node": ">= 0.6" + } }, - "cookie-signature": { + "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, - "debug": { + "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { + "dependencies": { "ms": "2.0.0" } }, - "depd": { + "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } }, - "destroy": { + "node_modules/destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, - "ee-first": { + "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, - "encodeurl": { + "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } }, - "escape-html": { + "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, - "etag": { + "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } }, - "express": { + "node_modules/express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "requires": { + "dependencies": { "accepts": "~1.3.7", "array-flatten": "1.1.1", "body-parser": "1.19.0", @@ -136,13 +171,16 @@ "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" } }, - "finalhandler": { + "node_modules/finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "requires": { + "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -150,154 +188,211 @@ "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "forwarded": { + "node_modules/forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "engines": { + "node": ">= 0.6" + } }, - "fresh": { + "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } }, - "http-errors": { + "node_modules/http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "requires": { + "dependencies": { "depd": "~1.1.2", "inherits": "2.0.3", "setprototypeof": "1.1.1", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" } }, - "iconv-lite": { + "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { + "dependencies": { "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" } }, - "inherits": { + "node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, - "ipaddr.js": { + "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } }, - "media-typer": { + "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } }, - "merge-descriptors": { + "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, - "methods": { + "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } }, - "mime": { + "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } }, - "mime-db": { + "node_modules/mime-db": { "version": "1.47.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==" + "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", + "engines": { + "node": ">= 0.6" + } }, - "mime-types": { + "node_modules/mime-types": { "version": "2.1.30", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", - "requires": { + "dependencies": { "mime-db": "1.47.0" + }, + "engines": { + "node": ">= 0.6" } }, - "ms": { + "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "negotiator": { + "node_modules/negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "engines": { + "node": ">= 0.6" + } }, - "on-finished": { + "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { + "dependencies": { "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" } }, - "parseurl": { + "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } }, - "path-to-regexp": { + "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, - "proxy-addr": { + "node_modules/proxy-addr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", - "requires": { + "dependencies": { "forwarded": "~0.1.2", "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" } }, - "qs": { + "node_modules/qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "engines": { + "node": ">=0.6" + } }, - "range-parser": { + "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } }, - "raw-body": { + "node_modules/raw-body": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "requires": { + "dependencies": { "bytes": "3.1.0", "http-errors": "1.7.2", "iconv-lite": "0.4.24", "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "safe-buffer": { + "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "safer-buffer": { + "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "send": { + "node_modules/send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "requires": { + "dependencies": { "debug": "2.6.9", "depd": "~1.1.2", "destroy": "~1.0.4", @@ -312,63 +407,85 @@ "range-parser": "~1.2.1", "statuses": "~1.5.0" }, - "dependencies": { - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } + "engines": { + "node": ">= 0.8.0" } }, - "serve-static": { + "node_modules/send/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "node_modules/serve-static": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "requires": { + "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.17.1" + }, + "engines": { + "node": ">= 0.8.0" } }, - "setprototypeof": { + "node_modules/setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, - "statuses": { + "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "engines": { + "node": ">= 0.6" + } }, - "toidentifier": { + "node_modules/toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "engines": { + "node": ">=0.6" + } }, - "type-is": { + "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { + "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" } }, - "unpipe": { + "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } }, - "utils-merge": { + "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } }, - "vary": { + "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } } } }