/*
** SellaNMS
** $Id: syslog.cxx,v 1.53 2005/03/31 18:57:12 sella Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

#ifdef HAVE_CONFIG_H
	#include "config.h"
#endif

#include "syslog.h"
#include "debug.h"
#include "ip_util.h"
#include <fcntl.h>
#include <netdb.h>
#include <ctype.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/if.h>
#include <stl/vector>

#define BUF_SIZE 2024
#define WAIT_AND_KILL

Syslog::Syslog(): port(0), dupHist(NULL), dupHistCur(0), exitAfter(0), nextWarning(0) {
	this->name.append(" syslog");

	has_scheduler = false;
	has_execute = true;

	pthread_mutex_init(&syslog_mutex, NULL);
}

Syslog::~Syslog() {
	pthread_mutex_destroy(&syslog_mutex);

	SAFE_DELETE_ARRAY(this->dupHist);
}

RESULT Syslog::readConfig() {
	RESULT result = SUCCESS;
	string str;
	vector<string> v;
	vector<string>::iterator v_it;
	struct servent *s_ent;
	struct in_addr ip;

	if (config->assignValue("port", str)) {
		this->port = htons((unsigned short) strtoul(str.c_str(), NULL, 10));
	} else {
		s_ent = getservbyname("syslog", "udp");
		if (s_ent) {
			this->port = s_ent->s_port;
		} else {
			this->port = htons(MODULE_MONITOR_SYSLOG_PORT_DEFAULT);
		}
	}

	if (config->assignValue("source", str)) {
		if (inet_pton(AF_INET, str.c_str(), &ip) == 0) {
			this->source = htonl(INADDR_ANY);
		} else {
			this->source = ip.s_addr;
		}
	} else if (config->assignValue("interface", str)) {
		source_ip(str.c_str(), &ip);
		this->source = ip.s_addr;
	} else {
		this->source = htonl(INADDR_ANY);
	}

	config->assignValue("queue-depth", queueDepth, MODULE_MONITOR_SYSLOG_QUEUEDEPTH_DEFAULT, 1);

	config->assignValue("duplicate-history-max", dupHistMax, MODULE_MONITOR_SYSLOG_DUPHISTMAX_DEFAULT);
	/* If needed, allocate memory and clear (sets as stale). */
	if (this->dupHistMax > 0) {
		this->dupHist = new dup_hist_t[this->dupHistMax];
		bzero(this->dupHist, sizeof(dup_hist_t) * this->dupHistMax);
	}

	config->assignValue("duplicate-history-age", dupHistAge, MODULE_MONITOR_SYSLOG_DUPHISTAGE_DEFAULT);

	config->assignValue("duplicate-fuzz-factor", dupFuzFactor, MODULE_MONITOR_SYSLOG_DUPFUZFACTOR_DEFAULT);
	/* Exists after X syslog messages are recieved. Useful for memory debugging. */
	config->assignValue("exit-after", exitAfter, MODULE_MONITOR_SYSLOG_EXITAFTER_DEFAULT);

	/* Add and compile input policy. */
	if (config->assignValues("policy input", v)) {
		/* These are actually just added to the front of the output policies. */
		if (outputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("NOTICE: Failed to add/compile input policies.");

			return FAILURE;
		}
	}

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

			return FAILURE;
		}
	}

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

		return FAILURE;
	}

	return result;
}

RESULT Syslog::printConfig() {
	struct in_addr ip;
	char buf[INET_ADDRSTRLEN];

	LMSG("port: %lu", ntohs(this->port));
	LMSG("queue-depth: %lu", this->queueDepth);
	LMSG("duplicate-history-max: %lu entries", this->dupHistMax);
	LMSG("duplicate-history-age: %lu sec", this->dupHistAge);
	LMSG("duplicate-fuzz-factor: %lu", this->dupFuzFactor);
	LMSG("exit-after: %lu", this->exitAfter);
	ip.s_addr = this->source;
	inet_ntop(AF_INET, &ip, buf, INET_ADDRSTRLEN);
	LMSG("source: %s", buf);

	return Monitor::printConfig();
}

