/* (C) 2006-2011 by folkert@vanheusden.com GPLv2 applies */
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <signal.h>
#include <pwd.h>
#include <sys/types.h>
#include <regex.h>

#include "utils.h"
#include "pl.h"
extern "C" {
#include "error.h"
#include "ssl.h"
#include "log.h"
}

#define S_DISCONNECTED		1
#define S_CONNECTED		2
#define S_JOINING_CHANNEL	3
#define S_ON_CHANNEL		4
#define S_DISCONNECTING		127

#define L_HOST			1
#define L_FILE			2

#define ST_HARD			1
#define ST_SOFT			0

int check_interval = 60;	/* check for new events every 1 minute */
int default_sleep = 2;		/* initial sleep time between connect failures */
int max_sleep_time = 300;
double sleep_multiply_factor = 2.0;
int minimum_time_for_successfull_login = 25; // one needs to be on-channel for at least 5 seconds to be considered a successfull login
int join_timeout = 5;	// it should take no longer then 5 seconds to join a channel, otherwhise: abort connection and retry
int max_n_join_tries = 2;	// try 2 times to get on a channel
char *server = "localhost:6667";	/* default irc server */
char *channel = "#nagircbot";	/* default channel to connect to */
char *nick_prefix = "";   /* prefix text for all messages sent to channel */
char *keyword = NULL;	/* keyword for the channel to connect to */
char *nick = "nagircbot";
char *user = "nagircbot";
char *password = NULL;
int one_line = 1;
char *username = "Nagios IRC Bot " VERSION ", (C) www.vanheusden.com";	/* complete username */
int verbose = 255;		/* default is log everything */
char *statuslog = "/usr/local/nagios/var/status.log";
int statuslog_version = 2;
int statuslog_location = L_FILE;
char use_colors = 0;
char topic_summary = 0;
char hard_only = 1;
int max_time_last_host_update = 300, max_time_oldest_host_update = 3600, max_time_last_host_check = 300, max_time_oldest_host_check = 3 * 86400, max_time_last_service_check = 20 * 60, max_time_oldest_service_check = 3 * 86400, max_time_oldest_next_service_check = 20 * 60;
int irc_server_keep_alive = 60;	/* send an irc-command at least every 300 seconds (currently 'TIME') */
int announce_global_status_interval = 0;
time_t last_announce_global_status_interval = 0;
char critical_only = 0;
regex_t *filters = NULL;
int n_filters = 0;
int use_ssl       = 0;


server_t server_conn;

char *state_str[4] = { " OK ", "WARN", "CRIT", " ?? " };
char *color_str[4];
struct stats *prev = NULL;
int n_prev = 0;
char topic[4096] = { 0 };
time_t last_irc_io = 0;
char *pidfile = NULL;

int send_irc(server_t server_conn, char *format, ...)
{
	int rc;
	char buffer[4096];
	va_list ap;

	va_start(ap, format);
	vsnprintf(buffer, sizeof(buffer) - 3, format, ap);
	va_end(ap);

	if (verbose > 1) dolog("OUT: `%s'", buffer);

	strcat(buffer, "\r\n"); /* yes this is safe: check the vsnprintf command */

	rc = IRCWRITE(server_conn, buffer, strlen(buffer));
	if (rc == -1)
	{
		dolog("error sending: -1");
		return -1;
	}
	else if (rc == 0)
	{
		dolog("connection closed");
		return -1;
	}

	time(&last_irc_io);

	return 0;
}

int irc_set_nick(server_t server_conn, char *nick)
{
	if (send_irc(server_conn, "NICK %s", nick) == -1)
		return -1;

	return 0;
}

int irc_login(server_t server_conn, char *user, char *username, char *server, char *password)
{
	if (password != NULL && send_irc(server_conn, "PASS %s", password) == -1)
		return -1;

	if (irc_set_nick(server_conn, nick) == -1)
		return -1;

	/* FIXME: localhost must be, ehr, local host */
	if (send_irc(server_conn, "USER %s \"localhost\" \"%s\" :%s", user, server, username) == -1)
		return -1;

	return 0;
}

int irc_join_channel(server_t server_conn, char *channel, char *keyword)
{
	if (keyword != NULL)
		return send_irc(server_conn, "JOIN %s %s", channel, keyword);

	return send_irc(server_conn, "JOIN %s", channel);
}

