/*
** SellaNMS
** $Id: policy_statement_from_variable.cxx,v 1.23 2006/01/24 07:08:09 sella Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

static const char rcsid[] = "$Id: policy_statement_from_variable.cxx,v 1.23 2006/01/24 07:08:09 sella Exp $";

#include "policy_statement_from_variable.h"
#include "common/snmp_util.h"

#include <stdio.h>
#include <stl/vector>
#include <stl/slist>
#include <stl/string>

PolicyStatementFromVariable::PolicyStatementFromVariable():type(0), function(0), runtimeVariable(false) {
	this->name = "unknown";

	/*
		This expression validates times in the hh:mm and h:mm formats of a 24
		hour clock.
	*/
	if (regcomp(&(this->timeFormat), "^([0-9]|[0-1][0-9]|[2][0-3]):([0-5][0-9])$", REG_EXTENDED) != 0) {
		LMSG("DEBUG: regcomp() returned non-zero for this->timeFormat");
		abort();
	}

	/*
		This expression validates date in the m/d/yyyy format where m and d are 1 or 2 digits which
		may be zero padded and yyyy is a 4 digit year between 1900 and 2999.  The separator may be
		either a slash (/), dash (-), or dot (.).
	*/
	if (regcomp(&(this->dateFormat), "^([1-9]|0[1-9]|1[0-2])[/.\\-]([1-9]|[0-2][0-9]|3[0-1])[/.\\-](19[0-9][0-9]|2[0-9][0-9][0-9])", REG_EXTENDED) != 0) {
		LMSG("DEBUG: regcomp() returned non-zero for this->dateFormat");
		abort();
	}

	this->dayOfWeek["Sunday"] = 0;
	this->dayOfWeek["Monday"] = 1;
	this->dayOfWeek["Tuesday"] = 2;
	this->dayOfWeek["Wednesday"] = 3;
	this->dayOfWeek["Thursday"] = 4;
	this->dayOfWeek["Friday"] = 5;
	this->dayOfWeek["Saturday"] = 6;
}

PolicyStatementFromVariable::PolicyStatementFromVariable(PolicyStatementFromVariable const &src) {
	bzero(&(this->timeFormat), sizeof(regex_t));
	bzero(&(this->dateFormat), sizeof(regex_t));

   *this = src;
}

/*
	The default destructor.
*/
PolicyStatementFromVariable::~PolicyStatementFromVariable() {
	for (slist<pair<string, regex_t> >::iterator r = this->regexes.begin(); r != this->regexes.end(); ++r) {
		SAFE_REGFREE((&(r->second)));
	}

	SAFE_REGFREE((&(this->timeFormat)));
	SAFE_REGFREE((&(this->dateFormat)));
}

