/*
** SellaNMS
** $Id: email.cxx,v 1.17 2006/01/24 07:08:10 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: email.cxx,v 1.17 2006/01/24 07:08:10 sella Exp $";

#include "email.h"
#include "daemon.h"
#include "common/smtp.h"
#include "storage/storagedata.h"
#include "storage/storage.h"
#include <stdio.h>
#include <unistd.h>
#include <sched.h>
#include <sys/stat.h>
#include <stl/slist>
#include <stl/string>
#include <stl/vector>

extern ConfigNode* config;
extern bool interrupt;

Email::Email() {
}

Email::~Email() {
}

RESULT Email::execute() {
	RESULT result = SUCCESS;
	variable_binding_t polInput;
	variable_binding_t polOutput;

	TRACE(TRACE_THREAD, "Email::execute() starting");

	while (!interrupt) {
		if (readOutput("email") == FAILURE) {
			interrupt = true;
			result = FAILURE;
			continue;
		}

		if (output.size() <= 0) {
			usleep(interval); /* No data. Sleep. */
			continue;
		}

		while (!interrupt && policyInputNext(polInput) == SUCCESS) {
			/* Clear the output container. */
			polOutput.clear();
			polInput["process"] = "email";
			polInput["origin"] = "output";

			/* Run data though the input policy. */
			switch (inputPolicy.evaluate(polOutput, polInput)) {
				case ACTION_FAILURE:
					LMSG("NOTICE: Failed to apply input policy.");

					interrupt = true;
					result = FAILURE;
					continue;

				case ACTION_REJECT:
					TRACE(TRACE_DUMP, "Reject by input policy: %s", polInput["brief"].c_str());
					continue;
			}

			/* Make the switch to data for the output policy. */
			if (!inputPolicy.empty()) {
				polInput = polOutput;
				polOutput.clear();
			}

			/* Run data though the output policy. */
			switch (outputPolicy.evaluate(polOutput, polInput)) {
				case ACTION_FAILURE:
					LMSG("NOTICE: Failed to apply output policy.");

					interrupt = true;
					result = FAILURE;
					continue;

				case ACTION_REJECT:
					TRACE(TRACE_DUMP, "Reject by output policy: %s", polInput["brief"].c_str());
					continue;
			}

			TRACE(TRACE_DUMP, "Accepted: %s", polOutput["brief"].c_str());

			/* Process accepted data. */
			processPolicyOutput(polOutput);
		}

		/* Short sleep. */
#ifdef _POSIX_PRIORITY_SCHEDULING
		sched_yield();
#else
		usleep(0);
#endif
	}

	TRACE(TRACE_THREAD, "Email::execute() exiting");

	return result;
}

RESULT Email::readConfig() {
	RESULT result = SUCCESS;

	vector<string> v;
	ConfigNode node;

	if (!config->assignNode(getConfigPath(), node)) {
		LMSG("No '%s' section found within the config file.", getConfigPath());

		return FAILURE;
	}

	node.assignValue("interval", interval, OUTPUT_EMAIL_INTERVAL_DEF, OUTPUT_EMAIL_INTERVAL_MIN, OUTPUT_EMAIL_INTERVAL_MAX, OUTPUT_EMAIL_INTERVAL_MLT);
	node.assignValue("queue-depth", queueDepth, OUTPUT_EMAIL_QUEUEDEPTH_DEF, OUTPUT_EMAIL_QUEUEDEPTH_MIN, OUTPUT_EMAIL_QUEUEDEPTH_MAX);

	/* Clear any existing policies. */
	inputPolicy.clear();
	outputPolicy.clear();

	/* Add and compile input policy. */
	if (node.assignValues("policy input", v)) {
		if (inputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("NOTICE: Failed to add/compile input policies.");
			result = FAILURE;
		}
	}

	/* Add and compile output policy. */
	if (node.assignValues("policy output", v)) {
		if (outputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("NOTICE: Failed to add/compile output policies.");
			result = FAILURE;
		}
	}

	/* Verify we have required policies. */
	if (outputPolicy.empty()) {
		LMSG("NOTICE: A output policy is required for this module.");
		result = FAILURE;
	}

   /* Configure Storage */
	if (storage.setConfig(*config) != SUCCESS) {
		result = FAILURE;
	}

	printConfig();

	return result;
}

