1
2/*
3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Nginx, Inc.
5 */
6
7
8#include <ngx_config.h>
9#include <ngx_core.h>
10#include <ngx_http.h>
11#include <ngx_md5.h>
12
13
14typedef struct {
15    ngx_http_complex_value_t  *variable;
16    ngx_http_complex_value_t  *md5;
17    ngx_str_t                  secret;
18} ngx_http_secure_link_conf_t;
19
20
21typedef struct {
22    ngx_str_t                  expires;
23} ngx_http_secure_link_ctx_t;
24
25
26static ngx_int_t ngx_http_secure_link_old_variable(ngx_http_request_t *r,
27    ngx_http_secure_link_conf_t *conf, ngx_http_variable_value_t *v,
28    uintptr_t data);
29static ngx_int_t ngx_http_secure_link_expires_variable(ngx_http_request_t *r,
30    ngx_http_variable_value_t *v, uintptr_t data);
31static void *ngx_http_secure_link_create_conf(ngx_conf_t *cf);
32static char *ngx_http_secure_link_merge_conf(ngx_conf_t *cf, void *parent,
33    void *child);
34static ngx_int_t ngx_http_secure_link_add_variables(ngx_conf_t *cf);
35
36
37static ngx_command_t  ngx_http_secure_link_commands[] = {
38
39    { ngx_string("secure_link"),
40      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
41      ngx_http_set_complex_value_slot,
42      NGX_HTTP_LOC_CONF_OFFSET,
43      offsetof(ngx_http_secure_link_conf_t, variable),
44      NULL },
45
46    { ngx_string("secure_link_md5"),
47      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
48      ngx_http_set_complex_value_slot,
49      NGX_HTTP_LOC_CONF_OFFSET,
50      offsetof(ngx_http_secure_link_conf_t, md5),
51      NULL },
52
53    { ngx_string("secure_link_secret"),
54      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
55      ngx_conf_set_str_slot,
56      NGX_HTTP_LOC_CONF_OFFSET,
57      offsetof(ngx_http_secure_link_conf_t, secret),
58      NULL },
59
60      ngx_null_command
61};
62
63
64static ngx_http_module_t  ngx_http_secure_link_module_ctx = {
65    ngx_http_secure_link_add_variables,    /* preconfiguration */
66    NULL,                                  /* postconfiguration */
67
68    NULL,                                  /* create main configuration */
69    NULL,                                  /* init main configuration */
70
71    NULL,                                  /* create server configuration */
72    NULL,                                  /* merge server configuration */
73
74    ngx_http_secure_link_create_conf,      /* create location configuration */
75    ngx_http_secure_link_merge_conf        /* merge location configuration */
76};
77
78
79ngx_module_t  ngx_http_secure_link_module = {
80    NGX_MODULE_V1,
81    &ngx_http_secure_link_module_ctx,      /* module context */
82    ngx_http_secure_link_commands,         /* module directives */
83    NGX_HTTP_MODULE,                       /* module type */
84    NULL,                                  /* init master */
85    NULL,                                  /* init module */
86    NULL,                                  /* init process */
87    NULL,                                  /* init thread */
88    NULL,                                  /* exit thread */
89    NULL,                                  /* exit process */
90    NULL,                                  /* exit master */
91    NGX_MODULE_V1_PADDING
92};
93
94
95static ngx_str_t  ngx_http_secure_link_name = ngx_string("secure_link");
96static ngx_str_t  ngx_http_secure_link_expires_name =
97    ngx_string("secure_link_expires");
98
99
100static ngx_int_t
101ngx_http_secure_link_variable(ngx_http_request_t *r,
102    ngx_http_variable_value_t *v, uintptr_t data)
103{
104    u_char                       *p, *last;
105    ngx_str_t                     val, hash;
106    time_t                        expires;
107    ngx_md5_t                     md5;
108    ngx_http_secure_link_ctx_t   *ctx;
109    ngx_http_secure_link_conf_t  *conf;
110    u_char                        hash_buf[16], md5_buf[16];
111
112    conf = ngx_http_get_module_loc_conf(r, ngx_http_secure_link_module);
113
114    if (conf->secret.data) {
115        return ngx_http_secure_link_old_variable(r, conf, v, data);
116    }
117
118    if (conf->variable == NULL || conf->md5 == NULL) {
119        goto not_found;
120    }
121
122    if (ngx_http_complex_value(r, conf->variable, &val) != NGX_OK) {
123        return NGX_ERROR;
124    }
125
126    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
127                   "secure link: \"%V\"", &val);
128
129    last = val.data + val.len;
130
131    p = ngx_strlchr(val.data, last, ',');
132    expires = 0;
133
134    if (p) {
135        val.len = p++ - val.data;
136
137        expires = ngx_atotm(p, last - p);
138        if (expires <= 0) {
139            goto not_found;
140        }
141
142        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_secure_link_ctx_t));
143        if (ctx == NULL) {
144            return NGX_ERROR;
145        }
146
147        ngx_http_set_ctx(r, ctx, ngx_http_secure_link_module);
148
149        ctx->expires.len = last - p;
150        ctx->expires.data = p;
151    }
152
153    if (val.len > 24) {
154        goto not_found;
155    }
156
157    hash.len = 16;
158    hash.data = hash_buf;
159
160    if (ngx_decode_base64url(&hash, &val) != NGX_OK) {
161        goto not_found;
162    }
163
164    if (hash.len != 16) {
165        goto not_found;
166    }
167
168    if (ngx_http_complex_value(r, conf->md5, &val) != NGX_OK) {
169        return NGX_ERROR;
170    }
171
172    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
173                   "secure link md5: \"%V\"", &val);
174
175    ngx_md5_init(&md5);
176    ngx_md5_update(&md5, val.data, val.len);
177    ngx_md5_final(md5_buf, &md5);
178
179    if (ngx_memcmp(hash_buf, md5_buf, 16) != 0) {
180        goto not_found;
181    }
182
183    v->data = (u_char *) ((expires && expires < ngx_time()) ? "0" : "1");
184    v->len = 1;
185    v->valid = 1;
186    v->no_cacheable = 0;
187    v->not_found = 0;
188
189    return NGX_OK;
190
191not_found:
192
193    v->not_found = 1;
194
195    return NGX_OK;
196}
197
198
199static ngx_int_t
200ngx_http_secure_link_old_variable(ngx_http_request_t *r,
201    ngx_http_secure_link_conf_t *conf, ngx_http_variable_value_t *v,
202    uintptr_t data)
203{
204    u_char      *p, *start, *end, *last;
205    size_t       len;
206    ngx_int_t    n;
207    ngx_uint_t   i;
208    ngx_md5_t    md5;
209    u_char       hash[16];
210
211    p = &r->unparsed_uri.data[1];
212    last = r->unparsed_uri.data + r->unparsed_uri.len;
213
214    while (p < last) {
215        if (*p++ == '/') {
216            start = p;
217            goto md5_start;
218        }
219    }
220
221    goto not_found;
222
223md5_start:
224
225    while (p < last) {
226        if (*p++ == '/') {
227            end = p - 1;
228            goto url_start;
229        }
230    }
231
232    goto not_found;
233
234url_start:
235
236    len = last - p;
237
238    if (end - start != 32 || len == 0) {
239        goto not_found;
240    }
241
242    ngx_md5_init(&md5);
243    ngx_md5_update(&md5, p, len);
244    ngx_md5_update(&md5, conf->secret.data, conf->secret.len);
245    ngx_md5_final(hash, &md5);
246
247    for (i = 0; i < 16; i++) {
248        n = ngx_hextoi(&start[2 * i], 2);
249        if (n == NGX_ERROR || n != hash[i]) {
250            goto not_found;
251        }
252    }
253
254    v->len = len;
255    v->valid = 1;
256    v->no_cacheable = 0;
257    v->not_found = 0;
258    v->data = p;
259
260    return NGX_OK;
261
262not_found:
263
264    v->not_found = 1;
265
266    return NGX_OK;
267}
268
269
270static ngx_int_t
271ngx_http_secure_link_expires_variable(ngx_http_request_t *r,
272    ngx_http_variable_value_t *v, uintptr_t data)
273{
274    ngx_http_secure_link_ctx_t  *ctx;
275
276    ctx = ngx_http_get_module_ctx(r, ngx_http_secure_link_module);
277
278    if (ctx) {
279        v->len = ctx->expires.len;
280        v->valid = 1;
281        v->no_cacheable = 0;
282        v->not_found = 0;
283        v->data = ctx->expires.data;
284
285    } else {
286        v->not_found = 1;
287    }
288
289    return NGX_OK;
290}
291
292
293static void *
294ngx_http_secure_link_create_conf(ngx_conf_t *cf)
295{
296    ngx_http_secure_link_conf_t  *conf;
297
298    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_secure_link_conf_t));
299    if (conf == NULL) {
300        return NULL;
301    }
302
303    /*
304     * set by ngx_pcalloc():
305     *
306     *     conf->variable = NULL;
307     *     conf->md5 = NULL;
308     *     conf->secret = { 0, NULL };
309     */
310
311    return conf;
312}
313
314
315static char *
316ngx_http_secure_link_merge_conf(ngx_conf_t *cf, void *parent, void *child)
317{
318    ngx_http_secure_link_conf_t *prev = parent;
319    ngx_http_secure_link_conf_t *conf = child;
320
321    if (conf->secret.data) {
322        if (conf->variable || conf->md5) {
323            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
324                               "\"secure_link_secret\" cannot be mixed with "
325                               "\"secure_link\" and \"secure_link_md5\"");
326            return NGX_CONF_ERROR;
327        }
328
329        return NGX_CONF_OK;
330    }
331
332    if (conf->variable == NULL) {
333        conf->variable = prev->variable;
334    }
335
336    if (conf->md5 == NULL) {
337        conf->md5 = prev->md5;
338    }
339
340    if (conf->variable == NULL && conf->md5 == NULL) {
341        conf->secret = prev->secret;
342    }
343
344    return NGX_CONF_OK;
345}
346
347
348static ngx_int_t
349ngx_http_secure_link_add_variables(ngx_conf_t *cf)
350{
351    ngx_http_variable_t  *var;
352
353    var = ngx_http_add_variable(cf, &ngx_http_secure_link_name, 0);
354    if (var == NULL) {
355        return NGX_ERROR;
356    }
357
358    var->get_handler = ngx_http_secure_link_variable;
359
360    var = ngx_http_add_variable(cf, &ngx_http_secure_link_expires_name, 0);
361    if (var == NULL) {
362        return NGX_ERROR;
363    }
364
365    var->get_handler = ngx_http_secure_link_expires_variable;
366
367    return NGX_OK;
368}
369