RESULT Syslog::execute() {
	RESULT result = SUCCESS;
	RESULT t_result = SUCCESS;

	pthread_t thread_id = 0;

	slist<pthread_t> t_list;
	slist<pthread_t>::iterator it;

	if (trace.thread) {
		LMSG("Syslog::execute() starting");
	}

	/* Create the storage thread */
	if ((result = createThread(thread_id, (void* (*)(void*))Syslog::storage_thread, this)) == SUCCESS) {
		t_list.push_front(thread_id);
	}

	/* Create the receiver thread */
	if (result == SUCCESS) {
		if ((result = createThread(thread_id, (void* (*)(void*))Syslog::receive_thread, this)) == SUCCESS) {
			t_list.push_front(thread_id);
		}
	}

#ifdef WAIT_AND_KILL
	/* Wait and kill all threads. */
	for (it = t_list.begin(); it != t_list.end();) {
		/* Wait for thread to complete. */
		if (this->getThreadResult(*it, t_result) != SUCCESS || t_result != SUCCESS) {
			result = FAILURE;
		}
		/* Kill the next thread. */
		if (++it != t_list.end()) {
			this->killThread(*it);
		}
	}
#else
	/* Wait on all threads. */
	for (it = t_list.begin(); it != t_list.end(); ++it) {
		/* Wait for thread to complete. */
		if (this->getThreadResult(*it, t_result) != SUCCESS || t_result != SUCCESS) {
			result = FAILURE;
		}
	}
#endif

#if 0
	/* Wait on all threads. */
	for (it = t_list.begin(); it != t_list.end(); ++it) {
		/* Wait for thread to complete. */
		if (this->getThreadResult(*it, t_result) != SUCCESS || t_result != SUCCESS) {
			result = FAILURE;
		}
	}
#endif

	if (trace.thread) {
		LMSG("Syslog::execute() exiting");
	}

	return result;
}

