/*
 * Program: Synonym
 * File: config.c
 * Author: Ionut Nistor
 * Date: 20 Feb 2003
 *
 * $Id: config.c,v 1.6.2.1 2004/10/20 12:30:11 ionut Exp $
 *
 * Licensed under the Modulo Consulting Software License
 * (see file license.txt)
 * 
 */

const char config_c_objid[]="$Id: config.c,v 1.6.2.1 2004/10/20 12:30:11 ionut Exp $";

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sys/types.h>
#include <regex.h>
#include <libxml/tree.h>


#include "synonym.h"
#include "config.h"

char * Synonym_Trim_Whitespaces(char *target)
{
	char buf[512];
	char *tok;
	tok=target;
	while((*tok==' ')||(*tok=='\t'))
		tok++;
	strcpy(buf, tok);
	if(strlen(buf))
	{
		tok=buf+strlen(buf)-1;
		while((*tok==' ')||(*tok=='\t')||(*tok=='\r')||(*tok=='\n'))
			tok--;
		*(tok+1)='\0';

	}
	strcpy(target, buf);
	
	return target;
}

sresult Synonym_Parse_Condition(xmlNodePtr condition_node, synonym_condition *condition)
{
	xmlNodePtr current_node;
	for(current_node=condition_node->next; current_node != NULL; current_node=current_node->next)
	{
		if(!strcasecmp(current_node->name, TAG_HEADER) && current_node->type==XML_ELEMENT_NODE) /* Ok, go to the Header condition details - look for the node content */
		{
			if(condition->header_field[0] != 0) /* Did we find a Header element before? */
			{
				syslog(LOG_ERR, "More then one Header defined in a condition");
				return SYNONYM_CONFIG_ERROR;
			}
			else
				if(current_node->children != NULL)
					if(current_node->children->type==XML_TEXT_NODE) /* Has to be - otherwise we got a problem */
					{
						strncpy(condition->header_field, current_node->children->content, MAX_FIELD_SIZE);
						condition->header_field[MAX_FIELD_SIZE] = '\0';
						if(condition->header_field[0] == '\0')
						{
							syslog(LOG_ERR, "Void Header defined");
							return SYNONYM_CONFIG_ERROR;
						}
					}
					else
					{
						syslog(LOG_ERR, "Internal error in %s, line number %d", __FILE__, __LINE__);
						return SYNONYM_CONFIG_INTERNAL_ERROR;
					}
				else
				{
					syslog(LOG_ERR, "The header tag must contain a value");
					return SYNONYM_CONFIG_ERROR;
				}
		}

		if(!strcasecmp(current_node->name, TAG_MATCH) && current_node->type==XML_ELEMENT_NODE) /* Ok, go to the Match condition details - look for the node content */
		{
			if(condition->header_regexp[0] != 0) /* Did we find a Match element before? */
			{
				syslog(LOG_ERR, "More then one Match defined in a condition");
				return SYNONYM_CONFIG_ERROR;
			}
			else
				if(current_node->children != NULL)
					if(current_node->children->type==XML_TEXT_NODE) /* Has to be - otherwise we got a problem */
					{
						strncpy(condition->header_regexp, current_node->children->content, MAX_REGEXP_SIZE);
						condition->header_regexp[MAX_REGEXP_SIZE] = '\0';
						if(condition->header_regexp[0] == '\0')
						{
							syslog(LOG_ERR, "Void Match defined");
							return SYNONYM_CONFIG_ERROR;
						}
						if(regcomp(&condition->header_compiled_regexp, condition->header_regexp, REG_NOSUB|REG_ICASE))
						{
							syslog(LOG_ERR, "Invalid regular expression defined - \"%s\"", condition->header_regexp);
							return SYNONYM_CONFIG_ERROR;
						}
					}
					else
					{
						syslog(LOG_ERR, "Internal error in %s, line number %d", __FILE__, __LINE__);
						return SYNONYM_CONFIG_INTERNAL_ERROR;
					}
				else
				{ 
					syslog(LOG_ERR, "The match field must contain a value");
					return SYNONYM_CONFIG_ERROR;
				}
		}

		if(!strcasecmp(current_node->name, TAG_FROM) && current_node->type==XML_ELEMENT_NODE) /* Ok, go to the 'From' condition details - look for the node content */
		{
			if(condition->env_from_condition[0] != '\0') /* Did we find a 'From' element before? */
			{
				syslog(LOG_ERR, "More then one 'From' defined in a condition");
				return SYNONYM_CONFIG_ERROR;
			}
			else
				if(current_node->children != NULL)
					if(current_node->children->type==XML_TEXT_NODE) /* Has to be - otherwise we got a problem */
					{
						strncpy(condition->env_from_condition, current_node->children->content, MAX_REGEXP_SIZE);
						condition->env_from_condition[MAX_REGEXP_SIZE] = '\0';
						if(condition->env_from_condition[0] == '\0')
						{
							syslog(LOG_ERR, "Void 'From' defined");
							return SYNONYM_CONFIG_ERROR;
						}
						if(regcomp(&condition->env_from_compiled_regexp, condition->env_from_condition, REG_NOSUB|REG_ICASE))
						{
							syslog(LOG_ERR, "Invalid regular expression defined for 'From' - \"%s\"", condition->env_from_condition);
							return SYNONYM_CONFIG_ERROR;
						}
					}
					else
					{
						syslog(LOG_ERR, "Internal error in %s, line number %d", __FILE__, __LINE__);
						return SYNONYM_CONFIG_INTERNAL_ERROR;
					}
				else
				{
					syslog(LOG_ERR, "The 'From' must contain a value");
					return SYNONYM_CONFIG_ERROR;
				}
		}

		if(!strcasecmp(current_node->name, TAG_TO) && current_node->type==XML_ELEMENT_NODE) /* Ok, go to the 'To' condition details - look for the node content */
		{
			if(condition->env_to_condition[0] != '\0') /* Did we find a 'To' element before? */
			{
				syslog(LOG_ERR, "More then one 'To' defined in a condition");
				return SYNONYM_CONFIG_ERROR;
			}
			else
				if(current_node->children != NULL)
					if(current_node->children->type==XML_TEXT_NODE) /* Has to be - otherwise we got a problem */
					{
						strncpy(condition->env_to_condition, current_node->children->content, MAX_REGEXP_SIZE);
						condition->env_to_condition[MAX_REGEXP_SIZE] = '\0';
						if(condition->env_to_condition[0] == '\0')
						{
							syslog(LOG_ERR, "Void 'To' defined");
							return SYNONYM_CONFIG_ERROR;
						}
						if(regcomp(&condition->env_to_compiled_regexp, condition->env_to_condition, REG_NOSUB|REG_ICASE))
						{
							syslog(LOG_ERR, "Invalid regular expression defined for 'To' - \"%s\"", condition->env_from_condition);
							return SYNONYM_CONFIG_ERROR;
						}
					}
					else
					{
						syslog(LOG_ERR, "Internal error in %s, line number %d", __FILE__, __LINE__);
						return SYNONYM_CONFIG_INTERNAL_ERROR;
					}
				else
				{
					syslog(LOG_ERR, "The 'To' must contain a value");
					return SYNONYM_CONFIG_ERROR;
				}
		}

	}
	if((condition->env_from_condition[0] != '\0' && condition->env_to_condition[0] != '\0') ||
		(condition->env_from_condition[0] != '\0' && condition->header_field[0] != '\0') ||
		(condition->header_field[0] != '\0' && condition->env_to_condition[0] != '\0'))
	{ /* Only one of 'Header', 'From', 'To' may be defined in a single condition */
		syslog(LOG_ERR, "Only one of 'Header', 'From', 'To' may be defined in one condition");
		return SYNONYM_CONFIG_ERROR;
	}
	if(condition->header_field[0] == '\0' && condition->env_from_condition[0] == '\0' && condition->env_to_condition[0] == '\0')
	{
		syslog(LOG_ERR, "No 'Header', 'To' or 'From' defined in the condition");
		return SYNONYM_CONFIG_ERROR;
	}
	if(condition->header_field[0] != '\0' && condition->header_regexp[0] == '\0')
	{
		syslog(LOG_ERR, "No Match directive for the header condition");
		return SYNONYM_CONFIG_ERROR;
	}

	return SYNONYM_OK;
}

