/*
 * sntop - simple network top - a top-like console network status tool
 * sntop.c
 * 04.30.2000 - 03.21.2001
 *
 * robert m love	<rml@tech9.net>
 * chris m rivera	<cmrivera@ufl.edu>
 *
 * http://sntop.sourceforge.net - homepage
 * ftp://sntop.sourceforge.net/pub/sntop/ - anon ftp
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, v2, as
 * published by the Free Software Foundation
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Copyright (C) 2001 Robert M. Love and Christopher M. Rivera
 *
 * All code by the above authors unless otherwise specified.
 */

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h> 		
#include <signal.h>		
#include <ncurses.h>

#if HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif /* HAVE_SYS_WAIT_H */
#ifndef WEXITSTATUS
#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
#endif /* WEXITSTATUS */
#ifndef WIFEXITED
#define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
#endif /* WIFEXITED */

#if HAVE_GETOPT_LONG
#include <getopt.h>
#else
#include "getopt_long.h"
#endif /* HAVE_GETOPT_LONG */

#include "sntop.h"

#define SNTOP_VERSION "sntop v1.4.3"

int main(int argc, char *argv[])
{
	char up_status[] = UP;
	char down_status[] = DOWN;
	char buf[IN_BUF + PING_BUF + PEND_BUF];
	char cfile[CONF_BUF];
	char hfile[CONF_BUF] = HTML_FILE;
	/* Default to FPING_LINE */
	char ping[PING_BUF + 1] = FPING_LINE;
	char action[ACTION_BUF];
	char ch;
	FILE *hf = NULL;
	fd_set readfds;
	struct timeval timeout;
	hdc *head = NULL, *current = NULL;
	unsigned int up;
	unsigned int refresh = REFRESH_TIME;
	unsigned int i;
	unsigned short int val;
	unsigned int bold_attribute = 0; /* no bold if no color */
	unsigned short flags = DO_COLOR;

	struct option longopts[] = {
		{ "once", 0, 0, 'o' },
		{ "nocolor", 0, 0, 'c' },
		{ "ping", 0, 0, 'p' },
		{ "html", 0, 0, 'w' },
		{ "secure", 0, 0, 's' },
		{ "wfile", 1, 0, 'e' },
		{ "refresh", 1, 0, 'r' },
		{ "version", 0, 0, 'v' },
		{ "help", 0, 0, 'h' },
		{ "conf", 1, 0, 'f' },
		{ "alarm", 1, 0, 'a' },
		{ "log", 1, 0, 'l' },
		{ "byte", 1, 0, 'b' },
		{ "daemon", 0, 0, 'd' }, 
		{ 0, 0, 0, 0 }
	};
		
	/* we need to construct the user's home, for the conf file, now,
	   so we can change it with -f in the following getopt loop */
	snprintf(cfile, CONF_BUF, "%s%s", getenv("HOME"), CONF_UFILE);

	while((i = getopt_long(argc, argv, "docpwse:f:r:vhb:a:l:", longopts, (int *) 0)) != -1) {
		switch(i) {
			case 'd':
				flags ^= DAEMON;
				break;
			case 'o':
				flags ^= DO_ONCE;
				refresh = 0;
				break;
			case 'c':
				flags ^= DO_COLOR;
				break;
			case 'p':
				strcpy(ping, PING_LINE);
				break;
			case 'w':
				flags ^= DO_HTML;
				break;
			case 's':
				flags ^= SECURE_MODE;
				break;
			case 'e':
				strncpy(hfile, optarg, CONF_BUF);
				break;
			case 'f':
				strncpy(cfile, optarg, CONF_BUF);
				break;
			case 'r':
				refresh = atoi(optarg);
				break;
			case 'a':
				flags ^= DO_ALARM;
				strncpy(action, optarg, ACTION_BUF);
				break;
			case 'l':
				flags ^= DO_LOG;
				strncpy(action, optarg, ACTION_BUF);
				break;
			case 'v':
				printf("\r" SNTOP_VERSION);
			       	printf(" - a top-like console network status monitor\n" SNTOP_AUTHORS "\n");
				exit(EXIT_SUCCESS);
				break;
			case 'b':
				snprintf(ping, PING_BUF, "fping -r 1 -b %d -q", atoi(optarg));
				break;
			case ':': case '?': case 'h':
				printf("\r" SNTOP_VERSION ": " SNTOP_AUTHORS "\n\n");
				printf("Usage: %s [FLAG(S)...]\n\n", argv[0]);
				printf(" -d, --daemon           allow use as a daemon\n");
				printf(" -o, --once		poll and display once, then exit\n");
				printf(" -c, --nocolor		disable use of ncurses' color\n");
				printf(" -p, --ping		use 'ping' in lieu of 'fping'. MUCH SLOWER!\n");
				printf(" -w, --html		generate html output of results\n");
				printf(" -s, --secure		secure mode: interactive commands are disabled\n");
				printf(" -e, --wfile=file	output html to <file> instead of " HTML_FILE "\n");
				printf(" -f, --conf=file	read conf data from <file> instead of ~" CONF_UFILE ".\n");
				printf("			note, sntop will still try to read from\n");
				printf("			%s if <file> fails\n", CONF_SFILE);
				printf(" -r, --refresh=time	refresh every <time> seconds instead of every %d\n", REFRESH_TIME);
				printf(" -a, --alarm=action	when a site goes down, execute action\n");
				printf(" -l, --log=action	when a site changes status, execute action\n");
				printf(" -b, --byte=bytes	bytes of ping data to send\n");
				printf(" -v, --version		display version information and exit\n");
				printf(" -h, --help		display this help and exit\n");
				printf("\nSee 'man sntop'\n");
				exit(EXIT_SUCCESS);
				break;
		}
	}
	
	signal(SIGINT, aborted);

	load_config(cfile, &head);

	if (!(flags & DAEMON)) {
		printf(SNTOP_VERSION " - loading ... ");
		initscr();
		/* color code orig. by Ron Cooper <rcooper@crstexas.com> 20001024 */
		if ((flags & DO_COLOR) && has_colors()) {
			start_color();
			bold_attribute = A_BOLD;
			/* remap all colors onto a black background */
			init_pair(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK);
			init_pair(COLOR_GREEN, COLOR_GREEN, COLOR_BLACK);
			init_pair(COLOR_RED, COLOR_RED, COLOR_BLACK);
			init_pair(COLOR_CYAN, COLOR_CYAN, COLOR_BLACK);
			init_pair(COLOR_WHITE, COLOR_WHITE, COLOR_BLACK);
			init_pair(COLOR_MAGENTA, COLOR_MAGENTA, COLOR_BLACK);
			init_pair(COLOR_BLUE, COLOR_BLUE, COLOR_BLACK);
			init_pair(COLOR_YELLOW, COLOR_YELLOW, COLOR_BLACK); 
		}
		attrset(COLOR_PAIR(COLOR_YELLOW) | bold_attribute);
		printw("(sntop) simple network top\n");
		standout();
		printw("%-16s  %-12s  %-32s\n", "HOST", "STATUS", "COMMENT");
		standend();
	}

	do {
		if (!(flags & DAEMON)) {
			move(2, 0);
			clrtobot();
		}
	
		current = head;
		i = up = 0;

		if (flags & DO_HTML) start_html(hfile, &hf, refresh);

		do {
			sprintf(buf, "%s %s %s", ping, current->host, PING_END);
			val = sntop_exec(buf);
			if (val == 127) {
				abortion("error, execution of (f)ping failed", 6);
			} else if (val == 0) {
				/* site is up */
				if ((flags & DO_LOG) && (current->status == down_status))
					exec_alarm(action, current->name, current->host, up_status);
				current->status = up_status;
				if (!(flags & DAEMON)) attrset(COLOR_PAIR(COLOR_GREEN) | bold_attribute);
				up++;
			} else {
				/* site is down */
				if (((flags & DO_ALARM) || (flags & DO_LOG))
				    && (current->status == up_status))
					exec_alarm(action, current->name, current->host, down_status);
				current->status = down_status;
				if (!(flags & DAEMON)) attrset(COLOR_PAIR(COLOR_RED) | bold_attribute);
			}
			if (!(flags & DAEMON)) printw("%-16s  %-12s  %-32s\n", current->name, current->status, current->note);
			if (flags & DO_HTML) add_html(hf, current->name, current->status, current->note);
			i++;
			current = current->next;
		} while(current->next != NULL);

		if (!(flags & DAEMON)) {
			attrset(COLOR_PAIR(COLOR_CYAN) | bold_attribute);
			printw("\n\n%u hosts polled: %u up, %u down", i, up, i - up);
			attrset(COLOR_PAIR(COLOR_YELLOW) | bold_attribute);
			refresh();
		}

		if (flags & DO_HTML) end_html(hf, i, up);
		
		if (!(flags & DAEMON)) {
			/* we use select() to sleep for REFRESH seconds or until a key is pressed */
			FD_ZERO(&readfds);
			FD_SET(0, &readfds);
			timeout.tv_sec = refresh; 
			timeout.tv_usec = 0;
			if (select(1, &readfds, NULL, NULL, &timeout) > 0) {
				if (read(0, &ch, 1) != 1)
					abortion("error, stdin cannot be read", 4);
				if (!(flags & SECURE_MODE)) {
					switch (ch) {
						case 'q':
							flags ^= DO_ONCE;
							break;
						case 'r':
							printw("\nreloading conf data...");
							refresh();
							load_config(cfile, &head);
							printw("done\nrefreshing...");
							refresh();
							break;
						case 'w':
							flags ^= DO_HTML;
							load_config(cfile, &head);
							printw("\nhtml generation toggled %s",
								(flags & DO_HTML) ? "ON" : "OFF");
							refresh();
							break;
						default:
							printw("\nrefreshing...");
							refresh();
					}
				}
			}
		}
		else sleep(refresh);
	} while(!(flags & DO_ONCE));
	if (!(flags & DAEMON)) endwin();
	exit(EXIT_SUCCESS);
}