RESULT Syslog::receive_thread(void *args) {
	RESULT result = SUCCESS;	
	STATUS status;

	pthread_t thread_id = pthread_self();

	Syslog *instance = (Syslog*) args;
	instance->setThreadStatus(thread_id, THREAD_RUNNING);

	received_queue_t *syslog_queue = &(instance->syslog_queue);
	pthread_mutex_t *syslog_mutex = &(instance->syslog_mutex);

	int s, n;
	bool running;
	struct protoent *protocol;
	struct sockaddr_in socketaddr;
	char buf[BUF_SIZE], buf2[INET_ADDRSTRLEN];
	pair<string, string> raw_msg;
	socklen_t len = 0;
	unsigned long rx_count = 0;

	if (instance->trace.thread) {
		LMSG("Syslog::receive_thread() starting main loop");
	}

	fd_set	sockets;
	timeval	timeout;

	bzero((char*) &socketaddr, sizeof(socketaddr));
	socketaddr.sin_family = AF_INET;
	socketaddr.sin_addr.s_addr = instance->source;
	socketaddr.sin_port = instance->port;

	instance->grantPriv();

	running = false;
	if (!(protocol = getprotobyname("udp"))) {
		LMSG("WARNING: Failed to resolve protocol by name.");
		running = false;
		result = FAILURE;
	} else if ((s = socket(PF_INET, SOCK_DGRAM, protocol->p_proto)) == -1) { /* The 0 here may suppose to be protocol->p_proto */
		LMSG("WARNING: Failed to open socket.");
		running = false;
		result = FAILURE;
	} else if (bind(s, (struct sockaddr*) &socketaddr, sizeof(socketaddr)) < 0) {
		LMSG("WARNING: Failed to bind to port %d.",  ntohs(instance->port));
		running = false;
		result = FAILURE;
	} else {
		running = true;
	}

	instance->dropPriv();

	while (running && !instance->interrupt) {
		instance->getThreadStatus(thread_id, status);
		if (status == THREAD_KILLED) {
			if (instance->trace.debug) {
				LMSG("Syslog::receive_thread status == THREAD_KILLED");
			}
			running = false;
		} else if (instance->exitAfter > 0 && rx_count >= instance->exitAfter) {
			LMSG("NOTICE: Syslog::recieve_thread exiting. We have now recieved %lu syslog messages (exit-after: %lu).", rx_count, instance->exitAfter);
			running = false;
		} else {
			FD_ZERO(&sockets);
			FD_SET(s, &sockets);

			timeout.tv_sec = 1;
			timeout.tv_usec = 0;

			if (select(s + 1, &sockets, NULL, NULL, &timeout) == 1) {
				len = sizeof(struct sockaddr_in);
				if ((n = recvfrom(s, buf, BUF_SIZE, 0, (struct sockaddr*) &socketaddr, &len)) != -1) {
					buf[n] = '\0'; /* Remove trailing garbage. */
					rx_count++;

					if (instance->suppressDuplicate((u_int32_t*) &socketaddr.sin_addr.s_addr, buf, n) == false) {
						inet_ntop(AF_INET, &socketaddr.sin_addr, buf2, INET_ADDRSTRLEN);
						raw_msg.first = buf2;
						raw_msg.second = buf;

						if (instance->trace.internal) {
							LMSG("Syslog::queueDepth == %lu, rx_count: %lu", instance->queueDepth, rx_count);
						}

						if (syslog_queue->size() >= instance->queueDepth) {
							if (time(NULL) >= instance->nextWarning) {
								instance->nextWarning = time(NULL) + MODULE_MONITOR_SYSLOG_NEXTWARN_DEFAULT;
								LMSG("NOTICE: Syslog queue depth exceeded %lu. Dropping entry from %s: %s (Suppressing warning for %lus)", instance->queueDepth, raw_msg.first.c_str(), raw_msg.second.c_str(), MODULE_MONITOR_SYSLOG_NEXTWARN_DEFAULT);
							}
						} else {
							pthread_mutex_lock(syslog_mutex);
								syslog_queue->push(raw_msg);
							pthread_mutex_unlock(syslog_mutex);
						}
					}
				} else {
					LMSG("WARNING: Error received from recvfrom()");
					running = false;
				}
			}
		}
	}

	/* Close our socket. */
	close(s);

	if (instance->trace.thread) {
		LMSG("Syslog::receive_thread() exiting main loop");
	}

	instance->setThreadStatus(thread_id, THREAD_COMPLETED);

	THREAD_RETURN(result);
}

bool Syslog::suppressDuplicate(u_int32_t *source, char* msg, int n) {
	bool ret_val = false;
	unsigned long signature = 0;
	struct in_addr ip;
	time_t stale = time(NULL) - dupHistAge;
	char buf[INET_ADDRSTRLEN];

	/* If no dup history, then no duplicates to detect. */
	if (dupHist == NULL) {
		return false;
	}

	/* Generate the signature. */
	for (int x = 0; x < n; x++) {
		signature += msg[x];
	}

	/* Check for the duplicate */
	for (int x = 0; x < dupHistMax; x++) {
		/* If the source is the same, and the entry isn't stale check for a duplicate. */
		if (dupHist[x].source == *source && dupHist[x].timestamp >= stale) {
			/* Is the entry close enough to be a duplicate? */
			if (dupHist[x].signature - dupFuzFactor <= signature && dupHist[x].signature + dupFuzFactor >= signature) {
				if (trace.debug) {
					ip.s_addr = *source;
					inet_ntop(AF_INET, &ip, buf, INET_ADDRSTRLEN);
					LMSG("Syslog duplicate detected (age: %d, var: %d) from %s: %s", time(NULL) - dupHist[x].timestamp, dupHist[x].signature - signature, buf, msg);
				}
				ret_val = true;
				break;
			}
		}
	}

	/* Insert new entry into history. */
	if (trace.internal) {
		LMSG("Syslog entry inserted into position %lu/%lu", dupHistCur, dupHistMax);
	}

	dupHist[dupHistCur].source = *source;
	dupHist[dupHistCur].timestamp = time(NULL);
	dupHist[dupHistCur].signature = signature;

	/* Increment current entry. Ring buffer. */
	dupHistCur = (dupHistCur + 1 >= dupHistMax) ? 0 : dupHistCur + 1;

	return ret_val;
}

