/*

(C) 2009-2019 Mika Ilmaranta <ilmis@nullnet.fi>

License: GPLv2

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <dirent.h>
#include <fnmatch.h>

#include "config.h"
#include "defs.h"

#define DEFAULT_SCRIPT_FILE SCRIPTDIR "/default_script"

static CONFIG defaults;
static int errors = 0;

GLOBAL cfg;

static void read_one_config(char *fn, CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg);
static int find_all_configs(char* fn, int mustexist, CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg);
static void reassign(char **dst, char *src);
static void release(char **dst);
static int eqcmp(char *str, char *pat);
static int check_addrs(CONFIG *cur);

static void reassign(char **dst, char *src)
{
	if(*dst) free(*dst);
	*dst = strdup(src);
}

static void release(char **dst)
{
	if(*dst) free(*dst);
	*dst = NULL;
}

static int eqcmp(char *str, char *pat)
{
	int i;

	i = strlen(pat);
	if(!strncmp(str, pat, i) && str[i] == '=') return 0;
	return 1;
}

int reload_config(char *fn, CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg) {
	free_config(first, last, firstg, lastg);
	init_config();
	return(read_config(fn, first, last, firstg, lastg));
}

void free_config(CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg) {
	CONFIG *cur, *prev;
	GROUPS *curg, *prevg;
	GROUP_MEMBERS *curgm, *prevgm;

	cur = (*first);
	while(cur) {
		if(cur->name && cur->name != defaults.name)                            release(&cur->name);
		if(cur->sourceip && cur->sourceip != defaults.sourceip)                release(&cur->sourceip);
		if(cur->srcinfo)                                                       freeaddrinfo(cur->srcinfo);
		if(cur->checkip && cur->checkip != defaults.checkip)                   release(&cur->checkip);
		if(cur->dstinfo)                                                       freeaddrinfo(cur->dstinfo);
		if(cur->eventscript && cur->eventscript != defaults.eventscript)       release(&cur->eventscript);
		if(cur->notifyscript && cur->notifyscript != defaults.notifyscript)    release(&cur->notifyscript);
		if(cur->warn_email && cur->warn_email != defaults.warn_email)          release(&cur->warn_email);
		if(cur->device && cur->device != defaults.device)                      release(&cur->device);
		if(cur->queue && cur->queue != defaults.queue)                         release(&cur->queue);
		if(cur->long_down_email && cur->long_down_email != defaults.long_down_email) release(&cur->long_down_email);
		if(cur->long_down_notifyscript && cur->long_down_notifyscript != defaults.long_down_notifyscript) release(&cur->long_down_notifyscript);
		if(cur->long_down_eventscript && cur->long_down_eventscript != defaults.long_down_eventscript) release(&cur->long_down_eventscript);

		prev = cur;
		cur = cur->next;
		free(prev);
	}

	*first = NULL;
	*last = NULL;

	curg = (*firstg);
	while(curg) {
		curgm = curg->fgm;
		while(curgm) {
			free(curgm->name);

			prevgm = curgm;
			curgm = curgm->next;
			free(prevgm);
		}
		curg->fgm = NULL;
		curg->lgm = NULL;

		if(curg->name && curg->name != defaults.name)				release(&curg->name);
		if(curg->eventscript && curg->eventscript != defaults.eventscript)	release(&curg->eventscript);
		if(curg->notifyscript && curg->notifyscript != defaults.notifyscript)	release(&curg->notifyscript);
		if(curg->warn_email && curg->warn_email != defaults.warn_email)		release(&curg->warn_email);
		if(curg->device && curg->device != defaults.device)                     release(&curg->device);
		if(curg->queue && curg->queue != defaults.queue)                        release(&curg->queue);

		prevg = curg;
		curg = curg->next;
		free(prevg);
	}

	*firstg = NULL;
	*lastg = NULL;

	if(defaults.name)                   release(&defaults.name);
	if(defaults.checkip)                release(&defaults.checkip);
	if(defaults.eventscript)            release(&defaults.eventscript);
	if(defaults.notifyscript)           release(&defaults.notifyscript);
	if(defaults.warn_email)             release(&defaults.warn_email);
	if(defaults.sourceip)               release(&defaults.sourceip);
	if(defaults.device)                 release(&defaults.device);
	if(defaults.queue)                  release(&defaults.queue);

	if(defaults.long_down_email)        release(&defaults.long_down_email);
	if(defaults.long_down_notifyscript) release(&defaults.long_down_notifyscript);
	if(defaults.long_down_eventscript)  release(&defaults.long_down_eventscript);
}

void init_config(void)
{
	/* zero cfg and defaults */
	memset(&cfg, 0, sizeof(cfg));
	memset(&defaults, 0, sizeof(defaults));

	/* initialize to sane value */
	cfg.debug = 8;

	defaults.name = strdup("defaults");
	defaults.checkip = strdup("127.0.0.1");
	defaults.eventscript = NULL;
	defaults.notifyscript = strdup(DEFAULT_SCRIPT_FILE);
	defaults.max_packet_loss = 15;
	defaults.max_successive_pkts_lost = 7;
	defaults.min_packet_loss = 5;
	defaults.min_successive_pkts_rcvd = 10;
	defaults.interval_ms = 1000;
	defaults.timeout_ms = 1000;
	defaults.warn_email = strdup("root");
	defaults.check_arp = 0;
	defaults.sourceip = NULL;
	defaults.ttl = 0;

	/* assume default unknown state for connections unless user has stated otherwise later in config */
	defaults.status = UNKNOWN;

	/* no exec queue by default */
	defaults.queue = NULL;

	defaults.long_down_email = NULL;

	/* by default don't execute notify script on unkown to up event */
	defaults.unknown_up_notify = 0;

	/* by default no accelerated startup */
	defaults.startup_acceleration = 0;

	/* by default no startup burst */
	defaults.startup_burst_pkts = 0;
	defaults.startup_burst_interval = MIN_PERHOST_INTERVAL;
}