/*
 * a replacement for my nemesis, fgets() ... reads from f into s, stopping
 * at n-1 bytes, d, or EOF. clears the remaining line so future reads
 * start on newline. ignores inline comments, and removes trailing
 * whitespace. returns upto n bytes in s, with d stripped and \0 appended.;
 */
char * get(char *s, int n, char d, FILE *f)
{
	int c = 0;
	char *cs = s;

	while(--n > 0 && (c = getc(f)) != EOF && (*cs++ = c) != d && c != '#') ;

	if (c == d || c == '#') *--cs = '\0';
		else *cs = '\0';

	/* remove trailing whitespace */
	cs = strchr(s, '\0');
	while (*--cs == ' ' || *cs == '\t') /* nop */ ;
	*++cs = '\0';

	while (c != '\n' && c != EOF) c = getc(f); /* clear the line */

	return (c == EOF) ? NULL : s;
}

void abortion(const char *msg, int type)
{
	endwin();
	fprintf(stderr, "sntop: %s\n", msg);
	exit(type);
}

RETSIGTYPE aborted(int ignore)
	{ abortion("caught SIGINT -- aborted", 5); }

/*
 * glibc's system() disables interrupt signals which is a pretty evil
 * thing to do, especially in a loop, as we cant break out.
 *  
 * we return 127 on error, as this is what waitpid's status will show
 * on error since it is unsigned, so we can check against a single value
 */
