/*----------------------------------------------------------------------------*/
/* Xymon alert acknowledgment CGI tool.                                       */
/*                                                                            */
/* This is a CGI script for handling acknowledgments of alerts.               */
/* If called with no CGI query, it will present the acknowledgment form;      */
/* if called with a proper CGI query string it will send an ack-message to    */
/* the Xymon daemon.                                                          */
/*                                                                            */
/* Copyright (C) 2004-2011 Henrik Storner <henrik@storner.dk>                 */
/* Minute/Hour/Day options for duration by Heather Keen 2011.                 */
/*                                                                            */
/* This program is released under the GNU General Public License (GPL),       */
/* version 2. See the file "COPYING" for details.                             */
/*                                                                            */
/*----------------------------------------------------------------------------*/

static char rcsid[] = "$Id: acknowledge.c 8069 2019-07-23 15:29:06Z jccleaver $";

#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "libxymon.h"
#include "version.h"

static cgidata_t *cgidata = NULL;
static int  nopin = 0;

typedef struct acklist_t {
	int  id, checked;
	int acknum;
	int validity;
	char *period;
	char *hostname;
	char *testname;
	char *ackmsg;
	struct acklist_t *next;
} acklist_t;
acklist_t *ackhead = NULL;
acklist_t *acktail = NULL;
char *validityall = NULL;
char *periodall = NULL;
char *ackmsgall = NULL;
enum { ACK_UNKNOWN, ACK_OLDSTYLE, ACK_ONE, ACK_MANY } reqtype = ACK_UNKNOWN;
int sendnum = 0;

static void parse_query(void)
{
	cgidata_t *cwalk;

	/* See what kind of request this is */
	for (cwalk=cgidata; (cwalk); cwalk = cwalk->next) {
		if (nopin && (strcmp(cwalk->name, "Send_all") == 0)) {
			/* User pushed the "Send all" button */
			reqtype = ACK_MANY;
		}
		else if (nopin && (strncmp(cwalk->name, "Send_", 5) == 0)) {
			/* User pushed a specific "Send" button */
			sendnum = atoi(cwalk->name+5);
			reqtype = ACK_ONE;
		}
		else if (!nopin && (strcmp(cwalk->name, "Send") == 0)) {
			/* Old style request */
			reqtype = ACK_OLDSTYLE;
		}
	}

	for (cwalk=cgidata; (cwalk); cwalk = cwalk->next) {
		/*
		 * cwalk->name points to the name of the setting.
		 * cwalk->value points to the value (may be an empty string).
		 */
		int id = 0;
		char *acknum = NULL, *validity = NULL, *ackmsg = NULL, *period = NULL;
		char *hostname = NULL, *testname = NULL, *checked = NULL;
		char *delim;

		if (strncasecmp(cwalk->name, "NUMBER", 6) == 0) {
			if (*cwalk->value) acknum = cwalk->value;
			delim = strchr(cwalk->name, '_'); if (delim) id = atoi(delim+1);
		}
		else if (strcasecmp(cwalk->name, "DELAY_all") == 0) {
			if (*cwalk->value) validityall = cwalk->value;
		}
		else if (strcasecmp(cwalk->name, "PERIOD_all") == 0) {
			if (*cwalk->value) periodall = cwalk->value;
		}
		else if (strcasecmp(cwalk->name, "MESSAGE_all") == 0) {
			if (*cwalk->value) ackmsgall = cwalk->value;
		}
		else if (strncasecmp(cwalk->name, "DELAY", 5) == 0) {
			if (*cwalk->value) validity = cwalk->value;
			delim = strchr(cwalk->name, '_'); if (delim) id = atoi(delim+1);
		}
		else if (strncasecmp(cwalk->name, "PERIOD", 5) == 0) {
			if (*cwalk->value) period = cwalk->value;
			delim = strchr(cwalk->name, '_'); if (delim) id = atoi(delim+1);
		}
		else if (strncasecmp(cwalk->name, "MESSAGE", 7) == 0) {
			if (*cwalk->value) ackmsg = cwalk->value;
			delim = strchr(cwalk->name, '_'); if (delim) id = atoi(delim+1);
		}
		else if (strncasecmp(cwalk->name, "HOSTNAME", 8) == 0) {
			if (*cwalk->value) hostname = cwalk->value;
			delim = strchr(cwalk->name, '_'); if (delim) id = atoi(delim+1);
		}
		else if (strncasecmp(cwalk->name, "TESTNAME", 8) == 0) {
			if (*cwalk->value) testname = cwalk->value;
			delim = strchr(cwalk->name, '_'); if (delim) id = atoi(delim+1);
		}
		else if (strncasecmp(cwalk->name, "CHECKED", 7) == 0) {
			if (*cwalk->value) checked = cwalk->value;
			delim = strchr(cwalk->name, '_'); if (delim) id = atoi(delim+1);
		}

		switch (reqtype) {
		  case ACK_UNKNOWN:
			break;

		  case ACK_OLDSTYLE:
			id = 1;
			/* Fall through */
		  case ACK_ONE:
			if ((id == sendnum) || (reqtype == ACK_OLDSTYLE)) checked = "checked";
			/* Fall through */
		  case ACK_MANY:
			if (id) {
				acklist_t *awalk;

				awalk = ackhead; while (awalk && (awalk->id != id)) awalk = awalk->next;
				if (!awalk) {
					awalk = (acklist_t *)calloc(1, sizeof(acklist_t));
					awalk->id = id;
					awalk->next = NULL;

					if (!ackhead) ackhead = acktail = awalk;
					else { acktail->next = awalk; acktail = awalk; }
				}

				if (acknum) awalk->acknum = atoi(acknum);
				if (validity) awalk->validity = durationvalue(validity);
				if (ackmsg) awalk->ackmsg = strdup(ackmsg);
				if (hostname) awalk->hostname = strdup(hostname);
				if (testname) awalk->testname = strdup(testname);
				if (period) awalk->period = strdup(period);
				if (checked) awalk->checked = 1;
			}
			break;
		}
	}
}

