src/http/modules/ngx_http_referer_module.c - nginx source code

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


  38. static ngx_command_t  ngx_http_referer_commands[] = {

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

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

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

  57.       ngx_null_command
  58. };


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

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

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

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


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


  83. static ngx_str_t  ngx_http_invalid_referer_name = ngx_string("invalid_referer");


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

  98.     rlcf = ngx_http_get_module_loc_conf(r, ngx_http_referer_module);

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

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

  114.         goto invalid;
  115.     }

  116.     len = r->headers_in.referer->value.len;
  117.     ref = r->headers_in.referer->value.data;

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

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

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

  130.     if (rlcf->blocked_referer) {
  131.         goto valid;
  132.     }

  133.     goto invalid;

  134. valid_scheme:

  135.     i = 0;
  136.     key = 0;

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

  141.         if (i == 256) {
  142.             goto invalid;
  143.         }

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

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

  148.     if (uri) {
  149.         goto uri;
  150.     }

  151. #if (NGX_PCRE)

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

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

  157.         if (rc == NGX_OK) {
  158.             goto valid;
  159.         }

  160.         if (rc == NGX_ERROR) {
  161.             return rc;
  162.         }

  163.         /* NGX_DECLINED */
  164.     }

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

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

  169.         if (rc == NGX_OK) {
  170.             goto valid;
  171.         }

  172.         if (rc == NGX_ERROR) {
  173.             return rc;
  174.         }

  175.         /* NGX_DECLINED */
  176.     }

  177. #endif

  178. invalid:

  179.     *v = ngx_http_variable_true_value;

  180.     return NGX_OK;

  181. uri:

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

  187.     len = last - p;

  188.     if (uri == NGX_HTTP_REFERER_NO_URI_PART) {
  189.         goto valid;
  190.     }

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

  194. valid:

  195.     *v = ngx_http_variable_null_value;

  196.     return NGX_OK;
  197. }


  198. static ngx_int_t
  199. ngx_http_referer_add_variables(ngx_conf_t *cf)
  200. {
  201.     ngx_http_variable_t  *var;

  202.     var = ngx_http_add_variable(cf, &ngx_http_invalid_referer_name,
  203.                                 NGX_HTTP_VAR_CHANGEABLE);
  204.     if (var == NULL) {
  205.         return NGX_ERROR;
  206.     }

  207.     var->get_handler = ngx_http_referer_variable;

  208.     return NGX_OK;
  209. }


  210. static void *
  211. ngx_http_referer_create_conf(ngx_conf_t *cf)
  212. {
  213.     ngx_http_referer_conf_t  *conf;

  214.     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_referer_conf_t));
  215.     if (conf == NULL) {
  216.         return NULL;
  217.     }

  218.     /*
  219.      * set by ngx_pcalloc():
  220.      *
  221.      *     conf->hash = { NULL };
  222.      *     conf->server_names = 0;
  223.      *     conf->keys = NULL;
  224.      */

  225. #if (NGX_PCRE)
  226.     conf->regex = NGX_CONF_UNSET_PTR;
  227.     conf->server_name_regex = NGX_CONF_UNSET_PTR;
  228. #endif

  229.     conf->no_referer = NGX_CONF_UNSET;
  230.     conf->blocked_referer = NGX_CONF_UNSET;
  231.     conf->referer_hash_max_size = NGX_CONF_UNSET_UINT;
  232.     conf->referer_hash_bucket_size = NGX_CONF_UNSET_UINT;

  233.     return conf;
  234. }


  235. static char *
  236. ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent, void *child)
  237. {
  238.     ngx_http_referer_conf_t *prev = parent;
  239.     ngx_http_referer_conf_t *conf = child;

  240.     ngx_uint_t                 n;
  241.     ngx_hash_init_t            hash;
  242.     ngx_http_server_name_t    *sn;
  243.     ngx_http_core_srv_conf_t  *cscf;

  244.     if (conf->keys == NULL) {
  245.         conf->hash = prev->hash;

  246. #if (NGX_PCRE)
  247.         ngx_conf_merge_ptr_value(conf->regex, prev->regex, NULL);
  248.         ngx_conf_merge_ptr_value(conf->server_name_regex,
  249.                                  prev->server_name_regex, NULL);
  250. #endif
  251.         ngx_conf_merge_value(conf->no_referer, prev->no_referer, 0);
  252.         ngx_conf_merge_value(conf->blocked_referer, prev->blocked_referer, 0);
  253.         ngx_conf_merge_uint_value(conf->referer_hash_max_size,
  254.                                   prev->referer_hash_max_size, 2048);
  255.         ngx_conf_merge_uint_value(conf->referer_hash_bucket_size,
  256.                                   prev->referer_hash_bucket_size, 64);

  257.         return NGX_CONF_OK;
  258.     }

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

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

  263. #if (NGX_PCRE)
  264.             if (sn[n].regex) {

  265.                 if (ngx_http_add_regex_server_name(cf, conf, sn[n].regex)
  266.                     != NGX_OK)
  267.                 {
  268.                     return NGX_CONF_ERROR;
  269.                 }

  270.                 continue;
  271.             }
  272. #endif

  273.             if (ngx_http_add_referer(cf, conf->keys, &sn[n].name, NULL)
  274.                 != NGX_OK)
  275.             {
  276.                 return NGX_CONF_ERROR;
  277.             }
  278.         }
  279.     }

  280.     if ((conf->no_referer == 1 || conf->blocked_referer == 1)
  281.         && conf->keys->keys.nelts == 0
  282.         && conf->keys->dns_wc_head.nelts == 0
  283.         && conf->keys->dns_wc_tail.nelts == 0)
  284.     {
  285.         ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
  286.                       "the \"none\" or \"blocked\" referers are specified "
  287.                       "in the \"valid_referers\" directive "
  288.                       "without any valid referer");
  289.         return NGX_CONF_ERROR;
  290.     }

  291.     ngx_conf_merge_uint_value(conf->referer_hash_max_size,
  292.                               prev->referer_hash_max_size, 2048);
  293.     ngx_conf_merge_uint_value(conf->referer_hash_bucket_size,
  294.                               prev->referer_hash_bucket_size, 64);
  295.     conf->referer_hash_bucket_size = ngx_align(conf->referer_hash_bucket_size,
  296.                                                ngx_cacheline_size);

  297.     hash.key = ngx_hash_key_lc;
  298.     hash.max_size = conf->referer_hash_max_size;
  299.     hash.bucket_size = conf->referer_hash_bucket_size;
  300.     hash.name = "referer_hash";
  301.     hash.pool = cf->pool;

  302.     if (conf->keys->keys.nelts) {
  303.         hash.hash = &conf->hash.hash;
  304.         hash.temp_pool = NULL;

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

  311.     if (conf->keys->dns_wc_head.nelts) {

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

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

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

  324.         conf->hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
  325.     }

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

  327.         ngx_qsort(conf->keys->dns_wc_tail.elts,
  328.                   (size_t) conf->keys->dns_wc_tail.nelts,
  329.                   sizeof(ngx_hash_key_t),
  330.                   ngx_http_cmp_referer_wildcards);

  331.         hash.hash = NULL;
  332.         hash.temp_pool = cf->temp_pool;

  333.         if (ngx_hash_wildcard_init(&hash, conf->keys->dns_wc_tail.elts,
  334.                                    conf->keys->dns_wc_tail.nelts)
  335.             != NGX_OK)
  336.         {
  337.             return NGX_CONF_ERROR;
  338.         }

  339.         conf->hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash;
  340.     }

  341. #if (NGX_PCRE)
  342.     ngx_conf_merge_ptr_value(conf->regex, prev->regex, NULL);
  343.     ngx_conf_merge_ptr_value(conf->server_name_regex, prev->server_name_regex,
  344.                              NULL);
  345. #endif

  346.     if (conf->no_referer == NGX_CONF_UNSET) {
  347.         conf->no_referer = 0;
  348.     }

  349.     if (conf->blocked_referer == NGX_CONF_UNSET) {
  350.         conf->blocked_referer = 0;
  351.     }

  352.     conf->keys = NULL;

  353.     return NGX_CONF_OK;
  354. }


  355. static char *
  356. ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  357. {
  358.     ngx_http_referer_conf_t  *rlcf = conf;

  359.     u_char      *p;
  360.     ngx_str_t   *value, uri;
  361.     ngx_uint_t   i;

  362.     if (rlcf->keys == NULL) {
  363.         rlcf->keys = ngx_pcalloc(cf->temp_pool, sizeof(ngx_hash_keys_arrays_t));
  364.         if (rlcf->keys == NULL) {
  365.             return NGX_CONF_ERROR;
  366.         }

  367.         rlcf->keys->pool = cf->pool;
  368.         rlcf->keys->temp_pool = cf->pool;

  369.         if (ngx_hash_keys_array_init(rlcf->keys, NGX_HASH_SMALL) != NGX_OK) {
  370.             return NGX_CONF_ERROR;
  371.         }
  372.     }

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

  374.     for (i = 1; i < cf->args->nelts; i++) {
  375.         if (value[i].len == 0) {
  376.             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  377.                                "invalid referer \"%V\"", &value[i]);
  378.             return NGX_CONF_ERROR;
  379.         }

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

  384.         if (ngx_strcmp(value[i].data, "blocked") == 0) {
  385.             rlcf->blocked_referer = 1;
  386.             continue;
  387.         }

  388.         if (ngx_strcmp(value[i].data, "server_names") == 0) {
  389.             rlcf->server_names = 1;
  390.             continue;
  391.         }

  392.         if (value[i].data[0] == '~') {
  393.             if (ngx_http_add_regex_referer(cf, rlcf, &value[i]) != NGX_OK) {
  394.                 return NGX_CONF_ERROR;
  395.             }

  396.             continue;
  397.         }

  398.         ngx_str_null(&uri);

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

  400.         if (p) {
  401.             uri.len = (value[i].data + value[i].len) - p;
  402.             uri.data = p;
  403.             value[i].len = p - value[i].data;
  404.         }

  405.         if (ngx_http_add_referer(cf, rlcf->keys, &value[i], &uri) != NGX_OK) {
  406.             return NGX_CONF_ERROR;
  407.         }
  408.     }

  409.     return NGX_CONF_OK;
  410. }


  411. static ngx_int_t
  412. ngx_http_add_referer(ngx_conf_t *cf, ngx_hash_keys_arrays_t *keys,
  413.     ngx_str_t *value, ngx_str_t *uri)
  414. {
  415.     ngx_int_t   rc;
  416.     ngx_str_t  *u;

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

  419.     } else {
  420.         u = ngx_palloc(cf->pool, sizeof(ngx_str_t));
  421.         if (u == NULL) {
  422.             return NGX_ERROR;
  423.         }

  424.         *u = *uri;
  425.     }

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

  427.     if (rc == NGX_OK) {
  428.         return NGX_OK;
  429.     }

  430.     if (rc == NGX_DECLINED) {
  431.         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  432.                            "invalid hostname or wildcard \"%V\"", value);
  433.     }

  434.     if (rc == NGX_BUSY) {
  435.         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  436.                            "conflicting parameter \"%V\"", value);
  437.     }

  438.     return NGX_ERROR;
  439. }


  440. static ngx_int_t
  441. ngx_http_add_regex_referer(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf,
  442.     ngx_str_t *name)
  443. {
  444. #if (NGX_PCRE)
  445.     ngx_regex_elt_t      *re;
  446.     ngx_regex_compile_t   rc;
  447.     u_char                errstr[NGX_MAX_CONF_ERRSTR];

  448.     if (name->len == 1) {
  449.         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "empty regex in \"%V\"", name);
  450.         return NGX_ERROR;
  451.     }

  452.     if (rlcf->regex == NGX_CONF_UNSET_PTR) {
  453.         rlcf->regex = ngx_array_create(cf->pool, 2, sizeof(ngx_regex_elt_t));
  454.         if (rlcf->regex == NULL) {
  455.             return NGX_ERROR;
  456.         }
  457.     }

  458.     re = ngx_array_push(rlcf->regex);
  459.     if (re == NULL) {
  460.         return NGX_ERROR;
  461.     }

  462.     name->len--;
  463.     name->data++;

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

  465.     rc.pattern = *name;
  466.     rc.pool = cf->pool;
  467.     rc.options = NGX_REGEX_CASELESS;
  468.     rc.err.len = NGX_MAX_CONF_ERRSTR;
  469.     rc.err.data = errstr;

  470.     if (ngx_regex_compile(&rc) != NGX_OK) {
  471.         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
  472.         return NGX_ERROR;
  473.     }

  474.     re->regex = rc.regex;
  475.     re->name = name->data;

  476.     return NGX_OK;

  477. #else

  478.     ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  479.                        "using regex \"%V\" requires PCRE library",
  480.                        name);

  481.     return NGX_ERROR;

  482. #endif
  483. }


  484. #if (NGX_PCRE)

  485. static ngx_int_t
  486. ngx_http_add_regex_server_name(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf,
  487.     ngx_http_regex_t *regex)
  488. {
  489.     ngx_regex_elt_t  *re;

  490.     if (rlcf->server_name_regex == NGX_CONF_UNSET_PTR) {
  491.         rlcf->server_name_regex = ngx_array_create(cf->pool, 2,
  492.                                                    sizeof(ngx_regex_elt_t));
  493.         if (rlcf->server_name_regex == NULL) {
  494.             return NGX_ERROR;
  495.         }
  496.     }

  497.     re = ngx_array_push(rlcf->server_name_regex);
  498.     if (re == NULL) {
  499.         return NGX_ERROR;
  500.     }

  501.     re->regex = regex->regex;
  502.     re->name = regex->name.data;

  503.     return NGX_OK;
  504. }

  505. #endif


  506. static int ngx_libc_cdecl
  507. ngx_http_cmp_referer_wildcards(const void *one, const void *two)
  508. {
  509.     ngx_hash_key_t  *first, *second;

  510.     first = (ngx_hash_key_t *) one;
  511.     second = (ngx_hash_key_t *) two;

  512.     return ngx_dns_strcmp(first->key.data, second->key.data);
  513. }