/*----------------------------------------------------------------------------*/
/* Xymon demonstration tool.                                                  */
/*                                                                            */
/* This tool fakes several hosts that can be tested by Xymon, both with       */
/* fake network services and fake client data. It is used to demonstrate      */
/* features in Xymon.                                                         */
/*                                                                            */
/* Copyright (C) 2005-2011 Henrik Storner <henrik@hswn.dk>                    */
/*                                                                            */
/* This program is released under the GNU General Public License (GPL),       */
/* version 2. See the file "COPYING" for details.                             */
/*                                                                            */
/*----------------------------------------------------------------------------*/

static char rcsid[] = "$Id: demotool.c 6712 2011-07-31 21:01:52Z storner $";

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <limits.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <dirent.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

char *CONFIGDIR = "/etc/hdemo";
struct sockaddr_in srvaddr;
volatile int reconfig = 1;

typedef struct netsvc_t {
	int listenfd;
	int delay;
	char *response;
	int respsize;
	struct netsvc_t *next;
} netsvc_t;
netsvc_t *nethead = NULL;

typedef struct active_t {
	int fd;
	netsvc_t *svc;
	struct timeval rbegin;
	char *respbuf, *respptr;
	int readdone;
	int bytesleft;
	struct active_t *next;
} active_t;
active_t *acthead = NULL;

typedef struct client_t {
	time_t lastupd;
	char *hostname;
	char *ostype;
	time_t bootup;
	double minload, maxload;
	char *msg;
	struct client_t *next;
} client_t;
client_t *clihead = NULL;

static DIR *confdir = NULL;
struct dirent *dent = NULL;
static char *path = NULL;

char *nextservice(char *dirname, char *svc)
{
	struct stat st;
	char fn[PATH_MAX];
	FILE *fd;
	char *result;

	if (dirname) {
		if (confdir) closedir(confdir);
		if (path) free(path);
		confdir = opendir(dirname);
		path = strdup(dirname);
	}

	do {
		do { dent = readdir(confdir); } while (dent && (*(dent->d_name) == '.'));

		if (!dent) {
			closedir(confdir);
			free(path);
			path = NULL;
			confdir = NULL;
			dent = NULL;
			return NULL;
		}

		sprintf(fn, "%s/%s/%s", path, dent->d_name, svc);
	} while ( (stat(fn, &st) == -1) || ((fd = fopen(fn, "r")) == NULL) );

	result = (char *)malloc(st.st_size+1);
	fread(result, 1, st.st_size, fd);
	*(result + st.st_size) = '\0';
	fclose(fd);

	return result;
}

char *svcattrib(char *attr)
{
	struct stat st;
	char fn[PATH_MAX];
	FILE *fd;
	char *result;

	if (!dent) return NULL;

	if (!attr) {
		sprintf(fn, "%s/%s", path, dent->d_name);
		return strdup(fn);
	}

	sprintf(fn, "%s/%s/%s", path, dent->d_name, attr);
	if (stat(fn, &st) == -1) return NULL;
	fd = fopen(fn, "r"); if (!fd) return NULL;

	result = (char *)calloc(1, st.st_size+1);
	fread(result, 1, st.st_size, fd);
	fclose(fd);

	return result;
}

void addtobuffer(char **buf, int *bufsz, char *newtext)
{
	if (*buf == NULL) {
		*bufsz = strlen(newtext) + 4096;
		*buf = (char *) malloc(*bufsz);
		**buf = '\0';
	}
	else if ((strlen(*buf) + strlen(newtext) + 1) > *bufsz) {
		*bufsz += strlen(newtext) + 4096;
		*buf = (char *) realloc(*buf, *bufsz);
	}

	strcat(*buf, newtext);
}