static int find_all_configs(char* fn, int mustexist, CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg)
{
	struct dirent **namelist;
	char dir[BUFSIZ], pattern[128], *p, s[BUFSIZ];
	int n, i, found;

	/* Split fn to dir/pattern */
	strcpy(dir, fn);
	if((p = strrchr(dir, '/')) == NULL) {
		strcpy(dir, ".");
		strcpy(pattern, fn);
	} else {
		*p = 0;
		strcpy(pattern, p + 1);
	}

	/* Find list of all files */
	n = scandir(dir, &namelist, 0, alphasort);
	if (n < 0) {
		if (mustexist == 0)
			return(0);
		syslog(LOG_ERR, "%s: can't read directory \"%s\"", __FUNCTION__, dir);
		return(-1);
	}

	/* See if name matches pattern */
	found = 0;
	for (i = 0; i < n; i++) {
		if (fnmatch(pattern, namelist[i]->d_name, 0) == 0 &&
		    fnmatch("*~", namelist[i]->d_name, 0) != 0) {
			snprintf(s, BUFSIZ, "%s/%s", dir, namelist[i]->d_name);
			read_one_config(s, first, last, firstg, lastg);
			found++;
		}
		free(namelist[i]);
	}
	free(namelist);

	/* Fail if no matches found */
	if (found == 0) {
		if (mustexist == 0)
			return(0);
		syslog(LOG_ERR, "%s: no config files found for \"%s\"", __FUNCTION__, fn);
		return(-1);
	}

	return(0);
}