unsigned short int sntop_exec(char *command)
{
	extern char **environ;
        int pid, status;
	pid = fork();
	if (pid == -1) return 127;
	if (pid == 0) {
		char * argv[4];
		argv[0] = "sh";
		argv[1] = "-c";
		argv[2] = command;
		argv[3] = NULL;
		execve("/bin/sh", argv, environ);
		exit(127);
	}
	while(1) {
		if (waitpid(pid, &status, 0) == -1)
			return 127;
		else if (WIFEXITED(status)) return WEXITSTATUS(status);
			else return 127;
	}
}

void load_config(char *cfile, hdc **head)
{
	char *field[3];
	char buf[IN_BUF];
	char up_status[] = UP;
	int i;
	FILE *cf;
	hdc *current = NULL, *last = NULL;

	if ((cf = fopen(cfile, "r")) == NULL) {
		/* lets try opening the system-wide conf file, then */
		strncpy(cfile, CONF_SFILE, CONF_BUF);	
		if ((cf = fopen(cfile, "r")) == NULL)
			abortion("error, cannot open systen or user conf file", 1);
	}

	if ((*head) != NULL) {
		/* if we are called a subsequent time, we free() first */
		current = (*head);
	        do {
			last = current->next;
	                free(current);
			current = last;
		} while (current != NULL);
		(*head) = NULL;
	}

	while (!feof(cf)) {
		if ((current = malloc(sizeof(hdc))) == NULL)
			abortion("error, malloc() returned NULL", 2);
		i = 0;
		field[0] = current->name;
		field[1] = current->host;
		field[2] = current->note;
		if ((*head) == NULL) { (*head) = current; last = current; }
		last->next = current;
		last = current;
		current->next = NULL;
		current->status = up_status;
		while(i < 3) {
			if (get(buf, IN_BUF, '\n', cf) == NULL) break;
			if (buf[0])
				strncpy(field[i++], buf, IN_BUF);
		}
	}
	fclose(cf);
}