sresult Synonym_Parse_Action(xmlNodePtr action_node, synonym_action *action)
{
	xmlNodePtr current_node;
	for(current_node=action_node->next; current_node != NULL; current_node=current_node->next)
	{
		if(!strcasecmp(current_node->name, TAG_ACTIONTYPE) && current_node->type==XML_ELEMENT_NODE) /* Ok, go to the ActionType action details - look for the node content */
		{
			if(action->action_type != ACTION_NONE) /* Did we find an ActionType element before? */
			{
				syslog(LOG_ERR, "More then one action type defined in an action");
				return SYNONYM_CONFIG_ERROR;
			}
			else
				if(current_node->children != NULL)
					if(current_node->children->type==XML_TEXT_NODE) /* Has to be - otherwise we got a problem */
					{
						if(!strcasecmp(current_node->children->content, ACTIONNAME_COPY))
							action->action_type = ACTION_COPY;
						if(!strcasecmp(current_node->children->content, ACTIONNAME_DELETE))
							action->action_type = ACTION_DELETE;
						if(!strcasecmp(current_node->children->content, ACTIONNAME_REJECT))
							action->action_type = ACTION_REJECT;
						if(!strcasecmp(current_node->children->content, ACTIONNAME_DISCLAIMER))
							action->action_type = ACTION_DISCLAIMER;
						if(action->action_type == ACTION_NONE)
						{
							syslog(LOG_ERR, "Invalid action type defined");
							return SYNONYM_CONFIG_ERROR;
						}
					}
					else
					{
						syslog(LOG_ERR, "Internal error in %s, line number %d", __FILE__, __LINE__);
						return SYNONYM_CONFIG_INTERNAL_ERROR;
					}
				else
				{
					syslog(LOG_ERR, "Action must contain a value");
					return SYNONYM_CONFIG_ERROR;
				}
		}
		if(!strcasecmp(current_node->name, TAG_ACTIONADDRESS) && current_node->type==XML_ELEMENT_NODE) /* Ok, go to the Match condition details - look for the node content */
		{
			if(action->action_custom_field[0] != '\0') /* Did we find a address element before? */
			{
				syslog(LOG_ERR, "More then one address defined in the action");
				return SYNONYM_CONFIG_ERROR;
			}
			else
				if(current_node->children != NULL)
					if(current_node->children->type==XML_TEXT_NODE) /* Has to be - otherwise we got a problem */
					{
						strncpy(action->action_custom_field, current_node->children->content, MAX_ACTION_CUSTOMFIELD_SIZE);
						action->action_custom_field[MAX_ACTION_CUSTOMFIELD_SIZE] = '\0';
						if(action->action_custom_field[0] == '\0')
						{
							syslog(LOG_ERR, "Void action address defined");
							return SYNONYM_CONFIG_ERROR;
						}
					}
					else
					{
						syslog(LOG_ERR, "Internal error in %s, line number %d", __FILE__, __LINE__);
						return SYNONYM_CONFIG_INTERNAL_ERROR;
					}
				else
				{
					syslog(LOG_ERR, "The action address must contain a value");
					return SYNONYM_CONFIG_ERROR;
				}
		}
		if(!strcasecmp(current_node->name, TAG_TEXT_DISCLAIMER) && current_node->type==XML_ELEMENT_NODE) /* Ok, go to the Match condition details - look for the node content */
		{
#ifdef DISCLAIMER_SUPPORT
			if(action->action_text_disclaimer[0] != '\0') /* Did we find a address element before? */
			{
				syslog(LOG_ERR, "More then one text disclaimer defined in the action");
				return SYNONYM_CONFIG_ERROR;
			}
			else
				if(current_node->children != NULL)
					if(current_node->children->type==XML_TEXT_NODE) /* Has to be - otherwise we got a problem */
					{
						strncpy(action->action_text_disclaimer, current_node->children->content, MAX_DISCLAIMER_SIZE);
						action->action_text_disclaimer[MAX_DISCLAIMER_SIZE] = '\0';
						if(action->action_text_disclaimer[0] == '\0')
						{
							syslog(LOG_ERR, "Void text disclaimer defined");
							return SYNONYM_CONFIG_ERROR;
						}
					}
					else
					{
						syslog(LOG_ERR, "Internal error in %s, line number %d", __FILE__, __LINE__);
						return SYNONYM_CONFIG_INTERNAL_ERROR;
					}
				else
				{
					syslog(LOG_ERR, "The text disclaimer must contain a value");
					return SYNONYM_CONFIG_ERROR;
				}
#else
			syslog(LOG_ERR, "Disclaimer support not available. Please recompile.");
			return SYNONYM_CONFIG_ERROR;
#endif
		}
		if(!strcasecmp(current_node->name, TAG_HTML_DISCLAIMER) && current_node->type==XML_ELEMENT_NODE) /* Ok, go to the Match condition details - look for the node content */
		{
#ifdef DISCLAIMER_SUPPORT
			if(action->action_html_disclaimer[0] != '\0') /* Did we find a address element before? */
			{
				syslog(LOG_ERR, "More then one html disclaimer defined in the action");
				return SYNONYM_CONFIG_ERROR;
			}
			else
				if(current_node->children != NULL)
					if(current_node->children->type==XML_TEXT_NODE) /* Has to be - otherwise we got a problem */
					{
						strncpy(action->action_html_disclaimer, current_node->children->content, MAX_DISCLAIMER_SIZE);
						action->action_html_disclaimer[MAX_DISCLAIMER_SIZE] = '\0';
						if(action->action_html_disclaimer[0] == '\0')
						{
							syslog(LOG_ERR, "Void html disclaimer defined");
							return SYNONYM_CONFIG_ERROR;
						}
					}
					else
					{
						syslog(LOG_ERR, "Internal error in %s, line number %d", __FILE__, __LINE__);
						return SYNONYM_CONFIG_INTERNAL_ERROR;
					}
				else
				{
					syslog(LOG_ERR, "The html disclaimer must contain a value");
					return SYNONYM_CONFIG_ERROR;
				}
#else
			syslog(LOG_ERR, "Disclaimer support not available. Please recompile.");
			return SYNONYM_CONFIG_ERROR;
#endif
		}
		
	}
	if(action->action_type == ACTION_NONE)
	{
		syslog(LOG_ERR, "No action defined for the rule");
		return SYNONYM_CONFIG_ERROR;
	}
	if(action->action_custom_field[0] == '\0' && (action->action_type != ACTION_DELETE && action->action_type != ACTION_DISCLAIMER)) /* We enforce this rule only for actions other then delete; delete does not need the custom field */
	{
		syslog(LOG_ERR, "No address defined for the action of the rule");
		return SYNONYM_CONFIG_ERROR;
	}
#ifdef DISCLAIMER_SUPPORT
	if(action->action_text_disclaimer[0] == '\0' && action->action_html_disclaimer[0] == '\0' && action->action_type == ACTION_DISCLAIMER)
	{
		syslog(LOG_ERR, "Either text or html disclaimer body must be defined");
		return SYNONYM_CONFIG_ERROR;
	}
#endif

	return SYNONYM_OK;
}