int irc_topic(server_t server_conn, char *channel, char *topic)
{
	return send_irc(server_conn, "TOPIC %s :%s", channel, topic);
}

int check_ping(server_t server_conn, char *data)
{
	if (strncmp(data, "PING ", 5) == 0)
	{
		char *colon = strchr(data, ':');
		char *cr = strchr(data, '\r');
		char *lf = strchr(data, '\n');

		if (cr>lf && cr != NULL)
			cr[1] = 0x00;
		else if (lf != NULL)
			lf[1] = 0x00;
		if (colon)
		{
			if (send_irc(server_conn, "PONG %s", colon + 1) == -1)
				return -1;
		}
		else if (verbose > 1)
		{
			dolog("Malformed PING request (%s)", data);
		}
	}

	return 0;
}

int irc_privmsg(server_t server_conn, char *channel, char *msg)
{
	return send_irc(server_conn, "PRIVMSG %s :%s", channel, msg);
}

int irc_time(server_t server_conn)
{
	return send_irc(server_conn, "TIME");
}

void load_statuslog(char *statuslog, char is_file, char is_20_format, int prev_n_elements_in_stats_array, struct stats **pstats, int *n_stats)
{
	char reload = 0;

	for(;;)
	{
		int fd;

		/* open file or connection to nagios status socket */
		if (is_file == 1)     /* file */
			fd = open64(statuslog, O_RDONLY);
		else
			fd = connect_to(statuslog);
		if (fd == -1)
		{
			dolog("Failed to open nagios status-log at %s\n", statuslog);
			sleep(1);
			continue;
		}

		/* file/socket open, load data */
		if (is_20_format)
			parse_2_0_statuslog(fd, pstats, n_stats);
		else
			parse_1_0_statuslog(fd, pstats, n_stats);

		close(fd);

		if (*n_stats == prev_n_elements_in_stats_array)
		{
			break;
		}

		dolog("Number of elements in status-log is different (%d) from previous run (%d), retrying", *n_stats, prev_n_elements_in_stats_array);
		reload = 1;
		sleep(1);

		/* remember the current number of elements for this load */
		prev_n_elements_in_stats_array = *n_stats;
		/* and free up - it'll be reloaded */
		free_stats_array(*pstats, *n_stats);
	}

	if (reload)
	{
		dolog("Size of status.log is stable (%d elements), continuing", *n_stats);
	}
}

int emit_status(server_t server_conn, char *channel, struct stats *what)
{
	int loop;
	char buffer[4096];
	char *prefix = "", *suffix = "";
	struct tm *ptm = localtime(&what -> last_state_change);

	if (use_colors)
	{
		prefix = color_str[what -> current_state];
		suffix = "\03";
	}

	if (n_filters || one_line)
	{
		snprintf(buffer, sizeof(buffer), "%s%s%04d/%02d/%02d %02d:%02d %s %s %s %s%s",
				prefix,
				nick_prefix,
				ptm -> tm_year + 1900, ptm -> tm_mon + 1, ptm -> tm_mday, ptm -> tm_hour, ptm -> tm_min,
				state_str[what -> current_state],
				what -> host_name?what -> host_name:"",
				what -> service_description?what -> service_description:"",
				what -> plugin_output?what -> plugin_output:"",
				suffix);
	}

	/* regexp matches? then do not emit */
	for(loop=0; loop<n_filters; loop++)
	{
		int rc = regexec(&filters[loop], buffer, 0, NULL, 0);

		if (rc == 0)
		{
#ifdef _DEBUG
			printf("Line '%s' filtered by regexp\n", buffer);
#endif
			return 0; /* assume we're still on channel */
		}
		else if (rc != REG_NOMATCH)
		{
			char buffer[4096];

			regerror(rc, &filters[loop], buffer, sizeof(buffer));

			dolog("Executing of regexp failed: %s", buffer);
		}
	}

	if (one_line)
	{
		if (irc_privmsg(server_conn, channel, buffer) == -1)
			return -1;
	}
	else
	{
		snprintf(buffer, sizeof(buffer), "%s%04d/%02d/%02d %02d:%02d",
				prefix,
				ptm -> tm_year + 1900, ptm -> tm_mon + 1, ptm -> tm_mday, ptm -> tm_hour, ptm -> tm_min);

		if (irc_privmsg(server_conn, channel, buffer) == -1) return -1;

		if (irc_privmsg(server_conn, channel, state_str[what -> current_state]) == -1) return -1;

		if (irc_privmsg(server_conn, channel, (char *)(what -> host_name?what -> host_name:"")) == -1) return -1;

		if (irc_privmsg(server_conn, channel, (char *)(what -> service_description?what -> service_description:"")) == -1) return -1;

		snprintf(buffer, sizeof(buffer), "%s%s", (char *)(what -> plugin_output?what -> plugin_output:""), suffix);
		if (irc_privmsg(server_conn, channel, buffer) == -1) return -1;
	}

	return 0;
}

