src/http/modules/ngx_http_referer_module.c - nginx-1.7.10

Global variables defined

Data types defined

Functions defined

Macros defined

Source code


  1. /*
  2. * Copyright (C) Igor Sysoev
  3. * Copyright (C) Nginx, Inc.
  4. */


  5. #include <ngx_config.h>
  6. #include <ngx_core.h>
  7. #include <ngx_http.h>


  8. #define NGX_HTTP_REFERER_NO_URI_PART  ((void *) 4)


  9. typedef struct {
  10.     ngx_hash_combined_t      hash;

  11. #if (NGX_PCRE)
  12.     ngx_array_t             *regex;
  13.     ngx_array_t             *server_name_regex;
  14. #endif

  15.     ngx_flag_t               no_referer;
  16.     ngx_flag_t               blocked_referer;
  17.     ngx_flag_t               server_names;

  18.     ngx_hash_keys_arrays_t  *keys;

  19.     ngx_uint_t               referer_hash_max_size;
  20.     ngx_uint_t               referer_hash_bucket_size;
  21. } ngx_http_referer_conf_t;


  22. static void * ngx_http_referer_create_conf(ngx_conf_t *cf);
  23. static char * ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent,
  24.     void *child);
  25. static char *ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd,
  26.     void *conf);
  27. static ngx_int_t ngx_http_add_referer(ngx_conf_t *cf,
  28.     ngx_hash_keys_arrays_t *keys, ngx_str_t *value, ngx_str_t *uri);
  29. static ngx_int_t ngx_http_add_regex_referer(ngx_conf_t *cf,
  30.     ngx_http_referer_conf_t *rlcf, ngx_str_t *name);
  31. #if (NGX_PCRE)
  32. static ngx_int_t ngx_http_add_regex_server_name(ngx_conf_t *cf,
  33.     ngx_http_referer_conf_t *rlcf, ngx_http_regex_t *regex);
  34. #endif
  35. static int ngx_libc_cdecl ngx_http_cmp_referer_wildcards(const void *one,
  36.     const void *two);


  37. static ngx_command_t  ngx_http_referer_commands[] = {

  38.     { ngx_string("valid_referers"),
  39.       NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
  40.       ngx_http_valid_referers,
  41.       NGX_HTTP_LOC_CONF_OFFSET,
  42.       0,
  43.       NULL },

  44.     { ngx_string("referer_hash_max_size"),
  45.       NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
  46.       ngx_conf_set_num_slot,
  47.       NGX_HTTP_LOC_CONF_OFFSET,
  48.       offsetof(ngx_http_referer_conf_t, referer_hash_max_size),
  49.       NULL },

  50.     { ngx_string("referer_hash_bucket_size"),
  51.       NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
  52.       ngx_conf_set_num_slot,
  53.       NGX_HTTP_LOC_CONF_OFFSET,
  54.       offsetof(ngx_http_referer_conf_t, referer_hash_bucket_size),
  55.       NULL },

  56.       ngx_null_command
  57. };


  58. static ngx_http_module_t  ngx_http_referer_module_ctx = {
  59.     NULL,                                  /* preconfiguration */
  60.     NULL,                                  /* postconfiguration */

  61.     NULL,                                  /* create main configuration */
  62.     NULL,                                  /* init main configuration */

  63.     NULL,                                  /* create server configuration */
  64.     NULL,                                  /* merge server configuration */

  65.     ngx_http_referer_create_conf,          /* create location configuration */
  66.     ngx_http_referer_merge_conf            /* merge location configuration */
  67. };


  68. ngx_module_t  ngx_http_referer_module = {
  69.     NGX_MODULE_V1,
  70.     &ngx_http_referer_module_ctx,          /* module context */
  71.     ngx_http_referer_commands,             /* module directives */
  72.     NGX_HTTP_MODULE,                       /* module type */
  73.     NULL,                                  /* init master */
  74.     NULL,                                  /* init module */
  75.     NULL,                                  /* init process */
  76.     NULL,                                  /* init thread */
  77.     NULL,                                  /* exit thread */
  78.     NULL,                                  /* exit process */
  79.     NULL,                                  /* exit master */
  80.     NGX_MODULE_V1_PADDING
  81. };


  82. static ngx_int_t
  83. ngx_http_referer_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
  84.      uintptr_t data)
  85. {
  86.     u_char                    *p, *ref, *last;
  87.     size_t                     len;
  88.     ngx_str_t                 *uri;
  89.     ngx_uint_t                 i, key;
  90.     ngx_http_referer_conf_t   *rlcf;
  91.     u_char                     buf[256];
  92. #if (NGX_PCRE)
  93.     ngx_int_t                  rc;
  94.     ngx_str_t                  referer;
  95. #endif

  96.     rlcf = ngx_http_get_module_loc_conf(r, ngx_http_referer_module);

  97.     if (rlcf->hash.hash.buckets == NULL
  98.         && rlcf->hash.wc_head == NULL
  99.         && rlcf->hash.wc_tail == NULL
  100. #if (NGX_PCRE)
  101.         && rlcf->regex == NULL
  102.         && rlcf->server_name_regex == NULL
  103. #endif
  104.        )
  105.     {
  106.         goto valid;
  107.     }

  108.     if (r->headers_in.referer == NULL) {
  109.         if (rlcf->no_referer) {
  110.             goto valid;
  111.         }

  112.         goto invalid;
  113.     }

  114.     len = r->headers_in.referer->value.len;
  115.     ref = r->headers_in.referer->value.data;

  116.     if (len >= sizeof("http://i.ru") - 1) {
  117.         last = ref + len;

  118.         if (ngx_strncasecmp(ref, (u_char *) "http://", 7) == 0) {
  119.             ref += 7;
  120.             len -= 7;
  121.             goto valid_scheme;

  122.         } else if (ngx_strncasecmp(ref, (u_char *) "https://", 8) == 0) {
  123.             ref += 8;
  124.             len -= 8;
  125.             goto valid_scheme;
  126.         }
  127.     }

  128.     if (rlcf->blocked_referer) {
  129.         goto valid;
  130.     }

  131.     goto invalid;

  132. valid_scheme:

  133.     i = 0;
  134.     key = 0;

  135.     for (p = ref; p < last; p++) {
  136.         if (*p == '/' || *p == ':') {
  137.             break;
  138.         }

  139.         if (i == 256) {
  140.             goto invalid;
  141.         }

  142.         buf[i] = ngx_tolower(*p);
  143.         key = ngx_hash(key, buf[i++]);
  144.     }

  145.     uri = ngx_hash_find_combined(&rlcf->hash, key, buf, p - ref);

  146.     if (uri) {
  147.         goto uri;
  148.     }

  149. #if (NGX_PCRE)

  150.     if (rlcf->server_name_regex) {
  151.         referer.len = p - ref;
  152.         referer.data = buf;

  153.         rc = ngx_regex_exec_array(rlcf->server_name_regex, &referer,
  154.                                   r->connection->log);

  155.         if (rc == NGX_OK) {
  156.             goto valid;
  157.         }

  158.         if (rc == NGX_ERROR) {
  159.             return rc;
  160.         }

  161.         /* NGX_DECLINED */
  162.     }

  163.     if (rlcf->regex) {
  164.         referer.len = len;
  165.         referer.data = ref;

  166.         rc = ngx_regex_exec_array(rlcf->regex, &referer, r->connection->log);

  167.         if (rc == NGX_OK) {
  168.             goto valid;
  169.         }

  170.         if (rc == NGX_ERROR) {
  171.             return rc;
  172.         }

  173.         /* NGX_DECLINED */
  174.     }

  175. #endif

  176. invalid:

  177.     *v = ngx_http_variable_true_value;

  178.     return NGX_OK;

  179. uri:

  180.     for ( /* void */ ; p < last; p++) {
  181.         if (*p == '/') {
  182.             break;
  183.         }
  184.     }

  185.     len = last - p;

  186.     if (uri == NGX_HTTP_REFERER_NO_URI_PART) {
  187.         goto valid;
  188.     }

  189.     if (len < uri->len || ngx_strncmp(uri->data, p, uri->len) != 0) {
  190.         goto invalid;
  191.     }

  192. valid:

  193.     *v = ngx_http_variable_null_value;

  194.     return NGX_OK;
  195. }


  196. static void *
  197. ngx_http_referer_create_conf(ngx_conf_t *cf)
  198. {
  199.     ngx_http_referer_conf_t  *conf;

  200.     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_referer_conf_t));
  201.     if (conf == NULL) {
  202.         return NULL;
  203.     }

  204.     /*
  205.      * set by ngx_pcalloc():
  206.      *
  207.      *     conf->hash = { NULL };
  208.      *     conf->server_names = 0;
  209.      *     conf->keys = NULL;
  210.      */

  211. #if (NGX_PCRE)
  212.     conf->regex = NGX_CONF_UNSET_PTR;
  213.     conf->server_name_regex = NGX_CONF_UNSET_PTR;
  214. #endif

  215.     conf->no_referer = NGX_CONF_UNSET;
  216.     conf->blocked_referer = NGX_CONF_UNSET;
  217.     conf->referer_hash_max_size = NGX_CONF_UNSET_UINT;
  218.     conf->referer_hash_bucket_size = NGX_CONF_UNSET_UINT;

  219.     return conf;
  220. }


  221. static char *
  222. ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent, void *child)
  223. {
  224.     ngx_http_referer_conf_t *prev = parent;
  225.     ngx_http_referer_conf_t *conf = child;

  226.     ngx_uint_t                 n;
  227.     ngx_hash_init_t            hash;
  228.     ngx_http_server_name_t    *sn;
  229.     ngx_http_core_srv_conf_t  *cscf;

  230.     if (conf->keys == NULL) {
  231.         conf->hash = prev->hash;

  232. #if (NGX_PCRE)
  233.         ngx_conf_merge_ptr_value(conf->regex, prev->regex, NULL);
  234.         ngx_conf_merge_ptr_value(conf->server_name_regex,
  235.                                  prev->server_name_regex, NULL);
  236. #endif
  237.         ngx_conf_merge_value(conf->no_referer, prev->no_referer, 0);
  238.         ngx_conf_merge_value(conf->blocked_referer, prev->blocked_referer, 0);
  239.         ngx_conf_merge_uint_value(conf->referer_hash_max_size,
  240.                                   prev->referer_hash_max_size, 2048);
  241.         ngx_conf_merge_uint_value(conf->referer_hash_bucket_size,
  242.                                   prev->referer_hash_bucket_size, 64);

  243.         return NGX_CONF_OK;
  244.     }

  245.     if (conf->server_names == 1) {
  246.         cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module);

  247.         sn = cscf->server_names.elts;
  248.         for (n = 0; n < cscf->server_names.nelts; n++) {

  249. #if (NGX_PCRE)
  250.             if (sn[n].regex) {

  251.                 if (ngx_http_add_regex_server_name(cf, conf, sn[n].regex)
  252.                     != NGX_OK)
  253.                 {
  254.                     return NGX_CONF_ERROR;
  255.                 }

  256.                 continue;
  257.             }
  258. #endif

  259.             if (ngx_http_add_referer(cf, conf->keys, &sn[n].name, NULL)
  260.                 != NGX_OK)
  261.             {
  262.                 return NGX_CONF_ERROR;
  263.             }
  264.         }
  265.     }

  266.     if ((conf->no_referer == 1 || conf->blocked_referer == 1)
  267.         && conf->keys->keys.nelts == 0
  268.         && conf->keys->dns_wc_head.nelts == 0
  269.         && conf->keys->dns_wc_tail.nelts == 0)
  270.     {
  271.         ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
  272.                       "the \"none\" or \"blocked\" referers are specified "
  273.                       "in the \"valid_referers\" directive "
  274.                       "without any valid referer");
  275.         return NGX_CONF_ERROR;
  276.     }

  277.     ngx_conf_merge_uint_value(conf->referer_hash_max_size,
  278.                               prev->referer_hash_max_size, 2048);
  279.     ngx_conf_merge_uint_value(conf->referer_hash_bucket_size,
  280.                               prev->referer_hash_bucket_size, 64);
  281.     conf->referer_hash_bucket_size = ngx_align(conf->referer_hash_bucket_size,
  282.                                                ngx_cacheline_size);

  283.     hash.key = ngx_hash_key_lc;
  284.     hash.max_size = conf->referer_hash_max_size;
  285.     hash.bucket_size = conf->referer_hash_bucket_size;
  286.     hash.name = "referer_hash";
  287.     hash.pool = cf->pool;

  288.     if (conf->keys->keys.nelts) {
  289.         hash.hash = &conf->hash.hash;
  290.         hash.temp_pool = NULL;

  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.     if (conf->keys->dns_wc_head.nelts) {

  298.         ngx_qsort(conf->keys->dns_wc_head.elts,
  299.                   (size_t) conf->keys->dns_wc_head.nelts,
  300.                   sizeof(ngx_hash_key_t),
  301.                   ngx_http_cmp_referer_wildcards);

  302.         hash.hash = NULL;
  303.         hash.temp_pool = cf->temp_pool;

  304.         if (ngx_hash_wildcard_init(&hash, conf->keys->dns_wc_head.elts,
  305.                                    conf->keys->dns_wc_head.nelts)
  306.             != NGX_OK)
  307.         {
  308.             return NGX_CONF_ERROR;
  309.         }

  310.         conf->hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
  311.     }

  312.     if (conf->keys->dns_wc_tail.nelts) {

  313.         ngx_qsort(conf->keys->dns_wc_tail.elts,
  314.                   (size_t) conf->keys->dns_wc_tail.nelts,
  315.                   sizeof(ngx_hash_key_t),
  316.                   ngx_http_cmp_referer_wildcards);

  317.         hash.hash = NULL;
  318.         hash.temp_pool = cf->temp_pool;

  319.         if (ngx_hash_wildcard_init(&hash, conf->keys->dns_wc_tail.elts,
  320.                                    conf->keys->dns_wc_tail.nelts)
  321.             != NGX_OK)
  322.         {
  323.             return NGX_CONF_ERROR;
  324.         }

  325.         conf->hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash;
  326.     }

  327. #if (NGX_PCRE)
  328.     ngx_conf_merge_ptr_value(conf->regex, prev->regex, NULL);
  329.     ngx_conf_merge_ptr_value(conf->server_name_regex, prev->server_name_regex,
  330.                              NULL);
  331. #endif

  332.     if (conf->no_referer == NGX_CONF_UNSET) {
  333.         conf->no_referer = 0;
  334.     }

  335.     if (conf->blocked_referer == NGX_CONF_UNSET) {
  336.         conf->blocked_referer = 0;
  337.     }

  338.     conf->keys = NULL;

  339.     return NGX_CONF_OK;
  340. }


  341. static char *
  342. ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  343. {
  344.     ngx_http_referer_conf_t  *rlcf = conf;

  345.     u_char                    *p;
  346.     ngx_str_t                 *value, uri, name;
  347.     ngx_uint_t                 i;
  348.     ngx_http_variable_t       *var;

  349.     ngx_str_set(&name, "invalid_referer");

  350.     var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
  351.     if (var == NULL) {
  352.         return NGX_CONF_ERROR;
  353.     }

  354.     var->get_handler = ngx_http_referer_variable;

  355.     if (rlcf->keys == NULL) {
  356.         rlcf->keys = ngx_pcalloc(cf->temp_pool, sizeof(ngx_hash_keys_arrays_t));
  357.         if (rlcf->keys == NULL) {
  358.             return NGX_CONF_ERROR;
  359.         }

  360.         rlcf->keys->pool = cf->pool;
  361.         rlcf->keys->temp_pool = cf->pool;

  362.         if (ngx_hash_keys_array_init(rlcf->keys, NGX_HASH_SMALL) != NGX_OK) {
  363.             return NGX_CONF_ERROR;
  364.         }
  365.     }

  366.     value = cf->args->elts;

  367.     for (i = 1; i < cf->args->nelts; i++) {
  368.         if (value[i].len == 0) {
  369.             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  370.                                "invalid referer \"%V\"", &value[i]);
  371.             return NGX_CONF_ERROR;
  372.         }

  373.         if (ngx_strcmp(value[i].data, "none") == 0) {
  374.             rlcf->no_referer = 1;
  375.             continue;
  376.         }

  377.         if (ngx_strcmp(value[i].data, "blocked") == 0) {
  378.             rlcf->blocked_referer = 1;
  379.             continue;
  380.         }

  381.         if (ngx_strcmp(value[i].data, "server_names") == 0) {
  382.             rlcf->server_names = 1;
  383.             continue;
  384.         }

  385.         if (value[i].data[0] == '~') {
  386.             if (ngx_http_add_regex_referer(cf, rlcf, &value[i]) != NGX_OK) {
  387.                 return NGX_CONF_ERROR;
  388.             }

  389.             continue;
  390.         }

  391.         ngx_str_null(&uri);

  392.         p = (u_char *) ngx_strchr(value[i].data, '/');

  393.         if (p) {
  394.             uri.len = (value[i].data + value[i].len) - p;
  395.             uri.data = p;
  396.             value[i].len = p - value[i].data;
  397.         }

  398.         if (ngx_http_add_referer(cf, rlcf->keys, &value[i], &uri) != NGX_OK) {
  399.             return NGX_CONF_ERROR;
  400.         }
  401.     }

  402.     return NGX_CONF_OK;
  403. }


  404. static ngx_int_t
  405. ngx_http_add_referer(ngx_conf_t *cf, ngx_hash_keys_arrays_t *keys,
  406.     ngx_str_t *value, ngx_str_t *uri)
  407. {
  408.     ngx_int_t   rc;
  409.     ngx_str_t  *u;

  410.     if (uri == NULL || uri->len == 0) {
  411.         u = NGX_HTTP_REFERER_NO_URI_PART;

  412.     } else {
  413.         u = ngx_palloc(cf->pool, sizeof(ngx_str_t));
  414.         if (u == NULL) {
  415.             return NGX_ERROR;
  416.         }

  417.         *u = *uri;
  418.     }

  419.     rc = ngx_hash_add_key(keys, value, u, NGX_HASH_WILDCARD_KEY);

  420.     if (rc == NGX_OK) {
  421.         return NGX_OK;
  422.     }

  423.     if (rc == NGX_DECLINED) {
  424.         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  425.                            "invalid hostname or wildcard \"%V\"", value);
  426.     }

  427.     if (rc == NGX_BUSY) {
  428.         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  429.                            "conflicting parameter \"%V\"", value);
  430.     }

  431.     return NGX_ERROR;
  432. }


  433. static ngx_int_t
  434. ngx_http_add_regex_referer(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf,
  435.     ngx_str_t *name)
  436. {
  437. #if (NGX_PCRE)
  438.     ngx_regex_elt_t      *re;
  439.     ngx_regex_compile_t   rc;
  440.     u_char                errstr[NGX_MAX_CONF_ERRSTR];

  441.     if (name->len == 1) {
  442.         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "empty regex in \"%V\"", name);
  443.         return NGX_ERROR;
  444.     }

  445.     if (rlcf->regex == NGX_CONF_UNSET_PTR) {
  446.         rlcf->regex = ngx_array_create(cf->pool, 2, sizeof(ngx_regex_elt_t));
  447.         if (rlcf->regex == NULL) {
  448.             return NGX_ERROR;
  449.         }
  450.     }

  451.     re = ngx_array_push(rlcf->regex);
  452.     if (re == NULL) {
  453.         return NGX_ERROR;
  454.     }

  455.     name->len--;
  456.     name->data++;

  457.     ngx_memzero(&rc, sizeof(ngx_regex_compile_t));

  458.     rc.pattern = *name;
  459.     rc.pool = cf->pool;
  460.     rc.options = NGX_REGEX_CASELESS;
  461.     rc.err.len = NGX_MAX_CONF_ERRSTR;
  462.     rc.err.data = errstr;

  463.     if (ngx_regex_compile(&rc) != NGX_OK) {
  464.         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
  465.         return NGX_ERROR;
  466.     }

  467.     re->regex = rc.regex;
  468.     re->name = name->data;

  469.     return NGX_OK;

  470. #else

  471.     ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  472.                        "the using of the regex \"%V\" requires PCRE library",
  473.                        name);

  474.     return NGX_ERROR;

  475. #endif
  476. }


  477. #if (NGX_PCRE)

  478. static ngx_int_t
  479. ngx_http_add_regex_server_name(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf,
  480.     ngx_http_regex_t *regex)
  481. {
  482.     ngx_regex_elt_t  *re;

  483.     if (rlcf->server_name_regex == NGX_CONF_UNSET_PTR) {
  484.         rlcf->server_name_regex = ngx_array_create(cf->pool, 2,
  485.                                                    sizeof(ngx_regex_elt_t));
  486.         if (rlcf->server_name_regex == NULL) {
  487.             return NGX_ERROR;
  488.         }
  489.     }

  490.     re = ngx_array_push(rlcf->server_name_regex);
  491.     if (re == NULL) {
  492.         return NGX_ERROR;
  493.     }

  494.     re->regex = regex->regex;
  495.     re->name = regex->name.data;

  496.     return NGX_OK;
  497. }

  498. #endif


  499. static int ngx_libc_cdecl
  500. ngx_http_cmp_referer_wildcards(const void *one, const void *two)
  501. {
  502.     ngx_hash_key_t  *first, *second;

  503.     first = (ngx_hash_key_t *) one;
  504.     second = (ngx_hash_key_t *) two;

  505.     return ngx_dns_strcmp(first->key.data, second->key.data);
  506. }