int read_config(char *fn, CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg)
{
	CONFIG *cur = NULL;
	GROUPS *curg = NULL;
	GROUP_MEMBERS *curgm = NULL;

	errors = 0;

	read_one_config(fn, first, last, firstg, lastg);

	for(curg = *firstg; curg; curg = curg->next) {
		for(curgm = curg->fgm; curgm; curgm = curgm->next) {
			int found = 0;

			for(cur = *first; cur; cur = cur->next) {
				if(!strcmp(cur->name, curgm->name)) {
					curgm->cfg_ptr = cur;
					found = 1;
					break;
				}
			}
			if(!found) {
				syslog(LOG_ERR, "%s: %s: connection group member \"%s\" not found", __FILE__, __FUNCTION__, curgm->name);
				errors++;
			}
		}
	}

	/* some parameter sanity checking */
	for(cur = *first; cur; cur = cur->next) {
		if(strlen(cur->checkip) == 0) {
			syslog(LOG_ERR, "WARNING: connection \"%s\" has no checkip parameter set", cur->name);
			errors++;
		} else {
			if(check_addrs(cur) < 0) {
				errors++;
			}
		}

		if(cur->max_packet_loss <= cur->min_packet_loss) {
			syslog(LOG_ERR, "WARNING: connection \"%s\" max_packet_loss (%d) <= min_packet_loss (%d). that would cause flip-flop effect", cur->name, cur->max_packet_loss, cur->min_packet_loss);
			errors++;
		}

	}
	if(errors) return(-1);

	return(0);
}