sresult Synonym_Parse_Config_Rule(xmlNodePtr rule_node, synonym_rule *rule)
{
	xmlNodePtr current_node, text_node;

	synonym_condition *new_condition, *temp_condition;
	for(current_node=rule_node->next; current_node != NULL; current_node=current_node->next)
	{
		if(!strcasecmp(current_node->name, TAG_CONDITION) && current_node->type==XML_ELEMENT_NODE) /* Ok, go to the Condition details - look for the node content */
		{
			for(text_node=current_node->children; text_node!=NULL; text_node = text_node->children)
				if(text_node->type == XML_TEXT_NODE) /* Got the content */
				{
					new_condition = (synonym_condition*)malloc(sizeof(synonym_condition));
					if(new_condition == NULL)
					{
						syslog(LOG_CRIT, "CRITICAL: Memory allocation error - cannont continue");
						return SYNONYM_ALLOC_FAILED;
					}
					new_condition->next=NULL;
					new_condition->header_field[0]='\0';
					new_condition->header_regexp[0]='\0';
					new_condition->env_from_condition[0]='\0';
					new_condition->env_to_condition[0]='\0';

					if(Synonym_Parse_Condition(text_node, new_condition) != SYNONYM_OK)
					{
						free(new_condition);
						syslog(LOG_ERR, "Problem located in the Condition section of the config file");
						return SYNONYM_CONFIG_ERROR;
					}
					else /* add the rule to the end of the rule chain */
					{
						if(rule->conditions != NULL)
						{
							for(temp_condition=rule->conditions; temp_condition->next!=NULL; temp_condition=temp_condition->next)
								;
							temp_condition->next=new_condition;
						}
						else
							rule->conditions=new_condition;
					}
				}


		}
		if(!strcasecmp(current_node->name, TAG_ACTION) && current_node->type==XML_ELEMENT_NODE) /* Ok, go to the Rule details - look for the node content */
		{
			for(text_node=current_node->children; text_node!=NULL; text_node = text_node->children)
				if(text_node->type == XML_TEXT_NODE) /* Got the content */
					if(Synonym_Parse_Action(text_node, &(rule->action))!=SYNONYM_OK)
					{
						syslog(LOG_ERR, "Failed to parse an action section");
						return SYNONYM_CONFIG_ERROR;
					}
		}
	}
	if(rule->conditions == NULL)
	{
		syslog(LOG_ERR, "No conditions found in the rule");
		return SYNONYM_CONFIG_ERROR;
	}
	if(rule->action.action_type == ACTION_NONE)
	{
		syslog(LOG_ERR, "Action was not defined in the rule");
		return SYNONYM_CONFIG_ERROR;
	}
	return SYNONYM_OK;
}

