1 
2 /*
3  * Copyright (C) Maxim Dounin
4  * Copyright (C) Nginx, Inc.
5  */
6 
7 
8 #include <ngx_config.h>
9 #include <ngx_core.h>
10 #include <ngx_http.h>
11 
12 
13 typedef struct {
14     ngx_str_t                 uri;
15     ngx_array_t              *vars;
16 } ngx_http_auth_request_conf_t;
17 
18 
19 typedef struct {
20     ngx_uint_t                done;
21     ngx_uint_t                status;
22     ngx_http_request_t       *subrequest;
23 } ngx_http_auth_request_ctx_t;
24 
25 
26 typedef struct {
27     ngx_int_t                 index;
28     ngx_http_complex_value_t  value;
29     ngx_http_set_variable_pt  set_handler;
30 } ngx_http_auth_request_variable_t;
31 
32 
33 static ngx_int_t ngx_http_auth_request_handler(ngx_http_request_t *r);
34 static ngx_int_t ngx_http_auth_request_done(ngx_http_request_t *r,
35     void *data, ngx_int_t rc);
36 static ngx_int_t ngx_http_auth_request_set_variables(ngx_http_request_t *r,
37     ngx_http_auth_request_conf_t *arcf, ngx_http_auth_request_ctx_t *ctx);
38 static ngx_int_t ngx_http_auth_request_variable(ngx_http_request_t *r,
39     ngx_http_variable_value_t *v, uintptr_t data);
40 static void *ngx_http_auth_request_create_conf(ngx_conf_t *cf);
41 static char *ngx_http_auth_request_merge_conf(ngx_conf_t *cf,
42     void *parent, void *child);
43 static ngx_int_t ngx_http_auth_request_init(ngx_conf_t *cf);
44 static char *ngx_http_auth_request(ngx_conf_t *cf, ngx_command_t *cmd,
45     void *conf);
46 static char *ngx_http_auth_request_set(ngx_conf_t *cf, ngx_command_t *cmd,
47     void *conf);
48 
49 
50 static ngx_command_t  ngx_http_auth_request_commands[] = {
51 
52     { ngx_string("auth_request"),
53       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
54       ngx_http_auth_request,
55       NGX_HTTP_LOC_CONF_OFFSET,
56       0,
57       NULL },
58 
59     { ngx_string("auth_request_set"),
60       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
61       ngx_http_auth_request_set,
62       NGX_HTTP_LOC_CONF_OFFSET,
63       0,
64       NULL },
65 
66       ngx_null_command
67 };
68 
69 
70 static ngx_http_module_t  ngx_http_auth_request_module_ctx = {
71     NULL,                                  /* preconfiguration */
72     ngx_http_auth_request_init,            /* postconfiguration */
73 
74     NULL,                                  /* create main configuration */
75     NULL,                                  /* init main configuration */
76 
77     NULL,                                  /* create server configuration */
78     NULL,                                  /* merge server configuration */
79 
80     ngx_http_auth_request_create_conf,     /* create location configuration */
81     ngx_http_auth_request_merge_conf       /* merge location configuration */
82 };
83 
84 
85 ngx_module_t  ngx_http_auth_request_module = {
86     NGX_MODULE_V1,
87     &ngx_http_auth_request_module_ctx,     /* module context */
88     ngx_http_auth_request_commands,        /* module directives */
89     NGX_HTTP_MODULE,                       /* module type */
90     NULL,                                  /* init master */
91     NULL,                                  /* init module */
92     NULL,                                  /* init process */
93     NULL,                                  /* init thread */
94     NULL,                                  /* exit thread */
95     NULL,                                  /* exit process */
96     NULL,                                  /* exit master */
97     NGX_MODULE_V1_PADDING
98 };
99 
100 
101 static ngx_int_t
ngx_http_auth_request_handler(ngx_http_request_t * r)102 ngx_http_auth_request_handler(ngx_http_request_t *r)
103 {
104     ngx_table_elt_t               *h, *ho;
105     ngx_http_request_t            *sr;
106     ngx_http_post_subrequest_t    *ps;
107     ngx_http_auth_request_ctx_t   *ctx;
108     ngx_http_auth_request_conf_t  *arcf;
109 
110     arcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_request_module);
111 
112     if (arcf->uri.len == 0) {
113         return NGX_DECLINED;
114     }
115 
116     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
117                    "auth request handler");
118 
119     ctx = ngx_http_get_module_ctx(r, ngx_http_auth_request_module);
120 
121     if (ctx != NULL) {
122         if (!ctx->done) {
123             return NGX_AGAIN;
124         }
125 
126         /*
127          * as soon as we are done - explicitly set variables to make
128          * sure they will be available after internal redirects
129          */
130 
131         if (ngx_http_auth_request_set_variables(r, arcf, ctx) != NGX_OK) {
132             return NGX_ERROR;
133         }
134 
135         /* return appropriate status */
136 
137         if (ctx->status == NGX_HTTP_FORBIDDEN) {
138             return ctx->status;
139         }
140 
141         if (ctx->status == NGX_HTTP_UNAUTHORIZED) {
142             sr = ctx->subrequest;
143 
144             h = sr->headers_out.www_authenticate;
145 
146             if (!h && sr->upstream) {
147                 h = sr->upstream->headers_in.www_authenticate;
148             }
149 
150             if (h) {
151                 ho = ngx_list_push(&r->headers_out.headers);
152                 if (ho == NULL) {
153                     return NGX_ERROR;
154                 }
155 
156                 *ho = *h;
157 
158                 r->headers_out.www_authenticate = ho;
159             }
160 
161             return ctx->status;
162         }
163 
164         if (ctx->status >= NGX_HTTP_OK
165             && ctx->status < NGX_HTTP_SPECIAL_RESPONSE)
166         {
167             return NGX_OK;
168         }
169 
170         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
171                       "auth request unexpected status: %ui", ctx->status);
172 
173         return NGX_HTTP_INTERNAL_SERVER_ERROR;
174     }
175 
176     ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_auth_request_ctx_t));
177     if (ctx == NULL) {
178         return NGX_ERROR;
179     }
180 
181     ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
182     if (ps == NULL) {
183         return NGX_ERROR;
184     }
185 
186     ps->handler = ngx_http_auth_request_done;
187     ps->data = ctx;
188 
189     if (ngx_http_subrequest(r, &arcf->uri, NULL, &sr, ps,
190                             NGX_HTTP_SUBREQUEST_WAITED)
191         != NGX_OK)
192     {
193         return NGX_ERROR;
194     }
195 
196     /*
197      * allocate fake request body to avoid attempts to read it and to make
198      * sure real body file (if already read) won't be closed by upstream
199      */
200 
201     sr->request_body = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
202     if (sr->request_body == NULL) {
203         return NGX_ERROR;
204     }
205 
206     sr->header_only = 1;
207 
208     ctx->subrequest = sr;
209 
210     ngx_http_set_ctx(r, ctx, ngx_http_auth_request_module);
211 
212     return NGX_AGAIN;
213 }
214 
215 
216 static ngx_int_t
ngx_http_auth_request_done(ngx_http_request_t * r,void * data,ngx_int_t rc)217 ngx_http_auth_request_done(ngx_http_request_t *r, void *data, ngx_int_t rc)
218 {
219     ngx_http_auth_request_ctx_t   *ctx = data;
220 
221     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
222                    "auth request done s:%ui", r->headers_out.status);
223 
224     ctx->done = 1;
225     ctx->status = r->headers_out.status;
226 
227     return rc;
228 }
229 
230 
231 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)232 ngx_http_auth_request_set_variables(ngx_http_request_t *r,
233     ngx_http_auth_request_conf_t *arcf, ngx_http_auth_request_ctx_t *ctx)
234 {
235     ngx_str_t                          val;
236     ngx_http_variable_t               *v;
237     ngx_http_variable_value_t         *vv;
238     ngx_http_auth_request_variable_t  *av, *last;
239     ngx_http_core_main_conf_t         *cmcf;
240 
241     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
242                    "auth request set variables");
243 
244     if (arcf->vars == NULL) {
245         return NGX_OK;
246     }
247 
248     cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
249     v = cmcf->variables.elts;
250 
251     av = arcf->vars->elts;
252     last = av + arcf->vars->nelts;
253 
254     while (av < last) {
255         /*
256          * explicitly set new value to make sure it will be available after
257          * internal redirects
258          */
259 
260         vv = &r->variables[av->index];
261 
262         if (ngx_http_complex_value(ctx->subrequest, &av->value, &val)
263             != NGX_OK)
264         {
265             return NGX_ERROR;
266         }
267 
268         vv->valid = 1;
269         vv->not_found = 0;
270         vv->data = val.data;
271         vv->len = val.len;
272 
273         if (av->set_handler) {
274             /*
275              * set_handler only available in cmcf->variables_keys, so we store
276              * it explicitly
277              */
278 
279             av->set_handler(r, vv, v[av->index].data);
280         }
281 
282         av++;
283     }
284 
285     return NGX_OK;
286 }
287 
288 
289 static ngx_int_t
ngx_http_auth_request_variable(ngx_http_request_t * r,ngx_http_variable_value_t * v,uintptr_t data)290 ngx_http_auth_request_variable(ngx_http_request_t *r,
291     ngx_http_variable_value_t *v, uintptr_t data)
292 {
293     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
294                    "auth request variable");
295 
296     v->not_found = 1;
297 
298     return NGX_OK;
299 }
300 
301 
302 static void *
ngx_http_auth_request_create_conf(ngx_conf_t * cf)303 ngx_http_auth_request_create_conf(ngx_conf_t *cf)
304 {
305     ngx_http_auth_request_conf_t  *conf;
306 
307     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_request_conf_t));
308     if (conf == NULL) {
309         return NULL;
310     }
311 
312     /*
313      * set by ngx_pcalloc():
314      *
315      *     conf->uri = { 0, NULL };
316      */
317 
318     conf->vars = NGX_CONF_UNSET_PTR;
319 
320     return conf;
321 }
322 
323 
324 static char *
ngx_http_auth_request_merge_conf(ngx_conf_t * cf,void * parent,void * child)325 ngx_http_auth_request_merge_conf(ngx_conf_t *cf, void *parent, void *child)
326 {
327     ngx_http_auth_request_conf_t *prev = parent;
328     ngx_http_auth_request_conf_t *conf = child;
329 
330     ngx_conf_merge_str_value(conf->uri, prev->uri, "");
331     ngx_conf_merge_ptr_value(conf->vars, prev->vars, NULL);
332 
333     return NGX_CONF_OK;
334 }
335 
336 
337 static ngx_int_t
ngx_http_auth_request_init(ngx_conf_t * cf)338 ngx_http_auth_request_init(ngx_conf_t *cf)
339 {
340     ngx_http_handler_pt        *h;
341     ngx_http_core_main_conf_t  *cmcf;
342 
343     cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
344 
345     h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
346     if (h == NULL) {
347         return NGX_ERROR;
348     }
349 
350     *h = ngx_http_auth_request_handler;
351 
352     return NGX_OK;
353 }
354 
355 
356 static char *
ngx_http_auth_request(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)357 ngx_http_auth_request(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
358 {
359     ngx_http_auth_request_conf_t *arcf = conf;
360 
361     ngx_str_t        *value;
362 
363     if (arcf->uri.data != NULL) {
364         return "is duplicate";
365     }
366 
367     value = cf->args->elts;
368 
369     if (ngx_strcmp(value[1].data, "off") == 0) {
370         arcf->uri.len = 0;
371         arcf->uri.data = (u_char *) "";
372 
373         return NGX_CONF_OK;
374     }
375 
376     arcf->uri = value[1];
377 
378     return NGX_CONF_OK;
379 }
380 
381 
382 static char *
ngx_http_auth_request_set(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)383 ngx_http_auth_request_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
384 {
385     ngx_http_auth_request_conf_t *arcf = conf;
386 
387     ngx_str_t                         *value;
388     ngx_http_variable_t               *v;
389     ngx_http_auth_request_variable_t  *av;
390     ngx_http_compile_complex_value_t   ccv;
391 
392     value = cf->args->elts;
393 
394     if (value[1].data[0] != '$') {
395         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
396                            "invalid variable name \"%V\"", &value[1]);
397         return NGX_CONF_ERROR;
398     }
399 
400     value[1].len--;
401     value[1].data++;
402 
403     if (arcf->vars == NGX_CONF_UNSET_PTR) {
404         arcf->vars = ngx_array_create(cf->pool, 1,
405                                       sizeof(ngx_http_auth_request_variable_t));
406         if (arcf->vars == NULL) {
407             return NGX_CONF_ERROR;
408         }
409     }
410 
411     av = ngx_array_push(arcf->vars);
412     if (av == NULL) {
413         return NGX_CONF_ERROR;
414     }
415 
416     v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE);
417     if (v == NULL) {
418         return NGX_CONF_ERROR;
419     }
420 
421     av->index = ngx_http_get_variable_index(cf, &value[1]);
422     if (av->index == NGX_ERROR) {
423         return NGX_CONF_ERROR;
424     }
425 
426     if (v->get_handler == NULL) {
427         v->get_handler = ngx_http_auth_request_variable;
428         v->data = (uintptr_t) av;
429     }
430 
431     av->set_handler = v->set_handler;
432 
433     ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
434 
435     ccv.cf = cf;
436     ccv.value = &value[2];
437     ccv.complex_value = &av->value;
438 
439     if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
440         return NGX_CONF_ERROR;
441     }
442 
443     return NGX_CONF_OK;
444 }
445