static void read_one_config(char *fn, CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg)
{
	CONFIG *cur = NULL;
	GROUPS *curg = NULL;
	GROUP_MEMBERS *curgm = NULL;
	FILE *fp;
	char buf[BUFSIZ];
	int mode = 0;
	int line = 1;

	if((fp = fopen(fn, "r")) == 0) {
		syslog(LOG_ERR, "%s: can't open config file \"%s\"", __FUNCTION__, fn);
		return;
	}

	while(fgets(buf, BUFSIZ, fp)) {
		char *p = NULL;

		if(*buf && buf[strlen(buf) - 1] == '\n') buf[strlen(buf) - 1] = '\0'; /* strip lf */
		if((p = strchr(buf, '#')) != NULL) *p = '\0'; /* strip comment */
		while((p = strchr(buf, '\t')) != NULL) *p = ' '; /* tabs -> spaces */
		while(*buf == ' ') memmove(buf, buf + 1, strlen(buf)); /* strip leading space */
		while((p = strstr(buf, "  ")) != NULL) memmove(p, p + 1, strlen(p)); /* strip multi white space */
		while((p = strstr(buf, " =")) != NULL) memmove(p, p + 1, strlen(p)); /* strip spaces before = */
		while((p = strstr(buf, "= ")) != NULL) memmove(p + 1, p + 2, strlen(p + 1)); /* strip spaces after = */
		while(*buf && buf[strlen(buf) - 1] == ' ') buf[strlen(buf) - 1] = '\0'; /* strip tailing space */

		if(!*buf) continue;

		if(mode) {
			if (!strcmp(buf, "}")) {
				mode=0;
				continue;
			}

			switch(mode) {
			case 1: /* defaults */
				if(!eqcmp(buf, "name"))
					reassign(&defaults.name, strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "checkip"))
					reassign(&defaults.checkip, strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "eventscript"))
					reassign(&defaults.eventscript, strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "notifyscript"))
					reassign(&defaults.notifyscript, strchr(buf, '=') + 1);

				else if(!eqcmp(buf, "unknown_up_notify"))
					defaults.unknown_up_notify = atoi(strchr(buf, '=') + 1);

				else if(!eqcmp(buf, "max_packet_loss"))
					defaults.max_packet_loss = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "max_successive_pkts_lost"))
					defaults.max_successive_pkts_lost = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "min_packet_loss"))
					defaults.min_packet_loss = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "min_successive_pkts_rcvd"))
					defaults.min_successive_pkts_rcvd = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "interval_ms"))
					defaults.interval_ms = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "timeout_ms"))
					defaults.timeout_ms = atoi(strchr(buf, '=') + 1);

				else if(!eqcmp(buf, "warn_email"))
					reassign(&defaults.warn_email, strchr(buf, '=') + 1);

				else if(!eqcmp(buf, "check_arp"))
					defaults.check_arp = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "sourceip"))
					reassign(&defaults.sourceip, strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "device"))
					reassign(&defaults.device, strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "ttl"))
					defaults.ttl = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "status"))
					defaults.status = atoi(strchr(buf, '=') + 1);

				else if(!eqcmp(buf, "queue"))
					reassign(&defaults.queue, strchr(buf, '=') + 1);

				else if(!eqcmp(buf, "long_down_time"))
					defaults.long_down_time = atoi(strchr(buf, '=') + 1);

				else if(!eqcmp(buf, "long_down_email"))
					reassign(&defaults.long_down_email, strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "long_down_notifyscript"))
					reassign(&defaults.long_down_notifyscript, strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "long_down_eventscript"))
					reassign(&defaults.long_down_eventscript, strchr(buf, '=') + 1);

				else if(!eqcmp(buf, "startup_acceleration"))
					defaults.startup_acceleration = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "startup_burst_pkts"))
					defaults.startup_burst_pkts = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "startup_burst_interval"))
					defaults.startup_burst_interval = atoi(strchr(buf, '=') + 1);
				else {
					syslog(LOG_ERR, "%s: %s: unrecognised "
					       "default config option on "
					       "line %d \"%s\"", __FILE__,
					       __FUNCTION__, line, buf);
					errors++;
				}
				break;
			case 2: /* connection */
				if(!cur) {
					syslog(LOG_ERR, "read_config: cur == NULL");
					break;
				}

				if(!eqcmp(buf, "name"))                            cur->name                          = strdup(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "checkip"))                    cur->checkip                       = strdup(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "eventscript"))                cur->eventscript                   = strdup(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "notifyscript"))               cur->notifyscript                  = strdup(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "unknown_up_notify"))          cur->unknown_up_notify             = atoi(strchr(buf, '=') + 1);

				else if(!eqcmp(buf, "max_packet_loss"))            cur->max_packet_loss               = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "max_successive_pkts_lost"))   cur->max_successive_pkts_lost      = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "min_packet_loss"))            cur->min_packet_loss               = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "min_successive_pkts_rcvd"))   cur->min_successive_pkts_rcvd      = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "interval_ms"))                cur->interval_ms                   = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "timeout_ms"))                 cur->timeout_ms                    = atoi(strchr(buf, '=') + 1);

				else if(!eqcmp(buf, "warn_email"))                 cur->warn_email                    = strdup(strchr(buf, '=') + 1);

				else if(!eqcmp(buf, "check_arp"))                  cur->check_arp                     = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "sourceip"))                   cur->sourceip                      = strdup(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "device"))                     cur->device                        = strdup(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "ttl"))                        cur->ttl                           = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "status"))                     cur->status                        = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "queue"))                      cur->queue                         = strdup(strchr(buf, '=') + 1);

				else if(!eqcmp(buf, "long_down_time"))             cur->long_down_time                = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "long_down_email"))            cur->long_down_email               = strdup(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "long_down_notifyscript"))     cur->long_down_notifyscript        = strdup(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "long_down_eventscript"))      cur->long_down_eventscript         = strdup(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "startup_acceleration"))       cur->startup_acceleration          = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "startup_burst_pkts"))         cur->startup_burst_pkts            = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "startup_burst_interval"))     cur->startup_burst_interval        = atoi(strchr(buf, '=') + 1);
				else {
					syslog(LOG_ERR, "%s: %s: unrecognised connection config option on line %d \"%s\"", __FILE__, __FUNCTION__, line, buf);
					errors++;
				}
				break;
			case 3: /* group */
				if(!eqcmp(buf, "name"))                            curg->name				= strdup(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "eventscript"))                curg->eventscript			= strdup(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "notifyscript"))               curg->notifyscript			= strdup(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "unknown_up_notify"))          curg->unknown_up_notify              = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "warn_email"))                 curg->warn_email			= strdup(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "logic"))                      curg->logic				= atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "device"))                     curg->device                         = strdup(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "status"))			   curg->status                         = atoi(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "queue"))                      curg->queue                          = strdup(strchr(buf, '=') + 1);
				else if(!eqcmp(buf, "member-connection")) {
					if((curgm = (GROUP_MEMBERS *)malloc(sizeof(GROUP_MEMBERS))) == NULL) {
						syslog(LOG_ERR, "%s: %s: can't malloc for group member", __FILE__, __FUNCTION__);
						fclose(fp);
						return;
					}
					curgm->name = strdup(strchr(buf, '=') + 1);
					curgm->cfg_ptr = NULL;

					if(curg->lgm) { /* insert as last */
						curgm->next = NULL;
						curgm->prev = curg->lgm;
						curg->lgm->next = curgm;
						curg->lgm = curgm;
					} else { /* empty member list */
						curgm->next = NULL;
						curgm->prev = NULL;
						curg->fgm = curgm;
						curg->lgm = curgm;
					}
				}
				else {
					syslog(LOG_ERR, "%s: %s: unrecognised group config option on line %d \"%s\"", __FILE__, __FUNCTION__, line, buf);
					errors++;
				}
				break;
			default:
				syslog(LOG_ERR, "%s: %s: switch(mode) hit default: should never happen. mode was %d", __FILE__, __FUNCTION__, mode);
				errors++;
				break;
			}
		}
		else {
			/* global config */
			if(!eqcmp(buf, "debug"))
				cfg.debug = atoi(strchr(buf, '=') + 1);

			/* per connection configs */
			else if(!strcmp(buf, "defaults {"))
				mode=1;
			else if(!strcmp(buf, "connection {")) {
				mode=2;
				if((cur = malloc(sizeof(CONFIG))) == NULL) {
					syslog(LOG_ERR, "%s: %s: can't malloc for config", __FILE__, __FUNCTION__);
					return;
				}

				if(*last) { /* not first */
					(*last)->next = cur;
					cur->prev = *last;
					cur->next = NULL;
					*last = cur;
				}
				else {
					*first = cur;
					*last = cur;
					cur->prev = NULL;
					cur->next = NULL;
				}
				/* fill in defaults */
				if(defaults.name) {
					cur->name                       = defaults.name;
					cur->sourceip                   = defaults.sourceip;
					cur->srcinfo                    = NULL;
					cur->checkip                    = defaults.checkip;
					cur->dstinfo                    = NULL;
					cur->eventscript                = defaults.eventscript;
					cur->notifyscript               = defaults.notifyscript;
					cur->unknown_up_notify          = defaults.unknown_up_notify;
					cur->max_packet_loss            = defaults.max_packet_loss;
					cur->max_successive_pkts_lost   = defaults.max_successive_pkts_lost;
					cur->min_packet_loss            = defaults.min_packet_loss;
					cur->min_successive_pkts_rcvd   = defaults.min_successive_pkts_rcvd;
					cur->interval_ms                = defaults.interval_ms;
					cur->timeout_ms                 = defaults.timeout_ms;
					cur->warn_email                 = defaults.warn_email;
					cur->check_arp                  = defaults.check_arp;
					cur->device                     = defaults.device;
					cur->ttl                        = defaults.ttl;
					cur->status			= defaults.status;
					cur->queue                      = defaults.queue;
					cur->long_down_time             = defaults.long_down_time;
					cur->long_down_email            = defaults.long_down_email;
					cur->long_down_notifyscript     = defaults.long_down_notifyscript;
					cur->long_down_eventscript      = defaults.long_down_eventscript;
					cur->startup_acceleration	= defaults.startup_acceleration;
					cur->startup_burst_pkts		= defaults.startup_burst_pkts;
					cur->startup_burst_interval     = defaults.startup_burst_interval;
				}
				else
					syslog(LOG_ERR, "%s: %s: defaults not set", __FILE__, __FUNCTION__);
			}
			else if(!strcmp(buf, "group {")) {
				mode = 3;

				if((curg = (GROUPS *)malloc(sizeof(GROUPS))) == NULL) {
					syslog(LOG_ERR, "read_config: can't malloc for group");
					return;
				}

				/* apply sane defaults for group */
				curg->name            = defaults.name;
				curg->eventscript     = defaults.eventscript;
				curg->notifyscript    = defaults.notifyscript;
				curg->unknown_up_notify = defaults.unknown_up_notify;
				curg->warn_email      = defaults.warn_email;
				curg->logic           = 0; /* default group logic or */
				curg->device          = defaults.device;
				curg->status          = defaults.status;
				curg->queue           = defaults.queue;

				curg->fgm = NULL;
				curg->lgm = NULL;

				if(*lastg) { /* not first group */
					(*lastg)->next = curg;
					curg->prev = *lastg;
					curg->next = NULL;
					*lastg = curg;
				} else {
					*firstg = curg;
					*lastg = curg;
					curg->prev = NULL;
					curg->next = NULL;
				}
			}
			else if(!strncmp(buf, "include ", 8)) {
				if(find_all_configs(strchr(buf, ' ') + 1, 1, first, last, firstg, lastg) != 0) {
					syslog(LOG_ERR, "%s: %s: failed to process included config file on line %d \"%s\"", __FILE__, __FUNCTION__, line, strchr(buf, ' ') + 1);
					errors++;
				}
				continue;
			}
			else if(!strncmp(buf, "-include ", 9)) {
				if(find_all_configs(strchr(buf, ' ') + 1, 0, first, last, firstg, lastg) != 0) {
					syslog(LOG_ERR, "%s: %s: failed to process included config file on line %d \"%s\"", __FILE__, __FUNCTION__, line, strchr(buf, ' ') + 1);
					errors++;
				}
				continue;
			}
			else {
				syslog(LOG_ERR, "%s: %s: unrecognised global config option in file \"%s\" on line %d \"%s\"", __FILE__, __FUNCTION__, fn, line, buf);
				errors++;
			}
		}
		line++;
	}

	if(mode != 0) {
		syslog(LOG_ERR, "%s: %s: missing closing bracket at the end of config file \"%s\"", __FILE__, __FUNCTION__, fn);
		errors++;
	}

	fclose(fp);
}