void calc_statistics(struct stats *stats, int n_stats, char list_all_problems, char always_notify, char also_acknowledged, char hide_ok, char *buffer, int buffer_size)
{
	int n_critical=0, n_warning=0, n_ok=0, n_up=0, n_down=0, n_unreachable=0, n_pending=0;

	calc_stats_stats(stats, n_stats, list_all_problems, always_notify, also_acknowledged, hide_ok, &n_critical, &n_warning, &n_ok, &n_up, &n_down, &n_unreachable, &n_pending);
	snprintf(buffer, buffer_size, "Critical: %d, warning: %d, ok: %d, up: %d, down: %d, unreachable: %d, pending: %d",
			n_critical, n_warning, n_ok, n_up, n_down, n_unreachable, n_pending);
}

int check_nagios_status(server_t server_conn)
{
	struct stats *cur = NULL;
	int n_cur = 0;
	int loop;
	int any_shown = 0;
#ifdef _DEBUG
	time_t now = time(NULL);
	printf("Checking @ %s", ctime(&now));
#endif

	load_statuslog(statuslog, statuslog_location == L_FILE?1:0, statuslog_version == 2?1:0, n_prev, &cur, &n_cur);

	if (topic_summary)
	{
		char buffer[4096];

		calc_statistics(cur, n_cur, 0, 0, 0, 1, buffer, sizeof(buffer));

		if (strcmp(topic, buffer) != 0)
		{
			strcpy(topic, buffer);

			if (irc_topic(server_conn, channel, buffer) == -1)
				return -1;
		}
	}

	if (announce_global_status_interval > 0)
	{
		if ((now - last_announce_global_status_interval) >= announce_global_status_interval)
		{
			char buffer[4096];

			last_announce_global_status_interval = now;

			calc_statistics(cur, n_cur, 0, 0, 0, 1, buffer, sizeof(buffer));

			if (irc_privmsg(server_conn, channel, buffer) == -1)
				return -1;
		}
	}

	for(loop=0; loop<n_cur; loop++)
	{
		char show = 0;
		int prev_index = -1;

		if (prev)
			prev_index = find_index_by_host_and_service(prev, n_prev, cur[loop].host_name, cur[loop].service_description);

		if (prev_index == -1)
		{
			if (prev == NULL)
			{
				if (should_i_show_entry(cur, n_cur, loop, 0, 0, 0, 1))
				{
					show = 1;
				}
			}
			else
			{
				show = 1;
			}
		}
		else
		{
			if (hard_only)
			{
				if (prev[prev_index].state_type == ST_HARD && cur[loop].state_type == ST_HARD)
				{
					if (prev[prev_index].current_state != cur[loop].current_state)
					{
						show = 1;
					}
				}
				else if (prev[prev_index].state_type == ST_SOFT && cur[loop].state_type == ST_HARD)
				{
					if (cur[loop].current_state != 0)
					{
						show = 1;
					}
				}
			}
			else
			{
				if (prev[prev_index].current_state != cur[loop].current_state)
				{
					show = 1;
				}
			}
		}

		/* also 'unknown' is emitted when 'critical_only' is selected */
		if (show && cur[loop].current_state < 2 && critical_only)
		{
			show = 0;
		}

		if (show)
		{
#ifdef _DEBUG
			if (prev != NULL && prev_index != -1)
			{
				printf("%s %s\n", cur[loop].host_name, cur[loop].service_description);
				printf("cur[current_state]: %d, prev[current_state]: %d\n", cur[loop].current_state, prev[prev_index].current_state);
				printf("cur[state_type]: %d, prev[state_type]: %d\n", cur[loop].state_type, prev[prev_index].state_type);
			}
#endif

#ifdef _DEBUG
			if (any_shown == 0)
			{
				any_shown = 1;
				dolog("emitting results");
			}
#endif
			if (emit_status(server_conn, channel, &cur[loop]) == -1)
				return -1;
		}
	}

	free_stats_array(prev, n_prev);
	prev = cur;
	n_prev = n_cur;

	return 0;
}

