1 /*----------------------------------------------------------------------------*/
2 /* Xymon overview webpage generator tool.                                     */
3 /*                                                                            */
4 /* This file contains code to load the current Xymon status data.             */
5 /*                                                                            */
6 /* Copyright (C) 2002-2011 Henrik Storner <henrik@storner.dk>                 */
7 /*                                                                            */
8 /* This program is released under the GNU General Public License (GPL),       */
9 /* version 2. See the file "COPYING" for details.                             */
10 /*                                                                            */
11 /*----------------------------------------------------------------------------*/
12 
13 static char rcsid[] = "$Id: loaddata.c 7904 2016-02-19 19:29:58Z jccleaver $";
14 
15 #include <limits.h>
16 #include <stdio.h>
17 #include <string.h>
18 #include <stdlib.h>
19 #include <ctype.h>
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <dirent.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <errno.h>
26 
27 #include "xymongen.h"
28 #include "util.h"
29 #include "loadlayout.h"
30 #include "loaddata.h"
31 
32 int		statuscount = 0;
33 
34 char		*ignorecolumns = NULL;			/* Columns that will be ignored totally */
35 char		*dialupskin = NULL;			/* XYMONSKIN used for dialup tests */
36 char		*reverseskin = NULL;			/* XYMONSKIN used for reverse tests */
37 time_t		recentgif_limit = 86400;		/* Limit for recent-gifs display, in seconds */
38 
39 xymongen_col_t 	null_column = { "", NULL };		/* Null column */
40 
41 char		*purplelogfn = NULL;
42 static FILE	*purplelog = NULL;
43 int		colorcount[COL_COUNT] = { 0, };
44 int		colorcount_noprop[COL_COUNT] = { 0, };
45 
46 static time_t oldestentry;
47 
48 
49 typedef struct compact_t {
50 	char *compactname;
51 	int color;
52 	time_t fileage;
53 	char *members;
54 } compact_t;
55 
56 
57 
58 typedef struct logdata_t {
59 	/* hostname|testname|color|testflags|lastchange|logtime|validtime|acktime|disabletime|sender|cookie|1st line of message */
60 	char *hostname;
61 	char *testname;
62 	int  color;
63 	char *testflags;
64 	time_t lastchange;
65 	time_t logtime;
66 	time_t validtime;
67 	time_t acktime;
68 	time_t disabletime;
69 	char *sender;
70 	int cookie;
71 	char *msg;
72 } logdata_t;
73 
parse_testflags(char * l)74 char *parse_testflags(char *l)
75 {
76 	char *result = NULL;
77 	char *flagstart = strstr(l, "[flags:");
78 
79 	if (flagstart) {
80 		char *flagend;
81 
82 		flagstart += 7;
83 		flagend = strchr(flagstart, ']');
84 
85 		if (flagend) {
86 			*flagend = '\0';
87 			result = strdup(flagstart);
88 			*flagend = ']';
89 		}
90 	}
91 
92 	return result;
93 }
94 
testflag_set(entry_t * e,char flag)95 int testflag_set(entry_t *e, char flag)
96 {
97 	if (e->testflags)
98 		return (strchr(e->testflags, flag) != NULL);
99 	else
100 		return 0;
101 }
102 
103 
unwantedcolumn(char * hostname,char * testname)104 int unwantedcolumn(char *hostname, char *testname)
105 {
106 	void *hinfo;
107 	char *nc, *tok;
108 	int result = 0;
109 
110 	hinfo = hostinfo(hostname);
111 	if (!hinfo) return 1;
112 
113 	nc = xmh_item(hinfo, XMH_NOCOLUMNS);
114 	if (!nc) return 0;
115 
116 	nc = strdup(nc);
117 	tok = strtok(nc, ",");
118 	while (tok && (result == 0)) {
119 		if (strcmp(tok, testname) == 0) result = 1;
120 		tok = strtok(NULL, ",");
121 	}
122 
123 	return result;
124 }
125 
126 
init_state(char * filename,logdata_t * log)127 state_t *init_state(char *filename, logdata_t *log)
128 {
129 	FILE 		*fd = NULL;
130 	char		*p;
131 	char		*hostname = NULL;
132 	char		*testname = NULL;
133 	char		*testnameidx;
134 	state_t 	*newstate;
135 	char		fullfn[PATH_MAX];
136 	host_t		*host;
137 	struct stat 	log_st;
138 	time_t		now = getcurrenttime(NULL);
139 	time_t		histentry_start;
140 	int		propagating, isacked;
141 
142 	dbgprintf("init_state(%s, %d, ...)\n", textornull(filename));
143 
144 	/* Ignore summary files and dot-files (this catches "." and ".." also) */
145 	if ( (strncmp(filename, "summary.", 8) == 0) || (filename[0] == '.')) {
146 		return NULL;
147 	}
148 
149 	if (reportstart || snapshot) {
150 		/* Don't do reports for info- and trends-columns */
151 		p = strrchr(filename, '.');
152 		if (p == NULL) return NULL;
153 		p++;
154 
155 		if (strcmp(p, xgetenv("INFOCOLUMN")) == 0) return NULL;
156 		if (strcmp(p, xgetenv("TRENDSCOLUMN")) == 0) return NULL;
157 		if (strcmp(p, xgetenv("CLIENTCOLUMN")) == 0) return NULL;
158 
159 		/*
160 		 * When doing reports, we are scanning the XYMONHISTDIR directory. It may
161 		 * contain files that are named as a host only (no test-name).
162 		 * Skip those.
163 		 */
164 		if (find_host(filename)) return NULL;
165 	}
166 
167 	if (!reportstart && !snapshot) {
168 		if (log->hostname) hostname = strdup(log->hostname);
169 		if (log->testname) testname = strdup(log->testname);
170 	}
171 	else {
172 		sprintf(fullfn, "%s/%s", xgetenv("XYMONHISTDIR"), filename);
173 
174 		/* Check that we can access this file */
175 		if (stat(fullfn, &log_st) == -1) {
176 			errprintf("Error searching for '%s' history file: %s\n", filename, strerror(errno));
177 			return NULL;
178 		}
179 		if (!S_ISREG(log_st.st_mode)) {
180 			errprintf("Weird file '%s' skipped\n", fullfn);
181 			return NULL;
182 		}
183 
184 		if ((fd = fopen(fullfn, "r")) == NULL) {
185 			errprintf("Unable to read file '%s', skipped: %s\n", fullfn, strerror(errno));
186 			return NULL;
187 		}
188 
189 		/* Pick out host- and test-name */
190 		hostname = strdup(filename);
191 		p = strrchr(hostname, '.');
192 
193 		/* Skip files that have no '.' in filename */
194 		if (p) {
195 			/* Pick out the testname ... */
196 			*p = '\0'; p++;
197 			testname = strdup(p);
198 
199 			/* ... and change hostname back into normal form */
200 			for (p=hostname; (*p); p++) {
201 				if (*p == ',') *p='.';
202 			}
203 		}
204 		else {
205 			xfree(hostname);
206 			fclose(fd);
207 			return NULL;
208 		}
209 	}
210 
211 	/* Must do these first to get the propagation value for the statistics */
212 	host = find_host(hostname);
213 	isacked = (log->acktime > now);
214 	propagating = checkpropagation(host, testname, log->color, isacked);
215 
216 	/* Count all of the real columns */
217 	if ( (strcmp(testname, xgetenv("INFOCOLUMN")) != 0) && (strcmp(testname, xgetenv("TRENDSCOLUMN")) != 0) ) {
218 		statuscount++;
219 		switch (log->color) {
220 		  case COL_RED:
221 		  case COL_YELLOW:
222 			if (propagating) colorcount[log->color] += 1;
223 			else colorcount_noprop[log->color] += 1;
224 			break;
225 
226 		  default:
227 			colorcount[log->color] += 1;
228 			break;
229 		}
230 	}
231 
232 	testnameidx = (char *)malloc(strlen(testname) + 3);
233 	sprintf(testnameidx, ",%s,", testname);
234 	if (unwantedcolumn(hostname, testname) || (ignorecolumns && strstr(ignorecolumns, testnameidx))) {
235 		xfree(hostname);
236 		xfree(testname);
237 		xfree(testnameidx);
238 		if (fd) fclose(fd);
239 		return NULL;	/* Ignore this type of test */
240 	}
241 	xfree(testnameidx);
242 
243 	newstate = (state_t *) calloc(1, sizeof(state_t));
244 	newstate->entry = (entry_t *) calloc(1, sizeof(entry_t));
245 	newstate->next = NULL;
246 
247 	newstate->entry->column = find_or_create_column(testname, 1);
248 	newstate->entry->color = -1;
249 	strcpy(newstate->entry->age, "");
250 	newstate->entry->oldage = 0;
251 	newstate->entry->propagate = 1;
252 	newstate->entry->testflags = NULL;
253 	newstate->entry->skin = NULL;
254 	newstate->entry->repinfo = NULL;
255 	newstate->entry->causes = NULL;
256 	newstate->entry->histlogname = NULL;
257 	newstate->entry->shorttext = NULL;
258 
259 	if (host) {
260 		newstate->entry->alert = checkalert(host->alerts, testname);
261 
262 		/* If no WAP's specified, default all tests to be on WAP page */
263 		newstate->entry->onwap = (host->waps ? checkalert(host->waps, testname) : 1);
264 	}
265 	else {
266 		dbgprintf("   hostname %s not found\n", hostname);
267 		newstate->entry->alert = newstate->entry->onwap = 0;
268 	}
269 
270 	newstate->entry->sumurl = NULL;
271 
272 	if (reportstart) {
273 		/* Determine "color" for this test from the historical data */
274 		newstate->entry->repinfo = (reportinfo_t *) calloc(1, sizeof(reportinfo_t));
275 		newstate->entry->color = parse_historyfile(fd, newstate->entry->repinfo,
276 				(dynamicreport ? NULL: hostname), (dynamicreport ? NULL : testname),
277 				reportstart, reportend, 0,
278 				(host ? host->reportwarnlevel : reportwarnlevel),
279 				reportgreenlevel,
280 				(host ? host->reportwarnstops : reportwarnstops),
281 				(host ? host->reporttime : NULL));
282 		newstate->entry->causes = (dynamicreport ? NULL : save_replogs());
283 	}
284 	else if (snapshot) {
285 		time_t fileage;
286 
287 		newstate->entry->color = history_color(fd, snapshot, &histentry_start, &newstate->entry->histlogname);
288 		fileage = snapshot - histentry_start;
289 
290 		newstate->entry->oldage = (fileage >= recentgif_limit);
291 		newstate->entry->fileage = fileage;
292 		strcpy(newstate->entry->age, agestring(fileage));
293 	}
294 	else {
295 		time_t fileage = (now - log->lastchange);
296 
297 		newstate->entry->color = log->color;
298 		newstate->entry->testflags = strdup(log->testflags ? log->testflags : "");
299 		if (testflag_set(newstate->entry, 'D')) newstate->entry->skin = dialupskin;
300 		if (testflag_set(newstate->entry, 'R')) newstate->entry->skin = reverseskin;
301 		newstate->entry->shorttext = strdup(log->msg);
302 		newstate->entry->acked = isacked;
303 
304 		newstate->entry->oldage = (fileage >= recentgif_limit);
305 		newstate->entry->fileage = (log->lastchange ? fileage : -1);
306 		if (log->lastchange == 0)
307 			strcpy(newstate->entry->age, "");
308 		else
309 			strcpy(newstate->entry->age, agestring(fileage));
310 	}
311 
312 	if (purplelog && (newstate->entry->color == COL_PURPLE)) {
313 		fprintf(purplelog, "%s %s%s\n",
314 		       hostname, testname, (host ? " (expired)" : " (unknown host)"));
315 	}
316 
317 	newstate->entry->propagate = propagating;
318 
319 	dbgprintf("init_state: hostname=%s, testname=%s, color=%d, acked=%d, age=%s, oldage=%d, propagate=%d, alert=%d\n",
320 		textornull(hostname), textornull(testname),
321 		newstate->entry->color, newstate->entry->acked,
322 		textornull(newstate->entry->age), newstate->entry->oldage,
323 		newstate->entry->propagate, newstate->entry->alert);
324 
325 	if (host) {
326         	hostlist_t *l;
327 
328 		/* Add this state entry to the host's list of state entries. */
329 		newstate->entry->next = host->entries;
330 		host->entries = newstate->entry;
331 
332 		/* There may be multiple host entries, if a host is
333 		 * listed in several locations in hosts.cfg (for display purposes).
334 		 * This is handled by updating ALL of the cloned host records.
335 		 * Bug reported by Bluejay Adametz of Fuji.
336 		 */
337 
338 		/* Cannot use "find_host()" here, as we need the hostlink record, not the host record */
339 		l = find_hostlist(hostname);
340 
341 		/* Walk through the clone-list and set the "entries" for all hosts */
342 		for (l=l->clones; (l); l = l->clones) l->hostentry->entries = host->entries;
343 	}
344 	else {
345 		/* No host for this test - must be missing from hosts.cfg */
346 		newstate->entry->next = NULL;
347 	}
348 
349 	xfree(hostname);
350 	xfree(testname);
351 	if (fd) fclose(fd);
352 
353 	return newstate;
354 }
355 
init_displaysummary(char * fn,logdata_t * log)356 dispsummary_t *init_displaysummary(char *fn, logdata_t *log)
357 {
358 	char l[MAX_LINE_LEN];
359 	dispsummary_t *newsum = NULL;
360 	time_t now = getcurrenttime(NULL);
361 
362 	dbgprintf("init_displaysummary(%s)\n", textornull(fn));
363 
364 	if (log->validtime < now) return NULL;
365 	strcpy(l, log->msg);
366 
367 	if (strlen(l)) {
368 		char *p;
369 		char *color = (char *) malloc(strlen(l));
370 
371 		newsum = (dispsummary_t *) calloc(1, sizeof(dispsummary_t));
372 		newsum->url = (char *) malloc(strlen(l));
373 
374 		if (sscanf(l, "%s %s", color, newsum->url) == 2) {
375 			char *rowcol;
376 			newsum->color = parse_color(color);
377 
378 			rowcol = (char *) malloc(strlen(fn) + 1);
379 			strcpy(rowcol, fn+8);
380 			p = strrchr(rowcol, '.');
381 			if (p) *p = ' ';
382 
383 			newsum->column = (char *) malloc(strlen(rowcol)+1);
384 			newsum->row = (char *) malloc(strlen(rowcol)+1);
385 			sscanf(rowcol, "%s %s", newsum->row, newsum->column);
386 			newsum->next = NULL;
387 
388 			xfree(rowcol);
389 		}
390 		else {
391 			xfree(newsum->url);
392 			xfree(newsum);
393 			newsum = NULL;
394 		}
395 
396 		xfree(color);
397 	}
398 
399 	return newsum;
400 }
401 
402 
generate_compactitems(state_t ** topstate)403 void generate_compactitems(state_t **topstate)
404 {
405 	void *xmh;
406 	compact_t **complist = NULL;
407 	int complistsz = 0;
408 	hostlist_t 	*h;
409 	entry_t		*e;
410 	char *compacted;
411 	char *tok1, *savep1, *savep2;
412 	compact_t *itm;
413 	int i;
414 	state_t *newstate;
415 	time_t now = getcurrenttime(NULL);
416 
417 	for (h = hostlistBegin(); (h); h = hostlistNext()) {
418 		xmh = hostinfo(h->hostentry->hostname);
419 		compacted = xmh_item(xmh, XMH_COMPACT);
420 		if (!compacted) continue;
421 
422 		tok1 = strtok_r(compacted, ",", &savep1);
423 		while (tok1) {
424 			char *members;
425 
426 			itm = (compact_t *)calloc(1, sizeof(compact_t));
427 			itm->compactname = strdup(strtok_r(tok1, "=", &savep2));
428 			members = strtok_r(NULL, "\n", &savep2);
429 			itm->members = (char *)malloc(3 + (members ? strlen(members) : 0) );
430 			sprintf(itm->members, "|%s|", (members ? members : "") );
431 
432 			if (complistsz == 0) {
433 				complist = (compact_t **)calloc(2, sizeof(compact_t *));
434 			}
435 			else {
436 				complist = (compact_t **)realloc(complist, (complistsz+2)*sizeof(compact_t *));
437 			}
438 
439 			complist[complistsz++] = itm;
440 			complist[complistsz] = NULL;
441 
442 			tok1 = strtok_r(NULL, ",", &savep1);
443 		}
444 
445 		for (e = h->hostentry->entries; (e); e = e->next) {
446 			for (i = 0; (i < complistsz); i++) {
447 				if (wantedcolumn(e->column->name, complist[i]->members)) {
448 					e->compacted = 1;
449 					if (e->color > complist[i]->color) complist[i]->color = e->color;
450 					if (e->fileage > complist[i]->fileage) complist[i]->fileage = e->fileage;
451 				}
452 			}
453 		}
454 
455 		for (i = 0; (i < complistsz); i++) {
456 			logdata_t log;
457 			char fn[PATH_MAX];
458 
459 			memset(&log, 0, sizeof(log));
460 			sprintf(fn, "%s.%s", commafy(h->hostentry->hostname), complist[i]->compactname);
461 			log.hostname = h->hostentry->hostname;
462 			log.testname = complist[i]->compactname;
463 			log.color = complist[i]->color;
464 			log.testflags = "";
465 			log.lastchange = now - complist[i]->fileage;
466 			log.logtime = getcurrenttime(NULL);
467 			log.validtime = log.logtime + 300;
468 			log.sender = "";
469 			log.msg = "";
470 			newstate = init_state(fn, &log);
471 			if (newstate) {
472 				newstate->next = *topstate;
473 				*topstate = newstate;
474 			}
475 		}
476 	}
477 }
478 
479 
load_state(dispsummary_t ** sumhead)480 state_t *load_state(dispsummary_t **sumhead)
481 {
482 	int 		xymondresult;
483 	char		fn[PATH_MAX];
484 	state_t		*newstate, *topstate;
485 	dispsummary_t	*newsum, *topsum;
486 	char 		*board = NULL;
487 	char		*nextline;
488 	int		done;
489 	logdata_t	log;
490 	sendreturn_t	*sres;
491 
492 	dbgprintf("load_state()\n");
493 
494 	sres = newsendreturnbuf(1, NULL);
495 
496 	if (!reportstart && !snapshot) {
497 		char *dumpfn = getenv("BOARDDUMP");
498 		char *filter = getenv("BOARDFILTER");
499 
500 		if (dumpfn) {
501 			/* Debugging - read data from a dump file */
502 			struct stat st;
503 			FILE *fd;
504 
505 			xymondresult = XYMONSEND_ETIMEOUT;
506 			if (stat(dumpfn, &st) == 0) {
507 				fd = fopen(dumpfn, "r");
508 				if (fd) {
509 					board = (char *)malloc(st.st_size + 1); *board = '\0';
510 					if (fread(board, 1, st.st_size, fd)) {
511 						fclose(fd);
512 						xymondresult = XYMONSEND_OK;
513 					}
514 				}
515 			}
516 		}
517 		else {
518 			char *bcmd;
519 
520 			bcmd = (char *)malloc(1024 + (filter ? strlen(filter) : 0));
521 			sprintf(bcmd, "xymondboard fields=hostname,testname,color,flags,lastchange,logtime,validtime,acktime,disabletime,sender,cookie,line1,acklist %s", (filter ? filter: ""));
522 			xymondresult = sendmessage(bcmd, NULL, XYMON_TIMEOUT, sres);
523 			board = getsendreturnstr(sres, 1);
524 			xfree(bcmd);
525 		}
526 	}
527 	else {
528 		xymondresult = sendmessage("xymondboard fields=hostname,testname", NULL, XYMON_TIMEOUT, sres);
529 		board = getsendreturnstr(sres, 1);
530 	}
531 
532 	freesendreturnbuf(sres);
533 
534 	if ((xymondresult != XYMONSEND_OK) || (board == NULL) || (*board == '\0')) {
535 		errprintf("xymond status-board not available, code %d\n", xymondresult);
536 		return NULL;
537 	}
538 
539 	if (reportstart || snapshot) {
540 		oldestentry = getcurrenttime(NULL);
541 		purplelog = NULL;
542 		purplelogfn = NULL;
543 	}
544 	else {
545 		if (purplelogfn) {
546 			purplelog = fopen(purplelogfn, "w");
547 			if (purplelog == NULL) errprintf("Cannot open purplelog file %s\n", purplelogfn);
548 			else fprintf(purplelog, "Stale (purple) logfiles as of %s\n\n", timestamp);
549 		}
550 	}
551 
552 	topstate = NULL;
553 	topsum = NULL;
554 
555 	done = 0; nextline = board;
556 	while (!done) {
557 		char *bol = nextline;
558 		char *onelog, *acklist;
559 		char *p;
560 		int i;
561 
562 		nextline = strchr(nextline, '\n');
563 		if (nextline) { *nextline = '\0'; nextline++; } else done = 1;
564 
565 		if (strlen(bol) == 0) {
566 			done = 1;
567 			continue;
568 		}
569 
570 		memset(&log, 0, sizeof(log));
571 		onelog = strdup(bol);
572 		acklist = NULL;
573 		p = gettok(onelog, "|"); i = 0;
574 		while (p) {
575 			switch (i) {
576 			  /* hostname|testname|color|testflags|lastchange|logtime|validtime|acktime|disabletime|sender|cookie|1st line of message|acklist */
577 			  case  0: log.hostname = p; break;
578 			  case  1: log.testname = p; break;
579 			  case  2: log.color = parse_color(p); break;
580 			  case  3: log.testflags = p; break;
581 			  case  4: log.lastchange = atoi(p); break;
582 			  case  5: log.logtime = atoi(p); break;
583 			  case  6: log.validtime = atoi(p); break;
584 			  case  7: log.acktime = atoi(p); break;
585 			  case  8: log.disabletime = atoi(p); break;
586 			  case  9: log.sender = p; break;
587 			  case 10: log.cookie = atoi(p); break;
588 			  case 11: log.msg = p; break;
589 			  case 12: acklist = p; break;
590 			}
591 
592 			p = gettok(NULL, "|");
593 			i++;
594 		}
595 		if (!log.hostname || !log.testname) {
596 			errprintf("Found incomplete or corrupt log line (%s|%s); skipping\n", textornull(log.hostname), textornull(log.testname) );
597 			xfree(onelog);
598 			continue;
599 		}
600 		if (!log.msg) log.msg = "";
601 		sprintf(fn, "%s.%s", commafy(log.hostname), log.testname);
602 
603 		/* Get the data */
604 		if (strncmp(fn, "summary.", 8) == 0) {
605 			if (!reportstart && !snapshot) {
606 				newsum = init_displaysummary(fn, &log);
607 				if (newsum) {
608 					newsum->next = topsum;
609 					topsum = newsum;
610 				}
611 			}
612 		}
613 		else {
614 			if (acklist && *acklist) {
615 				/*
616 				 * It's been acked. acklist looks like
617 				 * 1149489234:1149510834:1:henrik:Joe promised to take care of this right after lunch\n
618 				 * The "\n" is the delimiter between multiple acks.
619 				 */
620 				char *tok;
621 
622 				tok = strtok(acklist, ":");
623 				if (tok) tok = strtok(NULL, ":");
624 				if (tok) log.acktime = atol(tok);
625 			}
626 			newstate = init_state(fn, &log);
627 			if (newstate) {
628 				newstate->next = topstate;
629 				topstate = newstate;
630 				if (reportstart && (newstate->entry->repinfo->reportstart < oldestentry)) {
631 					oldestentry = newstate->entry->repinfo->reportstart;
632 				}
633 			}
634 		}
635 		xfree(onelog);
636 	}
637 
638 	generate_compactitems(&topstate);
639 
640 	if (reportstart) sethostenv_report(oldestentry, reportend, reportwarnlevel, reportgreenlevel);
641 	if (purplelog) fclose(purplelog);
642 
643 	*sumhead = topsum;
644 	return topstate;
645 }
646 
647