sresult Synonym_Parse_Rules_Config_Section(xmlNodePtr rules_node, _type_synonym_config *config)
{
	xmlNodePtr current_node, text_node;
	synonym_rule *new_rule, *temp_rule;
	
	for(current_node=rules_node->next; current_node != NULL; current_node=current_node->next)
	{
		if(!strcasecmp(current_node->name, TAG_RULE) && current_node->type==XML_ELEMENT_NODE) /* Ok, go to the Rule details - look for the node content */
			for(text_node=current_node->children; text_node!=NULL; text_node = text_node->children)
				if(text_node->type == XML_TEXT_NODE) /* Got the content */
				{
					new_rule = (synonym_rule*)malloc(sizeof(synonym_rule));
					if(new_rule == NULL)
					{
						syslog(LOG_CRIT, "CRITICAL: Memory allocation error - cannont continue");
						return SYNONYM_ALLOC_FAILED;
					}
					new_rule->next=NULL;
					new_rule->conditions=NULL;
					new_rule->action.action_type=ACTION_NONE;
					new_rule->action.action_custom_field[0]='\0';
#ifdef DISCLAIMER_SUPPORT
					new_rule->action.action_text_disclaimer[0] = '\0';
					new_rule->action.action_html_disclaimer[0] = '\0';
#endif
					if(Synonym_Parse_Config_Rule(text_node, new_rule) != SYNONYM_OK)
					{
						free(new_rule);
						return SYNONYM_CONFIG_ERROR;
					}
					else /* add the rule to the end of the rule chain */
					{
						if(config->rules != NULL)
						{
							for(temp_rule=config->rules; temp_rule->next!=NULL; temp_rule=temp_rule->next)
								;
							temp_rule->next=new_rule;
						}
						else
							config->rules=new_rule;
					}
						
				}
	}
	if(config->rules == NULL) /* No rules were found in the config file */
	{
		syslog(LOG_ERR, "No rules defined in the configuration file");
		return SYNONYM_CONFIG_ERROR;
	}
	else
		return SYNONYM_OK;
}