int resend_nagios_status(server_t server_conn, char *to_who)
{
	int something_sent = 0;

	if (n_prev == 0)
		return irc_privmsg(server_conn, to_who, "not available yet");

	for(int loop=0; loop<n_prev; loop++)
	{
		if (should_i_show_entry(prev, n_prev, loop, 0, 0, 0, 1))
		{
			something_sent = 1;

			if (emit_status(server_conn, to_who, &prev[loop]) == -1)
				return -1;
		}
	}

	if (!something_sent)
		return irc_privmsg(server_conn, to_who, "all fine");

	return 0;
}

int send_help(server_t server_conn, char *to_who)
{
	if (irc_privmsg(server_conn, to_who, "'resend' lets the bot resend the current nagios status in a private message") == -1)
		return -1;

	if (irc_privmsg(server_conn, to_who, "'statistics' sends you the statistics") == -1)
		return -1;

	if (irc_privmsg(server_conn, to_who, "'check' verifies if Nagios is still checking hosts/services") == -1)
		return -1;

	return 0;
}

int reload_statuslog(void)
{
	int fd_sl;
	struct stats *cur = NULL;
	int n_cur = 0;

	if (verbose > 1) dolog("reload_statuslog started");

	if (statuslog_location == L_FILE)     /* file */
		fd_sl = open64(statuslog, O_RDONLY);
	else
		fd_sl = connect_to(statuslog);

	if (fd_sl != -1)
	{
		if (statuslog_version == 2)
			parse_2_0_statuslog(fd_sl, &cur, &n_cur);
		else
			parse_1_0_statuslog(fd_sl, &cur, &n_cur);

		close(fd_sl);

		free_stats_array(prev, n_prev);
		prev = cur;
		n_prev = n_cur;

		return 0;
	}

	dolog("reload failed: %s", strerror(errno));

	return -1;
}

char * check_nagios()
{
	char *message = NULL;

	(void)check_max_age_last_check(prev, n_prev, max_time_last_host_update, max_time_oldest_host_update, max_time_last_host_check, max_time_oldest_host_check, max_time_last_service_check, max_time_oldest_service_check, max_time_oldest_next_service_check, &message);

	return message;
}

