Newer
Older
* Copyright (C) Maxim Dounin
* Copyright (C) Nginx, Inc.
* Copyright (C) 2014, Luca Bruno
Distributed under 2-clause BSD license, see LICENSE file.
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef struct {
ngx_str_t uri;
ngx_array_t *vars;
} ngx_http_auth_request_conf_t;
typedef struct {
ngx_uint_t done;
ngx_uint_t status;
ngx_http_request_t *subrequest;
} ngx_http_auth_request_ctx_t;
typedef struct {
ngx_int_t index;
ngx_http_complex_value_t value;
ngx_http_set_variable_pt set_handler;
} ngx_http_auth_request_variable_t;
static ngx_int_t ngx_http_auth_request_handler(ngx_http_request_t *r);
static ngx_int_t ngx_http_auth_request_done(ngx_http_request_t *r,
void *data, ngx_int_t rc);
static ngx_int_t ngx_http_auth_request_set_variables(ngx_http_request_t *r,
ngx_http_auth_request_conf_t *arcf, ngx_http_auth_request_ctx_t *ctx);
static ngx_int_t ngx_http_auth_request_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data);
static void *ngx_http_auth_request_create_conf(ngx_conf_t *cf);
static char *ngx_http_auth_request_merge_conf(ngx_conf_t *cf,
void *parent, void *child);
static ngx_int_t ngx_http_auth_request_init(ngx_conf_t *cf);
static char *ngx_http_auth_request(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_http_auth_request_set(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static ngx_command_t ngx_http_auth_request_commands[] = {

David\ Beitey
committed
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_http_auth_request,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
ngx_http_auth_request_set,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_http_shibboleth_module_ctx = {
NULL, /* preconfiguration */
ngx_http_auth_request_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_auth_request_create_conf, /* create location configuration */
ngx_http_auth_request_merge_conf /* merge location configuration */
};
ngx_module_t ngx_http_shibboleth_module = {
&ngx_http_shibboleth_module_ctx, /* module context */
ngx_http_auth_request_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
};
static ngx_int_t
ngx_http_auth_request_handler(ngx_http_request_t *r)
{
ngx_uint_t i;
ngx_list_part_t *part;
ngx_http_request_t *sr;
ngx_http_post_subrequest_t *ps;
ngx_http_auth_request_ctx_t *ctx;
ngx_http_auth_request_conf_t *arcf;
arcf = ngx_http_get_module_loc_conf(r, ngx_http_shibboleth_module);
if (arcf->uri.len == 0) {
return NGX_DECLINED;
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
ctx = ngx_http_get_module_ctx(r, ngx_http_shibboleth_module);
if (ctx != NULL) {
if (!ctx->done) {
return NGX_AGAIN;
}
/*
* as soon as we are done - explicitly set variables to make
* sure they will be available after internal redirects
*/
if (ngx_http_auth_request_set_variables(r, arcf, ctx) != NGX_OK) {
return NGX_ERROR;
}

David\ Beitey
committed
* Handle the subrequest
* as per the FastCGI authorizer specification.
*/

David\ Beitey
committed
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"shib request authorizer handler");
sr = ctx->subrequest;

David\ Beitey
committed
if (ctx->status == NGX_HTTP_OK) {

David\ Beitey
committed
* 200 response may include headers prefixed with `Variable-`
* back into initial headers
*/
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,

David\ Beitey
committed
"shib request authorizer allows access");

David\ Beitey
committed
part = &sr->headers_out.headers.part;
h = part->elts;

David\ Beitey
committed
for (i = 0; /* void */; i++) {

David\ Beitey
committed
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}

David\ Beitey
committed
part = part->next;
h = part->elts;
i = 0;
}

David\ Beitey
committed
if (h[i].hash == 0) {
continue;

David\ Beitey
committed
if (ngx_strncasecmp(h[i].key.data,
(u_char *) "Variable-", 9) == 0) {
/* copy header into original request */
hi = ngx_list_push(&r->headers_in.headers);

David\ Beitey
committed
if (hi == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

David\ Beitey
committed
/* Strip the Variable- prefix */
hi->key.len = h[i].key.len - 9;
hi->key.data = h[i].key.data + 9;
hi->hash = ngx_hash_key(hi->key.data, hi->key.len);

David\ Beitey
committed
hi->value = h[i].value;

David\ Beitey
committed
hi->lowcase_key = ngx_pnalloc(r->pool, hi->key.len);
if (hi->lowcase_key == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_strlow(hi->lowcase_key, hi->key.data, hi->key.len);
ngx_log_debug2(
NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"shib request authorizer copied header: \"%V: %V\"",
&hi->key, &hi->value);
}
}

David\ Beitey
committed
/*
* Unconditionally return subrequest response status, headers
* and content.
*/
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"shib request authorizer returning sub-response");

David\ Beitey
committed
r->headers_out = sr->headers_out;
return ctx->status;
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
}
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_auth_request_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
if (ps == NULL) {
return NGX_ERROR;
}
ps->handler = ngx_http_auth_request_done;
ps->data = ctx;
if (ngx_http_subrequest(r, &arcf->uri, NULL, &sr, ps,
NGX_HTTP_SUBREQUEST_WAITED)
!= NGX_OK)
{
return NGX_ERROR;
}
/*
* allocate fake request body to avoid attempts to read it and to make
* sure real body file (if already read) won't be closed by upstream
*/
sr->request_body = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
if (sr->request_body == NULL) {
return NGX_ERROR;
}

David\ Beitey
committed
* true FastCGI authorizers should always return the subrequest
* response body but the Nginx FastCGI handler does not support
* NGX_HTTP_SUBREQUEST_IN_MEMORY at present.
*/
sr->header_only = 1;
ctx->subrequest = sr;
ngx_http_set_ctx(r, ctx, ngx_http_shibboleth_module);
return NGX_AGAIN;
}
static ngx_int_t
ngx_http_auth_request_done(ngx_http_request_t *r, void *data, ngx_int_t rc)
{
ngx_http_auth_request_ctx_t *ctx = data;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"shib request done s:%d", r->headers_out.status);
ctx->done = 1;
ctx->status = r->headers_out.status;
return rc;
}
static ngx_int_t
ngx_http_auth_request_set_variables(ngx_http_request_t *r,
ngx_http_auth_request_conf_t *arcf, ngx_http_auth_request_ctx_t *ctx)
{
ngx_str_t val;
ngx_http_variable_t *v;
ngx_http_variable_value_t *vv;
ngx_http_auth_request_variable_t *av, *last;
ngx_http_core_main_conf_t *cmcf;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
if (arcf->vars == NULL) {
return NGX_OK;
}
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
v = cmcf->variables.elts;
av = arcf->vars->elts;
last = av + arcf->vars->nelts;
while (av < last) {
/*
* explicitly set new value to make sure it will be available after
* internal redirects
*/
vv = &r->variables[av->index];
if (ngx_http_complex_value(ctx->subrequest, &av->value, &val)
!= NGX_OK)
{
return NGX_ERROR;
}
vv->valid = 1;
vv->not_found = 0;
vv->data = val.data;
vv->len = val.len;
if (av->set_handler) {
/*
* set_handler only available in cmcf->variables_keys, so we store
* it explicitly
*/
av->set_handler(r, vv, v[av->index].data);
}
av++;
}
return NGX_OK;
}
static ngx_int_t
ngx_http_auth_request_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
v->not_found = 1;
return NGX_OK;
}
static void *
ngx_http_auth_request_create_conf(ngx_conf_t *cf)
{
ngx_http_auth_request_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_request_conf_t));
if (conf == NULL) {
return NULL;
}
/*
* set by ngx_pcalloc():
*
* conf->uri = { 0, NULL };
*/
conf->vars = NGX_CONF_UNSET_PTR;
return conf;
}
static char *
ngx_http_auth_request_merge_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_auth_request_conf_t *prev = parent;
ngx_http_auth_request_conf_t *conf = child;
ngx_conf_merge_str_value(conf->uri, prev->uri, "");
ngx_conf_merge_ptr_value(conf->vars, prev->vars, NULL);
return NGX_CONF_OK;
}
static ngx_int_t
ngx_http_auth_request_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_auth_request_handler;
return NGX_OK;
}
static char *
ngx_http_auth_request(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_auth_request_conf_t *arcf = conf;

David\ Beitey
committed
ngx_str_t *value;
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
if (arcf->uri.data != NULL) {
return "is duplicate";
}
value = cf->args->elts;
if (ngx_strcmp(value[1].data, "off") == 0) {
arcf->uri.len = 0;
arcf->uri.data = (u_char *) "";
return NGX_CONF_OK;
}
arcf->uri = value[1];
return NGX_CONF_OK;
}
static char *
ngx_http_auth_request_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_auth_request_conf_t *arcf = conf;
ngx_str_t *value;
ngx_http_variable_t *v;
ngx_http_auth_request_variable_t *av;
ngx_http_compile_complex_value_t ccv;
value = cf->args->elts;
if (value[1].data[0] != '$') {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid variable name \"%V\"", &value[1]);
return NGX_CONF_ERROR;
}
value[1].len--;
value[1].data++;
if (arcf->vars == NGX_CONF_UNSET_PTR) {
arcf->vars = ngx_array_create(cf->pool, 1,
sizeof(ngx_http_auth_request_variable_t));
if (arcf->vars == NULL) {
return NGX_CONF_ERROR;
}
}
av = ngx_array_push(arcf->vars);
if (av == NULL) {
return NGX_CONF_ERROR;
}
v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE);
if (v == NULL) {
return NGX_CONF_ERROR;
}
av->index = ngx_http_get_variable_index(cf, &value[1]);
if (av->index == NGX_ERROR) {
return NGX_CONF_ERROR;
}
if (v->get_handler == NULL) {
v->get_handler = ngx_http_auth_request_variable;
v->data = (uintptr_t) av;
}
av->set_handler = v->set_handler;
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &value[2];
ccv.complex_value = &av->value;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}