void Email::printConfig() {
	string str;
	vector<string> v;
	vector<string>::iterator v_it;

	if (!TRACE_ISSET(TRACE_CONFIG)) {
		return;
	}

	TRACE(TRACE_CONFIG, "interval: %lu sec", interval / OUTPUT_EMAIL_INTERVAL_MLT);
	TRACE(TRACE_CONFIG, "queue-depth: %lu", queueDepth);

	if (config->assignValues("output email default", v)) {
		TRACE(TRACE_CONFIG, "default {");
		for (v_it = v.begin(); v_it != v.end(); ++v_it) {
			if (config->assignValue("output email default " + *v_it, str)) {
				TRACE(TRACE_CONFIG, "  %s %s", v_it->c_str(), str.c_str());
			}
		}
		TRACE(TRACE_CONFIG, "}");
	}

	if (config->assignValues("output email alias", v)) {
		TRACE(TRACE_CONFIG, "alias {");
		for (v_it = v.begin(); v_it != v.end(); ++v_it) {
			if (config->assignValue("output email alias " + *v_it, str)) {
				TRACE(TRACE_CONFIG, "  %s", v_it->c_str());
			}
		}
		TRACE(TRACE_CONFIG, "}");
	}

	if (config->assignValues("output email policy input", v)) {
		str.erase();
		for (v_it = v.begin(); v_it != v.end(); ++v_it) {
			str.append(*v_it + " ");
		}
		TRACE(TRACE_CONFIG, "policy input: %s", str.c_str());
	} else {
		TRACE(TRACE_CONFIG, "policy input: none");
	}

	if (config->assignValues("output email policy output", v)) {
		str.erase();
		for (v_it = v.begin(); v_it != v.end(); ++v_it) {
			str.append(*v_it + " ");
		}
		TRACE(TRACE_CONFIG, "policy output: %s", str.c_str());
	} else {
		TRACE(TRACE_CONFIG, "policy output: none");
	}
}

RESULT Email::readOutput(const string &module) {
	RESULT result = SUCCESS;

	output_t out;
	string str;
	StorageData data;
	slist<unsigned long> output_ids;

	if (storage.queryf(data, "SELECT id, timestamp, UNIX_TIMESTAMP(timestamp) AS unix_timestamp, element, interface, ip, source, module, protocol, channel, state, brief, detail FROM output WHERE module = '%s' ORDER BY timestamp LIMIT %lu", module.c_str(), queueDepth) == SUCCESS) {
		while (data.nextRow()) {
			data.getULong(out.id, "id");

			//No idea what libdbi thinks the type should be. Its angry about every type.
			data.getAuto(str, "unix_timestamp");
			out.unix_timestamp = (time_t) atol(str.c_str());

			data.getLong(out.element, "element");

			data.getLong(out.interface, "interface");

			data.getLong(out.ip, "ip");

			data.getString(out.source, "source");

			data.getString(out.module, "module");

			data.getString(out.protocol, "protocol");

			data.getString(out.channel, "channel");

			data.getString(out.status, "state");

			data.getString(out.brief, "brief");

			data.getBinary(out.detail, "detail");

			output.push_front(out);

			output_ids.push_front(out.id);
		}
		result = SUCCESS;
	} else {
		LMSG("NOTICE: Failed to read output data from storage.");
		result = FAILURE;
	}

	if (!output_ids.empty()) {
		result = deleteOutput(output_ids);
	}

	return result;
}

RESULT Email::policyInputNext(variable_binding_t &policy_input) {
	char buf[256];
	output_t out;

	tm ts;

	if (output.empty()) {
		return FAILURE;
	}

	policy_input.clear();

	out = output.front();
	output.pop_front();

	/* Element */
	policy_input["element-id"] = out.element;

	/* Interface */
	policy_input["interface-id"] = out.interface;

	/* IPAddress */
	policy_input["ip-id"] = out.ip;

	/* Timestamp */
//		policy_input["timestamp"] = out.timestamp;

	/* Source */
	policy_input["source"] = out.source;

	/* Module */
	policy_input["module"] = out.module;

	/* Protocol */
	policy_input["protocol"] = out.protocol;

	/* Channel */
	policy_input["channel"] = out.channel;

	/* Status */
	policy_input["state"] = out.status;

	/* Brief */
	policy_input["brief"] = out.brief;

	/* Detail */
	policy_input["detail"] = out.detail;

	/* Setup the time. */
	localtime_r(&out.unix_timestamp, &ts);

	/* Date/Time */
	strftime(buf, 256, "%m/%d/%Y %H:%M:%S", &ts);
	policy_input["date-time"] = buf;

	/* Date */
	strftime(buf, 256, "%m/%d/%Y", &ts);
	policy_input["date"] = buf;

	/* Day of Week */
	strftime(buf, 256, "%a", &ts);
	policy_input["day-of-week"] = buf;

	/* Begin and End Time */
	strftime(buf, 256, "%H:%M", &ts);
	policy_input["begin-time"] = buf;
	policy_input["end-time"] = buf;

	/* Queue Size */
	snprintf(buf, 256, "%lu", output.size() + 1);
	policy_input["queue-size"] = buf;

	return SUCCESS;
}