int do_nagircbot(server_t server_conn, char *channel)
{
	time_t last_check = 0;

	for(;;)
	{
		fd_set rfds;
		struct timeval tv;
		time_t now = time(NULL);

		if ((now - last_check) >= check_interval)
		{
			if (check_nagios_status(server_conn) == -1)
				return -1;

			last_check = now;
		}

		if (now >= (last_irc_io + irc_server_keep_alive))
		{
			if (irc_time(server_conn) == -1)
				return -1;
		}

		FD_ZERO(&rfds);
		FD_SET(server_conn.fd, &rfds);

		tv.tv_sec = max(check_interval - (now - last_check), 0);
		tv.tv_usec = 0;

		if (irc_server_keep_alive > 0)
		{
			tv.tv_sec = min(max(0, (last_irc_io + irc_server_keep_alive) - now), tv.tv_sec);
		}

		if (announce_global_status_interval > 0)
		{
			tv.tv_sec = min(max(0, (last_announce_global_status_interval + announce_global_status_interval) - now), tv.tv_sec);
		}

		if (select(server_conn.fd + 1, &rfds, NULL, NULL, &tv) == -1)
		{
			if (errno == EAGAIN || errno == EINTR)
				continue;

			error_exit("select() failed %s\n", strerror(errno));
		}

		if (FD_ISSET(server_conn.fd, &rfds))
		{
			char recv_buffer[4096];
			int rc = IRCREAD(server_conn, recv_buffer, sizeof(recv_buffer) - 1);

			if (rc == 0)
			{
				dolog("connection closed by IRC server");
				return -1;
			}
			else if (rc == -1)
			{
				if (errno != EAGAIN && errno != EINTR)
				{
					dolog("error transmitting data to IRC server: %s", strerror(errno));
					return -1;
				}
			}
			else
			{
				char *next_response = recv_buffer;
				char *response = NULL;
				char *cmd = NULL;
				char *crlf = NULL;

				recv_buffer[rc] = 0x00;

				while ((response = next_response)
				       && (crlf = strchr( response, '\r')))
				{
					if (*(crlf + 1) != '\n')
					{
						dolog( "Malformed server response: `%s' at `%s'.", recv_buffer, response );
						return -1;
					}

					*crlf = '\0';
					if (( crlf + 2 ) - recv_buffer < rc)
						next_response = crlf + 2;
					else
						next_response = NULL;

					if (verbose > 1) dolog("IN: `%s'", response);

					if (check_ping(server_conn, response) == -1)
						return -1;

					cmd = strchr(response, ' ');
					if (cmd)
					{
						while(*cmd == ' ') cmd++;

						if (strncmp(cmd, "KICK ", 5) == 0)
						{
							char *dummy = strchr(cmd, ' ');
							if (dummy)
							{
								while(*dummy == ' ') dummy++;
								dummy = strchr(dummy, ' ');
							}
							if (dummy)
							{
								while(*dummy == ' ') dummy++;

								if (strncmp(dummy, nick, strlen(nick)) == 0)
								{
									dolog("nagircbot got kicked (%s)", response);
									return -1;
								}
								else if (verbose > 1)
								{
									dolog("user %s got kicked from channel", dummy);
								}
							}
						}
						else if (strncmp(cmd, "PRIVMSG ", 8) == 0)
						{
							/* :flok!~flok@rammstein.amc.nl PRIVMSG nagircbot :test */

							char *to_me = &cmd[8];
							while(*to_me == ' ') to_me++;

							/* message to this bot? */
							if (strncmp(to_me, nick, strlen(nick)) == 0)
							{
								/* yes */
								char *msg = strchr(cmd, ':');
								char *from_who = response + 1;
								char *dummy = strchr(from_who, '!');

								if (msg != NULL && dummy != NULL)
								{
									msg++;
									*dummy = 0x00;

									if (strcasecmp(msg, "help") == 0)
									{
										int rc = 0;

										dolog("help requested by %s", from_who);

										rc |= irc_privmsg(server_conn, from_who, "resend - resend the last known problems");
										rc |= irc_privmsg(server_conn, from_who, "statistics - returns the number of criticals/warnings/etc.");
										rc |= irc_privmsg(server_conn, from_who, "reload - completely forced reload the nagios status");
										rc |= irc_privmsg(server_conn, from_who, "check - check if nagios is still running");

										if (rc == -1)
											return -1;
									}
									else if (strcmp(msg, "resend") == 0)
									{
										dolog("resend requested by %s", from_who);

										if (resend_nagios_status(server_conn, from_who) == -1)
											return -1;
									}
									else if (strcmp(msg, "check") == 0)
									{
										char *message;
										int rc;

										dolog("nagios check requested by %s", from_who);

										message = check_nagios();
										if (message)
										{
											char buffer[4096];
											snprintf(buffer, sizeof(buffer), "Nagios has stopped running! -> %s", message);
											rc = irc_privmsg(server_conn, from_who, buffer);
											free(message);
										}
										else
											rc = irc_privmsg(server_conn, from_who, "Nagios is still running");

										if (rc == -1)
											return -1;
									}
									else if (strcmp(msg, "statistics") == 0)
									{
										char buffer[4096];

										dolog("statistics requested by %s", from_who);

										calc_statistics(prev, n_prev, 0, 0, 0, 1, buffer, sizeof(buffer));

										if (irc_privmsg(server_conn, from_who, buffer) == -1)
											return -1;
									}
									else if (strcmp(msg, "reload") == 0)
									{
										dolog("reload requested by %s", from_who);

										if (reload_statuslog() == -1)
										{
											if (irc_privmsg(server_conn, from_who, "cannot access status.log") == -1)
												return -1;
										}
										else
										{
											if (irc_privmsg(server_conn, from_who, "nagios status reloaded") == -1)
												return -1;
										}
									}
									else
									{
										char buffer[4096];

										dolog("giberish sent by %s", from_who);

										snprintf(buffer, sizeof(buffer), "'%s' is not understood - send 'help' for help", msg);
										if (irc_privmsg(server_conn, from_who, buffer) == -1)
											return -1;

										if (send_help(server_conn, from_who) == -1)
											return -1;
									}
								}
							}

						}
						else if (strncmp(cmd, "QUIT ", 5) == 0)
						{
							char *quit_nick = &response[1];
							char *exclamation_mark = strchr(quit_nick, '!');
							if (exclamation_mark) *exclamation_mark = 0x00;

							if (strcmp(nick, quit_nick) == 0)
							{
								dolog("Got QUITed (%s)", cmd);
								return -1;
							}
							else if (verbose > 1)
							{
								dolog("user %s quit", quit_nick);
							}
						}

					}
				}
			}
		}
	}

	dolog("NagIRCBot dropped of IRC server");

	return -1;
}

