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
12
13#define NGX_HTTP_REFERER_NO_URI_PART  ((void *) 4)
14
15
16typedef struct {
17    ngx_hash_combined_t      hash;
18
19#if (NGX_PCRE)
20    ngx_array_t             *regex;
21    ngx_array_t             *server_name_regex;
22#endif
23
24    ngx_flag_t               no_referer;
25    ngx_flag_t               blocked_referer;
26    ngx_flag_t               server_names;
27
28    ngx_hash_keys_arrays_t  *keys;
29
30    ngx_uint_t               referer_hash_max_size;
31    ngx_uint_t               referer_hash_bucket_size;
32} ngx_http_referer_conf_t;
33
34
35static void * ngx_http_referer_create_conf(ngx_conf_t *cf);
36static char * ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent,
37    void *child);
38static char *ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd,
39    void *conf);
40static ngx_int_t ngx_http_add_referer(ngx_conf_t *cf,
41    ngx_hash_keys_arrays_t *keys, ngx_str_t *value, ngx_str_t *uri);
42static ngx_int_t ngx_http_add_regex_referer(ngx_conf_t *cf,
43    ngx_http_referer_conf_t *rlcf, ngx_str_t *name);
44#if (NGX_PCRE)
45static ngx_int_t ngx_http_add_regex_server_name(ngx_conf_t *cf,
46    ngx_http_referer_conf_t *rlcf, ngx_http_regex_t *regex);
47#endif
48static int ngx_libc_cdecl ngx_http_cmp_referer_wildcards(const void *one,
49    const void *two);
50
51
52static ngx_command_t  ngx_http_referer_commands[] = {
53
54    { ngx_string("valid_referers"),
55      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
56      ngx_http_valid_referers,
57      NGX_HTTP_LOC_CONF_OFFSET,
58      0,
59      NULL },
60
61    { ngx_string("referer_hash_max_size"),
62      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
63      ngx_conf_set_num_slot,
64      NGX_HTTP_LOC_CONF_OFFSET,
65      offsetof(ngx_http_referer_conf_t, referer_hash_max_size),
66      NULL },
67
68    { ngx_string("referer_hash_bucket_size"),
69      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
70      ngx_conf_set_num_slot,
71      NGX_HTTP_LOC_CONF_OFFSET,
72      offsetof(ngx_http_referer_conf_t, referer_hash_bucket_size),
73      NULL },
74
75      ngx_null_command
76};
77
78
79static ngx_http_module_t  ngx_http_referer_module_ctx = {
80    NULL,                                  /* preconfiguration */
81    NULL,                                  /* postconfiguration */
82
83    NULL,                                  /* create main configuration */
84    NULL,                                  /* init main configuration */
85
86    NULL,                                  /* create server configuration */
87    NULL,                                  /* merge server configuration */
88
89    ngx_http_referer_create_conf,          /* create location configuration */
90    ngx_http_referer_merge_conf            /* merge location configuration */
91};
92
93
94ngx_module_t  ngx_http_referer_module = {
95    NGX_MODULE_V1,
96    &ngx_http_referer_module_ctx,          /* module context */
97    ngx_http_referer_commands,             /* module directives */
98    NGX_HTTP_MODULE,                       /* module type */
99    NULL,                                  /* init master */
100    NULL,                                  /* init module */
101    NULL,                                  /* init process */
102    NULL,                                  /* init thread */
103    NULL,                                  /* exit thread */
104    NULL,                                  /* exit process */
105    NULL,                                  /* exit master */
106    NGX_MODULE_V1_PADDING
107};
108
109
110static ngx_int_t
111ngx_http_referer_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
112    uintptr_t data)
113{
114    u_char                    *p, *ref, *last;
115    size_t                     len;
116    ngx_str_t                 *uri;
117    ngx_uint_t                 i, key;
118    ngx_http_referer_conf_t   *rlcf;
119    u_char                     buf[256];
120#if (NGX_PCRE)
121    ngx_int_t                  rc;
122    ngx_str_t                  referer;
123#endif
124
125    rlcf = ngx_http_get_module_loc_conf(r, ngx_http_referer_module);
126
127    if (rlcf->hash.hash.buckets == NULL
128        && rlcf->hash.wc_head == NULL
129        && rlcf->hash.wc_tail == NULL
130#if (NGX_PCRE)
131        && rlcf->regex == NULL
132        && rlcf->server_name_regex == NULL
133#endif
134       )
135    {
136        goto valid;
137    }
138
139    if (r->headers_in.referer == NULL) {
140        if (rlcf->no_referer) {
141            goto valid;
142        }
143
144        goto invalid;
145    }
146
147    len = r->headers_in.referer->value.len;
148    ref = r->headers_in.referer->value.data;
149
150    if (len >= sizeof("http://i.ru") - 1) {
151        last = ref + len;
152
153        if (ngx_strncasecmp(ref, (u_char *) "http://", 7) == 0) {
154            ref += 7;
155            len -= 7;
156            goto valid_scheme;
157
158        } else if (ngx_strncasecmp(ref, (u_char *) "https://", 8) == 0) {
159            ref += 8;
160            len -= 8;
161            goto valid_scheme;
162        }
163    }
164
165    if (rlcf->blocked_referer) {
166        goto valid;
167    }
168
169    goto invalid;
170
171valid_scheme:
172
173    i = 0;
174    key = 0;
175
176    for (p = ref; p < last; p++) {
177        if (*p == '/' || *p == ':') {
178            break;
179        }
180
181        if (i == 256) {
182            goto invalid;
183        }
184
185        buf[i] = ngx_tolower(*p);
186        key = ngx_hash(key, buf[i++]);
187    }
188
189    uri = ngx_hash_find_combined(&rlcf->hash, key, buf, p - ref);
190
191    if (uri) {
192        goto uri;
193    }
194
195#if (NGX_PCRE)
196
197    if (rlcf->server_name_regex) {
198        referer.len = p - ref;
199        referer.data = buf;
200
201        rc = ngx_regex_exec_array(rlcf->server_name_regex, &referer,
202                                  r->connection->log);
203
204        if (rc == NGX_OK) {
205            goto valid;
206        }
207
208        if (rc == NGX_ERROR) {
209            return rc;
210        }
211
212        /* NGX_DECLINED */
213    }
214
215    if (rlcf->regex) {
216        referer.len = len;
217        referer.data = ref;
218
219        rc = ngx_regex_exec_array(rlcf->regex, &referer, r->connection->log);
220
221        if (rc == NGX_OK) {
222            goto valid;
223        }
224
225        if (rc == NGX_ERROR) {
226            return rc;
227        }
228
229        /* NGX_DECLINED */
230    }
231
232#endif
233
234invalid:
235
236    *v = ngx_http_variable_true_value;
237
238    return NGX_OK;
239
240uri:
241
242    for ( /* void */ ; p < last; p++) {
243        if (*p == '/') {
244            break;
245        }
246    }
247
248    len = last - p;
249
250    if (uri == NGX_HTTP_REFERER_NO_URI_PART) {
251        goto valid;
252    }
253
254    if (len < uri->len || ngx_strncmp(uri->data, p, uri->len) != 0) {
255        goto invalid;
256    }
257
258valid:
259
260    *v = ngx_http_variable_null_value;
261
262    return NGX_OK;
263}
264
265
266static void *
267ngx_http_referer_create_conf(ngx_conf_t *cf)
268{
269    ngx_http_referer_conf_t  *conf;
270
271    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_referer_conf_t));
272    if (conf == NULL) {
273        return NULL;
274    }
275
276    /*
277     * set by ngx_pcalloc():
278     *
279     *     conf->hash = { NULL };
280     *     conf->server_names = 0;
281     *     conf->keys = NULL;
282     */
283
284#if (NGX_PCRE)
285    conf->regex = NGX_CONF_UNSET_PTR;
286    conf->server_name_regex = NGX_CONF_UNSET_PTR;
287#endif
288
289    conf->no_referer = NGX_CONF_UNSET;
290    conf->blocked_referer = NGX_CONF_UNSET;
291    conf->referer_hash_max_size = NGX_CONF_UNSET_UINT;
292    conf->referer_hash_bucket_size = NGX_CONF_UNSET_UINT;
293
294    return conf;
295}
296
297
298static char *
299ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent, void *child)
300{
301    ngx_http_referer_conf_t *prev = parent;
302    ngx_http_referer_conf_t *conf = child;
303
304    ngx_uint_t                 n;
305    ngx_hash_init_t            hash;
306    ngx_http_server_name_t    *sn;
307    ngx_http_core_srv_conf_t  *cscf;
308
309    if (conf->keys == NULL) {
310        conf->hash = prev->hash;
311
312#if (NGX_PCRE)
313        ngx_conf_merge_ptr_value(conf->regex, prev->regex, NULL);
314        ngx_conf_merge_ptr_value(conf->server_name_regex,
315                                 prev->server_name_regex, NULL);
316#endif
317        ngx_conf_merge_value(conf->no_referer, prev->no_referer, 0);
318        ngx_conf_merge_value(conf->blocked_referer, prev->blocked_referer, 0);
319        ngx_conf_merge_uint_value(conf->referer_hash_max_size,
320                                  prev->referer_hash_max_size, 2048);
321        ngx_conf_merge_uint_value(conf->referer_hash_bucket_size,
322                                  prev->referer_hash_bucket_size, 64);
323
324        return NGX_CONF_OK;
325    }
326
327    if (conf->server_names == 1) {
328        cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module);
329
330        sn = cscf->server_names.elts;
331        for (n = 0; n < cscf->server_names.nelts; n++) {
332
333#if (NGX_PCRE)
334            if (sn[n].regex) {
335
336                if (ngx_http_add_regex_server_name(cf, conf, sn[n].regex)
337                    != NGX_OK)
338                {
339                    return NGX_CONF_ERROR;
340                }
341
342                continue;
343            }
344#endif
345
346            if (ngx_http_add_referer(cf, conf->keys, &sn[n].name, NULL)
347                != NGX_OK)
348            {
349                return NGX_CONF_ERROR;
350            }
351        }
352    }
353
354    if ((conf->no_referer == 1 || conf->blocked_referer == 1)
355        && conf->keys->keys.nelts == 0
356        && conf->keys->dns_wc_head.nelts == 0
357        && conf->keys->dns_wc_tail.nelts == 0)
358    {
359        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
360                      "the \"none\" or \"blocked\" referers are specified "
361                      "in the \"valid_referers\" directive "
362                      "without any valid referer");
363        return NGX_CONF_ERROR;
364    }
365
366    ngx_conf_merge_uint_value(conf->referer_hash_max_size,
367                              prev->referer_hash_max_size, 2048);
368    ngx_conf_merge_uint_value(conf->referer_hash_bucket_size,
369                              prev->referer_hash_bucket_size, 64);
370    conf->referer_hash_bucket_size = ngx_align(conf->referer_hash_bucket_size,
371                                               ngx_cacheline_size);
372
373    hash.key = ngx_hash_key_lc;
374    hash.max_size = conf->referer_hash_max_size;
375    hash.bucket_size = conf->referer_hash_bucket_size;
376    hash.name = "referer_hash";
377    hash.pool = cf->pool;
378
379    if (conf->keys->keys.nelts) {
380        hash.hash = &conf->hash.hash;
381        hash.temp_pool = NULL;
382
383        if (ngx_hash_init(&hash, conf->keys->keys.elts, conf->keys->keys.nelts)
384            != NGX_OK)
385        {
386            return NGX_CONF_ERROR;
387        }
388    }
389
390    if (conf->keys->dns_wc_head.nelts) {
391
392        ngx_qsort(conf->keys->dns_wc_head.elts,
393                  (size_t) conf->keys->dns_wc_head.nelts,
394                  sizeof(ngx_hash_key_t),
395                  ngx_http_cmp_referer_wildcards);
396
397        hash.hash = NULL;
398        hash.temp_pool = cf->temp_pool;
399
400        if (ngx_hash_wildcard_init(&hash, conf->keys->dns_wc_head.elts,
401                                   conf->keys->dns_wc_head.nelts)
402            != NGX_OK)
403        {
404            return NGX_CONF_ERROR;
405        }
406
407        conf->hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
408    }
409
410    if (conf->keys->dns_wc_tail.nelts) {
411
412        ngx_qsort(conf->keys->dns_wc_tail.elts,
413                  (size_t) conf->keys->dns_wc_tail.nelts,
414                  sizeof(ngx_hash_key_t),
415                  ngx_http_cmp_referer_wildcards);
416
417        hash.hash = NULL;
418        hash.temp_pool = cf->temp_pool;
419
420        if (ngx_hash_wildcard_init(&hash, conf->keys->dns_wc_tail.elts,
421                                   conf->keys->dns_wc_tail.nelts)
422            != NGX_OK)
423        {
424            return NGX_CONF_ERROR;
425        }
426
427        conf->hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash;
428    }
429
430#if (NGX_PCRE)
431    ngx_conf_merge_ptr_value(conf->regex, prev->regex, NULL);
432    ngx_conf_merge_ptr_value(conf->server_name_regex, prev->server_name_regex,
433                             NULL);
434#endif
435
436    if (conf->no_referer == NGX_CONF_UNSET) {
437        conf->no_referer = 0;
438    }
439
440    if (conf->blocked_referer == NGX_CONF_UNSET) {
441        conf->blocked_referer = 0;
442    }
443
444    conf->keys = NULL;
445
446    return NGX_CONF_OK;
447}
448
449
450static char *
451ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
452{
453    ngx_http_referer_conf_t  *rlcf = conf;
454
455    u_char                    *p;
456    ngx_str_t                 *value, uri, name;
457    ngx_uint_t                 i;
458    ngx_http_variable_t       *var;
459
460    ngx_str_set(&name, "invalid_referer");
461
462    var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
463    if (var == NULL) {
464        return NGX_CONF_ERROR;
465    }
466
467    var->get_handler = ngx_http_referer_variable;
468
469    if (rlcf->keys == NULL) {
470        rlcf->keys = ngx_pcalloc(cf->temp_pool, sizeof(ngx_hash_keys_arrays_t));
471        if (rlcf->keys == NULL) {
472            return NGX_CONF_ERROR;
473        }
474
475        rlcf->keys->pool = cf->pool;
476        rlcf->keys->temp_pool = cf->pool;
477
478        if (ngx_hash_keys_array_init(rlcf->keys, NGX_HASH_SMALL) != NGX_OK) {
479            return NGX_CONF_ERROR;
480        }
481    }
482
483    value = cf->args->elts;
484
485    for (i = 1; i < cf->args->nelts; i++) {
486        if (value[i].len == 0) {
487            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
488                               "invalid referer \"%V\"", &value[i]);
489            return NGX_CONF_ERROR;
490        }
491
492        if (ngx_strcmp(value[i].data, "none") == 0) {
493            rlcf->no_referer = 1;
494            continue;
495        }
496
497        if (ngx_strcmp(value[i].data, "blocked") == 0) {
498            rlcf->blocked_referer = 1;
499            continue;
500        }
501
502        if (ngx_strcmp(value[i].data, "server_names") == 0) {
503            rlcf->server_names = 1;
504            continue;
505        }
506
507        if (value[i].data[0] == '~') {
508            if (ngx_http_add_regex_referer(cf, rlcf, &value[i]) != NGX_OK) {
509                return NGX_CONF_ERROR;
510            }
511
512            continue;
513        }
514
515        ngx_str_null(&uri);
516
517        p = (u_char *) ngx_strchr(value[i].data, '/');
518
519        if (p) {
520            uri.len = (value[i].data + value[i].len) - p;
521            uri.data = p;
522            value[i].len = p - value[i].data;
523        }
524
525        if (ngx_http_add_referer(cf, rlcf->keys, &value[i], &uri) != NGX_OK) {
526            return NGX_CONF_ERROR;
527        }
528    }
529
530    return NGX_CONF_OK;
531}
532
533
534static ngx_int_t
535ngx_http_add_referer(ngx_conf_t *cf, ngx_hash_keys_arrays_t *keys,
536    ngx_str_t *value, ngx_str_t *uri)
537{
538    ngx_int_t   rc;
539    ngx_str_t  *u;
540
541    if (uri == NULL || uri->len == 0) {
542        u = NGX_HTTP_REFERER_NO_URI_PART;
543
544    } else {
545        u = ngx_palloc(cf->pool, sizeof(ngx_str_t));
546        if (u == NULL) {
547            return NGX_ERROR;
548        }
549
550        *u = *uri;
551    }
552
553    rc = ngx_hash_add_key(keys, value, u, NGX_HASH_WILDCARD_KEY);
554
555    if (rc == NGX_OK) {
556        return NGX_OK;
557    }
558
559    if (rc == NGX_DECLINED) {
560        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
561                           "invalid hostname or wildcard \"%V\"", value);
562    }
563
564    if (rc == NGX_BUSY) {
565        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
566                           "conflicting parameter \"%V\"", value);
567    }
568
569    return NGX_ERROR;
570}
571
572
573static ngx_int_t
574ngx_http_add_regex_referer(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf,
575    ngx_str_t *name)
576{
577#if (NGX_PCRE)
578    ngx_regex_elt_t      *re;
579    ngx_regex_compile_t   rc;
580    u_char                errstr[NGX_MAX_CONF_ERRSTR];
581
582    if (name->len == 1) {
583        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "empty regex in \"%V\"", name);
584        return NGX_ERROR;
585    }
586
587    if (rlcf->regex == NGX_CONF_UNSET_PTR) {
588        rlcf->regex = ngx_array_create(cf->pool, 2, sizeof(ngx_regex_elt_t));
589        if (rlcf->regex == NULL) {
590            return NGX_ERROR;
591        }
592    }
593
594    re = ngx_array_push(rlcf->regex);
595    if (re == NULL) {
596        return NGX_ERROR;
597    }
598
599    name->len--;
600    name->data++;
601
602    ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
603
604    rc.pattern = *name;
605    rc.pool = cf->pool;
606    rc.options = NGX_REGEX_CASELESS;
607    rc.err.len = NGX_MAX_CONF_ERRSTR;
608    rc.err.data = errstr;
609
610    if (ngx_regex_compile(&rc) != NGX_OK) {
611        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
612        return NGX_ERROR;
613    }
614
615    re->regex = rc.regex;
616    re->name = name->data;
617
618    return NGX_OK;
619
620#else
621
622    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
623                       "the using of the regex \"%V\" requires PCRE library",
624                       name);
625
626    return NGX_ERROR;
627
628#endif
629}
630
631
632#if (NGX_PCRE)
633
634static ngx_int_t
635ngx_http_add_regex_server_name(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf,
636    ngx_http_regex_t *regex)
637{
638    ngx_regex_elt_t  *re;
639
640    if (rlcf->server_name_regex == NGX_CONF_UNSET_PTR) {
641        rlcf->server_name_regex = ngx_array_create(cf->pool, 2,
642                                                   sizeof(ngx_regex_elt_t));
643        if (rlcf->server_name_regex == NULL) {
644            return NGX_ERROR;
645        }
646    }
647
648    re = ngx_array_push(rlcf->server_name_regex);
649    if (re == NULL) {
650        return NGX_ERROR;
651    }
652
653    re->regex = regex->regex;
654    re->name = regex->name.data;
655
656    return NGX_OK;
657}
658
659#endif
660
661
662static int ngx_libc_cdecl
663ngx_http_cmp_referer_wildcards(const void *one, const void *two)
664{
665    ngx_hash_key_t  *first, *second;
666
667    first = (ngx_hash_key_t *) one;
668    second = (ngx_hash_key_t *) two;
669
670    return ngx_dns_strcmp(first->key.data, second->key.data);
671}
672