void dump_config(CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg)
{
	CONFIG *cur;
	GROUPS *curg;
	GROUP_MEMBERS *curgm;

	syslog(LOG_INFO,   "cfg.debug                     = \"%d\"", cfg.debug);

	for(cur = *first; cur; cur = cur->next) {
		syslog(LOG_INFO, "cur->name                     = \"%s\"", cur->name);
		syslog(LOG_INFO, "cur->sourceip                 = \"%s\"", cur->sourceip);
#if defined(DEBUG)
		if(cur->srcinfo) {
			char sbuf[INET6_ADDRSTRLEN];

			syslog(LOG_INFO, "cur->srcinfo                  = \"%s\"", inet_ntop(cur->srcinfo->ai_family, &cur->srcinfo->ai_addr, sbuf, INET6_ADDRSTRLEN));
		}
#endif
		syslog(LOG_INFO, "cur->checkip                  = \"%s\"", cur->checkip);
#if defined(DEBUG)
		if(cur->dstinfo) {
			char sbuf[INET6_ADDRSTRLEN];

			syslog(LOG_INFO, "cur->dstinfo                  = \"%s\"", inet_ntop(cur->dstinfo->ai_family, &cur->dstinfo->ai_addr, sbuf, INET6_ADDRSTRLEN));
		}
#endif
		syslog(LOG_INFO, "cur->eventscript              = \"%s\"", cur->eventscript);
		syslog(LOG_INFO, "cur->notifyscript             = \"%s\"", cur->notifyscript);
		syslog(LOG_INFO, "cur->unknown_up_notify        = \"%d\"", cur->unknown_up_notify);

		syslog(LOG_INFO, "cur->max_packet_loss          = \"%d\"", cur->max_packet_loss);
		syslog(LOG_INFO, "cur->max_successive_pkts_lost = \"%d\"", cur->max_successive_pkts_lost);
		syslog(LOG_INFO, "cur->min_packet_loss          = \"%d\"", cur->min_packet_loss);
		syslog(LOG_INFO, "cur->min_successive_pkts_rcvd = \"%d\"", cur->min_successive_pkts_rcvd);
		syslog(LOG_INFO, "cur->interval_ms              = \"%d\"", cur->interval_ms);
		syslog(LOG_INFO, "cur->timeout_ms               = \"%d\"", cur->timeout_ms);

		syslog(LOG_INFO, "cur->warn_email               = \"%s\"", cur->warn_email);

		syslog(LOG_INFO, "cur->check_arp                = \"%d\"", cur->check_arp);
		syslog(LOG_INFO, "cur->device                   = \"%s\"", cur->device);
		syslog(LOG_INFO, "cur->ttl                      = \"%d\"", cur->ttl);
		syslog(LOG_INFO, "cur->status                   = \"%d\"", cur->status);
		syslog(LOG_INFO, "cur->startup_acceleration     = \"%d\"", cur->startup_acceleration);
		syslog(LOG_INFO, "cur->startup_burst_pkts       = \"%d\"", cur->startup_burst_pkts);
		syslog(LOG_INFO, "cur->startup_burst_interval   = \"%d\"", cur->startup_burst_interval);
	}

	for(curg = *firstg; curg; curg = curg->next) {
		syslog(LOG_INFO, "curg->name                    = \"%s\"", curg->name);
		syslog(LOG_INFO, "curg->eventscript             = \"%s\"", curg->eventscript);
		syslog(LOG_INFO, "curg->notifyscript            = \"%s\"", curg->notifyscript);
		syslog(LOG_INFO, "curg->unknown_up_notify       = \"%d\"", curg->unknown_up_notify);
		syslog(LOG_INFO, "curg->warn_email              = \"%s\"", curg->warn_email);
		syslog(LOG_INFO, "curg->device                  = \"%s\"", curg->device);
		syslog(LOG_INFO, "curg->logic                   = \"%s\"", curg->logic == 0 ? "OR" : "AND");

		for(curgm = curg->fgm; curgm; curgm = curgm->next) {
			syslog(LOG_INFO, "curgm->name                   = \"%s\"", curgm->name);
		}
	}
}

