/* * Copyright (C) Igor Sysoev */ #include #include #include #define NGX_HTTP_REFERER_NO_URI_PART ((void *) 4) typedef struct { ngx_hash_t hash; ngx_hash_wildcard_t *dns_wildcards; ngx_flag_t no_referer; ngx_flag_t blocked_referer; ngx_hash_keys_arrays_t *keys; } ngx_http_referer_conf_t; static void * ngx_http_referer_create_conf(ngx_conf_t *cf); static char * ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_add_referer(ngx_conf_t *cf, ngx_hash_keys_arrays_t *keys, ngx_str_t *value, ngx_str_t *uri); static int ngx_libc_cdecl ngx_http_cmp_referer_wildcards(const void *one, const void *two); static ngx_command_t ngx_http_referer_commands[] = { { ngx_string("valid_referers"), NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_valid_referers, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_referer_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_referer_create_conf, /* create location configuration */ ngx_http_referer_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_referer_module = { NGX_MODULE_V1, &ngx_http_referer_module_ctx, /* module context */ ngx_http_referer_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_int_t ngx_http_referer_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p, *ref, *last; size_t len; ngx_str_t *uri; ngx_uint_t i, key; ngx_http_referer_conf_t *rlcf; u_char buf[256]; rlcf = ngx_http_get_module_loc_conf(r, ngx_http_referer_module); if (rlcf->hash.buckets == NULL && rlcf->dns_wildcards == NULL) { goto valid; } if (r->headers_in.referer == NULL) { if (rlcf->no_referer) { goto valid; } goto invalid; } len = r->headers_in.referer->value.len; ref = r->headers_in.referer->value.data; if (len < sizeof("http://i.ru") - 1 || (ngx_strncasecmp(ref, "http://", 7) != 0)) { if (rlcf->blocked_referer) { goto valid; } goto invalid; } last = ref + len; ref += 7; i = 0; key = 0; for (p = ref; p < last; p++) { if (*p == '/' || *p == ':') { break; } buf[i] = ngx_tolower(*p); key = ngx_hash(key, buf[i++]); if (i == 256) { goto invalid; } } len = p - ref; if (rlcf->hash.buckets) { uri = ngx_hash_find(&rlcf->hash, key, buf, len); if (uri) { goto uri; } } if (rlcf->dns_wildcards) { uri = ngx_hash_find_wildcard(rlcf->dns_wildcards, buf, len); if (uri) { goto uri; } } invalid: *v = ngx_http_variable_true_value; return NGX_OK; uri: for ( /* void */ ; p < last; p++) { if (*p == '/') { break; } } len = last - p; if (uri == NGX_HTTP_REFERER_NO_URI_PART) { goto valid; } if (len < uri->len || ngx_strncmp(uri->data, p, uri->len) != 0) { goto invalid; } valid: *v = ngx_http_variable_null_value; return NGX_OK; } static void * ngx_http_referer_create_conf(ngx_conf_t *cf) { ngx_http_referer_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_referer_conf_t)); if (conf == NULL) { return NGX_CONF_ERROR; } conf->no_referer = NGX_CONF_UNSET; conf->blocked_referer = NGX_CONF_UNSET; return conf; } static char * ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_referer_conf_t *prev = parent; ngx_http_referer_conf_t *conf = child; ngx_hash_init_t hash; if (conf->keys == NULL) { conf->hash = prev->hash; conf->dns_wildcards = prev->dns_wildcards; ngx_conf_merge_value(conf->no_referer, prev->no_referer, 0); ngx_conf_merge_value(conf->blocked_referer, prev->blocked_referer, 0); return NGX_CONF_OK; } if ((conf->no_referer == 1 || conf->blocked_referer == 1) && conf->keys->keys.nelts == 0 && conf->keys->dns_wildcards.nelts == 0) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "the \"none\" or \"blocked\" referers are specified " "in the \"valid_referers\" directive " "without any valid referer"); return NGX_CONF_ERROR; } hash.key = ngx_hash_key_lc; hash.max_size = 2048; /* TODO: referer_hash_max_size; */ hash.bucket_size = 64; /* TODO: referer_hash_bucket_size; */ hash.name = "referers_hash"; hash.pool = cf->pool; if (conf->keys->keys.nelts) { hash.hash = &conf->hash; hash.temp_pool = NULL; if (ngx_hash_init(&hash, conf->keys->keys.elts, conf->keys->keys.nelts) != NGX_OK) { return NGX_CONF_ERROR; } } if (conf->keys->dns_wildcards.nelts) { ngx_qsort(conf->keys->dns_wildcards.elts, (size_t) conf->keys->dns_wildcards.nelts, sizeof(ngx_hash_key_t), ngx_http_cmp_referer_wildcards); hash.hash = NULL; hash.temp_pool = cf->temp_pool; if (ngx_hash_wildcard_init(&hash, conf->keys->dns_wildcards.elts, conf->keys->dns_wildcards.nelts) != NGX_OK) { return NGX_CONF_ERROR; } conf->dns_wildcards = (ngx_hash_wildcard_t *) hash.hash; } if (conf->no_referer == NGX_CONF_UNSET) { conf->no_referer = 0; } if (conf->blocked_referer == NGX_CONF_UNSET) { conf->blocked_referer = 0; } conf->keys = NULL; return NGX_CONF_OK; } static char * ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_referer_conf_t *rlcf = conf; u_char *p; ngx_str_t *value, uri, name; ngx_uint_t i, n; ngx_http_variable_t *var; ngx_http_server_name_t *sn; ngx_http_core_srv_conf_t *cscf; name.len = sizeof("invalid_referer") - 1; name.data = (u_char *) "invalid_referer"; var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGABLE|NGX_HTTP_VAR_NOHASH); if (var == NULL) { return NGX_CONF_ERROR; } var->get_handler = ngx_http_referer_variable; if (rlcf->keys == NULL) { rlcf->keys = ngx_pcalloc(cf->temp_pool, sizeof(ngx_hash_keys_arrays_t)); if (rlcf->keys == NULL) { return NGX_CONF_ERROR; } rlcf->keys->pool = cf->pool; rlcf->keys->temp_pool = cf->pool; if (ngx_hash_keys_array_init(rlcf->keys, NGX_HASH_SMALL) != NGX_OK) { return NGX_CONF_ERROR; } } value = cf->args->elts; for (i = 1; i < cf->args->nelts; i++) { if (value[i].len == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid referer \"%V\"", &value[i]); return NGX_CONF_ERROR; } if (ngx_strcmp(value[i].data, "none") == 0) { rlcf->no_referer = 1; continue; } if (ngx_strcmp(value[i].data, "blocked") == 0) { rlcf->blocked_referer = 1; continue; } uri.len = 0; uri.data = NULL; if (ngx_strcmp(value[i].data, "server_names") == 0) { cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module); sn = cscf->server_names.elts; for (n = 0; n < cscf->server_names.nelts; n++) { if (ngx_http_add_referer(cf, rlcf->keys, &sn[n].name, &uri) != NGX_OK) { return NGX_CONF_ERROR; } } continue; } p = (u_char *) ngx_strstr(value[i].data, "/"); if (p) { uri.len = (value[i].data + value[i].len) - p; uri.data = p; value[i].len = p - value[i].data; } if (ngx_http_add_referer(cf, rlcf->keys, &value[i], &uri) != NGX_OK) { return NGX_CONF_ERROR; } } return NGX_CONF_OK; } static char * ngx_http_add_referer(ngx_conf_t *cf, ngx_hash_keys_arrays_t *keys, ngx_str_t *value, ngx_str_t *uri) { u_char ch; ngx_int_t rc; ngx_str_t *u; ngx_uint_t flags; ch = value->data[0]; if ((ch == '*' && (value->len < 3 || value->data[1] != '.')) || (ch == '.' && value->len < 2)) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid DNS wildcard \"%V\"", value); return NGX_CONF_ERROR; } flags = (ch == '*' || ch == '.') ? NGX_HASH_WILDCARD_KEY : 0; if (uri->len == 0) { u = NGX_HTTP_REFERER_NO_URI_PART; } else { u = ngx_palloc(cf->pool, sizeof(ngx_str_t)); if (u == NULL) { return NGX_CONF_ERROR; } *u = *uri; } rc = ngx_hash_add_key(keys, value, u, flags); if (rc == NGX_OK) { return NGX_CONF_OK; } if (rc == NGX_BUSY) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "conflicting parameter \"%V\"", value); } return NGX_CONF_ERROR; } static int ngx_libc_cdecl ngx_http_cmp_referer_wildcards(const void *one, const void *two) { ngx_hash_key_t *first, *second; first = (ngx_hash_key_t *) one; second = (ngx_hash_key_t *) two; return ngx_strcmp(first->key.data, second->key.data); }