char *clientdata(char *cpath)
{
	char *res = NULL;
	int ressz = 0;
	DIR *cdir;
	struct dirent *d;
	char fn[PATH_MAX];
	struct stat st;
	int n;
	FILE *fd;
	char buf[4096];

	cdir = opendir(cpath);
	while ((d = readdir(cdir)) != NULL) {
		if (strncmp(d->d_name, "client_", 7) != 0) continue;

		sprintf(fn, "%s/%s", cpath, d->d_name);
		if (stat(fn, &st) == -1) continue;
		fd = fopen(fn, "r"); if (fd == NULL) continue;

		sprintf(buf, "[%s]\n", d->d_name+7);
		addtobuffer(&res, &ressz, buf);

		while ((n = fread(buf, 1, sizeof(buf)-1, fd)) > 0) {
			*(buf+n) = '\0';
			addtobuffer(&res, &ressz, buf);
		}

		fclose(fd);
	}
	closedir(cdir);

	if (!res) res = strdup("");

	return res;
}

int timeafter(struct timeval *lim, struct timeval *now)
{
	if (now->tv_sec > lim->tv_sec) return 1;
	if (now->tv_sec < lim->tv_sec) return 0;
	return (now->tv_usec >= lim->tv_usec);
}

void setuplisteners(void)
{
	netsvc_t *nwalk;
	char *lspec;
	struct sockaddr_in laddr;

	nwalk = nethead;
	while (nwalk) {
		netsvc_t *tmp = nwalk;
		nwalk = nwalk->next;

		if (tmp->listenfd > 0) close(tmp->listenfd);
		if (tmp->response) free(tmp->response);
		free(tmp);
	}
	nethead = NULL;

	lspec = nextservice(CONFIGDIR, "listen");
	while (lspec) {
		char *p, *listenip = NULL;
		int listenport = -1;
		int lsocket, opt;

		p = strchr(lspec, ':'); if (p) { *p = '\0'; p++; listenip = lspec; listenport = atoi(p); }

		if (listenip && (listenport > 0)) {
			memset(&laddr, 0, sizeof(laddr));
			inet_aton(listenip, (struct in_addr *) &laddr.sin_addr.s_addr);
			laddr.sin_port = htons(listenport);
			laddr.sin_family = AF_INET;
			lsocket = socket(AF_INET, SOCK_STREAM, 0);

			if (lsocket != -1) {
				opt = 1;
				setsockopt(lsocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
				fcntl(lsocket, F_SETFL, O_NONBLOCK);

				if ( (bind(lsocket, (struct sockaddr *)&laddr, sizeof(laddr)) != -1) &&
				     (listen(lsocket, 10) != -1) ) {
					netsvc_t *newitem = malloc(sizeof(netsvc_t));

					newitem->listenfd = lsocket;
					newitem->response = svcattrib("response");
					newitem->respsize = (newitem->response ? strlen(newitem->response) : 0);
					p = svcattrib("delay");
					newitem->delay = (p ? atoi(p) : 0);
					newitem->next = nethead;
					nethead = newitem;
				}
			}
		}

		free(lspec);
		lspec = nextservice(NULL, "listen");
	}
}

void setupclients(void)
{
	client_t *cwalk;
	char *cspec;

	cwalk = clihead;
	while (cwalk) {
		client_t *tmp = cwalk;
		cwalk = cwalk->next;

		if (tmp->hostname) free(tmp->hostname);
		if (tmp->ostype) free(tmp->ostype);
		if (tmp->msg) free(tmp->msg);
		free(tmp);
	}
	clihead = NULL;

	cspec = nextservice(CONFIGDIR, "client");
	while (cspec) {
		char *p;

		p = strchr(cspec, ':');
		if (p) {
			client_t *newitem = (client_t *)malloc(sizeof(client_t));

			*p = '\0';
			newitem->lastupd = 0;
			newitem->hostname = strdup(cspec);
			newitem->ostype = strdup(p+1);
			while ((p = strchr(newitem->hostname, '.')) != NULL) *p = ',';
			p = svcattrib("uptime");
			if (p) newitem->bootup = time(NULL) - 60*atoi(p);
			else newitem->bootup = time(NULL);
			p = svcattrib("minload");
			if (p) newitem->minload = atof(p); else newitem->minload = 0.2;
			p = svcattrib("maxload");
			if (p) newitem->maxload = atof(p); else newitem->maxload = 1.0;
			newitem->msg = clientdata(svcattrib(NULL));
			newitem->next = clihead;
			clihead = newitem;
		}

		free(cspec);
		cspec = nextservice(NULL, "client");
	}
}

void do_select(void)
{
	fd_set readfds, writefds;
	int maxfd, n;
	netsvc_t *nwalk;
	active_t *awalk, *aprev;
	struct timeval now, start;
	struct timezone tz;
	struct timeval tmo;
	char rbuf[4096];

	gettimeofday(&start, &tz);
	do {
		gettimeofday(&now, &tz);
		FD_ZERO(&readfds); FD_ZERO(&writefds); maxfd = -1;

		nwalk = nethead;
		while (nwalk) {
			if (nwalk->listenfd) {
				FD_SET(nwalk->listenfd, &readfds);
				if (nwalk->listenfd > maxfd) maxfd = nwalk->listenfd;
			}
			nwalk = nwalk->next;
		}

		awalk = acthead;
		while (awalk) {
			if (awalk->fd) {
				if (!awalk->readdone) {
					FD_SET(awalk->fd, &readfds);
					if (awalk->fd > maxfd) maxfd = awalk->fd;
				}
				if (timeafter(&awalk->rbegin, &now)) {
					FD_SET(awalk->fd, &writefds);
					if (awalk->fd > maxfd) maxfd = awalk->fd;
				}
			}
			awalk = awalk->next;
		}

		tmo.tv_sec = 0; tmo.tv_usec = 100000;
		n = select(maxfd+1, &readfds, &writefds, NULL, &tmo);
	} while ((n == 0) && ((now.tv_sec - start.tv_sec) < 10));

	if (n <= 0) return;

	gettimeofday(&now, &tz);

	awalk = acthead; aprev = NULL;
	while (awalk) {
		if (awalk->fd && !awalk->readdone && FD_ISSET(awalk->fd, &readfds)) {
			n = read(awalk->fd, rbuf, sizeof(rbuf));
			if (n <= 0) awalk->readdone = 1;
		}

		if (awalk->fd && awalk->respptr && FD_ISSET(awalk->fd, &writefds)) {
			n = write(awalk->fd, awalk->respptr, awalk->bytesleft);
			if (n > 0) { awalk->respptr += n; awalk->bytesleft -= n; }
		}
		else n = 0;

		if ((awalk->bytesleft == 0) || (n < 0)) {
			if (awalk->respbuf) free(awalk->respbuf);
			close(awalk->fd);
			awalk->fd = 0;
		}

		if (awalk->fd) {
			aprev = awalk; awalk = awalk->next;
		}
		else {
			active_t *tmp = awalk;

			awalk = awalk->next;

			if (aprev) aprev->next = awalk;
			else acthead = awalk;

			free(tmp);
		}
	}

	nwalk = nethead;
	while (nwalk) {
		int newfd;

		if (nwalk->listenfd && FD_ISSET(nwalk->listenfd, &readfds)) {
			while ((newfd = accept(nwalk->listenfd, NULL, 0)) > 0) {
				/* Pick up a new connection */
				fcntl(newfd, F_SETFL, O_NONBLOCK);
				active_t *newitem = (active_t *)malloc(sizeof(active_t));
				newitem->fd = newfd;
				newitem->rbegin.tv_sec = now.tv_sec + nwalk->delay;
				newitem->rbegin.tv_usec = now.tv_usec + (random() % 1000000);
				if (newitem->rbegin.tv_usec > 1000000) {
					newitem->rbegin.tv_usec -= 1000000;
					newitem->rbegin.tv_sec++;
				}
				newitem->svc = nwalk;
				if (nwalk->response) {
					newitem->respbuf = strdup(nwalk->response);
					newitem->respptr = newitem->respbuf;
					newitem->bytesleft = nwalk->respsize;
				}
				else {
					newitem->respbuf = NULL;
					newitem->respptr = NULL;
					newitem->bytesleft = 0;
				}
				newitem->readdone = 0;
				newitem->next = acthead;
				acthead = newitem;
			}
		}
		nwalk = nwalk->next;
	}
}

void do_clients(void)
{
	client_t *cwalk;
	active_t *conn;
	time_t now = time(NULL);
	time_t uptime;
	int updays, uphours, upmins;
	int n;
	struct tm *tm;
	double curload;

	cwalk = clihead;
	while (cwalk && ((cwalk->lastupd + 60) > now)) cwalk = cwalk->next;
	if (!cwalk) return;

	cwalk->lastupd = now;
	tm = localtime(&now);
	uptime = now - cwalk->bootup;
	updays = uptime / 86400;
	uphours = (uptime % 86400) / 3600;
	upmins = (uptime % 3600) / 60;
	curload = cwalk->minload + ((random() % 1000) / 1000.0) * (cwalk->maxload - cwalk->minload);

	conn = malloc(sizeof(active_t));
	conn->fd = socket(AF_INET, SOCK_STREAM, 0);
	fcntl(conn->fd, F_SETFL, O_NONBLOCK);
	conn->rbegin.tv_sec = now;
	conn->rbegin.tv_usec = 0;
	conn->svc = NULL;
	conn->respbuf = (char *)malloc(strlen(cwalk->msg) + 4096);
	sprintf(conn->respbuf, "client %s.%s\n[date]\n%s[uptime]\n %02d:%02d:%02d up %d days, %d:%02d, 1 users, load average: 0.21, %5.2f, 0.25\n\n%s\n",
		cwalk->hostname, cwalk->ostype, 
		ctime(&now),
		tm->tm_hour, tm->tm_min, tm->tm_sec,
		updays, uphours, upmins,
		curload, cwalk->msg);
	conn->respptr = conn->respbuf;
	conn->bytesleft = strlen(conn->respbuf);
	conn->readdone = 0;

	n = connect(conn->fd, (struct sockaddr *)&srvaddr, sizeof(srvaddr));

	if ((n == 0) || ((n == -1) && (errno == EINPROGRESS))) {
		conn->next = acthead;
		acthead = conn;
	}
	else {
		close(conn->fd);
		free(conn->respbuf);
		free(conn);
	}
}

void sigmisc_handler(int signum)
{
	if (signum == SIGHUP) reconfig = 1;
}
											   
int main(int argc, char *argv[])
{
	int argi;
	struct sigaction sa;
	time_t lastconf = 0;

        memset(&srvaddr, 0, sizeof(srvaddr));
	srvaddr.sin_port = htons(1984);
	srvaddr.sin_family = AF_INET;
	inet_aton("127.0.0.1", (struct in_addr *) &srvaddr.sin_addr.s_addr);

	for (argi = 1; (argi < argc); argi++) {
		if (strncmp(argv[argi], "--confdir=", 10) == 0) {
			char *p = strchr(argv[argi], '=');
			CONFIGDIR = strdup(p+1);
		}
		else if (strncmp(argv[argi], "--srvip=", 8) == 0) {
			char *p = strchr(argv[argi], '=');
			inet_aton(p+1, (struct in_addr *) &srvaddr.sin_addr.s_addr);
		}
	}

	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = sigmisc_handler;
	sigaction(SIGHUP, &sa, NULL);

	while (1) {
		if (reconfig || (time(NULL) > (lastconf+60))) {
			printf("(Re)loading config\n");
			setuplisteners();
			setupclients();
			reconfig = 0;
			lastconf = time(NULL);
		}

		do_clients();
		do_select();
	}

	return 0;
}