void generate_ackline(FILE *output, char *hname, char *tname, char *ackcode)
{
	static int num = 0;
	char numstr[15];

	num++;
	if (ackcode) {
		snprintf(numstr, sizeof(numstr), "%d", num); 
	}
	else {
		strncpy(numstr, "all", sizeof(numstr));
	}

	fprintf(output, "<tr>\n");

	fprintf(output, "    <td align=left>%s</td>\n", (hname ? htmlquoted(hname) : "&nbsp;"));
	fprintf(output, "    <td align=left>%s</td>\n", (tname ? htmlquoted(tname) : "&nbsp;"));
	fprintf(output, "    <TD NOWRAP><INPUT TYPE=TEXT NAME=\"DELAY_%s\" SIZE=4 MAXLENGTH=6><select NAME=\"PERIOD_%s\"><option value=\"min\" selected>min(s)</option><option value=\"hour\">hour(s)</option><option value=\"day\">day(s)</option></select></TD>\n", numstr, numstr);

	fprintf(output, "    <TD><INPUT TYPE=TEXT NAME=\"MESSAGE_%s\" SIZE=60 MAXLENGTH=80></TD>\n", numstr);

	fprintf(output, "    <TD>\n");
	if (ackcode && hname && tname) {
		fprintf(output, "       <INPUT TYPE=\"HIDDEN\" NAME=\"NUMBER_%d\" VALUE=\"%s\">\n", num, htmlquoted(ackcode));
		fprintf(output, "       <INPUT TYPE=\"HIDDEN\" NAME=\"HOSTNAME_%d\" VALUE=\"%s\">\n", num, htmlquoted(hname));
		fprintf(output, "       <INPUT TYPE=\"HIDDEN\" NAME=\"TESTNAME_%d\" VALUE=\"%s\">\n", num, htmlquoted(tname));
		fprintf(output, "       <INPUT TYPE=\"SUBMIT\" NAME=\"Send_%d\" VALUE=\"Send\" ALT=\"Send\">\n", num);
	}
	else {
		fprintf(output, "       &nbsp;\n");
	}
	fprintf(output, "    </TD>\n");

	fprintf(output, "    <TD>\n");
	if (ackcode) fprintf(output, "       <INPUT TYPE=\"CHECKBOX\" NAME=\"CHECKED_%d\" VALUE=\"OFF\">\n", num);
	else         fprintf(output, "       <INPUT TYPE=\"SUBMIT\" NAME=\"Send_all\" VALUE=\"Send\" ALT=\"Send\">\n");
	fprintf(output, "    </TD>\n");

	fprintf(output, "</tr>\n");
}

