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

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

extern ConfigNode* config;
extern bool interrupt;

Syslog::Syslog():priority(0) {
}

Syslog::~Syslog() {
}

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

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

	while (!interrupt) {
		if (readOutput("syslog") == 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"] = "syslog";
			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:
					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:
					continue;
			}

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

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

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

	return result;
}

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

	string str;
	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_SYSLOG_INTERVAL_DEF, OUTPUT_SYSLOG_INTERVAL_MIN, OUTPUT_SYSLOG_INTERVAL_MAX, OUTPUT_SYSLOG_INTERVAL_MLT);
	node.assignValue("queue-depth", queueDepth, OUTPUT_SYSLOG_QUEUEDEPTH_DEF, OUTPUT_SYSLOG_QUEUEDEPTH_MIN, OUTPUT_SYSLOG_QUEUEDEPTH_MAX);

	this->priority = 0;
	node.assignValue("facility", str);
	this->priority |= getFacility(str);
	node.assignValue("severity", str);
	this->priority |= getSeverity(str);

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

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

	printConfig();

	return result;
}

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

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

	TRACE(TRACE_CONFIG, "interval: %lu", interval / OUTPUT_SYSLOG_INTERVAL_MLT);
	TRACE(TRACE_CONFIG, "queue-depth: %lu", queueDepth);
	TRACE(TRACE_CONFIG, "facility: %s (%lu)", facility_names[LOG_FAC(priority)], LOG_FAC(priority));
	TRACE(TRACE_CONFIG, "severity: %s (%lu)", severity_names[LOG_PRI(priority)], LOG_PRI(priority));

	if (config->assignValues("output syslog 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 syslog 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 Syslog::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 Syslog::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 Syslog::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;
}

int Syslog::getFacility(string facility) {
	int ret_val = 0;

	if (!strcasecmp(facility.c_str(), "user")) {
		ret_val = LOG_USER;
	} else if (!strcasecmp(facility.c_str(), "daemon")) {
		ret_val = LOG_DAEMON;
	} else if (!strcasecmp(facility.c_str(), "local0")) {
		ret_val = LOG_LOCAL0;
	} else if (!strcasecmp(facility.c_str(), "local1")) {
		ret_val = LOG_LOCAL1;
	} else if (!strcasecmp(facility.c_str(), "local2")) {
		ret_val = LOG_LOCAL2;
	} else if (!strcasecmp(facility.c_str(), "local3")) {
		ret_val = LOG_LOCAL3;
	} else if (!strcasecmp(facility.c_str(), "local4")) {
		ret_val = LOG_LOCAL4;
	} else if (!strcasecmp(facility.c_str(), "local5")) {
		ret_val = LOG_LOCAL5;
	} else if (!strcasecmp(facility.c_str(), "local6")) {
		ret_val = LOG_LOCAL6;
	} else if (!strcasecmp(facility.c_str(), "local7")) {
		ret_val = LOG_LOCAL7;
	} else if (!strcasecmp(facility.c_str(), "auth") || !strcasecmp(facility.c_str(), "security")) {
		ret_val = LOG_AUTH;
	} else if (!strcasecmp(facility.c_str(), "authpriv")) {
		ret_val = LOG_AUTHPRIV;
	} else if (!strcasecmp(facility.c_str(), "cron")) {
		ret_val = LOG_CRON;
	} else if (!strcasecmp(facility.c_str(), "ftp")) {
		ret_val = LOG_FTP;
	} else if (!strcasecmp(facility.c_str(), "kern")) {
		ret_val = LOG_KERN;
	} else if (!strcasecmp(facility.c_str(), "lpr")) {
		ret_val = LOG_LPR;
	} else if (!strcasecmp(facility.c_str(), "mail")) {
		ret_val = LOG_MAIL;
	} else if (!strcasecmp(facility.c_str(), "news")) {
		ret_val = LOG_NEWS;
	} else if (!strcasecmp(facility.c_str(), "uucp")) {
		ret_val = LOG_UUCP;
	} else if (strlen(facility.c_str()) <= 2 && isdigit(facility.c_str()[0])) {
		ret_val = atol(facility.c_str());
		if (ret_val < 0 || ret_val > 23) {
			ret_val = OUTPUT_SYSLOG_FACILITY_DEF;
		}
	} else {
		ret_val = OUTPUT_SYSLOG_FACILITY_DEF;
	}

	return ret_val;
}

int Syslog::getSeverity(string severity) {
	int ret_val = 0;

	if (!strcasecmp(severity.c_str(), "emerg") || !strcasecmp(severity.c_str(), "emergency") || !strcasecmp(severity.c_str(), "panic")) {
		ret_val = LOG_EMERG;
	} else if (!strcasecmp(severity.c_str(), "alert")) {
		ret_val = LOG_ALERT;
	} else if (!strcasecmp(severity.c_str(), "crit") || !strcasecmp(severity.c_str(), "critical")) {
		ret_val = LOG_CRIT;
	} else if (!strcasecmp(severity.c_str(), "err") || !strcasecmp(severity.c_str(), "error")) {
		ret_val = LOG_ERR;
	} else if (!strcasecmp(severity.c_str(), "warn") || !strcasecmp(severity.c_str(), "warning")) {
		ret_val = LOG_WARNING;
	} else if (!strcasecmp(severity.c_str(), "notice")) {
		ret_val = LOG_NOTICE;
	} else if (!strcasecmp(severity.c_str(), "info") || !strcasecmp(severity.c_str(), "information")) {
		ret_val = LOG_INFO;
	} else if (!strcasecmp(severity.c_str(), "debug")) {
		ret_val = LOG_DEBUG;
	} else if (strlen(severity.c_str()) <= 1 && isdigit(severity.c_str()[0])) {
		ret_val = atol(severity.c_str());
		if (ret_val < 0 || ret_val > 7) {
			ret_val = OUTPUT_SYSLOG_SEVERITY_DEF;
		}
	} else {
		ret_val = OUTPUT_SYSLOG_SEVERITY_DEF;
	}

	return ret_val;
}

RESULT Syslog::processPolicyOutput(variable_binding_t &policy_output) {
	int priority;
	string facility, severity;
	string message;

	if (policy_output.find("message")  != policy_output.end()) {
		TRACE(TRACE_INTERNAL, "Message set using policy_output[\"message\"].");

		message = policy_output["message"];
	} else {
		if (policy_output.find("brief") != policy_output.end()) {
			message = policy_output["brief"];
		}

		if (policy_output.find("detail") != policy_output.end()) {
			if (message.size() > 0) {
				TRACE(TRACE_INTERNAL, "Message set using policy_output[\"brief\"] and policy_output[\"detail\"].");

				message.append(": " + policy_output["detail"]);
			} else {
				TRACE(TRACE_INTERNAL, "Message set using policy_output[\"detail\"].");

				message = policy_output["detail"];
			}
		}
	}

	/* Skip empty messages. */
	if (message.size() > 0) {
		/* Default to global priority. */
		priority = this->priority;

		TRACE(TRACE_INTERNAL, "Default facility '%s' (%lu) severity '%s' (%lu) -> Pri: 0x%04x", facility_names[LOG_FAC(priority)], LOG_FAC(priority), severity_names[LOG_PRI(priority)], LOG_PRI(priority), priority);

		/* Allow policy to modify the facility. */
		if (policy_output.find("facility") != policy_output.end()) {
			priority = LOG_PRI(priority) | getFacility(policy_output["facility"]);

			TRACE(TRACE_INTERNAL, "Policy set facility to %s (%lu) -> Pri: 0x%04x", facility_names[LOG_FAC(priority)], LOG_FAC(priority), priority);
		}

		/* Allow policy to modify the severity. */
		if (policy_output.find("severity") != policy_output.end()) {
			priority = LOG_FAC(priority) << 3 | getSeverity(policy_output["severity"]);

			TRACE(TRACE_INTERNAL, "Policy set severity to %s (%lu) -> Pri: %04x", severity_names[LOG_PRI(priority)], LOG_PRI(priority), priority);
		}

		TRACE(TRACE_DEBUG, "Using facility '%s' (%lu) severity '%s' (%lu) to send message: %s", facility_names[LOG_FAC(priority)], LOG_FAC(priority), severity_names[LOG_PRI(priority)], LOG_PRI(priority), message.c_str());

		/* Send the message to syslog. */
		syslog(priority, "%s", message.c_str()); /* Never pass a string with user-supplied data as a format. */

	}

	return SUCCESS;
}


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