PolicyStatementFromVariable const& PolicyStatementFromVariable::operator=(PolicyStatementFromVariable const &src) {
	/* Wipe contents. */
	this->clear();

	/* Do object specifics. */
	this->type = src.type;
	this->function = src.function;
	this->runtimeVariable = src.runtimeVariable;

	this->name = src.name;

	this->mapStatements = src.mapStatements;

	this->values = src.values;

	this->integers = src.integers;
	this->dates = src.dates;
	this->times = src.times;
	this->days = src.days;
	this->netblocks = src.netblocks;

	/* Deallocate any local regexes. */
	for (slist<pair<string, regex_t> >::iterator r = this->regexes.begin(); r != this->regexes.end(); ++r) {
		SAFE_REGFREE((&(r->second)));
	}
	this->regexes.clear();

	/* Duplicte external regexes and insert into local list. */
	pair<string, regex_t> tmp_entry;
	for (slist<pair<string, regex_t> >::const_iterator r = src.regexes.begin(); r != src.regexes.end(); ++r) {
		if (regcomp(&(tmp_entry.second), r->first.c_str(), REG_EXTENDED) != 0) {
	      LMSG("WARNING: regcomp() returned non-zero for %s", r->first.c_str());
	   } else {
			/* Insert new entry into list. */
			tmp_entry.first = r->first;
			this->regexes.insert(this->regexes.end(), tmp_entry);
		}
	}

	/* Iff !(this->dayOfWeek.empty()), then the default construct was called and we must free timeFormat and dateFormat to avoid a leak. */
	if (!this->dayOfWeek.empty()) {
		SAFE_REGFREE((&(this->timeFormat)));
		SAFE_REGFREE((&(this->dateFormat)));
	} else {
		this->dayOfWeek = src.dayOfWeek;
	}

	if (this->timeFormat.buffer != NULL) {
		LMSG("timeFormat.buffer != NULL. Allocated: %lu/%lu", this->timeFormat.allocated, this->timeFormat.used);
	}

	if (this->dateFormat.buffer != NULL) {
		LMSG("dateFormat.buffer != NULL. Allocated: %lu/%lu", this->dateFormat.allocated, this->dateFormat.used);
	}

	if (regcomp(&(this->timeFormat), "^([0-9]|[0-1][0-9]|[2][0-3]):([0-5][0-9])$", REG_EXTENDED) != 0) {
		LMSG("WARNING: regcomp() returned non-zero for this->timeFormat");
		abort();
	}

	if (regcomp(&(this->dateFormat), "^([1-9]|0[1-9]|1[0-2])[/.\\-]([1-9]|[0-2][0-9]|3[0-1])[/.\\-](19[0-9][0-9]|2[0-9][0-9][0-9])", REG_EXTENDED) != 0) {
		LMSG("WARNING: regcomp() returned non-zero for this->dateFormat");
		abort();
	}

	/* NOTE: I believe these are correct (shallow copy). */
	this->compiledPolicies = src.compiledPolicies;
	this->compiledMaps = src.compiledMaps;

	return *this;
}

RESULT PolicyStatementFromVariable::getAppliedMapStatements(slist<string> &map_names) {
	for (slist<string>::iterator m = this->mapStatements.begin(); m != this->mapStatements.end(); ++m) {
		map_names.push_front(*m);
	}
	return SUCCESS;
}

RESULT PolicyStatementFromVariable::getReferencedVariable(variable_t &variable) {
	variable.name = this->name;
	variable.values = this->values;

	return SUCCESS;
}

RESULT PolicyStatementFromVariable::setCompiledStatements(hash_map<string, PolicyStatement> *policies, hash_map<string, MapStatement> *maps) {
	this->compiledPolicies = policies;
	this->compiledMaps = maps;

	return SUCCESS;
}