RESULT Email::deleteOutput(slist<unsigned long> &output_ids) {
	RESULT result = SUCCESS;

	string sql;
	char buf[132];
	StorageData data;

	if (!output_ids.empty()) {
		/* Build DELETE string. */
		sql = "DELETE FROM output WHERE ";
		for (slist<unsigned long>::iterator it = output_ids.begin(); it != output_ids.end(); ++it) {
			snprintf(buf, 132, "id = %lu OR ", *it);
			sql.append(buf);
		}
		/* Remove the last ' OR ' */
		sql.erase(sql.size() - 4);

		if (storage.query(data, sql.c_str()) != SUCCESS) {
			LMSG("NOTICE: Failed to delete output data from storage.");
			result = FAILURE;
		}
	} else {
		LMSG("NOTICE: deleteOutput called with no input. Why??");
		return FAILURE;
	}

	return result;
}

RESULT Email::processPolicyOutput(variable_binding_t &policy_output) {
	unsigned int tmp;
	SMTP smtp;
	vector<string> values;
	vector<string>::iterator it;
	ConfigNode *alias_node = NULL, *default_node = NULL;
	bool delivered;

	smtp.setSyslog(true);
	smtp.setSeverity(TRACE_GET_SEV());

	if (TRACE_ISSET(TRACE_INTERNAL)) {
		smtp.setDebug(true);
	}

	/* Determine if we have a default. */
	config->assignNode("output email default", default_node);

	/* Determine if we have an alias. */
	if (policy_output.find("alias") != policy_output.end()) {
		config->assignNode("output email alias " + policy_output["alias"], alias_node);

		TRACE(TRACE_DEBUG, "set alias: %s", policy_output["alias"].c_str());
	} else {
		alias_node = NULL;
	}

	/* Set our From field(s). */
	if (assignValues("from", policy_output, alias_node, default_node, values)) {
		for (it = values.begin(); it != values.end(); ++it) {
			smtp.setFrom(*it);

			TRACE(TRACE_DEBUG, "set from: %s", it->c_str());
		}
	}

	/* Set our ReplyTo field(s). */
	if (assignValues("reply-to", policy_output, alias_node, default_node, values)) {
		for (it = values.begin(); it != values.end(); ++it) {
			smtp.setReplyTo(*it);

			TRACE(TRACE_DEBUG, "set reply-to: %s", it->c_str());
		}
	}

	/* Set our To field(s). */
	if (assignValues("to", policy_output, alias_node, default_node, values)) {
		for (it = values.begin(); it != values.end(); ++it) {
			smtp.setTo(*it);

			TRACE(TRACE_DEBUG, "set to: %s", it->c_str());
		}
	}

	/* Set our Cc field(s). */
	if (assignValues("cc", policy_output, alias_node, default_node, values)) {
		for (it = values.begin(); it != values.end(); ++it) {
			smtp.setCc(*it);

			TRACE(TRACE_DEBUG, "set cc: %s", it->c_str());
		}
	}

	/* Set our Bcc field(s). */
	if (assignValues("bcc", policy_output, alias_node, default_node, values)) {
		for (it = values.begin(); it != values.end(); ++it) {
			smtp.setBcc(*it);

			TRACE(TRACE_DEBUG, "set bcc: %s", it->c_str());
		}
	}

	/* Set our Subject field. */
	if (policy_output.find("subject") != policy_output.end()) {
		smtp.setSubject(policy_output["subject"]);

		TRACE(TRACE_DEBUG, "set subject: %s", policy_output["subject"].c_str());
	} else if (policy_output.find("brief") != policy_output.end()) {
		smtp.setSubject(policy_output["brief"]);

		TRACE(TRACE_DEBUG, "set subject: %s (using brief)", policy_output["brief"].c_str());
	}

	/* Set our Body field(s). */
	if (policy_output.find("body") != policy_output.end()) {
		smtp.setBody(policy_output["body"]);

		TRACE(TRACE_DEBUG, "set body: %s", policy_output["body"].c_str());
	} else if (policy_output.find("detail") != policy_output.end()) {
		smtp.setBody(policy_output["detail"]);

		TRACE(TRACE_DEBUG, "set body: %s (using detail)", policy_output["detail"].c_str());
	}
	
	/* Set our Priority field. */
	if (assignValues("priority", policy_output, alias_node, default_node, values)) {
		tmp = strtoul(values[0].c_str(), NULL, 10);
		smtp.setPriority(tmp);

		TRACE(TRACE_DEBUG, "set priority: %lu", tmp);
	}

	/* Set our Length. */
	if (assignValues("length", policy_output, alias_node, default_node, values)) {
		tmp = strtoul(values[0].c_str(), NULL, 10);
		smtp.setLength(tmp);

		TRACE(TRACE_DEBUG, "set length: %lu", tmp);
	}

	/* Set our Timeout. */
	if (assignValues("timeout", policy_output, alias_node, default_node, values)) {
		tmp = strtoul(values[0].c_str(), NULL, 10);
		smtp.setTimeout(tmp);

		TRACE(TRACE_DEBUG, "set timeout: %lu", tmp);
	}

	/* Set our Port field. */
	if (assignValues("port", policy_output, alias_node, default_node, values)) {
		tmp = strtoul(values[0].c_str(), NULL, 10);
		smtp.setPort(tmp);

		TRACE(TRACE_DEBUG, "set port: %lu", tmp);
	}

	/* Set the desired server(s). */
	assignValues("server", policy_output, alias_node, default_node, values);
	if (values.size() < 1) {
		values.push_back("127.0.0.1");
	}

	/* Cleanup, since we don't need these anymore. */
	SAFE_DELETE(alias_node);
	SAFE_DELETE(default_node);

	/* Verify email is valid for delivery. */
	if (!smtp.isValid()) {
		LMSG("Discarding email. Must contain a valid recipient and a subject or body.");

		return FAILURE;
	}

	/* Loop thru each server, until one succeeds. */
	for (it = values.begin(), delivered = false; it != values.end() && delivered == false; ++it) {
		/* Overwrite the last server. */
		smtp.setServer(*it);

		TRACE(TRACE_DEBUG, "set server: %s", it->c_str());

		/* Try to send. */
		if (smtp.send() == SUCCESS) {
			delivered = true;

			TRACE(TRACE_DEBUG, "Email successfully delivered to server: %s", it->c_str());
		} else {
			TRACE(TRACE_DEBUG, "Failed to deliver email to server: %s", it->c_str());
		}
	}

	if (!delivered) {
		LMSG("Discarding email. Failed to deliver to server(s).");

		return FAILURE;
	}

	return SUCCESS;
}

bool Email::assignValues(const string &req, variable_binding_t &policy_output, ConfigNode *alias_node, ConfigNode *default_node, vector<string> &values) {
	values.clear();

	/* Try settings from policy_output. */
	if (policy_output.find(req) != policy_output.end()) {
		values.push_back(policy_output[req]);
		TRACE(TRACE_INTERNAL, "Setting '%s' from policy output", req.c_str());

		return true;
	}
	
	/* Try settings from alias. */
	if (values.size() == 0 && alias_node != NULL) {
		if (alias_node->assignValues(req, values)) {
			TRACE(TRACE_INTERNAL, "Setting '%s' from alias", req.c_str());

			return true;
		}
	}
	
	/* Try default settings. */
	if (values.size() == 0 && default_node != NULL) {
		if (default_node->assignValues(req, values)) {
			TRACE(TRACE_INTERNAL, "Setting '%s' from default", req.c_str());

			return true;
		}
	}

	TRACE(TRACE_INTERNAL, "Not setting '%s'. Not found in policy output, alias or default.", req.c_str());

	return false;
}

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