RESULT Syslog::storage_thread(void *args) {
	RESULT result = SUCCESS;
	ACTION action;
	STATUS status;

	pthread_t thread_id = pthread_self();

	Syslog *instance = (Syslog*) args;
	instance->setThreadStatus(thread_id, THREAD_RUNNING);

	received_queue_t *syslog_queue = &(instance->syslog_queue);
	pthread_mutex_t *syslog_mutex = &(instance->syslog_mutex);

	variable_binding_t policy_input;
	variable_binding_t policy_output;

	bool write = false;
	bool running = true;
	unsigned int len;
	char buf[BUF_SIZE], *ret_buf;
	string raw;
	string* last_id;
	syslog_t syslog;
	pair<string, string> raw_msg;
	StorageData data;
	time_t t;
	tm *tm;
	struct tm now;

	/* NOTE: Syslog packet format described within README in local directory. */

	if (instance->trace.thread) {
		LMSG("Syslog::storage_thread() starting main loop");
	}

	// Until we are asked to stop, run syslog input thru policy.
	while (running && !instance->interrupt) {
		instance->getThreadStatus(thread_id, status);
		if (status == THREAD_KILLED) {
			if (instance->trace.debug) {
				LMSG("Syslog::storage_thread status == THREAD_KILLED");
			}
			running = false;
		}
		while (!syslog_queue->empty()) {
			/* Fetch Data from queue */
			pthread_mutex_lock(syslog_mutex);
				raw_msg = syslog_queue->front();
				syslog_queue->pop();
			pthread_mutex_unlock(syslog_mutex);

			t = time(NULL);
			localtime_r(&t, &now);
			raw = raw_msg.second;

			/* PRI */
			if (ret_buf = ereg_replace("^<([0-9][0-9]*)>.*$", "\\1", raw.c_str())) {
				/* PRI must be 1-3 digits. */
				len = strlen(ret_buf);
				if (len >= 1 && len <= 3) {
					syslog.priority = atoi(ret_buf);
					raw.erase(0, len + 2); //Consume PRI + 2 angle brackets.
				} else {
					syslog.priority = LOG_MAKEPRI(1, 5);
				}
				SAFE_FREE(ret_buf);
			} else {
				syslog.priority = LOG_MAKEPRI(1, 5);
			}

			/* HEADER - TIMESTAMP + HOSTNAME/IP, TIMESTAMP or HOSTNAME/IP */
			if (ret_buf = ereg_replace("^([A-Z][a-z]{2} [ 0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [A-Za-z0-9_-]+\\.[A-Za-z0-9\\._-]+|[A-Z][a-z]{2} [ 0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]|[A-Za-z0-9_-]+\\.[A-Za-z0-9\\._-]+) .*$", "\\1", raw.c_str())) {
				if ((len = strlen(ret_buf)) != raw.size()) {
					syslog.header = ret_buf;
					SAFE_FREE(ret_buf);

					/* TIMESTAMP */
					if ((ret_buf = ereg_replace("^([A-Z][a-z][a-z] [ 0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]).*$", "\\1", raw.c_str())) && strlen(ret_buf) != raw.size()) {
						syslog.timestamp = ret_buf;
						SAFE_FREE(ret_buf);
						raw.erase(0, syslog.timestamp.size());
						len -= syslog.timestamp.size();
					}

					if ((ret_buf = ereg_replace("^ ?([A-Za-z0-9_-]+\\.[A-Za-z0-9\\._-]+) .*$", "\\1", raw.c_str())) && strlen(ret_buf) != raw.size()) {
						syslog.hostname = ret_buf;
						SAFE_FREE(ret_buf);
					}

					raw.erase(0, len + 1); //Consume HEADER + 1 space.
				}
				SAFE_FREE(ret_buf);
			}
			
			/* Verify TIMESTAMP */
			if (syslog.timestamp.empty()) {
				/* No TIMESTAMP was found, insert one. */
				strftime(buf, BUF_SIZE, "%b %e %T", &now);
				syslog.timestamp = buf;
			}

			/* Verify HOSTNAME */
			if (syslog.hostname.empty()) {
				/* No HOSTNAME was found, insert one. */
				syslog.hostname = raw_msg.first;
			}

			/* Verify HEADER */
			if (syslog.header.empty()) {
				/* No HEADER was found, insert one. */
				syslog.header = syslog.timestamp + " ";
				syslog.header += syslog.hostname;
			}

			/* MSG */
			syslog.msg = raw;

			/* TAG */
			if (ret_buf = ereg_replace("^([^: \\[]+)[: \\[].*$", "\\1", raw.c_str())) {
				if ((len = strlen(ret_buf)) != raw.size()) {
					syslog.tag = ret_buf;
					SAFE_FREE(ret_buf);
					raw.erase(0, len); //Consume TAG

					/* PID */
					if (!raw.empty() && raw[0] == '[' && (ret_buf = ereg_replace("^\\[([0-9]+)\\].*$", "\\1", raw.c_str()))) {
						if ((len = strlen(ret_buf)) != raw.size()) {
							syslog.pid = ret_buf;
							raw.erase(0, len + 2); ////Consume PID + 2 square brackets
						}
						SAFE_FREE(ret_buf);

					}
					/* Do we need to eat the colon? */
					if (!raw.empty() && raw[0] == ':') {
						raw.erase(0, 1); //Consume colon.
					}
					/* Now, do we need to eat a space? */
					if (!raw.empty() && raw[0] == ' ') {
						raw.erase(0, 1); //Consume space.
					}

					/* CONTENT */
					syslog.content = raw;
				}
				SAFE_FREE(ret_buf);
			}

			/* Free. */
			raw.erase();

			/* Setup policy_input structure. */
			policy_input["protocol"] = "syslog";
			policy_input["raw"] = raw_msg.second;
			policy_input["prefix"] = raw_msg.first;
			policy_input["source"] = "network";
			policy_input["prefix-except"] = raw_msg.first;
			policy_input["header"] = syslog.header;
			policy_input["timestamp"] = syslog.timestamp;
			policy_input["hostname"] = syslog.hostname;
			policy_input["tag"] = syslog.tag;
			policy_input["pid"] = syslog.pid;
			policy_input["content"] = syslog.content;
			policy_input["msg"] = syslog.msg;
			snprintf(buf, BUF_SIZE, "%d", syslog.priority);
			policy_input["priority"] = buf;
			strftime(buf, BUF_SIZE, "%T", &now);
			policy_input["time"] = buf;
			strftime(buf, BUF_SIZE, "%A", &now);
			policy_input["day-of-week"] = buf;

			/* Default syslog structure. */
			syslog.priority = 0;
			syslog.header.erase();
			syslog.timestamp.erase();
			syslog.hostname.erase();
			syslog.msg.erase();
			syslog.tag.erase();
			syslog.pid.erase();
			syslog.content.erase();

			switch (LOG_PRI(syslog.priority)) {
				case LOG_EMERG:
					policy_input["severity"] = "emerg";
					break;

				case LOG_ALERT:
					policy_input["severity"] = "alert";
					break;

				case LOG_CRIT:
					policy_input["severity"] = "crit";
					break;

				case LOG_ERR:
					policy_input["severity"] = "err";
					break;

				case LOG_WARNING:
					policy_input["severity"] = "warning";
					break;

				case LOG_NOTICE:
					policy_input["severity"] = "notice";
					break;

				case LOG_INFO:
					policy_input["severity"] = "info";
					break;

				case LOG_DEBUG:
					policy_input["severity"] = "debug";
					break;

				default:
					snprintf(buf, BUF_SIZE, "%d", LOG_PRI(syslog.priority));
					policy_input["severity"] = buf;
			}

			switch (LOG_FAC(syslog.priority) << 3) {
				case LOG_KERN:
					policy_input["facility"] = "kern";
					break;

				case LOG_USER:
					policy_input["facility"] = "user";
					break;

				case LOG_MAIL:
					policy_input["facility"] = "mail";
					break;

				case LOG_DAEMON:
					policy_input["facility"] = "daemon";
					break;

				case LOG_AUTH:
					policy_input["facility"] = "auth";
					break;

				case LOG_SYSLOG:
					policy_input["facility"] = "syslog";
					break;

				case LOG_LPR:
					policy_input["facility"] = "lrp";
					break;

				case LOG_NEWS:
					policy_input["facility"] = "news";
					break;

				case LOG_UUCP:
					policy_input["facility"] = "uucp";
					break;

				case LOG_CRON:
					policy_input["facility"] = "cron";
					break;

				case LOG_AUTHPRIV:
					policy_input["facility"] = "authpriv";
					break;

				case LOG_FTP:
					policy_input["facility"] = "ftp";
					break;

				case (12<<3):
					policy_input["facility"] = "ntp";
					break;

				case (13<<3):
					policy_input["facility"] = "audit";
					break;

				case (14<<3):
					policy_input["facility"] = "alert";
					break;

				case (15<<3):
					policy_input["facility"] = "at";
					break;

				case LOG_LOCAL0:
					policy_input["facility"] = "local0";
					break;

				case LOG_LOCAL1:
					policy_input["facility"] = "local1";
					break;

				case LOG_LOCAL2:
					policy_input["facility"] = "local2";
					break;

				case LOG_LOCAL3:
					policy_input["facility"] = "local3";
					break;

				case LOG_LOCAL4:
					policy_input["facility"] = "local4";
					break;

				case LOG_LOCAL5:
					policy_input["facility"] = "local5";
					break;

				case LOG_LOCAL6:
					policy_input["facility"] = "local6";
					break;

				case LOG_LOCAL7:
					policy_input["facility"] = "local7";
					break;

				default:
					snprintf(buf, BUF_SIZE, "%d", LOG_FAC(syslog.priority));
					policy_input["facility"] = buf;
			}

#if 0
			if (instance->trace.policy_out) {
				LMSG("Input to outputPolicy...");
				instance->outputPolicy.printVariableBinding(policy_input);
				LMSG("Calling outputPolicy.evaluate()");
			}
#endif

			/* Run policy over policy_input. */
			if ((action = instance->outputPolicy.evaluate(policy_output, policy_input)) == ACTION_FAILURE) {
				instance->interrupt = true;
				result = FAILURE;
				LMSG("DEBUG: Call to outputPolicy.evaluate() returned ACTION_FAILURE. Exiting storage_thread.");
			}
			if (instance->trace.timer) {
				LMSG("outputPolicy.evaluate() ran %lu sec for raw: %s", time(NULL) - t, policy_input["raw"].c_str());
			}

#if 0
			if (instance->trace.policy_out) {
				LMSG("Policy action/result was: %lu", action);
				LMSG("Output from outputPolicy...");
				instance->outputPolicy.printVariableBinding(policy_output);
			}
#endif

			/* Cleanup for the next round. */
			policy_input.clear();
			policy_output.clear();
		}

		usleep(USLEEP_NORMLOOP_TIME);
	}

	if (instance->trace.thread) {
		LMSG("Syslog::storage_thread() exiting main loop");
	}

	instance->setThreadStatus(thread_id, THREAD_COMPLETED);

	THREAD_RETURN(result);
}

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