/* written 30,000ft in the air */
void exec_alarm(char *command, char *name, char *host, char *status)
{
	extern char **environ;
	int pid;
	pid = fork();
	if (pid == -1) return;
	if (pid == 0) {
		char *argv[5];
		argv[1] = name;
		argv[2] = host;
		argv[3] = status;
		argv[4] = NULL;
		if (execve(command, argv, environ) == -1)
			abortion("error executing alarm command", 7);
	}
}

void start_html(char *file, FILE **hf, unsigned int refresh)
{
	if (((*hf) = fopen(file, "w")) == NULL)
		abortion("error, cannot open html file", 3);

	fputs("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n", (*hf));
	fputs("<HTML>\n<HEAD>\n", (*hf));
	if (refresh) fprintf((*hf), "<META http-equiv=\"Refresh\" content=\"%d\">\n", refresh);
	fputs("<TITLE>sntop network status</TITLE>\n</HEAD>\n", (*hf));
	fputs("<BODY TEXT=\"#000000\" BGCOLOR=\"#FFFFFF\">\n\n", (*hf));
	fputs("<P><B>sntop network status - automatically generated html</B></P><BR>\n\n", (*hf));
	fputs("<TABLE BORDER CELLPADDING=5>\n<TR VALIGN=top>\n", (*hf));
	fputs("<TD BGCOLOR=#EEEEEE><B>Host</B></TD>\n<TD BGCOLOR=#EEEEEE>", (*hf));
	fputs("<B>Status</B></TD>\n<TD BGCOLOR=#EEEEEE><B>Comments</B></TD>\n", (*hf));
	fputs("</TR>\n", (*hf));
}

void add_html(FILE *hf, char *name, char *status, char *comments)
{
	fprintf(hf, "<TR>\n<TD BGCOLOR=#EEEEEE>%s</TD>\n", name);
	fprintf(hf, "<TD BGCOLOR=#EEEEEE><B><FONT COLOR=%s",
			strcmp(status, UP) ? HTML_DOWN : HTML_UP);
	fprintf(hf, "</FONT></B></TD>\n<TD BGCOLOR=#EEEEEE>%s</TD>\n", comments);
}

void end_html(FILE *hf, unsigned int hosts, unsigned int up) {
	extern char *tzname[2];
	time_t t;
	char *s;

	fputs("</TABLE>\n\n", hf);
	fprintf(hf, "<P><B>%u</B> hosts polled: <B>%u</B> up, <B>%u</B> down</P><BR>\n\n", hosts, up, hosts - up);

	/* ok, so these sanity checks are a bit much -- but they arent overkill */
	if (time(&t) != (time_t) -1)
		if ((s = ctime(&t)) && tzname[0])
			fprintf(hf, "<P><SMALL>Last updated: %s %s</SMALL></P>\n\n", s, tzname[0]);

	fputs("<P><SMALL>Generated by <A HREF=\"http://sntop.sourceforge.net\">", hf);
	fputs(SNTOP_VERSION "</A> - " SNTOP_AUTHORS "</SMALL></P>\n\n", hf);
	fputs("</BODY>\n</HTML>", hf);
	fclose(hf);
}

/* EOF */