void sighandler(int pid)
{
	if (pid == SIGTERM)
	{
		if (pidfile)
			unlink(pidfile);

		exit(1);
	}
}

void version(void)
{
	printf("nagircbot, v" VERSION " (C) 2006-2011 by folkert@vanheusden.com\n");
}

void usage(void)
{
	version();

	printf("\n");

	printf("-f     path to status.log ('status_file' parameter in nagios.cfg)\n");
	printf("-F     host:port for retrieving status.log\n");
	printf("-x     status.log is in nagios 1.0 format\n");
	printf("-X     status.log is in nagios 2.0/3.0 format\n");
	printf("-s     IRC server to connect to (host:port)\n");
	printf("-c     channel to connect to (#channel - do not forget to escape the '#' in your shell)\n");
	printf("-k     keyword for the channel to connect to (default: no keyword)\n");
	printf("-C     use colors\n");
	printf("-n     nick\n");
	printf("-u     username (for logging into the irc server)\n");
	printf("-U     name (as seen by other users)\n");
	printf("-p     password (for logging into the irc server)\n");
	printf("-N     prefix for all in-channel messages, e.g. for nick highlight\n");
	printf("-m     display all information on separate lines\n");
	printf("-t     show a summary in the topic-line\n");
	printf("-i     check interval (in seconds - default 60)\n");
	printf("-I     how often to announce the global status in the channel (in seconds, 0 for off)\n");
	printf("       do not set it smaller then the -i value\n");
	printf("-d     do not fork into the background\n");
	printf("-T x   checks to see if nagios is still running. comma-seperated list\n");
	printf("       (without spaces!) with the following elements:\n");
	printf("       max_time_last_host_update, max_time_oldest_host_update,\n");
	printf("       max_time_last_host_check, max_time_oldest_host_check,\n");
	printf("       max_time_last_service_check, max_time_oldest_service_check,\n");
	printf("       max_time_oldest_next_service_check\n");
	printf("       send 'check' in a private message to invoke the check\n");
	printf("-z user	user to run as\n");
	printf("-H     show only state type 'HARD' (default)\n");
	printf("-S     show also state type 'SOFT'\n");
	printf("-R     only announce CRITICAL/UNKNOWN errors on the channel\n");
	printf("-A x   filter (omit) lines that match with the given regular expression\n");
	printf("-P x   write pid to file x\n");
	printf("-e     Use encryption (SSL) (default: no)\n");
}

void add_filter(char *string)
{
	filters = (regex_t *)realloc(filters, sizeof(regex_t) * (n_filters + 1));
	if (!filters)
		error_exit("add_filter: out of memory");

	if (regcomp(&filters[n_filters], string, REG_EXTENDED))
		error_exit("add_filter: failed to compile regexp '%s'", string);

	n_filters++;
}