sresult Synonym_Get_Config(char *configfile, _type_synonym_config *config)
{
	xmlDocPtr config_tree;
	xmlNodePtr current_node, text_node;

	config->rules=NULL; /* In order to be able to check wether at least one rule was defined */

	config_tree = xmlParseFile(configfile);

	if(configfile == NULL)
	{
		syslog(LOG_ERR, "Unable to load the configuration file %s (file does not exist or parse error)", configfile);
		return SYNONYM_FAILED_LOAD_CONFIG;
	}
	for(current_node=xmlDocGetRootElement(config_tree);current_node!=NULL;current_node=current_node->children)
	{
		if(!strcasecmp(current_node->name, TAG_RULES)) /* We are in the global section - look for the node content */
			for(text_node=current_node->children; text_node!=NULL; text_node = text_node->children)
				if(text_node->type == XML_TEXT_NODE)
					return Synonym_Parse_Rules_Config_Section(text_node, config);
				else
				{
					syslog(LOG_ERR, "Internal error in %s, line number %d", __FILE__, __LINE__);
					return SYNONYM_CONFIG_INTERNAL_ERROR;
				}
		else
		{
			syslog(LOG_ERR, "Unknown section (%s) encountered", current_node->name);
			return SYNONYM_CONFIG_ERROR;
		}
	}


	/* If we got here, no 'Rules' section was found so we got a problem */
	syslog(LOG_ERR, "Not Rules section found");
	return SYNONYM_CONFIG_ERROR;
}