int main(int argc, char *argv[])
{
	int argi;
	char *envarea = NULL;
	int obeycookies = 1;
	char *accessfn = NULL;

	for (argi = 1; (argi < argc); argi++) {
		if (argnmatch(argv[argi], "--env=")) {
			char *p = strchr(argv[argi], '=');
			loadenv(p+1, envarea);
		}
		else if (argnmatch(argv[argi], "--area=")) {
			char *p = strchr(argv[argi], '=');
			envarea = strdup(p+1);
		}
		else if (strcmp(argv[argi], "--debug") == 0) {
			debug = 1;
		}
		else if (strcmp(argv[argi], "--no-pin") == 0) {
			nopin = 1;
		}
		else if (strcmp(argv[argi], "--no-cookies") == 0) {
			obeycookies = 0;
		}
		else if (argnmatch(argv[argi], "--access=")) {
			char *p = strchr(argv[argi], '=');
			accessfn = strdup(p+1);
		}

	}

	redirect_cgilog("ack");

	cgidata = cgi_request();
	if ( (nopin && (cgi_method == CGI_GET)) || (!nopin && (cgidata == NULL)) ) {
		/* Present the query form */
		sethostenv("", "", "", colorname(COL_RED), NULL);

		printf("Content-Type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));

		if (!nopin) {
			showform(stdout, "acknowledge", "acknowledge_form", COL_RED, getcurrenttime(NULL), 
				 NULL, NULL);
		}
		else {
			SBUF_DEFINE(cmd);
			char *respbuf = NULL;
			char *hostname, *pagename;
			int gotfilter = 0, filtererror = 0;
			sendreturn_t *sres = NULL;
			int col, firstcolor = 1, alertcolors = colorset(xgetenv("ALERTCOLORS"), ((1 << COL_GREEN) | (1 << COL_BLUE)));

			headfoot(stdout, "acknowledge", "", "header", COL_RED);

			SBUF_MALLOC(cmd, 1024);
			strncpy(cmd, "xymondboard fields=hostname,testname,cookie color=", cmd_buflen);
			for (col = 0; (col < COL_COUNT); col++) {
				if ((1 << col) & alertcolors) {
					if (!firstcolor) strncat(cmd, ",", cmd_buflen - strlen(cmd));
					strncat(cmd, colorname(col), cmd_buflen - strlen(cmd));
					firstcolor = 0;
				}
			}
			// printf("<!-- cmd = %s -->\n", cmd);

			if (obeycookies && !gotfilter && ((hostname = get_cookie("host")) != NULL)) {
				if (*hostname) {
					pcre *dummy;
					SBUF_DEFINE(re);

					SBUF_MALLOC(re, 3+strlen(hostname));
					snprintf(re, re_buflen, "^%s$", hostname);
					dummy = compileregex(re);
					if (dummy) {
						/* Valid expression */
						freeregex(dummy);
						SBUF_REALLOC(cmd, 1024 + strlen(cmd) + strlen(re));
						snprintf(cmd + strlen(cmd), cmd_buflen - strlen(cmd), " host=%s", re);
						gotfilter = 1;
					}
					else {
						filtererror = 1;
						printf("<p align=\"center\">Invalid hostname filter</p>\n");
					}
				}
			}

			if (obeycookies && !gotfilter && ((pagename = get_cookie("pagepath")) != NULL)) {
				if (*pagename) {
					pcre *dummy;
					SBUF_DEFINE(re);

					SBUF_MALLOC(re, 8 + strlen(pagename)*2);
					snprintf(re, re_buflen, "%s$|^%s/.+", pagename, pagename);
					dummy = compileregex(re);
					if (dummy) {
						/* Valid expression */
						freeregex(dummy);
						SBUF_REALLOC(cmd, 1024 + strlen(cmd) + strlen(re));
						snprintf(cmd + strlen(cmd), cmd_buflen - strlen(cmd), " page=%s", re);
						gotfilter = 1;
					}
					else {
						filtererror = 1;
						printf("<p align=\"center\">Invalid pagename filter</p>\n");
					}
				}
			}

			sres = newsendreturnbuf(1, NULL);

			if (!filtererror && (sendmessage(cmd, NULL, XYMON_TIMEOUT, sres) == XYMONSEND_OK)) {
				char *bol, *eoln;
				int first = 1;

				respbuf = getsendreturnstr(sres, 1);

				bol = respbuf;
				while (bol) {
					char *hname, *tname, *ackcode;

					eoln = strchr(bol, '\n'); if (eoln) *eoln = '\0';
					hname = tname = ackcode = NULL;
					hname = strtok(bol, "|");
					if (hname) tname = strtok(NULL, "|");
					if (tname) ackcode = strtok(NULL, "|");
					if (hname && tname && ackcode && (strcmp(hname, "summary") != 0)) {
						if (first) {
							fprintf(stdout, "<form method=\"POST\" ACTION=\"%s\">\n", getenv("SCRIPT_NAME"));
							fprintf(stdout, "<center><table cellpadding=5 summary=\"Ack data\">\n");
							fprintf(stdout, "<tr><th align=left>Host</th><th align=left>Test</th><th align=left>Duration</th><th align=left>Cause</th><th>Ack</th><th>Ack Multiple</tr>\n");
							first = 0;
						}

						generate_ackline(stdout, hname, tname, ackcode);
					}

					if (eoln) bol = eoln+1; else bol = NULL;
				}

				if (first) {
					fprintf(stdout, "<center><font size=\"+1\"><b>No active alerts</b></font></center>\n");
				}
				else {
					generate_ackline(stdout, NULL, NULL, NULL);
					fprintf(stdout, "</table></center>\n");
					fprintf(stdout, "</form>\n");
				}
			}

			freesendreturnbuf(sres);

			headfoot(stdout, "acknowledge", "", "footer", COL_RED);
		}
	}
	else if ( (nopin && (cgi_method == CGI_POST)) || (!nopin && (cgidata != NULL)) ) {
		SBUF_DEFINE(xymonmsg);
		SBUF_DEFINE(acking_user);
		acklist_t *awalk;
		strbuffer_t *response = newstrbuffer(0);
		int count = 0;

		acking_user = "";

		/* We only want to accept posts from certain pages */
		{ 
			char cgisource[1024]; char *p;
			p = csp_header("acknowledge"); if (p) fprintf(stdout, "%s", p);
			snprintf(cgisource, sizeof(cgisource), "%s/%s", xgetenv("SECURECGIBINURL"), "acknowledge");
			if (!cgi_refererok(cgisource)) {
				fprintf(stdout, "Location: %s.sh?\n\n", cgisource);
				return 0;
			}
		}

		parse_query();
		if (getenv("REMOTE_USER")) {
			char *remaddr = getenv("REMOTE_ADDR");

			SBUF_MALLOC(acking_user, 1024 + strlen(getenv("REMOTE_USER")) + (remaddr ? strlen(remaddr) : 0));
			snprintf(acking_user, acking_user_buflen, "\nAcked by: %s", getenv("REMOTE_USER"));
			if (remaddr) snprintf(acking_user + strlen(acking_user), acking_user_buflen - strlen(acking_user), " (%s)", remaddr);
		}

		/* Load the host data (for access control) */
		if (accessfn) {
			load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn());
			load_web_access_config(accessfn);
		}

		addtobuffer(response, "<center>\n");
		for (awalk = ackhead; (awalk); awalk = awalk->next) {
			SBUF_DEFINE(msgline);

			SBUF_MALLOC(msgline, 1024 + (awalk->hostname ? MAX_HTMLQUOTE_FACTOR*strlen(awalk->hostname) : 0) + (awalk->testname ? MAX_HTMLQUOTE_FACTOR*strlen(awalk->testname) : 0));

			if (!awalk->checked) continue;
			if (accessfn && (!web_access_allowed(getenv("REMOTE_USER"), awalk->hostname, awalk->testname, WEB_ACCESS_CONTROL))) continue;

			if ((reqtype == ACK_ONE) && (awalk->id != sendnum)) continue;

			if (reqtype == ACK_MANY) {
				if (!awalk->ackmsg) awalk->ackmsg = ackmsgall;
				if (!awalk->validity && validityall) awalk->validity = durationvalue(validityall);
				if (periodall) awalk->period = periodall;
			}

			if (strncmp(awalk->period, "hour", 4) == 0) awalk->validity *= 60; 
			else if (strncmp(awalk->period, "day", 4) == 0) awalk->validity *= 60*24;

			count++;
			if (!awalk->ackmsg || !awalk->validity || !awalk->acknum) {
				if (awalk->hostname && awalk->testname) {
					snprintf(msgline, msgline_buflen, "<b>NO ACK</b> sent for host %s / test %s",
						htmlquoted(awalk->hostname), htmlquoted(awalk->testname));
				}
				else {
					snprintf(msgline, msgline_buflen, "<b>NO ACK</b> sent for item %d", awalk->id);
				}
				addtobuffer(response, msgline);
				addtobuffer(response, ": Duration or message not set<br>\n");
				continue;
			}

			SBUF_MALLOC(xymonmsg, 1024 + strlen(awalk->ackmsg) + strlen(acking_user));
			snprintf(xymonmsg, xymonmsg_buflen, "xymondack %d %d %s %s", awalk->acknum, awalk->validity, awalk->ackmsg, acking_user);
			if (sendmessage(xymonmsg, NULL, XYMON_TIMEOUT, NULL) == XYMONSEND_OK) {
				if (awalk->hostname && awalk->testname) {
					snprintf(msgline, msgline_buflen, "Acknowledge sent for host %s / test %s<br>\n", 
						htmlquoted(awalk->hostname), htmlquoted(awalk->testname));
				}
				else {
					snprintf(msgline, msgline_buflen, "Acknowledge sent for code %d<br>\n", awalk->acknum);
				}
			}
			else {
				if (awalk->hostname && awalk->testname) {
					snprintf(msgline, msgline_buflen, "Failed to send acknowledge for host %s / test %s<br>\n", 
						htmlquoted(awalk->hostname), htmlquoted(awalk->testname));
				}
				else {
					snprintf(msgline, msgline_buflen, "Failed to send acknowledge for code %d<br>\n", awalk->acknum);
				}
			}

			addtobuffer(response, msgline);
			xfree(xymonmsg);
		}

		if (count == 0) addtobuffer(response, "<b>No acks requested</b>\n");

		addtobuffer(response, "</center>\n");

		fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
	
		headfoot(stdout, "acknowledge", "", "header", COL_RED);
		fprintf(stdout, "%s", STRBUF(response));
		headfoot(stdout, "acknowledge", "", "footer", COL_RED);
	}

	return 0;
}