int main(int argc, char *argv[])
{
	int state = S_DISCONNECTED;
	int fd = -1;
	int sleep_time = default_sleep;
	int c;
	int do_fork = 1;
	time_t time_join_channel_started = (time_t)0;
	time_t time_tcp_connected = (time_t)0;
	int join_tries = 0;
	char *runas = NULL;

	color_str[0] = mystrdup("_3,1 ");
	color_str[1] = mystrdup("_8,1 ");
	color_str[2] = mystrdup("_4,1 ");
	color_str[3] = mystrdup("_11,1 ");
	color_str[0][0] = color_str[1][0] = color_str[2][0] = color_str[3][0] = 3;

	while((c = getopt(argc, argv, "N:A:eRP:xXF:f:i:hHSs:c:k:Ctn:u:U:p:T:mvdVz:I:")) != -1)
	{
		switch(c)
		{
			case 'A':
				add_filter(optarg);
				break;

			case 'R':
				critical_only = 1;
				break;

			case 'e':
				use_ssl = 1;
				break;

			case 'P':
				pidfile = optarg;
				break;

			case 'I':
				announce_global_status_interval = atoi(optarg);
				break;

			case 'z':
				runas = optarg;
				break;

			case 'T':
				{
					char *dummy = optarg;

					max_time_last_host_update = atoi(dummy);
					dummy = strchr(dummy, ',') + 1;

					max_time_oldest_host_update = atoi(dummy);
					dummy = strchr(dummy, ',') + 1;

					max_time_last_host_check = atoi(dummy);
					dummy = strchr(dummy, ',') + 1;

					max_time_oldest_host_check = atoi(dummy);
					dummy = strchr(dummy, ',') + 1;

					max_time_last_service_check = atoi(dummy);
					dummy = strchr(dummy, ',') + 1;

					max_time_oldest_service_check = atoi(dummy);
					dummy = strchr(dummy, ',') + 1;

					max_time_oldest_next_service_check = atoi(dummy);
					break;
				}

			case 'H':
				hard_only = 1;
				break;

			case 'S':
				hard_only = 0;
				break;

			case 'd':
				do_fork = 0;
				break;

			case 't':
				topic_summary = 1;
				break;

			case 'C':
				use_colors = 1;
				break;

			case 'm':
				one_line = 0;
				break;

			case 'N':
				nick_prefix = mystrdup(optarg);
				break;

			case 'p':
				password = mystrdup(optarg);
				break;

			case 'U':
				username = mystrdup(optarg);
				break;

			case 'n':
				nick = mystrdup(optarg);
				break;

			case 'u':
				user = mystrdup(optarg);
				break;

			case 's':
				server = mystrdup(optarg);
				break;

			case 'c':
				channel = mystrdup(optarg);
				break;

			case 'k':
				keyword = mystrdup(optarg);
				break;

			case 'x':
				statuslog_version = 1;
				break;

			case 'X':
				statuslog_version = 2;
				break;

			case 'f':
				statuslog = optarg;
				statuslog_location = L_FILE;
				break;

			case 'F':
				statuslog = optarg;
				statuslog_location = L_HOST;
				break;

			case 'i':
				check_interval = atoi(optarg);
				break;

			case 'v':
				verbose = 1;
				break;

			case 'V':
				version();
				return 0;

			case 'h':
				usage();
				return 0;

			default:
				usage();
				return 1;
		}
	}

	if (do_fork)
	{
		if (daemon(0, 0) == -1)
		{
			error_exit("Failed to become daemon process!");
		}
	}

	if (pidfile)
		write_pidfile(pidfile);

	if (runas)
	{
		struct passwd *p = getpwnam(runas);
		if (p)
		{
			if (setgid(p -> pw_gid) == -1)
				error_exit("setgid(%d) failed", p -> pw_gid);

			if (setuid(p -> pw_uid) == -1)
				error_exit("setuid(%d) failed", p -> pw_uid);
		}
		else
		{
			error_exit("User '%s' not found.", runas);
		}
	}

	signal(SIGPIPE, SIG_IGN);
	signal(SIGTERM, sighandler);
	server_conn.addr = server;
	for(;;)
	{
		if (state == S_DISCONNECTED)
		{
			dolog("Connecting to %s", server);

			fd = connect_to(server);
			if (fd == -1)
			{
				if (verbose > 1)
					dolog("Cannot connect to %s: %m, will sleep %d seconds", server, sleep_time);

				sleep(sleep_time);
				if (sleep_time < max_sleep_time)
					sleep_time = (int)((double)sleep_time * 1.5);
				else
					sleep_time = max_sleep_time;
			}
			else
			{
				server_conn.fd = fd;
				if( use_ssl ) {
					dolog("Doing SSL handshake");
					if( !ssl_handshake( &server_conn ) ){
						dolog("SSL handshake failed!");
						return 1;
					}
				}
				state = S_CONNECTED;
				join_tries = 0;
				time_tcp_connected = time(NULL);
			}

			if (verbose == 1)
				dolog("Connected to IRC server %s", server);
		}

		if (state == S_CONNECTED)
		{

			dolog("Logging in...");

			if (irc_login(server_conn, user, username, server, password) == -1)
				state = S_DISCONNECTING;

			if (irc_join_channel(server_conn, channel, keyword) == -1)
				state = S_DISCONNECTING;

			state = S_JOINING_CHANNEL;

			time_join_channel_started = time(NULL);
		}

		if (state == S_JOINING_CHANNEL)
		{
			fd_set rfds;
			struct timeval tv;

			dolog("Joining channel...");

			FD_ZERO(&rfds);
			FD_SET(server_conn.fd, &rfds);

			tv.tv_sec = 1;
			tv.tv_usec = 0;

			if (select(server_conn.fd + 1, &rfds, NULL, NULL, &tv) == -1)
			{
				if (errno != EAGAIN && errno != EINTR)
					error_exit("select() failed");
			}

			if (FD_ISSET(server_conn.fd, &rfds))
			{
				char recv_buffer[4096];
				int rc = IRCREAD(server_conn, recv_buffer, sizeof(recv_buffer) - 1);

				if (rc == 0)
					state = S_DISCONNECTING;
				else if (rc == -1)
				{
					if (errno != EAGAIN && errno != EINTR)
					{
						dolog("failed joining channel: %s", strerror(errno));
						state = S_DISCONNECTING;
					}
				}
				else
				{
					char *cmd, *pnt = recv_buffer, *end_marker;

					recv_buffer[rc] = 0x00;

					if (check_ping(server_conn, recv_buffer) == -1)
						return -1;

					do
					{
						end_marker = strchr(pnt, '\n');
						if (end_marker) *end_marker = 0x00;
						if (!end_marker) end_marker = strchr(pnt, '\r');
						if (end_marker) *end_marker = 0x00;
#ifdef _DEBUG
						//						printf("[%s]", pnt);
#endif

						cmd = strchr(pnt, ' ');
						if (cmd)
						{
							while(*cmd == ' ') cmd++;

							if (strncmp(cmd, "JOIN ", 5) == 0)
							{
								if (verbose > 1)
									dolog("on channel");
								state = S_ON_CHANNEL;
							}
							else if (strncmp(cmd, "KICK ", 5) == 0)
							{
								char *dummy = strchr(cmd, ' ');
								if (dummy)
								{
									while(*dummy == ' ') dummy++;
									dummy = strchr(dummy, ' ');
								}
								if (dummy)
								{
									while(*dummy == ' ') dummy++;

									if (strncmp(dummy, nick, strlen(nick)) == 0)
										state = S_CONNECTED;
								}
							}
						}

						pnt = end_marker + 1;
					}
					while(end_marker);
				}
			}
		}

		if (state == S_JOINING_CHANNEL && (time(NULL) - time_join_channel_started) > join_timeout)
		{
			if (++join_tries == max_n_join_tries)
			{
				state = S_DISCONNECTING;
				dolog("joining the channel %s too to long (%ds, timeout set to %ds), aborting and retrying ", channel, time(NULL) - time_join_channel_started, join_timeout);
			}
			else
			{
				state = S_CONNECTED;
				dolog("failed joining channel %s, retrying (%d/%d)", channel, join_tries, max_n_join_tries);
			}
		}

		if (state == S_ON_CHANNEL)
		{
			dolog("On channel, starting nagtail...");
			sleep_time = default_sleep;

			(void)do_nagircbot(server_conn, channel);

			state = S_DISCONNECTING;
		}

		if (state == S_DISCONNECTING)
		{
			int took = time(NULL) - time_tcp_connected;

			dolog("Disconnected");

			close(fd);
			fd = -1;
			state = S_DISCONNECTED;

			dolog("Session took %d seconds", took);

			if (took < minimum_time_for_successfull_login)
			{
				dolog("Will sleep %d seconds before retrying", sleep_time);

				sleep(sleep_time);

				if (sleep_time < max_sleep_time)
					sleep_time = (int)((double)sleep_time * sleep_multiply_factor);
				else
					sleep_time = max_sleep_time;
			}
		}
	}

	return 0;
}
