/*
 * 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);
}


