1 /*----------------------------------------------------------------------------*/
2 /* Xymon monitor library.                                                     */
3 /*                                                                            */
4 /* This is a library module for Xymon, responsible for loading the            */
5 /* alerts.cfg file which holds information about the Xymon alert       */
6 /* configuration.                                                             */
7 /*                                                                            */
8 /* Copyright (C) 2004-2011 Henrik Storner <henrik@hswn.dk>                    */
9 /*                                                                            */
10 /* This program is released under the GNU General Public License (GPL),       */
11 /* version 2. See the file "COPYING" for details.                             */
12 /*                                                                            */
13 /*----------------------------------------------------------------------------*/
14 
15 static char rcsid[] = "$Id: loadalerts.c 8069 2019-07-23 15:29:06Z jccleaver $";
16 
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <ctype.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <stdlib.h>
24 #include <time.h>
25 #include <limits.h>
26 #include <errno.h>
27 
28 #include <pcre.h>
29 
30 #include "libxymon.h"
31 
32 
33 /* token's are the pre-processor macros we expand while parsing the config file */
34 typedef struct token_t {
35 	char *name;
36 	char *value;
37 	struct token_t *next;
38 } token_t;
39 static token_t *tokhead = NULL;
40 
41 /* This defines a rule. Some general criteria, and a list of recipients. */
42 typedef struct rule_t {
43 	int cfid;
44 	criteria_t *criteria;
45 	recip_t *recipients;
46 	struct rule_t *next;
47 } rule_t;
48 static rule_t *rulehead = NULL;
49 static rule_t *ruletail = NULL;
50 static int cfid = 0;
51 static char cfline[256];
52 static int printmode = 0;
53 static rule_t *printrule = NULL;
54 
55 static enum { P_NONE, P_RULE, P_RECIP } pstate = P_NONE;
56 static int defaultcolors = 0;
57 static int localalertmode = 0;
58 
setup_criteria(rule_t ** currule,recip_t ** currcp)59 static criteria_t *setup_criteria(rule_t **currule, recip_t **currcp)
60 {
61 	criteria_t *crit = NULL;
62 
63 	MEMDEFINE(cfline);
64 
65 	switch (pstate) {
66 	  case P_NONE:
67 		*currule = (rule_t *)calloc(1, sizeof(rule_t));
68 		(*currule)->cfid = cfid;
69 		pstate = P_RULE;
70 		/* Fall through */
71 
72 	  case P_RULE:
73 		if (!(*currule)->criteria)
74 			(*currule)->criteria = (criteria_t *)calloc(1, sizeof(criteria_t));
75 		crit = (*currule)->criteria;
76 		crit->cfid = cfid;
77 		if (crit->cfline == NULL) crit->cfline = strdup(cfline);
78 		*currcp = NULL;
79 		break;
80 
81 	  case P_RECIP:
82 		if (!(*currcp)->criteria) {
83 			recip_t *rwalk;
84 
85 			(*currcp)->criteria = (criteria_t *)calloc(1, sizeof(criteria_t));
86 
87 			/* Make sure other recipients on the same rule also get these criteria */
88 			for (rwalk = (*currule)->recipients; (rwalk); rwalk = rwalk->next) {
89 				if (rwalk->cfid == cfid) rwalk->criteria = (*currcp)->criteria;
90 			}
91 		}
92 		crit = (*currcp)->criteria;
93 		crit->cfid = cfid;
94 		if (crit->cfline == NULL) crit->cfline = strdup(cfline);
95 		break;
96 	}
97 
98 	MEMUNDEFINE(cfline);
99 	return crit;
100 }
101 
set_localalertmode(int localmode)102 void set_localalertmode(int localmode)
103 {
104 	if (localmode) localalertmode = 1;
105 }
106 
preprocess(char * buf)107 static char *preprocess(char *buf)
108 {
109 	/* Expands config-file macros */
110 	static strbuffer_t *result = NULL;
111 	char *inp;
112 
113 	if (result == NULL) result = newstrbuffer(8192);
114 	clearstrbuffer(result);
115 
116 	inp = buf;
117 	while (inp) {
118 		char *p;
119 
120 		p = strchr(inp, '$');
121 		if (p == NULL) {
122 			addtobuffer(result, inp);
123 			inp = NULL;
124 		}
125 		else {
126 			token_t *twalk;
127 			char savech;
128 			int n;
129 
130 			*p = '\0';
131 			addtobuffer(result, inp);
132 			p = (p+1);
133 
134 			n = strcspn(p, "\t $.,|%!()[]{}+?/&@:;*");
135 			savech = *(p+n);
136 			*(p+n) = '\0';
137 			for (twalk = tokhead; (twalk && strcmp(p, twalk->name)); twalk = twalk->next) ;
138 			*(p+n) = savech;
139 
140 			if (twalk) addtobuffer(result, twalk->value);
141 			inp = p+n;
142 		}
143 	}
144 
145 	return STRBUF(result);
146 }
147 
flush_rule(rule_t * currule)148 static void flush_rule(rule_t *currule)
149 {
150 	if (currule == NULL) return;
151 
152 	currule->next = NULL;
153 
154 	if (rulehead == NULL) {
155 		rulehead = ruletail = currule;
156 	}
157 	else {
158 		ruletail->next = currule;
159 		ruletail = currule;
160 	}
161 }
162 
free_criteria(criteria_t * crit)163 static void free_criteria(criteria_t *crit)
164 {
165 	if (crit->cfline)        xfree(crit->cfline);
166 	if (crit->pagespec)      xfree(crit->pagespec);
167 	if (crit->pagespecre)    pcre_free(crit->pagespecre);
168 	if (crit->expagespec)    xfree(crit->expagespec);
169 	if (crit->expagespecre)  pcre_free(crit->expagespecre);
170 	if (crit->dgspec)        xfree(crit->dgspec);
171 	if (crit->dgspecre)      pcre_free(crit->dgspecre);
172 	if (crit->exdgspec)      xfree(crit->exdgspec);
173 	if (crit->exdgspecre)    pcre_free(crit->exdgspecre);
174 	if (crit->hostspec)      xfree(crit->hostspec);
175 	if (crit->hostspecre)    pcre_free(crit->hostspecre);
176 	if (crit->exhostspec)    xfree(crit->exhostspec);
177 	if (crit->exhostspecre)  pcre_free(crit->exhostspecre);
178 	if (crit->svcspec)       xfree(crit->svcspec);
179 	if (crit->svcspecre)     pcre_free(crit->svcspecre);
180 	if (crit->exsvcspec)     xfree(crit->exsvcspec);
181 	if (crit->exsvcspecre)   pcre_free(crit->exsvcspecre);
182 	if (crit->classspec)     xfree(crit->classspec);
183 	if (crit->classspecre)   pcre_free(crit->classspecre);
184 	if (crit->exclassspec)   xfree(crit->exclassspec);
185 	if (crit->exclassspecre) pcre_free(crit->exclassspecre);
186 	if (crit->groupspec)     xfree(crit->groupspec);
187 	if (crit->groupspecre)   pcre_free(crit->groupspecre);
188 	if (crit->exgroupspec)   xfree(crit->exgroupspec);
189 	if (crit->exgroupspecre) pcre_free(crit->exgroupspecre);
190 	if (crit->timespec)      xfree(crit->timespec);
191 	if (crit->extimespec)    xfree(crit->extimespec);
192 }
193 
load_alertconfig(char * configfn,int defcolors,int defaultinterval)194 int load_alertconfig(char *configfn, int defcolors, int defaultinterval)
195 {
196 	/* (Re)load the configuration file without leaking memory */
197 	static void *configfiles = NULL;
198 	char fn[PATH_MAX];
199 	FILE *fd;
200 	strbuffer_t *inbuf;
201 	char *p;
202 	rule_t *currule = NULL;
203 	recip_t *currcp = NULL, *rcptail = NULL;
204 
205 	MEMDEFINE(fn);
206 
207 	if (configfn) strncpy(fn, configfn, sizeof(fn)); else snprintf(fn, sizeof(fn), "%s/etc/alerts.cfg", xgetenv("XYMONHOME"));
208 
209 	/* First check if there were no modifications at all */
210 	if (configfiles) {
211 		if (!stackfmodified(configfiles)){
212 			dbgprintf("No files modified, skipping reload of %s\n", fn);
213 			MEMUNDEFINE(fn);
214 			return 0;
215 		}
216 		else {
217 			stackfclist(&configfiles);
218 			configfiles = NULL;
219 		}
220 	}
221 
222 	fd = stackfopen(fn, "r", &configfiles);
223 	if (!fd) {
224 		errprintf("Cannot open configuration file %s: %s\n", fn, strerror(errno));
225 		MEMUNDEFINE(fn);
226 		return 0;
227 	}
228 
229 	/* First, clean out the old rule set */
230 	while (rulehead) {
231 		rule_t *trule;
232 
233 		if (rulehead->criteria) {
234 			free_criteria(rulehead->criteria);
235 			xfree(rulehead->criteria);
236 		}
237 
238 		while (rulehead->recipients) {
239 			recip_t *trecip = rulehead->recipients;
240 
241 			if (trecip->criteria) {
242 				recip_t *rwalk;
243 
244 				/* Clear out the duplicate criteria that may exist, to avoid double-free'ing them */
245 				for (rwalk = trecip->next; (rwalk); rwalk = rwalk->next) {
246 					if (rwalk->criteria == trecip->criteria) rwalk->criteria = NULL;
247 				}
248 
249 				free_criteria(trecip->criteria);
250 				xfree(trecip->criteria);
251 			}
252 
253 			if (trecip->recipient)  xfree(trecip->recipient);
254 			if (trecip->scriptname) xfree(trecip->scriptname);
255 			rulehead->recipients = rulehead->recipients->next;
256 			xfree(trecip);
257 		}
258 		trule = rulehead;
259 		rulehead = rulehead->next;
260 		xfree(trule);
261 	}
262 
263 	while (tokhead) {
264 		token_t *ttok;
265 
266 		if (tokhead->name)  xfree(tokhead->name);
267 		if (tokhead->value) xfree(tokhead->value);
268 		ttok = tokhead;
269 		tokhead = tokhead->next;
270 		xfree(ttok);
271 	}
272 
273 	defaultcolors = defcolors;
274 
275 	MEMDEFINE(cfline);
276 
277 	cfid = 0;
278 	inbuf = newstrbuffer(0);
279 	while (stackfgets(inbuf, NULL)) {
280 		int firsttoken = 1;
281 		int mailcmdactive = 0, scriptcmdactive = 0;
282 		recip_t *curlinerecips = NULL;
283 
284 		cfid++;
285 		sanitize_input(inbuf, 1, 0);
286 
287 		/* Skip empty lines */
288 		if (STRBUFLEN(inbuf) == 0) continue;
289 
290 		if ((*STRBUF(inbuf) == '$') && strchr(STRBUF(inbuf), '=')) {
291 			/* Define a macro */
292 			token_t *newtok = (token_t *) malloc(sizeof(token_t));
293 			char *delim;
294 
295 			delim = strchr(STRBUF(inbuf), '=');
296 			*delim = '\0';
297 			newtok->name = strdup(STRBUF(inbuf)+1);	/* Skip the '$' */
298 			newtok->value = strdup(preprocess(delim+1));
299 			newtok->next = tokhead;
300 			tokhead = newtok;
301 			continue;
302 		}
303 
304 		strncpy(cfline, STRBUF(inbuf), (sizeof(cfline)-1));
305 		cfline[sizeof(cfline)-1] = '\0';
306 
307 		/* Expand macros inside the line before parsing */
308 		p = strtok(preprocess(STRBUF(inbuf)), " \t");
309 		while (p) {
310 			if ((strncasecmp(p, "PAGE=", 5) == 0) || (strncasecmp(p, "PAGES=", 6) == 0)) {
311 				char *val;
312 				criteria_t *crit;
313 
314 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
315 				val = strchr(p, '=')+1;
316 				crit = setup_criteria(&currule, &currcp);
317 				crit->pagespec = strdup(val);
318 				if (*(crit->pagespec) == '%') crit->pagespecre = compileregex(crit->pagespec+1);
319 				firsttoken = 0;
320 			}
321 			else if ((strncasecmp(p, "EXPAGE=", 7) == 0) || (strncasecmp(p, "EXPAGES=", 8) == 0)) {
322 				char *val;
323 				criteria_t *crit;
324 
325 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
326 				val = strchr(p, '=')+1;
327 				crit = setup_criteria(&currule, &currcp);
328 				crit->expagespec = strdup(val);
329 				if (*(crit->expagespec) == '%') crit->expagespecre = compileregex(crit->expagespec+1);
330 				firsttoken = 0;
331 			}
332 			else if ((strncasecmp(p, "DISPLAYGROUP=", 13) == 0) || (strncasecmp(p, "DISPLAYGROUPS=", 14) == 0)) {
333 				char *val;
334 				criteria_t *crit;
335 
336 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
337 				val = strchr(p, '=')+1;
338 				crit = setup_criteria(&currule, &currcp);
339 				crit->dgspec = strdup(val);
340 				if (*(crit->dgspec) == '%') crit->dgspecre = compileregex(crit->dgspec+1);
341 				firsttoken = 0;
342 			}
343 			else if ((strncasecmp(p, "EXDISPLAYGROUP=", 15) == 0) || (strncasecmp(p, "EXDISPLAYGROUPS=", 16) == 0)) {
344 				char *val;
345 				criteria_t *crit;
346 
347 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
348 				val = strchr(p, '=')+1;
349 				crit = setup_criteria(&currule, &currcp);
350 				crit->exdgspec = strdup(val);
351 				if (*(crit->exdgspec) == '%') crit->exdgspecre = compileregex(crit->exdgspec+1);
352 				firsttoken = 0;
353 			}
354 			else if ((strncasecmp(p, "HOST=", 5) == 0) || (strncasecmp(p, "HOSTS=", 6) == 0)) {
355 				char *val;
356 				criteria_t *crit;
357 
358 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
359 				val = strchr(p, '=')+1;
360 				crit = setup_criteria(&currule, &currcp);
361 				crit->hostspec = strdup(val);
362 				if (*(crit->hostspec) == '%') crit->hostspecre = compileregex(crit->hostspec+1);
363 				firsttoken = 0;
364 			}
365 			else if ((strncasecmp(p, "EXHOST=", 7) == 0) || (strncasecmp(p, "EXHOSTS=", 8) == 0)) {
366 				char *val;
367 				criteria_t *crit;
368 
369 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
370 				val = strchr(p, '=')+1;
371 				crit = setup_criteria(&currule, &currcp);
372 				crit->exhostspec = strdup(val);
373 				if (*(crit->exhostspec) == '%') crit->exhostspecre = compileregex(crit->exhostspec+1);
374 				firsttoken = 0;
375 			}
376 			else if ((strncasecmp(p, "SERVICE=", 8) == 0) || (strncasecmp(p, "SERVICES=", 9) == 0)) {
377 				char *val;
378 				criteria_t *crit;
379 
380 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
381 				val = strchr(p, '=')+1;
382 				crit = setup_criteria(&currule, &currcp);
383 				crit->svcspec = strdup(val);
384 				if (*(crit->svcspec) == '%') crit->svcspecre = compileregex(crit->svcspec+1);
385 				firsttoken = 0;
386 			}
387 			else if ((strncasecmp(p, "EXSERVICE=", 10) == 0) || (strncasecmp(p, "EXSERVICES=", 11) == 0)) {
388 				char *val;
389 				criteria_t *crit;
390 
391 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
392 				val = strchr(p, '=')+1;
393 				crit = setup_criteria(&currule, &currcp);
394 				crit->exsvcspec = strdup(val);
395 				if (*(crit->exsvcspec) == '%') crit->exsvcspecre = compileregex(crit->exsvcspec+1);
396 				firsttoken = 0;
397 			}
398 			else if (strncasecmp(p, "CLASS=", 6) == 0) {
399 				char *val;
400 				criteria_t *crit;
401 
402 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
403 				val = strchr(p, '=')+1;
404 				crit = setup_criteria(&currule, &currcp);
405 				crit->classspec = strdup(val);
406 				if (*(crit->classspec) == '%') crit->classspecre = compileregex(crit->classspec+1);
407 				firsttoken = 0;
408 			}
409 			else if (strncasecmp(p, "EXCLASS=", 8) == 0) {
410 				char *val;
411 				criteria_t *crit;
412 
413 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
414 				val = strchr(p, '=')+1;
415 				crit = setup_criteria(&currule, &currcp);
416 				crit->exclassspec = strdup(val);
417 				if (*(crit->exclassspec) == '%') crit->exclassspecre = compileregex(crit->exclassspec+1);
418 				firsttoken = 0;
419 			}
420 			else if (strncasecmp(p, "GROUP=", 6) == 0) {
421 				char *val;
422 				criteria_t *crit;
423 
424 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
425 				val = strchr(p, '=')+1;
426 				crit = setup_criteria(&currule, &currcp);
427 				crit->groupspec = strdup(val);
428 				if (*(crit->groupspec) == '%') crit->groupspecre = compileregex(crit->groupspec+1);
429 				firsttoken = 0;
430 			}
431 			else if (strncasecmp(p, "EXGROUP=", 8) == 0) {
432 				char *val;
433 				criteria_t *crit;
434 
435 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
436 				val = strchr(p, '=')+1;
437 				crit = setup_criteria(&currule, &currcp);
438 				crit->exgroupspec = strdup(val);
439 				if (*(crit->exgroupspec) == '%') crit->exgroupspecre = compileregex(crit->exgroupspec+1);
440 				firsttoken = 0;
441 			}
442 			else if ((strncasecmp(p, "COLOR=", 6) == 0) || (strncasecmp(p, "COLORS=", 7) == 0)) {
443 				criteria_t *crit;
444 				char *c1, *c2;
445 				int cval, reverse = 0;
446 
447 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
448 				crit = setup_criteria(&currule, &currcp);
449 
450 				/* Put a value in crit->colors so we know there is an explicit color setting */
451 				crit->colors = (1 << 30);
452 				c1 = strchr(p, '=')+1;
453 
454 				/*
455 				 * If the first colorspec is "!color", then apply the default colors and
456 				 * subtract colors from that.
457 				 */
458 				if (*c1 == '!') crit->colors |= defaultcolors;
459 
460 				do {
461 					c2 = strchr(c1, ',');
462 					if (c2) *c2 = '\0';
463 
464 					if (*c1 == '!') { reverse=1; c1++; }
465 					cval = (1 << parse_color(c1));
466 
467 					if (reverse)
468 						crit->colors &= (~cval);
469 					else
470 						crit->colors |= cval;
471 
472 					if (c2) c1 = (c2+1); else c1 = NULL;
473 				} while (c1);
474 				firsttoken = 0;
475 			}
476 			else if ((strncasecmp(p, "TIME=", 5) == 0) || (strncasecmp(p, "TIMES=", 6) == 0)) {
477 				char *val;
478 				criteria_t *crit;
479 
480 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
481 				val = strchr(p, '=')+1;
482 				crit = setup_criteria(&currule, &currcp);
483 				crit->timespec = strdup(val);
484 				firsttoken = 0;
485 			}
486 			else if ((strncasecmp(p, "EXTIME=", 7) == 0) || (strncasecmp(p, "EXTIMES=", 8) == 0)) {
487 				char *val;
488 				criteria_t *crit;
489 
490 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
491 				val = strchr(p, '=')+1;
492 				crit = setup_criteria(&currule, &currcp);
493 				crit->extimespec = strdup(val);
494 				firsttoken = 0;
495 			}
496 			else if (strncasecmp(p, "DURATION", 8) == 0) {
497 				criteria_t *crit;
498 
499 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
500 				crit = setup_criteria(&currule, &currcp);
501 				if (*(p+8) == '>') {
502 					if (*(p+9) == '=')
503 						crit->minduration = 60*durationvalue(p+10);
504 					else
505 						crit->minduration = 60*durationvalue(p+9) + 1;
506 				}
507 				else if (*(p+8) == '<') {
508 					if (*(p+9) == '=')
509 						crit->maxduration = 60*durationvalue(p+10);
510 					else
511 						crit->maxduration = 60*durationvalue(p+9) - 1;
512 				}
513 				else errprintf("Ignoring invalid DURATION at line %d: %s\n",cfid, p);
514 				firsttoken = 0;
515 			}
516 			else if (strncasecmp(p, "RECOVERED", 9) == 0) {
517 				criteria_t *crit;
518 
519 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
520 				crit = setup_criteria(&currule, &currcp);
521 				crit->sendrecovered = SR_WANTED;
522 				firsttoken = 0;
523 			}
524 			else if (strncasecmp(p, "NORECOVERED", 11) == 0) {
525 				criteria_t *crit;
526 
527 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
528 				crit = setup_criteria(&currule, &currcp);
529 				crit->sendrecovered = SR_NOTWANTED;
530 				firsttoken = 0;
531 			}
532 			else if (strncasecmp(p, "NOTICE", 6) == 0) {
533 				criteria_t *crit;
534 
535 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
536 				crit = setup_criteria(&currule, &currcp);
537 				crit->sendnotice = SR_WANTED;
538 				firsttoken = 0;
539 			}
540 			else if (strncasecmp(p, "NONOTICE", 8) == 0) {
541 				criteria_t *crit;
542 
543 				if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; }
544 				crit = setup_criteria(&currule, &currcp);
545 				crit->sendnotice = SR_NOTWANTED;
546 				firsttoken = 0;
547 			}
548 			else if ((pstate == P_RECIP) && (strncasecmp(p, "FORMAT=", 7) == 0)) {
549 				if (!currcp) errprintf("FORMAT used without a recipient (line %d), ignored\n", cfid);
550 				else if (strcasecmp(p+7, "TEXT") == 0) currcp->format = ALERTFORM_TEXT;
551 				else if (strcasecmp(p+7, "PLAIN") == 0) currcp->format = ALERTFORM_PLAIN;
552 				else if (strcasecmp(p+7, "SMS") == 0) currcp->format = ALERTFORM_SMS;
553 				else if (strcasecmp(p+7, "PAGER") == 0) currcp->format = ALERTFORM_PAGER;
554 				else if (strcasecmp(p+7, "SCRIPT") == 0) currcp->format = ALERTFORM_SCRIPT;
555 				else errprintf("Unknown FORMAT setting '%s' ignored\n", p);
556 				firsttoken = 0;
557 			}
558 			else if ((pstate == P_RECIP) && (strncasecmp(p, "REPEAT=", 7) == 0)) {
559 				if (!currcp) errprintf("REPEAT used without a recipient (line %d), ignored\n", cfid);
560 				else currcp->interval = 60*durationvalue(p+7);
561 				firsttoken = 0;
562 			}
563 			else if ((pstate == P_RECIP) && (strcasecmp(p, "STOP") == 0)) {
564 				if (!currcp) errprintf("STOP used without a recipient (line %d), ignored\n", cfid);
565 				else currcp->stoprule = 1;
566 				firsttoken = 0;
567 			}
568 			else if ((pstate == P_RECIP) && (strcasecmp(p, "UNMATCHED") == 0)) {
569 				if (!currcp) errprintf("UNMATCHED used without a recipient (line %d), ignored\n", cfid);
570 				else currcp->unmatchedonly = 1;
571 				firsttoken = 0;
572 			}
573 			else if ((pstate == P_RECIP) && (strncasecmp(p, "NOALERT", 7) == 0)) {
574 				if (!currcp) errprintf("NOALERT used without a recipient (line %d), ignored\n", cfid);
575 				else currcp->noalerts = 1;
576 				firsttoken = 0;
577 			}
578 			else if (currule && ((strncasecmp(p, "MAIL", 4) == 0) || mailcmdactive) ) {
579 				recip_t *newrcp;
580 
581 				mailcmdactive = 1;
582 				newrcp = (recip_t *)calloc(1, sizeof(recip_t));
583 				newrcp->cfid = cfid;
584 				newrcp->method = M_MAIL;
585 				newrcp->format = ALERTFORM_TEXT;
586 
587 				if (strncasecmp(p, "MAIL=", 5) == 0) {
588 					p += 5;
589 				}
590 				else if (strcasecmp(p, "MAIL") == 0) {
591 					p = strtok(NULL, " \t");
592 				}
593 				else {
594 					/* Second recipient on a rule - do nothing */
595 				}
596 
597 				if (p) {
598 					newrcp->recipient = strdup(p);
599 					newrcp->interval = defaultinterval;
600 					currcp = newrcp;
601 					if (curlinerecips == NULL) curlinerecips = newrcp;
602 					pstate = P_RECIP;
603 
604 					if (currule->recipients == NULL)
605 						currule->recipients = rcptail = newrcp;
606 					else {
607 						rcptail->next = newrcp;
608 						rcptail = newrcp;
609 					}
610 				}
611 				else {
612 					errprintf("Ignoring MAIL with no recipient at line %d\n", cfid);
613 					xfree(newrcp);
614 				}
615 				firsttoken = 0;
616 			}
617 			else if (currule && ((strncasecmp(p, "SCRIPT", 6) == 0) || scriptcmdactive)) {
618 				recip_t *newrcp;
619 
620 				scriptcmdactive = 1;
621 				newrcp = (recip_t *)calloc(1, sizeof(recip_t));
622 				newrcp->cfid = cfid;
623 				newrcp->method = M_SCRIPT;
624 				newrcp->format = ALERTFORM_SCRIPT;
625 
626 				if (strncasecmp(p, "SCRIPT=", 7) == 0) {
627 					p += 7;
628 					newrcp->scriptname = strdup(p);
629 					p = strtok(NULL, " \t");
630 				}
631 				else if (strcasecmp(p, "SCRIPT") == 0) {
632 					p = strtok(NULL, " \t");
633 					if (p) {
634 						newrcp->scriptname = strdup(p);
635 						p = strtok(NULL, " \t");
636 					}
637 					else {
638 						errprintf("Invalid SCRIPT command at line %d\n", cfid);
639 					}
640 				}
641 				else {
642 					/* A second recipient for the same script as the previous one */
643 					newrcp->scriptname = strdup(currcp->scriptname);
644 				}
645 
646 				if (p) {
647 					newrcp->recipient = strdup(p);
648 					newrcp->interval = defaultinterval;
649 					currcp = newrcp;
650 					if (curlinerecips == NULL) curlinerecips = newrcp;
651 					pstate = P_RECIP;
652 
653 					if (currule->recipients == NULL)
654 						currule->recipients = rcptail = newrcp;
655 					else {
656 						rcptail->next = newrcp;
657 						rcptail = newrcp;
658 					}
659 				}
660 				else {
661 					errprintf("Ignoring SCRIPT with no recipient at line %d\n", cfid);
662 					if (newrcp->scriptname) xfree(newrcp->scriptname);
663 					xfree(newrcp);
664 				}
665 				firsttoken = 0;
666 			}
667 			else if (currule && (strncasecmp(p, "IGNORE", 6) == 0)) {
668 				recip_t *newrcp;
669 
670 				newrcp = (recip_t *)calloc(1, sizeof(recip_t));
671 				newrcp->cfid = cfid;
672 				newrcp->method = M_IGNORE;
673 				newrcp->format = ALERTFORM_NONE;
674 				newrcp->interval = defaultinterval;
675 				newrcp->stoprule = 1;
676 				currcp = newrcp;
677 				if (curlinerecips == NULL) curlinerecips = newrcp;
678 				pstate = P_RECIP;
679 
680 				if (currule->recipients == NULL)
681 					currule->recipients = rcptail = newrcp;
682 				else {
683 					rcptail->next = newrcp;
684 					rcptail = newrcp;
685 				}
686 
687 				firsttoken = 0;
688 			}
689 			else {
690 				errprintf("Ignored unknown/unexpected token '%s' at line %d\n", p, cfid);
691 			}
692 
693 			if (p) p = strtok(NULL, " \t");
694 		}
695 
696 		if (curlinerecips && currcp && (curlinerecips != currcp)) {
697 			/* We have multiple recipients on one line. Make sure criteria etc. get copied */
698 			recip_t *rwalk;
699 
700 			/* All criteria etc. have been set on the last recipient (currcp) */
701 			for (rwalk = curlinerecips; (rwalk != currcp); rwalk = rwalk->next) {
702 				rwalk->format = currcp->format;
703 				rwalk->interval = currcp->interval;
704 				rwalk->criteria = currcp->criteria;
705 				rwalk->noalerts = currcp->noalerts;
706 			}
707 		}
708 	}
709 
710 	flush_rule(currule);
711 	stackfclose(fd);
712 	freestrbuffer(inbuf);
713 
714 	MEMUNDEFINE(cfline);
715 	MEMUNDEFINE(fn);
716 
717 	return 1;
718 }
719 
dump_criteria(criteria_t * crit,int isrecip)720 static void dump_criteria(criteria_t *crit, int isrecip)
721 {
722 	if (crit->pagespec) printf("PAGE=%s ", crit->pagespec);
723 	if (crit->expagespec) printf("EXPAGE=%s ", crit->expagespec);
724 	if (crit->dgspec) printf("DISPLAYGROUP=%s ", crit->dgspec);
725 	if (crit->exdgspec) printf("EXDISPLAYGROUP=%s ", crit->exdgspec);
726 	if (crit->hostspec) printf("HOST=%s ", crit->hostspec);
727 	if (crit->exhostspec) printf("EXHOST=%s ", crit->exhostspec);
728 	if (crit->svcspec) printf("SERVICE=%s ", crit->svcspec);
729 	if (crit->exsvcspec) printf("EXSERVICE=%s ", crit->exsvcspec);
730 	if (crit->classspec) printf("CLASS=%s ", crit->classspec);
731 	if (crit->exclassspec) printf("EXCLASS=%s ", crit->exclassspec);
732 	if (crit->groupspec) printf("GROUP=%s ", crit->groupspec);
733 	if (crit->exgroupspec) printf("EXGROUP=%s ", crit->exgroupspec);
734 	if (crit->colors) {
735 		int i, first = 1;
736 
737 		printf("COLOR=");
738 		for (i = 0; (i < COL_COUNT); i++) {
739 			if ((1 << i) & crit->colors) {
740 				dbgprintf("first=%d, i=%d\n", first, i);
741 				printf("%s%s", (first ? "" : ","), colorname(i));
742 				first = 0;
743 			}
744 		}
745 		printf(" ");
746 	}
747 
748 	if (crit->timespec) printf("TIME=%s ", crit->timespec);
749 	if (crit->extimespec) printf("EXTIME=%s ", crit->extimespec);
750 	if (crit->minduration) printf("DURATION>%d ", (crit->minduration / 60));
751 	if (crit->maxduration) printf("DURATION<%d ", (crit->maxduration / 60));
752 	if (isrecip) {
753 		switch (crit->sendrecovered) {
754 		  case SR_UNKNOWN: break;
755 		  case SR_WANTED: printf("RECOVERED "); break;
756 		  case SR_NOTWANTED: printf("NORECOVERED "); break;
757 		}
758 		switch (crit->sendnotice) {
759 		  case SR_UNKNOWN: break;
760 		  case SR_WANTED: printf("NOTICE "); break;
761 		  case SR_NOTWANTED: printf("NONOTICE "); break;
762 		}
763 	}
764 }
765 
dump_alertconfig(int showlines)766 void dump_alertconfig(int showlines)
767 {
768 	rule_t *rulewalk;
769 	recip_t *recipwalk;
770 
771 	for (rulewalk = rulehead; (rulewalk); rulewalk = rulewalk->next) {
772 		if (showlines) printf("%5d\t", rulewalk->cfid);
773 
774 		dump_criteria(rulewalk->criteria, 0);
775 		printf("\n");
776 
777 		for (recipwalk = rulewalk->recipients; (recipwalk); recipwalk = recipwalk->next) {
778 			printf("\t");
779 			switch (recipwalk->method) {
780 			  case M_MAIL   : printf("MAIL %s ", recipwalk->recipient); break;
781 			  case M_SCRIPT : printf("SCRIPT %s %s ", recipwalk->scriptname, recipwalk->recipient); break;
782 			  case M_IGNORE : printf("IGNORE "); break;
783 			}
784 			switch (recipwalk->format) {
785 			  case ALERTFORM_TEXT  : printf("FORMAT=TEXT "); break;
786 			  case ALERTFORM_PLAIN : printf("FORMAT=PLAIN "); break;
787 			  case ALERTFORM_SMS   : printf("FORMAT=SMS "); break;
788 			  case ALERTFORM_PAGER : printf("FORMAT=PAGER "); break;
789 			  case ALERTFORM_SCRIPT: printf("FORMAT=SCRIPT "); break;
790 			  case ALERTFORM_NONE  : break;
791 			}
792 			printf("REPEAT=%d ", (int)(recipwalk->interval / 60));
793 			if (recipwalk->criteria) dump_criteria(recipwalk->criteria, 1);
794 			if (recipwalk->unmatchedonly) printf("UNMATCHED ");
795 			if (recipwalk->stoprule) printf("STOP ");
796 			if (recipwalk->noalerts) printf("NOALERT ");
797 			printf("\n");
798 		}
799 		printf("\n");
800 	}
801 }
802 
803 int stoprulefound = 0;
804 
criteriamatch(activealerts_t * alert,criteria_t * crit,criteria_t * rulecrit,int * anymatch,time_t * nexttime)805 static int criteriamatch(activealerts_t *alert, criteria_t *crit, criteria_t *rulecrit, int *anymatch, time_t *nexttime)
806 {
807 	/*
808 	 * See if the "crit" matches the "alert".
809 	 * Match on pagespec, dgspec, hostspec, svcspec, classspec, groupspec, colors, timespec, extimespec, minduration, maxduration, sendrecovered
810 	 */
811 
812 	static char *pgnames = NULL;
813 	const char *dgname = NULL;
814 	int pgmatchres, pgexclres;
815 	time_t duration = (getcurrenttime(NULL) - alert->eventstart);
816 	int result, cfid = 0;
817 	char *pgtok, *cfline = NULL;
818 	void *hinfo = hostinfo(alert->hostname);
819 
820 	if (!hinfo) {
821 		logprintf("Checking criteria for host '%s', which is not yet defined; some alerts may not immediately fire\n", alert->hostname);
822 		if (localalertmode) hinfo = localhostinfo(alert->hostname);
823 	};
824 
825 	/* The top-level page needs a name - cannot match against an empty string */
826 	if (pgnames) xfree(pgnames);
827 	pgnames = strdup((*alert->location == '\0') ? "/" : alert->location);
828 	dgname = hinfo ? textornull(xmh_item(hinfo, XMH_DGNAME)) : strdup("");
829 
830 	if (crit) { cfid = crit->cfid; cfline = crit->cfline; }
831 	if (!cfid && rulecrit) cfid = rulecrit->cfid;
832 	if (!cfline && rulecrit) cfline = rulecrit->cfline;
833 	if (!cfline) cfline = "<undefined>";
834 
835 	traceprintf("Matching host:service:dgroup:page '%s:%s:%s:%s' against rule line %d\n",
836 			alert->hostname, alert->testname, dgname, alert->location, cfid);
837 
838 	if (alert->state == A_PAGING) {
839 		/* Check max-duration now - it's fast and easy. */
840 		if (crit && crit->maxduration && (duration > crit->maxduration)) {
841 			traceprintf("Failed '%s' (max. duration %d>%d)\n", cfline, duration, crit->maxduration);
842 			if (!printmode) return 0;
843 		}
844 	}
845 
846 	if (crit && crit->classspec && !namematch(alert->classname, crit->classspec, crit->classspecre)) {
847 		traceprintf("Failed '%s' (class not in include list)\n", cfline);
848 		return 0;
849 	}
850 	if (crit && crit->exclassspec && namematch(alert->classname, crit->exclassspec, crit->exclassspecre)) {
851 		traceprintf("Failed '%s' (class excluded)\n", cfline);
852 		return 0;
853 	}
854 
855 	/* alert->groups is a comma-separated list of groups, so it needs some special handling */
856 	/*
857 	 * NB: Don't check groups when RECOVERED - the group list for recovery messages is always empty.
858 	 * It doesn't matter if we match a recipient who was not in the group that originally
859 	 * got the alert - we will later check who has received the alert, and only those that
860 	 * have will get the recovery message.
861 	 */
862 	if (crit && (crit->groupspec || crit->exgroupspec) && (alert->state != A_RECOVERED)) {
863 		SBUF_DEFINE(grouplist);
864 		char *tokptr;
865 
866 		if ((alert->groups && (*(alert->groups)))) {
867 			SBUF_MALLOC(grouplist, strlen(alert->groups));
868 			strncpy(grouplist, alert->groups, grouplist_buflen);
869 		}
870 
871 		if (crit->groupspec) {
872 			char *onegroup;
873 			int iswanted = 0;
874 
875 			if (grouplist) {
876 				/* There is a group label on the alert, so it must match */
877 				onegroup = strtok_r(grouplist, ",", &tokptr);
878 				while (onegroup && !iswanted) {
879 					iswanted = (namematch(onegroup, crit->groupspec, crit->groupspecre));
880 					onegroup = strtok_r(NULL, ",", &tokptr);
881 				}
882 			}
883 
884 			if (!iswanted) {
885 				/*
886 				 * Either the alert had a group list that didn't match, or
887 				 * there was no group list and the rule listed one.
888 				 * In both cases, it's a failed match.
889 				 */
890 				traceprintf("Failed '%s' (group not in include list)\n", cfline);
891 				if (grouplist) xfree(grouplist);
892 				return 0;
893 			}
894 		}
895 
896 		if (crit->exgroupspec && grouplist) {
897 			char *onegroup;
898 
899 			/* Excluded groups are only handled when the alert does have a group list */
900 
901 			strncpy(grouplist, alert->groups, grouplist_buflen); /* Might have been used in the include list */
902 			onegroup = strtok_r(grouplist, ",", &tokptr);
903 			while (onegroup) {
904 				if (namematch(onegroup, crit->exgroupspec, crit->exgroupspecre)) {
905 					traceprintf("Failed '%s' (group excluded)\n", cfline);
906 					xfree(grouplist);
907 					return 0;
908 				}
909 				onegroup = strtok_r(NULL, ",", &tokptr);
910 			}
911 		}
912 
913 		if (grouplist) xfree(grouplist);
914 	}
915 
916 	pgmatchres = pgexclres = -1;
917 	pgtok = strtok(pgnames, ",");
918 	while (pgtok) {
919 		if (crit && crit->pagespec && (pgmatchres != 1))
920 			pgmatchres = (namematch(pgtok, crit->pagespec, crit->pagespecre) ? 1 : 0);
921 
922 		if (crit && crit->expagespec && (pgexclres != 1))
923 			pgexclres = (namematch(pgtok, crit->expagespec, crit->expagespecre) ? 1 : 0);
924 
925 		pgtok = strtok(NULL, ",");
926 	}
927 	if (pgexclres == 1) {
928 		traceprintf("Failed '%s' (pagename excluded)\n", cfline);
929 		return 0;
930 	}
931 	if (pgmatchres == 0) {
932 		traceprintf("Failed '%s' (pagename not in include list)\n", cfline);
933 		return 0;
934 	}
935 
936 	if (crit && crit->dgspec && !namematch(dgname, crit->dgspec, crit->dgspecre)) {
937 		traceprintf("Failed '%s' (displaygroup not in include list)\n", cfline);
938 		return 0;
939 	}
940 	if (crit && crit->exdgspec && namematch(dgname, crit->exdgspec, crit->exdgspecre)) {
941 		traceprintf("Failed '%s' (displaygroup excluded)\n", cfline);
942 		return 0;
943 	}
944 
945 	if (crit && crit->hostspec && !namematch(alert->hostname, crit->hostspec, crit->hostspecre)) {
946 		traceprintf("Failed '%s' (hostname not in include list)\n", cfline);
947 		return 0;
948 	}
949 	if (crit && crit->exhostspec && namematch(alert->hostname, crit->exhostspec, crit->exhostspecre)) {
950 		traceprintf("Failed '%s' (hostname excluded)\n", cfline);
951 		return 0;
952 	}
953 
954 	if (crit && crit->svcspec && !namematch(alert->testname, crit->svcspec, crit->svcspecre))  {
955 		traceprintf("Failed '%s' (service not in include list)\n", cfline);
956 		return 0;
957 	}
958 	if (crit && crit->exsvcspec && namematch(alert->testname, crit->exsvcspec, crit->exsvcspecre))  {
959 		traceprintf("Failed '%s' (service excluded)\n", cfline);
960 		return 0;
961 	}
962 
963 	if (alert->state == A_NOTIFY) {
964 		/*
965 		 * Don't do the check until we are checking individual recipients (rulecrit is set).
966 		 * You don't need to have NOTICE on the top-level rule, it's enough if a recipient
967 		 * has it set. However, we do want to allow there to be a default defined in the
968 		 * rule; but it doesn't take effect until we start checking the recipients.
969 		 */
970 		if (rulecrit) {
971 			int n = (crit ? crit->sendnotice : -1);
972 			traceprintf("Checking NOTICE setting %d (rule:%d)\n", n, rulecrit->sendnotice);
973 			if (crit && (crit->sendnotice == SR_NOTWANTED)) result = 0;	/* Explicit NONOTICE */
974 			else if (crit && (crit->sendnotice == SR_WANTED)) result = 1;	/* Explicit NOTICE */
975 			else result = (rulecrit->sendnotice == SR_WANTED);		/* Not set, but rule has NOTICE */
976 		}
977 		else {
978 			result = 1;
979 		}
980 
981 		if (!result) traceprintf("Failed '%s' (notice not wanted)\n", cfline);
982 		return result;
983 	}
984 
985 	/* At this point, we know the configuration may result in an alert. */
986 	if (anymatch) (*anymatch)++;
987 
988 	/*
989 	 * Duration checks should be done on real paging messages only.
990 	 * Not on recovery- or notify-messages.
991 	 */
992 	if (alert->state == A_PAGING) {
993 		if (crit && crit->minduration && (duration < crit->minduration)) {
994 			if (nexttime) {
995 				time_t mynext = alert->eventstart + crit->minduration;
996 
997 				if ((*nexttime == -1) || (*nexttime > mynext)) *nexttime = mynext;
998 			}
999 			traceprintf("Failed '%s' (min. duration %d<%d)\n", cfline, duration, crit->minduration);
1000 			if (!printmode) return 0;
1001 		}
1002 	}
1003 
1004 	/*
1005 	 * Time restrictions apply to ALL messages.
1006 	 * Before 4.2, these were only applied to ALERT messages,
1007 	 * not RECOVERED and NOTIFY messages. This caused some
1008 	 * unfortunate alerts in the middle of the night because
1009 	 * some random system recovered ... not good. So apply
1010 	 * this check to all messages.
1011 	 */
1012 	if (crit && ((!hinfo) || ( (crit->timespec && !timematch(xmh_item(hinfo, XMH_HOLIDAYS), crit->timespec)) ||
1013 		      (crit->extimespec && timematch(xmh_item(hinfo, XMH_HOLIDAYS), crit->extimespec)) ) ) ) {
1014 		/* Try again in a minute */
1015 		if (nexttime) *nexttime = getcurrenttime(NULL) + 60;
1016 		traceprintf("Failed '%s' (time/extime criteria)\n", cfline);
1017 		if (!printmode) return 0;
1018 	}
1019 
1020 	/* Check color. For RECOVERED messages, this holds the color of the alert, not the recovery state */
1021 	if (crit && crit->colors) {
1022 		result = (((1 << alert->color) & crit->colors) != 0);
1023 		if (printmode) return 1;
1024 	}
1025 	else {
1026 		result = (((1 << alert->color) & defaultcolors) != 0);
1027 		if (printmode) return 1;
1028 	}
1029 	if (!result) {
1030 		traceprintf("Failed '%s' (color)\n", cfline);
1031 		return result;
1032 	}
1033 
1034 	if ((alert->state == A_RECOVERED) || (alert->state == A_DISABLED)) {
1035 		/*
1036 		 * Don't do the check until we are checking individual recipients (rulecrit is set).
1037 		 * You don't need to have RECOVERED on the top-level rule, it's enough if a recipient
1038 		 * has it set. However, we do want to allow there to be a default defined in the
1039 		 * rule; but it doesn't take effect until we start checking the recipients.
1040 		 */
1041 		if (rulecrit) {
1042 			int n = (crit ? crit->sendrecovered : -1);
1043 			traceprintf("Checking recovered setting %d (rule:%d)\n", n, rulecrit->sendrecovered);
1044 			if (crit && (crit->sendrecovered == SR_NOTWANTED)) result = 0;		/* Explicit NORECOVERED */
1045 			else if (crit && (crit->sendrecovered == SR_WANTED)) result = 1;	/* Explicit RECOVERED */
1046 			else result = (rulecrit->sendrecovered == SR_WANTED);	/* Not set, but rule has RECOVERED */
1047 		}
1048 		else {
1049 			result = 1;
1050 		}
1051 
1052 		if (printmode) return result;
1053 	}
1054 
1055 	if (result) {
1056 		traceprintf("*** Match with '%s' ***\n", cfline);
1057 	}
1058 
1059 	return result;
1060 }
1061 
next_recipient(activealerts_t * alert,int * first,int * anymatch,time_t * nexttime)1062 recip_t *next_recipient(activealerts_t *alert, int *first, int *anymatch, time_t *nexttime)
1063 {
1064 	static rule_t *rulewalk = NULL;
1065 	static recip_t *recipwalk = NULL;
1066 
1067 	if (anymatch) *anymatch = 0;
1068 
1069 	do {
1070 		if (*first) {
1071 			/* Start at beginning of rules-list and find the first matching rule. */
1072 			*first = 0;
1073 			rulewalk = rulehead;
1074 			while (rulewalk && !criteriamatch(alert, rulewalk->criteria, NULL, NULL, NULL)) rulewalk = rulewalk->next;
1075 			if (rulewalk) {
1076 				/* Point recipwalk at the list of possible candidates */
1077 				dbgprintf("Found a first matching rule\n");
1078 				recipwalk = rulewalk->recipients;
1079 			}
1080 			else {
1081 				/* No matching rules */
1082 				dbgprintf("Found no first matching rule\n");
1083 				recipwalk = NULL;
1084 			}
1085 		}
1086 		else {
1087 			if (!recipwalk) {
1088 				/* Should not happen! */
1089 			}
1090 			else if (recipwalk->next) {
1091 				/* Check the next recipient in the current rule */
1092 				recipwalk = recipwalk->next;
1093 			}
1094 			else {
1095 				/* End of recipients in current rule. Go to the next matching rule */
1096 				do {
1097 					rulewalk = rulewalk->next;
1098 				} while (rulewalk && !criteriamatch(alert, rulewalk->criteria, NULL, NULL, NULL));
1099 
1100 				if (rulewalk) {
1101 					/* Point recipwalk at the list of possible candidates */
1102 					dbgprintf("Found a secondary matching rule\n");
1103 					recipwalk = rulewalk->recipients;
1104 				}
1105 				else {
1106 					/* No matching rules */
1107 					dbgprintf("No more secondary matching rule\n");
1108 					recipwalk = NULL;
1109 				}
1110 			}
1111 		}
1112 	} while (rulewalk && recipwalk && !criteriamatch(alert, recipwalk->criteria, rulewalk->criteria, anymatch, nexttime));
1113 
1114 	stoprulefound = (recipwalk && recipwalk->stoprule);
1115 
1116 	printrule = rulewalk;
1117 	return recipwalk;
1118 }
1119 
1120 
have_recipient(activealerts_t * alert,int * anymatch)1121 int have_recipient(activealerts_t *alert, int *anymatch)
1122 {
1123 	int first = 1;
1124 
1125 	return (next_recipient(alert, &first, anymatch, NULL) != NULL);
1126 }
1127 
1128 
alert_printmode(int on)1129 void alert_printmode(int on)
1130 {
1131 	printmode = on;
1132 }
1133 
print_alert_recipients(activealerts_t * alert,strbuffer_t * buf)1134 void print_alert_recipients(activealerts_t *alert, strbuffer_t *buf)
1135 {
1136 	char *normalfont = "COLOR=\"#FFFFCC\" FACE=\"Tahoma, Arial, Helvetica\"";
1137 	char *stopfont = "COLOR=\"#33ebf4\" FACE=\"Tahoma, Arial, Helvetica\"";
1138 
1139 	int first = 1;
1140 	recip_t *recip;
1141 	char l[4096];
1142 	int count = 0;
1143 	char *p, *fontspec;
1144 	char codes[25];
1145 	unsigned int codes_bytesleft;
1146 
1147 	MEMDEFINE(l);
1148 	MEMDEFINE(codes);
1149 
1150 	if (printmode == 2) {
1151 		/* For print-out usage - e.g. confreport.cgi */
1152 		normalfont = "COLOR=\"#000000\" FACE=\"Tahoma, Arial, Helvetica\"";
1153 		stopfont = "COLOR=\"#FF0000\" FACE=\"Tahoma, Arial, Helvetica\"";
1154 	}
1155 
1156 	fontspec = normalfont;
1157 	stoprulefound = 0;
1158 	while ((recip = next_recipient(alert, &first, NULL, NULL)) != NULL) {
1159 		int mindur = 0, maxdur = INT_MAX;
1160 		char *timespec = NULL; char *extimespec = NULL;
1161 		int colors = defaultcolors;
1162 		int i, firstcolor = 1;
1163 		int recovered = 0, notice = 0;
1164 
1165 		count++;
1166 
1167 		addtobuffer(buf, "<tr>");
1168 		if (count == 1) {
1169 			snprintf(l, sizeof(l), "<td valign=top rowspan=###>%s</td>", alert->testname);
1170 			addtobuffer(buf, l);
1171 		}
1172 
1173 		/*
1174 		 * The min/max duration of an alert can be controlled by both the actual rule,
1175 		 * and by the recipient specification.
1176 		 * The rule must be fulfilled before the recipient even gets into play, so
1177 		 * if there is a min/max duration on the rule then this becomes the default
1178 		 * and recipient-specific settings can only increase the minduration/decrease
1179 		 * the maxduration.
1180 		 * On the other hand, if there is no rule-setting then the recipient-specific
1181 		 * settings determine everything.
1182 		 */
1183 		if (printrule->criteria && printrule->criteria->minduration) mindur = printrule->criteria->minduration;
1184 		if (recip->criteria && recip->criteria->minduration && (recip->criteria->minduration > mindur)) mindur = recip->criteria->minduration;
1185 		if (printrule->criteria && printrule->criteria->maxduration) maxdur = printrule->criteria->maxduration;
1186 		if (recip->criteria && recip->criteria->maxduration && (recip->criteria->maxduration < maxdur)) maxdur = recip->criteria->maxduration;
1187 		if (printrule->criteria && printrule->criteria->timespec) timespec = printrule->criteria->timespec;
1188 		if (printrule->criteria && printrule->criteria->extimespec) extimespec = printrule->criteria->extimespec;
1189 		if (recip->criteria && recip->criteria->timespec) {
1190 			if (timespec == NULL) timespec = recip->criteria->timespec;
1191 			else errprintf("Cannot handle nested timespecs yet\n");
1192 		}
1193 		if (recip->criteria && recip->criteria->extimespec) {
1194 			if (extimespec == NULL) extimespec = recip->criteria->extimespec;
1195 			else errprintf("Cannot handle nested extimespecs yet\n");
1196 		}
1197 
1198 		if (printrule->criteria && printrule->criteria->colors) colors = (colors & printrule->criteria->colors);
1199 		if (recip->criteria && recip->criteria->colors) colors = (colors & recip->criteria->colors);
1200 
1201 		/*
1202 		 * Recoveries are sent if
1203 		 * - there are no recipient criteria, and the rule says yes;
1204 		 * - the recipient criteria does not have a RECOVERED setting, and the rule says yes;
1205 		 * - the recipient criteria says yes.
1206 		 */
1207 		if ( (!recip->criteria && printrule->criteria && (printrule->criteria->sendrecovered == SR_WANTED)) ||
1208 		     (recip->criteria && (printrule->criteria->sendrecovered == SR_WANTED) && (recip->criteria->sendrecovered == SR_UNKNOWN)) ||
1209 		     (recip->criteria && (recip->criteria->sendrecovered == SR_WANTED)) ) recovered = 1;
1210 
1211 		if ( (!recip->criteria && printrule->criteria && (printrule->criteria->sendnotice == SR_WANTED)) ||
1212 		     (recip->criteria && (printrule->criteria->sendnotice == SR_WANTED) && (recip->criteria->sendnotice == SR_UNKNOWN)) ||
1213 		     (recip->criteria && (recip->criteria->sendnotice == SR_WANTED)) ) notice = 1;
1214 
1215 		*codes = '\0';
1216 		codes_bytesleft = sizeof(codes);
1217 		if (recip->method == M_IGNORE) {
1218 			recip->recipient = "-- ignored --";
1219 		}
1220 		if (recip->noalerts) { if (*codes) strncat(codes, ",A", codes_bytesleft); else strncat(codes, "-A", codes_bytesleft); codes_bytesleft -= 2; }
1221 		if (recovered && !recip->noalerts) { if (*codes) strncat(codes, ",R", codes_bytesleft); else strncat(codes, "R", codes_bytesleft); codes_bytesleft -= 2; }
1222 		if (notice) { if (*codes) strncat(codes, ",N", codes_bytesleft); else strncat(codes, "N", codes_bytesleft); codes_bytesleft -= 2; }
1223 		if (recip->stoprule) { if (*codes) strncat(codes, ",S", codes_bytesleft); else strncat(codes, "S", codes_bytesleft); codes_bytesleft -= 2; }
1224 		if (recip->unmatchedonly) { if (*codes) strncat(codes, ",U", codes_bytesleft); else strncat(codes, "U", codes_bytesleft); codes_bytesleft -= 2; }
1225 
1226 		if (strlen(codes) == 0)
1227 			snprintf(l, sizeof(l), "<td><font %s>%s</font></td>", fontspec, recip->recipient);
1228 		else
1229 			snprintf(l, sizeof(l), "<td><font %s>%s (%s)</font></td>", fontspec, recip->recipient, codes);
1230 		addtobuffer(buf, l);
1231 
1232 		snprintf(l, sizeof(l), "<td align=center>%s</td>", durationstring(mindur));
1233 		addtobuffer(buf, l);
1234 
1235 		/* maxdur=INT_MAX means "no max duration". So set it to 0 for durationstring() to do the right thing */
1236 		if (maxdur == INT_MAX) maxdur = 0;
1237 		snprintf(l, sizeof(l), "<td align=center>%s</td>", durationstring(maxdur));
1238 		addtobuffer(buf, l);
1239 
1240 		snprintf(l, sizeof(l), "<td align=center>%s</td>", durationstring(recip->interval));
1241 		addtobuffer(buf, l);
1242 
1243 		if (timespec && extimespec) snprintf(l, sizeof(l), "<td align=center>%s, except %s</td>", timespec, extimespec);
1244 		else if (timespec) snprintf(l, sizeof(l), "<td align=center>%s</td>", timespec);
1245 		else if (extimespec) snprintf(l, sizeof(l), "<td align=center>all, except %s</td>", extimespec);
1246 		else strncpy(l, "<td align=center>-</td>", sizeof(l));
1247 		addtobuffer(buf, l);
1248 
1249 		addtobuffer(buf, "<td>");
1250 		for (i = 0; (i < COL_COUNT); i++) {
1251 			if ((1 << i) & colors) {
1252 				snprintf(l, sizeof(l), "%s%s", (firstcolor ? "" : ","), colorname(i));
1253 				addtobuffer(buf, l);
1254 				firstcolor = 0;
1255 			}
1256 		}
1257 		addtobuffer(buf, "</td>");
1258 
1259 		if (stoprulefound) fontspec = stopfont;
1260 
1261 		addtobuffer(buf, "</tr>\n");
1262 	}
1263 
1264 	/* This is hackish - patch up the "rowspan" value, so it matches the number of recipient lines */
1265 	snprintf(l, sizeof(l), "%d   ", count);
1266 	p = strstr(STRBUF(buf), "rowspan=###");
1267 	if (p) { p += strlen("rowspan="); memcpy(p, l, 3); }
1268 
1269 	MEMUNDEFINE(l);
1270 	MEMUNDEFINE(codes);
1271 }
1272 
1273