| /* |
| * NAXSI, a web application firewall for NGINX |
| * Copyright (C) 2011, Thibault 'bui' Koechlin |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * In addition, as a special exception, the copyright holders give |
| * permission to link the code of portions of this program with the |
| * OpenSSL library under certain conditions as described in each |
| * individual source file, and distribute linked combinations |
| * including the two. |
| * You must obey the GNU General Public License in all respects |
| * for all of the code used other than OpenSSL. If you modify |
| * file(s) with this exception, you may extend this exception to your |
| * version of the file(s), but you are not obligated to do so. If you |
| * do not wish to do so, delete this exception statement from your |
| * version. If you delete this exception statement from all source |
| * files in the program, then also delete it here. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include "naxsi.h" |
| /* |
| ** TOP LEVEL configuration parsing code |
| */ |
| /* |
| ** code to parse FLAGS and OPTIONS on each line. |
| */ |
| void *dummy_id(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule); |
| void *dummy_score(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule); |
| void *dummy_msg(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule); |
| void *dummy_rx(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule); |
| void *dummy_zone(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule); |
| void *dummy_str(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule); |
| void *dummy_negative(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule); |
| void *dummy_whitelist(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule); |
| /* |
| ** Structures related to the configuration parser |
| */ |
| typedef struct { |
| char *prefix; |
| void *(*pars)(ngx_conf_t *, ngx_str_t *, ngx_http_rule_t *); |
| } ngx_http_dummy_parser_t; |
| |
| |
| |
| static ngx_http_dummy_parser_t rule_parser[] = { |
| {ID_T, dummy_id}, |
| {SCORE_T, dummy_score}, |
| {MSG_T, dummy_msg}, |
| {RX_T, dummy_rx}, |
| {STR_T, dummy_str}, |
| {MATCH_ZONE_T, dummy_zone}, |
| {NEGATIVE_T, dummy_negative}, |
| {WHITELIST_T, dummy_whitelist}, |
| {NULL, NULL} |
| }; |
| |
| |
| |
| void * |
| dummy_negative(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule) |
| { |
| rule->br->negative = 1; |
| return (NGX_CONF_OK); |
| } |
| |
| //#define score_debug |
| void * |
| dummy_score(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule) |
| { |
| int score, len; |
| char *tmp_ptr, *tmp_end; |
| ngx_http_special_score_t *sc; |
| |
| rule->score = 0; |
| rule->block = 0; |
| rule->allow = 0; |
| tmp_ptr = (char *) (tmp->data + strlen(SCORE_T)); |
| #ifdef score_debug |
| ngx_conf_log_error(NGX_LOG_EMERG, r, 0, |
| "XX-(debug) dummy score (%V)", |
| tmp); |
| #endif |
| |
| /*allocate scores array*/ |
| if (!rule->sscores) { |
| rule->sscores = ngx_array_create(r->pool, 1, sizeof(ngx_http_special_score_t)); |
| } |
| |
| while (*tmp_ptr) { |
| if (tmp_ptr[0] == '$') { |
| #ifdef score_debug |
| ngx_conf_log_error(NGX_LOG_EMERG, r, 0, |
| "XX-(debug) special scoring rule (%s)", |
| tmp_ptr); |
| #endif |
| tmp_end = strchr(tmp_ptr, ':'); |
| if (!tmp_end) |
| return (NGX_CONF_ERROR); |
| len = tmp_end - tmp_ptr; |
| if (len <= 0) |
| return (NGX_CONF_ERROR); |
| sc = ngx_array_push(rule->sscores); |
| sc->sc_tag = ngx_pcalloc(r->pool, sizeof(ngx_str_t)); |
| if (!sc->sc_tag) |
| return (NGX_CONF_ERROR); |
| sc->sc_tag->data = ngx_pcalloc(r->pool, len+1); |
| if (!sc->sc_tag->data) |
| return (NGX_CONF_ERROR); |
| //memset(rule->sc_tag->data, 0, len+1); |
| memcpy(sc->sc_tag->data, tmp_ptr, len); |
| sc->sc_tag->len = len; |
| sc->sc_score = atoi(tmp_end+1); |
| #ifdef score_debug |
| ngx_conf_log_error(NGX_LOG_EMERG, r, 0, |
| "XX-(debug) special scoring (%V) => (%d)", |
| sc->sc_tag, sc->sc_score); |
| #endif |
| |
| /* move to end of score. */ |
| while ( /*don't overflow*/((unsigned int)((unsigned char *)tmp_ptr - tmp->data)) < tmp->len && |
| /*and seek for next score */ *tmp_ptr != ',') |
| ++tmp_ptr; |
| } |
| else if (tmp_ptr[0] == ',') |
| ++tmp_ptr; |
| else if (!strcasecmp(tmp_ptr, "BLOCK")) { |
| rule->block = 1; |
| tmp_ptr += 5; |
| } |
| else if (!strcasecmp(tmp_ptr, "ALLOW")) { |
| rule->allow = 1; |
| tmp_ptr += 5; |
| } |
| else if (!strcasecmp(tmp_ptr, "LOG")) { |
| rule->log = 1; |
| tmp_ptr += 3; |
| } |
| |
| //or maybe you just want to assign a score |
| else if ( (tmp_ptr[0] >= '0' && tmp_ptr[0] <= '9') || tmp_ptr[0] == '-') { |
| score = atoi((const char *)tmp->data+2); |
| rule->score = score; |
| } |
| else |
| return (NGX_CONF_ERROR); |
| } |
| #ifdef score_debug |
| unsigned int z; |
| ngx_http_special_score_t *scr; |
| scr = rule->sscores->elts; |
| if (rule->sscores) { |
| for (z = 0; z < rule->sscores->nelts; z++) { |
| ngx_conf_log_error(NGX_LOG_EMERG, r, 0, |
| "XX-score n°%d special scoring (%V) => (%d)", |
| z, scr[z].sc_tag, scr[z].sc_score); |
| |
| } |
| } |
| else |
| ngx_conf_log_error(NGX_LOG_EMERG, r, 0, |
| "XX-no custom scores for this rule."); |
| #endif |
| return (NGX_CONF_OK); |
| } |
| |
| //#define dummy_zone_debug |
| void * |
| dummy_zone(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule) |
| { |
| int tmp_len; |
| ngx_http_custom_rule_location_t *custom_rule; |
| char *tmp_ptr, *tmp_end; |
| |
| |
| if (!rule->br) |
| return (NGX_CONF_ERROR); |
| /* #ifdef dummy_zone_debug */ |
| /* ngx_conf_log_error(NGX_LOG_EMERG, r, 0, "FEU:%V", tmp); */ |
| /* #endif */ |
| |
| |
| tmp_ptr = (char *) tmp->data+strlen(MATCH_ZONE_T); |
| while (*tmp_ptr) { |
| /* #ifdef dummy_zone_debug */ |
| /* ngx_conf_log_error(NGX_LOG_EMERG, r, 0, "FEU:%s", tmp_ptr); */ |
| /* #endif */ |
| |
| if (tmp_ptr[0] == '|') |
| tmp_ptr++; |
| /* match global zones */ |
| if (!strncmp(tmp_ptr, "BODY", strlen("BODY"))) { |
| rule->br->body = 1; |
| tmp_ptr += strlen("BODY"); |
| continue; |
| } |
| else |
| if (!strncmp(tmp_ptr, "HEADERS", strlen("HEADERS"))) { |
| rule->br->headers = 1; |
| tmp_ptr += strlen("HEADERS"); |
| continue; |
| } |
| else |
| if (!strncmp(tmp_ptr, "URL", strlen("URL"))) { |
| rule->br->url = 1; |
| tmp_ptr += strlen("URL"); |
| continue; |
| } |
| else |
| if (!strncmp(tmp_ptr, "ARGS", strlen("ARGS"))) { |
| rule->br->args = 1; |
| tmp_ptr += strlen("ARGS"); |
| continue; |
| } |
| else |
| /* match against variable name*/ |
| if (!strncmp(tmp_ptr, "NAME", strlen("NAME"))) { |
| rule->br->target_name = 1; |
| tmp_ptr += strlen("NAME"); |
| continue; |
| } |
| else |
| /* for file_ext, just push'em in the body rules. |
| when multipart parsing comes in, it'll tag the zone as |
| FILE_EXT as the rule will be pushed in body rules it'll be |
| checked !*/ |
| if (!strncmp(tmp_ptr, "FILE_EXT", strlen("FILE_EXT"))) { |
| rule->br->file_ext = 1; |
| rule->br->body = 1; |
| tmp_ptr += strlen("FILE_EXT"); |
| continue; |
| } |
| else |
| /* custom match zones */ |
| #define MZ_GET_VAR_T "$ARGS_VAR:" |
| #define MZ_HEADER_VAR_T "$HEADERS_VAR:" |
| #define MZ_POST_VAR_T "$BODY_VAR:" |
| #define MZ_SPECIFIC_URL_T "$URL:" |
| //probably a custom zone |
| if (tmp_ptr[0] == '$') { |
| // tag as a custom_location rule. |
| rule->br->custom_location = 1; |
| if (!rule->br->custom_locations) { |
| rule->br->custom_locations = ngx_array_create(r->pool, 1, |
| sizeof(ngx_http_custom_rule_location_t)); |
| if (!rule->br->custom_locations) |
| return (NGX_CONF_ERROR); |
| } |
| custom_rule = ngx_array_push(rule->br->custom_locations); |
| if (!custom_rule) |
| return (NGX_CONF_ERROR); |
| memset(custom_rule, 0, sizeof(ngx_http_custom_rule_location_t)); |
| if (!strncmp(tmp_ptr, MZ_GET_VAR_T, strlen(MZ_GET_VAR_T))) { |
| custom_rule->args_var = 1; |
| rule->br->args_var = 1; |
| tmp_ptr += strlen(MZ_GET_VAR_T); |
| } |
| else if (!strncmp(tmp_ptr, MZ_POST_VAR_T, |
| strlen(MZ_POST_VAR_T))) { |
| custom_rule->body_var = 1; |
| rule->br->body_var = 1; |
| tmp_ptr += strlen(MZ_POST_VAR_T); |
| } |
| else if (!strncmp(tmp_ptr, MZ_HEADER_VAR_T, |
| strlen(MZ_HEADER_VAR_T))) { |
| custom_rule->headers_var = 1; |
| rule->br->headers_var = 1; |
| tmp_ptr += strlen(MZ_HEADER_VAR_T); |
| } |
| else if (!strncmp(tmp_ptr, MZ_SPECIFIC_URL_T, |
| strlen(MZ_SPECIFIC_URL_T))) { |
| custom_rule->specific_url = 1; |
| tmp_ptr += strlen(MZ_SPECIFIC_URL_T); |
| } |
| else |
| return (NGX_CONF_ERROR); |
| tmp_end = strchr((const char *) tmp_ptr, '|'); |
| if (!tmp_end) |
| tmp_end = tmp_ptr + strlen(tmp_ptr); |
| tmp_len = tmp_end - tmp_ptr; |
| if (tmp_len <= 0) |
| return (NGX_CONF_ERROR); |
| custom_rule->target.data = ngx_pcalloc(r->pool, tmp_len+1); |
| if (!custom_rule->target.data) |
| return (NGX_CONF_ERROR); |
| custom_rule->target.len = tmp_len; |
| memcpy(custom_rule->target.data, tmp_ptr, tmp_len); |
| custom_rule->hash = ngx_hash_key_lc(custom_rule->target.data, |
| custom_rule->target.len); |
| #ifdef dummy_zone_debug |
| ngx_conf_log_error(NGX_LOG_EMERG, r, 0, "XX- ZONE:[%V]", |
| &(custom_rule->target)); |
| #endif |
| tmp_ptr += tmp_len; |
| continue; |
| } |
| else |
| return (NGX_CONF_ERROR); |
| } |
| return (NGX_CONF_OK); |
| } |
| |
| void * |
| dummy_id(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule) |
| { |
| rule->rule_id = atoi((const char *) tmp->data+strlen(ID_T)); |
| if (rule->rule_id < 0) { |
| ngx_conf_log_error(NGX_LOG_EMERG, r, 0, |
| "id: failed (%s), should be numeric only", |
| tmp->data); |
| return (NGX_CONF_ERROR); |
| } |
| return (NGX_CONF_OK); |
| } |
| |
| void * |
| dummy_str(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule) |
| { |
| ngx_str_t *str; |
| uint i; |
| |
| if (!rule->br) |
| return (NGX_CONF_ERROR); |
| str = ngx_pcalloc(r->pool, sizeof(ngx_str_t)); |
| if (!str) |
| return (NGX_CONF_ERROR); |
| str->data = tmp->data + strlen(STR_T); |
| str->len = tmp->len - strlen(STR_T); |
| for (i = 0; i < tmp->len; i++) |
| str->data[i] = tolower(str->data[i]); |
| rule->br->str = str; |
| return (NGX_CONF_OK); |
| } |
| |
| void * |
| dummy_msg(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule) |
| { |
| ngx_str_t *str; |
| |
| if (!rule->br) |
| return (NGX_CONF_ERROR); |
| str = ngx_pcalloc(r->pool, sizeof(ngx_str_t)); |
| if (!str) |
| return (NGX_CONF_ERROR); |
| str->data = tmp->data + strlen(STR_T); |
| str->len = tmp->len - strlen(STR_T); |
| rule->log_msg = str; |
| return (NGX_CONF_OK); |
| } |
| |
| //#define whitelist_debug |
| void * |
| dummy_whitelist(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule) |
| { |
| |
| ngx_int_t *wl; |
| unsigned int i, ct; |
| ngx_str_t str; |
| |
| str.data = tmp->data + strlen(WHITELIST_T); |
| str.len = tmp->len - strlen(WHITELIST_T); |
| for (ct = 1, i = 0; i < str.len; i++) |
| if (str.data[i] == ',') |
| ct++; |
| wl = ngx_pcalloc(r->pool, sizeof(ngx_int_t) * (ct+1)); |
| if (!wl) |
| return (NGX_CONF_ERROR); |
| //as 0 means "all rules", memset to -1 |
| memset(wl, -1, sizeof(ngx_int_t) * (ct+1)); |
| #ifdef whitelist_debug |
| ngx_conf_log_error(NGX_LOG_EMERG, r, 0, "XX- allocated %d elems for WL", ct); |
| #endif |
| for (ct = 0, i = 0; i < str.len; i++) { |
| if (i == 0 || str.data[i-1] == ',') { |
| wl[ct] = atoi((const char *)str.data+i); |
| //rule ID can't be negative |
| if (wl[ct] < 0) |
| return (NGX_CONF_ERROR); |
| #ifdef whitelist_debug |
| ngx_conf_log_error(NGX_LOG_EMERG, r, 0, "XX-WL[%d]= %d (idx:%d,%s)", ct, wl[ct], i, str.data); |
| #endif |
| ct++; |
| } |
| } |
| rule->wl_id = wl; |
| return (NGX_CONF_OK); |
| } |
| |
| void * |
| dummy_rx(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule) |
| { |
| ngx_regex_compile_t *rgc; |
| ngx_str_t ha; |
| |
| |
| if (!rule->br) |
| return (NGX_CONF_ERROR); |
| //just prepare a string to hold the directive without 'rx:' |
| ha.data = tmp->data+strlen(RX_T); |
| ha.len = tmp->len-strlen(RX_T); |
| rgc = ngx_pcalloc(r->pool, sizeof(ngx_regex_compile_t)); |
| if (!rgc) |
| return (NGX_CONF_ERROR); |
| rgc->options = PCRE_CASELESS|PCRE_MULTILINE; |
| rgc->pattern = ha; |
| rgc->pool = r->pool; |
| rgc->err.len = 0; |
| rgc->err.data = NULL; |
| |
| if (ngx_regex_compile(rgc) != NGX_OK) { |
| #ifdef rx_debug |
| ngx_conf_log_error(NGX_LOG_EMERG, r, 0, "XX-FAILED RX:%V", |
| tmp); |
| #endif |
| return (NGX_CONF_ERROR); |
| } |
| rule->br->rx = rgc; |
| #ifdef rx_debug |
| ngx_conf_log_error(NGX_LOG_EMERG, r, 0, "XX- RX:[%V]", |
| &(rule->br->rx->pattern)); |
| #endif |
| return (NGX_CONF_OK); |
| } |
| |
| /* Parse one rule line */ |
| /* |
| ** in : nb elem, value array, rule to fill |
| ** does : creates a rule struct from configuration line |
| ** For each element name matching a tag |
| ** (cf. rule_parser), then call the associated func. |
| */ |
| //#define dummy_cfg_parse_one_rule_debug |
| void * |
| ngx_http_dummy_cfg_parse_one_rule(ngx_conf_t *cf, |
| ngx_str_t *value, |
| ngx_http_rule_t *current_rule, |
| ngx_int_t nb_elem) |
| { |
| int i, z; |
| void *ret; |
| int valid; |
| |
| if (!value || !value[0].data) |
| return NGX_CONF_ERROR; |
| /* |
| ** parse basic rule |
| */ |
| if (!ngx_strcmp(value[0].data, TOP_CHECK_RULE_T) || |
| !ngx_strcmp(value[0].data, TOP_CHECK_RULE_N) || |
| !ngx_strcmp(value[0].data, TOP_BASIC_RULE_T) || |
| !ngx_strcmp(value[0].data, TOP_BASIC_RULE_N) || |
| !ngx_strcmp(value[0].data, TOP_MAIN_BASIC_RULE_T) || |
| !ngx_strcmp(value[0].data, TOP_MAIN_BASIC_RULE_N)) { |
| #ifdef dummy_cfg_parse_one_rule_debug |
| ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "XX-basic rule %V", &(value[1])); |
| #endif |
| current_rule->type = BR; |
| current_rule->br = ngx_pcalloc(cf->pool, sizeof(ngx_http_basic_rule_t)); |
| if (!current_rule->br) |
| return (NGX_CONF_ERROR); |
| } |
| else |
| { |
| #ifdef dummy_cfg_parse_one_rule_debug |
| ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, |
| "XX-crit in rule %V", &(value[1])); |
| #endif |
| return (NGX_CONF_ERROR); |
| } |
| |
| // check each word of config line against each rule |
| for(i = 1; i < nb_elem && value[i].len > 0; i++) { |
| valid = 0; |
| for (z = 0; rule_parser[z].pars; z++) { |
| if (!ngx_strncmp(value[i].data, |
| rule_parser[z].prefix, |
| strlen(rule_parser[z].prefix))) { |
| ret = rule_parser[z].pars(cf, &(value[i]), |
| current_rule); |
| if (ret != NGX_CONF_OK) { |
| #ifdef dummy_cfg_parse_one_rule_debug |
| ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, |
| "XX-FAILED PARSING '%s'", |
| value[i].data); |
| #endif |
| return (ret); |
| } |
| valid = 1; |
| } |
| } |
| if (!valid) |
| return (NGX_CONF_ERROR); |
| } |
| /* validate the structure, and fill empty fields.*/ |
| if (!current_rule->log_msg) |
| { |
| current_rule->log_msg = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); |
| current_rule->log_msg->data = NULL; |
| current_rule->log_msg->len = 0; |
| } |
| return (NGX_CONF_OK); |
| } |
| |
| |