ACTION PolicyStatementFromVariable::evaluate(variable_binding_t &input) {
	long										value;
	char										substring[16];
	
	time_t									t;
	long										x;
	char										*p;

	Netblock									address;

	regmatch_t								pmatch[MAX_REGEX_MATCH];

	hash_map<string, long>::iterator	day;

	string									runtime_name = this->name;
	slist<string>							runtime_values = this->values;

	/* Perform the necessary map-statements to fill our input variables up. */
#define DISABLED
#ifdef DISABLED
	for (slist<string>::iterator m = this->mapStatements.begin(); m != this->mapStatements.end(); ++m) {
		if (this->compiledMaps->find(*m) != this->compiledMaps->end()) {
			((*(this->compiledMaps))[*m]).evaluate(input);
		}
	}
#endif
	/* If the name of this variable is runtime replacable, then replace it's value. */
	this->replaceVariables(input, runtime_name);

	variable_binding_t::iterator		in = input.find(runtime_name); /* Only perform check if its in our data set (input). */

	if (in != input.end()) {
		if (this->type & TYPE_PFX) {
			if (this->runtimeVariable) {
				/* FIXME: Can't do runtime replacements yet... */
				LMSG("WARNING: Runtime replacements on prefix types is not implemented yet.");
			}
			if (address.setNetblock(in->second.c_str()) == SUCCESS) {
				for (slist<Netblock>::iterator prefix = this->netblocks.begin(); prefix != this->netblocks.end(); ++prefix) {
					if (this->function == FUNCTION_EQ && prefix->compare(address) == SUCCESS) {
						return ACTION_THEN;
					} else if (this->function == FUNCTION_NE && prefix->compare(address) != SUCCESS) {
						return ACTION_THEN;
					}
				}
			}
		}
		if (this->type & TYPE_INT) {
			value = strtol(in->second.c_str(), NULL, 10);

			if (this->runtimeVariable) {
				this->integers.clear();
				runtime_values = this->values;
				for (slist<string>::iterator v = runtime_values.begin(); v != runtime_values.end(); ++v) {
					this->replaceVariables(input, *v);

					x = strtol(v->c_str(), &p, 10);
					if (*p == '\0') {
						this->integers.insert(this->integers.end(), x);
					}
				}
			}
			for (slist<long>::iterator i = this->integers.begin(); i != this->integers.end(); ++i) {
				if (this->evaluateFunction(value, *i) == ACTION_THEN) {
					return ACTION_THEN;
				}
			}
		}
		if (this->type & TYPE_STR) {
			if (this->runtimeVariable) {
				/* FIXME: Can't do runtime replacements yet... */
				LMSG("WARNING: Runtime replacements on string types is not implemented yet.");
			}
			for (slist<string>::iterator s = this->values.begin(); s != this->values.end(); ++s) {
				value = in->second.compare(*s);

				if (this->evaluateFunction(value, 0) == ACTION_THEN) {
					return ACTION_THEN;
				}
			}
		}
		if (this->type & TYPE_DAY) {
			day = this->dayOfWeek.find(in->second);

			if (this->runtimeVariable) {
				/* FIXME: Can't do runtime replacements yet... */
				LMSG("WARNING: Runtime replacements on day-of-week types is not implemented yet.");
			}
			if (day != this->dayOfWeek.end()) {
				for (slist<long>::iterator d = this->days.begin(); d != this->days.end(); ++d) {
					if (this->evaluateFunction(day->second, *d) == ACTION_THEN) {
						return ACTION_THEN;
					}
				}
			}
		}
		if (this->type & TYPE_DTE) {
			/*
				Uses American Date Format where 9/8/1977 represents September 8, 1977.

				Translates date into an integer value:
					9/8/1977 == 19970809;
			*/
			if (this->runtimeVariable) {
				/* FIXME: Can't do runtime replacements yet... */
				LMSG("WARNING: Runtime replacements on date types is not implemented yet.");
			}
			if (this->dateFormat.buffer == NULL) { LMSG("NULL dateformat!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); }
			if (regexec(&(this->dateFormat), in->second.c_str(), MAX_REGEX_MATCH, pmatch, 0) == 0) {
				value = strtol(in->second.substr(pmatch[3].rm_so, pmatch[3].rm_eo - pmatch[3].rm_so).c_str(), NULL, 10) * 10000;
				value += strtol(in->second.substr(pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so).c_str(), NULL, 10) * 100;
				value += strtol(in->second.substr(pmatch[2].rm_so, pmatch[2].rm_eo - pmatch[2].rm_so).c_str(), NULL, 10);

				for (slist<long>::iterator d = this->dates.begin(); d != this->dates.end(); ++d) {
					if (this->evaluateFunction(value, *d) == ACTION_THEN) {
						return ACTION_THEN;
					}
				}
			}
		}
		if (this->type & TYPE_TME) {
			/*
				Uses Time Format 23:15.

				Translates time into an integer value:
					23:15 == 2315;
			*/
			if (this->runtimeVariable) {
				/* FIXME: Can't do runtime replacements yet... */
				LMSG("WARNING: Runtime replacements on time types is not implemented yet.");
			}
			if (this->timeFormat.buffer == NULL) { LMSG("NULL timeformat !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); }
			if (regexec(&(this->timeFormat), in->second.c_str(), MAX_REGEX_MATCH, pmatch, 0) == 0) {
				value = strtol(in->second.substr(pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so).c_str(), NULL, 10) * 100;
				value += strtol(in->second.substr(pmatch[2].rm_so, pmatch[2].rm_eo - pmatch[2].rm_so).c_str(), NULL, 10);

				for (slist<long>::iterator d = this->times.begin(); d != this->times.end(); ++d) {
					if (this->evaluateFunction(value, *d) == ACTION_THEN) {
						return ACTION_THEN;
					}
				}
			}
		}
		if (this->type & TYPE_REG) {
			if (this->runtimeVariable) {
				/* FIXME: Can't do runtime replacements yet... */
				LMSG("WARNING: Runtime replacements on regex types is not implemented yet.");
			}
			for (slist<pair<string, regex_t> >::iterator r = this->regexes.begin(); r != this->regexes.end(); ++r) {
				t = time(NULL);
				if (r->second.buffer == NULL) { LMSG("NULL r->second.buffer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); }
				value = regexec(&(r->second), in->second.c_str(), MAX_REGEX_MATCH, pmatch, 0);

				if (this->evaluateFunction(value, 0) == ACTION_THEN) {
					for (long i = 0; i < MAX_REGEX_MATCH; i++) {
						if (pmatch[i].rm_so != -1) {
							snprintf(substring, 16, "[%ld]", i);

							input[this->name + substring] = in->second.substr(pmatch[i].rm_so, pmatch[i].rm_eo - pmatch[i].rm_so);
						} else {
							break;
						}
					}
					return ACTION_THEN;
				}
			}
		}
	}
	return ACTION_NEXT_TERM;
}

ACTION PolicyStatementFromVariable::evaluateFunction(long left, long right) {
	if (this->function & FUNCTION_EQ) {
		if (left == right) return ACTION_THEN;
	}
	if (this->function & FUNCTION_GE) {
		if (left >= right) return ACTION_THEN;
	}
	if (this->function & FUNCTION_LE) {
		if (left <= right) return ACTION_THEN;
	}
	if (this->function & FUNCTION_GT) {
		if (left > right) return ACTION_THEN;
	}
	if (this->function & FUNCTION_LT) {
		if (left < right) return ACTION_THEN;
	}
	if (this->function & FUNCTION_NE) {
		if (left != right) return ACTION_THEN;
	}
	return ACTION_NEXT_TERM;
}

/*
	Compile this variable.
	
	Parameters
		config:		the part of the configuration from the level "policy-options policy-statement <policy> term <term> from <variable>".

	Return Values
		SUCCESS:	if the variable was successfully compiled.
		FAILURE:	if the variable was unable to be compiled.
*/
RESULT PolicyStatementFromVariable::compile(ConfigNode *config) {
	RESULT result = SUCCESS;

	vector<string>	values;
	string			oid;

	if (config->assignValues("", values)) {
		for (vector<string>::iterator value = values.begin(); value != values.end(); ++value) {
			oid = *value;
			resolveMIBObject(oid);

			this->addValue(oid);
		}
	} else {
		result = FAILURE;
	}
	return result;
}

/*
	Set the name of this variable.

	Parameters
		name:		the name to set for this variable.

	Return Values
		SUCCESS:	if the name of the variable was successfully set.
		FAILURE:	if the name of the variable was unable to be set.
*/
RESULT PolicyStatementFromVariable::setName(string &name) {
	this->name = name;

	return SUCCESS;
}

/*
	Add an accepted value for this variable.

	Parameters
		value:		the accepted value to add to this variable.

	Return Values
		SUCCESS:	if the new value of the variable was successfully added.
		FAILURE:	if the new value of the variable was unable to be added.
*/
RESULT PolicyStatementFromVariable::addValue(string &value) {
	this->values.insert(this->values.end(), value);

	return SUCCESS;
}

/*
	Add a type for this variable.

	Parameters
		type:		the type macro to be added to this variable.

	Return Values
		SUCCESS:	if the new type of the variable was successfully added.
		FAILURE:	if the new type of the variable was unable to be added.
*/
RESULT PolicyStatementFromVariable::addType(unsigned long type) {
	this->type |= type;
	
	return SUCCESS;
}

/*
	Add a function to use for evaluation of this variable.

	Parameters
		function:	the function macro to be added to this variable.

	Return Values
		SUCCESS:	if the new evaluation function of the variable was successfully added.
		FAILURE:	if the new evaluation function of the variable was unable to be added.
*/
RESULT PolicyStatementFromVariable::addFunction(unsigned long function) {
	this->function |= function;
	
	return SUCCESS;
}

/*
	Add a map-statement to evaluate before performing any functions on this variable.

	Parameters
		map_name:	the name of the map-statement to add.
	
	Return Values
		SUCCESS:	if the new map-statement of the variable was successfully added.
		FAILURE:	if the new map-statement of the variable was unable to be added.
*/
RESULT PolicyStatementFromVariable::addMapStatement(string &map_name) {
	this->mapStatements.insert(this->mapStatements.end(), map_name);
	
	return SUCCESS;
}

RESULT PolicyStatementFromVariable::applyFormatStatement(FormatStatement &format) {
	RESULT result = SUCCESS;

	char 				*p = NULL;
	long 				x = 0;

	unsigned long	type = 0;
	unsigned long	function = 0;

	Netblock			network;

	pair<string, regex_t>	r;
	regmatch_t		pmatch[MAX_REGEX_MATCH];

	if (format.setVariableType(type, this->name) != SUCCESS) {
		LMSG("Policy: invalid variable '%s' in from clause.", this->name.c_str());

		result = FAILURE;
	} else if (format.setVariableFunction(function, this->name) != SUCCESS) {
		LMSG("Policy: invalid variable '%s' in from clause.", this->name.c_str());

		result = FAILURE;
#if 1
	} else if (format.setVariableMap(mapStatements, this->name) != SUCCESS) {
		LMSG("Policy: invalid variable '%s' in from clause.", this->name.c_str());

		result = FAILURE;
#endif
	} else {
		this->addType(type);
		this->addFunction(function);
		/* ADDED */
		// this->addMapStatement(this->name);

		if (this->type & TYPE_INT) {
			for (slist<string>::iterator v = this->values.begin(); v != this->values.end(); ++v) {
				if (this->containsVariable(*v)) {
					this->runtimeVariable = true;
				} else {
					x = strtol(v->c_str(), &p, 10);
					if (*p == '\0') {
						this->integers.insert(this->integers.end(), x);
					} else {
						LMSG("Policy: %s is not a valid integer value.", v->c_str());
	
						result = FAILURE;
					}
				}
			}
		}
		if (this->type & TYPE_REG) {
			for (slist<string>::iterator v = this->values.begin(); v != this->values.end(); ++v) {
				if (this->containsVariable(*v)) {
						LMSG("Policy: runtime variable resolution is not supported for regular expression types (variable: %s).", v->c_str());
	
						result = FAILURE;
				} else {
					if (regcomp(&(r.second), v->c_str(), REG_EXTENDED) == 0) {
						r.first = *v;
						this->regexes.insert(this->regexes.end(), r);
					} else {
						LMSG("Policy: %s is not a valid POSIX extended regular expression.", v->c_str());

						result = FAILURE;
					}
				}
			}
		}
		if (this->type & TYPE_DAY) {
			for (slist<string>::iterator v = this->values.begin(); v != this->values.end(); ++v) {
				if (this->containsVariable(*v)) {
						LMSG("Policy: runtime variable resolution is not supported for day types (variable: %s).", v->c_str());
	
						result = FAILURE;
				} else {
					if (this->dayOfWeek.find(*v) != this->dayOfWeek.end()) {
						this->days.insert(this->days.end(), this->dayOfWeek[*v]);
					} else {
						LMSG("Policy: %s is not a valid day of the week.", v->c_str());

						result = FAILURE;
					}
				}
			}
		}
		if (this->type & TYPE_DTE) {
			/*
				Uses American Date Format where 9/8/1977 represents September 8, 1977.

				Translates date into an integer value:
					9/8/1977 == 19970809;
			*/
			for (slist<string>::iterator v = this->values.begin(); v != this->values.end(); ++v) {
				if (this->containsVariable(*v)) {
						LMSG("Policy: runtime variable resolution is not supported for date types (variable: %s).", v->c_str());
	
						result = FAILURE;
				} else {
					if (regexec(&(this->dateFormat), v->c_str(), MAX_REGEX_MATCH, pmatch, 0) == 0) {
						x = strtol(v->substr(pmatch[3].rm_so, pmatch[3].rm_eo - pmatch[3].rm_so).c_str(), NULL, 10) * 10000;
						x += strtol(v->substr(pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so).c_str(), NULL, 10) * 100;
						x += strtol(v->substr(pmatch[2].rm_so, pmatch[2].rm_eo - pmatch[2].rm_so).c_str(), NULL, 10);

						this->dates.insert(this->dates.end(), x);
					} else {
						LMSG("Policy: %s is not a valid date using the American format where 9/8/1977 represents September 8, 1977.", v->c_str());

						result = FAILURE;
					}
				}
			}
		}
		if (this->type & TYPE_TME) {
			/*
				Uses Time Format 23:15.

				Translates time into an integer value:
					23:15 == 2315;
			*/
			for (slist<string>::iterator v = this->values.begin(); v != this->values.end(); ++v) {
				if (this->containsVariable(*v)) {
						LMSG("Policy: runtime variable resolution is not supported for time types (variable: %s).", v->c_str());
	
						result = FAILURE;
				} else {
					if (regexec(&(this->timeFormat), v->c_str(), MAX_REGEX_MATCH, pmatch, 0) == 0) {
						x = strtol(v->substr(pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so).c_str(), NULL, 10) * 100;
						x += strtol(v->substr(pmatch[2].rm_so, pmatch[2].rm_eo - pmatch[2].rm_so).c_str(), NULL, 10);

						this->times.insert(this->times.end(), x);
					} else {
						LMSG("Policy: %s is not a valid time using the 24 clock format of h/mm or hh/mm.", v->c_str());

						result = FAILURE;
					}
				}
			}
		}
		if (this->type & TYPE_PFX) {
			for (slist<string>::iterator v = this->values.begin(); v != this->values.end(); ++v) {
				if (this->containsVariable(*v)) {
						LMSG("Policy: runtime variable resolution is not supported for prefix types (variable: %s).", v->c_str());
	
						result = FAILURE;
				} else {
					if (network.setNetblock(v->c_str()) == SUCCESS) {
						this->netblocks.insert(this->netblocks.end(), network);
					} else {
						LMSG("Policy: %s is not a valid prefix in slash notation.", v->c_str());
	
						result = FAILURE;
					}
				}
			}
		}
	}
	return result;
}

/*
	Print the information about this variable.
	
	Return Values
		SUCCESS:	if the variable was successfully printed.
		FAILURE:	if the variable was unable to be printed.
*/
RESULT PolicyStatementFromVariable::print(unsigned long depth) {
	string	tab = "", str;
	for (unsigned long i = 0; i < depth; i++) {
		tab.append("   ");
	}

	LMSG("%svariable %s {", tab.c_str(), this->name.c_str());
	LMSG("%s   type         0x%x;", tab.c_str(), this->type);
	LMSG("%s   function     0x%x;", tab.c_str(), this->function);
	LMSG("");

	if (!this->values.empty()) {
		str.erase();
		for (slist<string>::iterator value = this->values.begin(); value != this->values.end(); ++value) {
			str.append(" " +  *value);
		}
		LMSG("%s   values       [%s ];", tab.c_str(), str.c_str());
	}
	LMSG("%s}", tab.c_str());
	
	if (!this->mapStatements.empty()) {
		str.erase();
		for (slist<string>::iterator m = this->mapStatements.begin(); m != this->mapStatements.end(); ++m) {
			str.append(" " + *m);
		}
		LMSG("%s   apply-map [%s ];", tab.c_str(), str.c_str());
	}

	return SUCCESS;
}

/*
	Clear the information about this variable.
	
	Return Values
		SUCCESS:	if the variable was successfully cleared.
		FAILURE:	if the variable was unable to be cleared.
*/
RESULT PolicyStatementFromVariable::clear() {
	this->type = 0;
	this->function = 0;
	this->name = "unknown";
	this->values.clear();
	this->mapStatements.clear();

	return SUCCESS;
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim:
** vim: noet ts=3 sw=3 sts=3
*/
