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