void Synonym_Free_Config(_type_synonym_config *config)
{
	synonym_rule *current_rule, *previous_rule;
	synonym_condition *current_condition, *previous_condition;
	
	current_rule = config->rules;
	previous_rule = current_rule;
	
	while(current_rule!=NULL)
	{
		/* Go to the conditions and free them */
		current_condition = current_rule->conditions;
		previous_condition = current_condition;
		while(current_condition != NULL)
		{
			/* Fix leak in config.c*/
			regfree(&current_condition->header_compiled_regexp);
			
			previous_condition = current_condition;
			current_condition = current_condition->next;
			free(previous_condition);
		}
		previous_rule=current_rule;
		current_rule = current_rule->next;
		free(previous_rule);
	}
	config->rules = NULL;
}

sresult Synonym_Reload_Config(char *configfile, _type_synonym_config *config)
{
	_type_synonym_config temp_config;

	/* We'll load the configuration in a temporary structure to check if it (the config) is consistent, then replace the 'live' config
	   We need to have some mutexes enabled for the config */

	if(Synonym_Get_Config(configfile, &temp_config) != SYNONYM_OK)
	{
		syslog(LOG_ERR, "Failed to reload the configuration");
		return SYNONYM_CONFIG_ERROR;
	}
	else
	{ /* The configuration was ok, we should replace the old one */
		/* Lock the config */
		Synonym_Free_Config(config);
		/* Copy the configuration structure */
		config->rules = temp_config.rules;
		/* Release the lock*/
	}
	return SYNONYM_OK;


}



syntax highlighted by Code2HTML, v. 0.9.1