static int check_addrs(CONFIG *cur)
{
	struct in6_addr serveraddr;
	struct addrinfo hints;
#if defined(DEBUG)
	struct addrinfo *rp;
#endif
	int rc;

	memset(&hints, 0, sizeof(hints));
	hints.ai_flags    = AI_NUMERICSERV;
	hints.ai_family   = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	if((rc = inet_pton(AF_INET, cur->checkip, &serveraddr)) == 1) { /* valid v4 addr */
		hints.ai_family = AF_INET;
		hints.ai_flags |= AI_NUMERICHOST;
	}
	else if((rc = inet_pton(AF_INET6, cur->checkip, &serveraddr)) == 1) { /* valid v6 addr */
		hints.ai_family = AF_INET6;
		hints.ai_flags |= AI_NUMERICHOST;
	}

	if((rc = getaddrinfo(cur->checkip, "1025", &hints, &(cur->dstinfo))) != 0) {
		syslog(LOG_ERR, "WARNING: connection \"%s\" checkip is invalid %s, %s", cur->name, cur->checkip, gai_strerror(rc));
		return(-1);
	}

#if defined(DEBUG)
	for(rp = cur->dstinfo; rp; rp = rp->ai_next) {
		unsigned char *s;
		char buf[BUFSIZ];
		char sbuf[INET6_ADDRSTRLEN];
		int i;

		memset(buf, 0, BUFSIZ);
		s = (unsigned char *)rp;

		strcat(buf, "hex dump:");
		for(i = 0; i < sizeof(struct addrinfo); i++)
			sprintf(buf + strlen(buf), " %2x", s[i]);
		syslog(LOG_INFO, "%s: %s: dst %s", __FILE__, __FUNCTION__, buf);

		memset(buf, 0, BUFSIZ);
		s = (unsigned char *)rp;

		strcat(buf, "dec dump:");
		for(i = 0; i < sizeof(struct addrinfo); i++)
			sprintf(buf + strlen(buf), " %3d", s[i]);
		syslog(LOG_INFO, "%s: %s: dst %s", __FILE__, __FUNCTION__, buf);

		syslog(LOG_INFO, "%s: %s: dst %s = %s", __FILE__, __FUNCTION__, cur->checkip, inet_ntop(rp->ai_family, &rp->ai_addr, sbuf, INET6_ADDRSTRLEN));
	}
#endif

	if(cur->dstinfo->ai_family == AF_INET6 && cur->check_arp) {
		syslog(LOG_ERR, "WARNING: connection \"%s\" ipv6 and arping are not compatible", cur->name);
		return(-1);
	}

	if(!cur->sourceip || !*cur->sourceip) return(0); /* sourceip is not mandatory */

	memset(&hints, 0, sizeof(hints));
	hints.ai_flags    = AI_NUMERICSERV;
	hints.ai_family   = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	if((rc = inet_pton(AF_INET, cur->sourceip, &serveraddr)) == 1) { /* valid v4 addr */
		hints.ai_family = AF_INET;
		hints.ai_flags |= AI_NUMERICHOST;
	}
	else if((rc = inet_pton(AF_INET6, cur->sourceip, &serveraddr)) == 1) { /* valid v6 addr */
		hints.ai_family = AF_INET6;
		hints.ai_flags |= AI_NUMERICHOST;
	}

	if((rc = getaddrinfo(cur->sourceip, "1025", &hints, &(cur->srcinfo))) != 0) {
		syslog(LOG_ERR, "WARNING: connection \"%s\" sourceip is invalid %s, %s", cur->name, cur->sourceip, gai_strerror(rc));
		return(-1);
	}

#if defined(DEBUG)
	for(rp = cur->srcinfo; rp; rp = rp->ai_next) {
		unsigned char *s;
		char buf[BUFSIZ];
		char sbuf[INET6_ADDRSTRLEN];
		int i;

		memset(buf, 0, BUFSIZ);
		s = (unsigned char *)rp;

		strcat(buf, "hex dump:");
		for(i = 0; i < sizeof(struct addrinfo); i++)
			sprintf(buf + strlen(buf), " %2x", s[i]);
		syslog(LOG_INFO, "%s: %s: src %s", __FILE__, __FUNCTION__, buf);

		memset(buf, 0, BUFSIZ);
		s = (unsigned char *)rp;

		strcat(buf, "dec dump:");
		for(i = 0; i < sizeof(struct addrinfo); i++)
			sprintf(buf + strlen(buf), " %3d", s[i]);
		syslog(LOG_INFO, "%s: %s: src %s", __FILE__, __FUNCTION__, buf);

		syslog(LOG_INFO, "%s: %s: src %s = %s", __FILE__, __FUNCTION__, cur->checkip, inet_ntop(rp->ai_family, &rp->ai_addr, sbuf, INET6_ADDRSTRLEN));
	}
#endif

	if(cur->srcinfo->ai_family != cur->dstinfo->ai_family) {
		syslog(LOG_ERR, "WARNING: connection \"%s\" sourceip and checkip have unmatching protocol families", cur->name);
		return(-1);
	}

	return(0);
}

/* EOF */