1 /*----------------------------------------------------------------------------*/
2 /* Xymon RRD graph generator.                                                 */
3 /*                                                                            */
4 /* This is a CGI script for generating graphs from the data stored in the     */
5 /* RRD databases.                                                             */
6 /*                                                                            */
7 /* Copyright (C) 2004-2011 Henrik Storner <henrik@hswn.dk>                    */
8 /*                                                                            */
9 /* This program is released under the GNU General Public License (GPL),       */
10 /* version 2. See the file "COPYING" for details.                             */
11 /*                                                                            */
12 /*----------------------------------------------------------------------------*/
13 
14 static char rcsid[] = "$Id: showgraph.c 8076 2019-08-12 19:23:00Z jccleaver $";
15 
16 #include <limits.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <unistd.h>
20 #include <stdlib.h>
21 #include <ctype.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <dirent.h>
25 #include <errno.h>
26 #include <sys/socket.h>
27 #include <sys/un.h>
28 #include <fcntl.h>
29 
30 #include <pcre.h>
31 #include <rrd.h>
32 
33 #include "libxymon.h"
34 
35 #define HOUR_GRAPH  "e-48h"
36 #define DAY_GRAPH   "e-12d"
37 #define WEEK_GRAPH  "e-48d"
38 #define MONTH_GRAPH "e-576d"
39 
40 /* RRDtool 1.0.x handles graphs with no DS definitions just fine. 1.2.x does not. */
41 #ifdef RRDTOOL12
42 #ifndef HIDE_EMPTYGRAPH
43 #define HIDE_EMPTYGRAPH 1
44 #endif
45 #endif
46 
47 #ifdef HIDE_EMPTYGRAPH
48 unsigned char blankimg[] = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x12\x00\x00\x0b\x12\x01\xd2\xdd\x7e\xfc\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xd1\x01\x14\x12\x21\x14\x7e\x4a\x3a\xd2\x00\x00\x00\x0d\x49\x44\x41\x54\x78\xda\x63\x60\x60\x60\x60\x00\x00\x00\x05\x00\x01\x7a\xa8\x57\x50\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82";
49 #endif
50 
51 
52 char *hostname = NULL;
53 char **hostlist = NULL;
54 int hostlistsize = 0;
55 char *displayname = NULL;
56 char *service = NULL;
57 char *period = NULL;
58 time_t persecs = 0;
59 char *gtype = NULL;
60 char *glegend = NULL;
61 enum {ACT_MENU, ACT_SELZOOM, ACT_VIEW} action = ACT_VIEW;
62 time_t graphstart = 0;
63 time_t graphend = 0;
64 double upperlimit = 0.0;
65 int haveupperlimit = 0;
66 double lowerlimit = 0.0;
67 int havelowerlimit = 0;
68 int graphwidth = 0;
69 int graphheight = 0;
70 int ignorestalerrds = 0;
71 int bgcolor = COL_GREEN;
72 
73 int coloridx = 0;
74 char *colorlist[] = {
75 	"0000FF", "FF0000", "00CC00", "FF00FF",
76 	"555555", "880000", "000088", "008800",
77 	"008888", "888888", "880088", "FFFF00",
78 	"888800", "00FFFF", "00FF00", "AA8800",
79 	"AAAAAA", "DD8833", "DDCC33", "8888FF",
80 	"5555AA", "B428D3", "FF5555", "DDDDDD",
81 	"AAFFAA", "AAFFFF", "FFAAFF", "FFAA55",
82 	"55AAFF", "AA55FF",
83 	NULL
84 };
85 
86 typedef struct gdef_t {
87 	char *name;
88 	char *fnpat;
89 	char *exfnpat;
90 	char *title;
91 	char *yaxis;
92 	char *graphopts;
93 	int  novzoom;
94 	char **defs;
95 	struct gdef_t *next;
96 } gdef_t;
97 gdef_t *gdefs = NULL;
98 
99 typedef struct rrddb_t {
100 	char *key;
101 	char *rrdfn;
102 	char *rrdparam;
103 } rrddb_t;
104 
105 rrddb_t *rrddbs = NULL;
106 int rrddbcount = 0;
107 int rrddbsize = 0;
108 int rrdidx = 0;
109 int paramlen = 0;
110 int firstidx = -1;
111 int idxcount = -1;
112 int lastidx = 0;
113 
errormsg(char * msg)114 void errormsg(char *msg)
115 {
116 	printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
117 	printf("<html><head><title>Invalid request</title></head>\n");
118 	printf("<body>%s</body></html>\n", msg);
119 	exit(1);
120 }
121 
request_cacheflush(char * hostname)122 void request_cacheflush(char *hostname)
123 {
124 	/* Build a cache-flush request, and send it to all of the $XYMONTMP/rrdctl.* sockets */
125 	SBUF_DEFINE(req);
126 	char *bufp;
127 	int bytesleft;
128 	DIR *dir;
129 	struct dirent *d;
130 	int ctlsocket = -1;
131 
132 	ctlsocket = socket(AF_UNIX, SOCK_DGRAM, 0);
133 	if (ctlsocket == -1) {
134 		errprintf("Cannot get socket: %s\n", strerror(errno));
135 		return;
136 	}
137 	fcntl(ctlsocket, F_SETFL, O_NONBLOCK);
138 
139 	dir = opendir(xgetenv("XYMONTMP"));
140 	if (!dir) {
141 		errprintf("Cannot access $XYMONTMP directory: %s\n", strerror(errno));
142 		return;
143 	}
144 
145 	SBUF_MALLOC(req, strlen(hostname)+3);
146 	snprintf(req, req_buflen, "/%s/", hostname);
147 
148 	while ((d = readdir(dir)) != NULL) {
149 		if (strncmp(d->d_name, "rrdctl.", 7) == 0) {
150 			struct sockaddr_un myaddr;
151 			socklen_t myaddrsz = 0;
152 			int n, sendfailed = 0;
153 			SBUF_DEFINE(fnam);
154 
155 			memset(&myaddr, 0, sizeof(myaddr));
156 			myaddr.sun_family = AF_UNIX;
157 
158 			SBUF_MALLOC(fnam, strlen(xgetenv("XYMONTMP"))+ strlen(d->d_name) + 2);
159 			snprintf(fnam, fnam_buflen, "%s/%s", xgetenv("XYMONTMP"), d->d_name);
160 			if (strlen(fnam) > sizeof(myaddr.sun_path)) {
161 				errprintf("rrdctl files located in XYMONTMP with too long pathname - max %d characters\n", sizeof(myaddr.sun_path));
162 				return;
163 			}
164 			strncpy(myaddr.sun_path, fnam, sizeof(myaddr.sun_path));
165 			xfree(fnam);
166 
167 			myaddrsz = sizeof(myaddr);
168 			bufp = req; bytesleft = strlen(req);
169 			do {
170 				n = sendto(ctlsocket, bufp, bytesleft, 0, (struct sockaddr *)&myaddr, myaddrsz);
171 				if (n == -1) {
172 					if (errno == EDESTADDRREQ) {
173 						/* Probably a left-over rrdctl file, ignore it */
174 					}
175 					else if (errno == EAGAIN) {
176 						/* Harmless */
177 					}
178 					else {
179 						errprintf("Sendto failed: %s\n", strerror(errno));
180 					}
181 
182 					sendfailed = 1;
183 				}
184 				else {
185 					bytesleft -= n;
186 					bufp += n;
187 				}
188 			} while ((!sendfailed) && (bytesleft > 0));
189 		}
190 	}
191 	closedir(dir);
192 	xfree(req);
193 
194 	/*
195 	 * Sleep 0.3 secs to allow the cache flush to happen.
196 	 * Note: It isn't guaranteed to happen in this time, but
197 	 * there's a good chance that it will.
198 	 */
199 	usleep(300000);
200 }
201 
202 
parse_query(void)203 void parse_query(void)
204 {
205 	cgidata_t *cgidata = NULL, *cwalk;
206 	char *stp = NULL;
207 
208 	cgidata = cgi_request();
209 
210 	cwalk = cgidata;
211 	while (cwalk) {
212 		if (strcmp(cwalk->name, "host") == 0) {
213 			char *hnames = strdup(cwalk->value);
214 
215 			hostname = strtok_r(cwalk->value, ",", &stp);
216 			while (hostname) {
217 				if (hostlist == NULL) {
218 					hostlistsize = 1;
219 					hostlist = (char **)malloc(sizeof(char *));
220 					hostlist[0] = strdup(hostname);
221 				}
222 				else {
223 					hostlistsize++;
224 					hostlist = (char **)realloc(hostlist, (hostlistsize * sizeof(char *)));
225 					hostlist[hostlistsize-1] = strdup(hostname);
226 				}
227 
228 				hostname = strtok_r(NULL, ",", &stp);
229 			}
230 
231 			xfree(hnames);
232 			if (hostlist) hostname = hostlist[0];
233 		}
234 		else if (strcmp(cwalk->name, "service") == 0) {
235 			service = strdup(cwalk->value);
236 		}
237 		else if (strcmp(cwalk->name, "disp") == 0) {
238 			displayname = strdup(cwalk->value);
239 		}
240 		else if (strcmp(cwalk->name, "graph") == 0) {
241 			if (strcmp(cwalk->value, "hourly") == 0) {
242 				period = HOUR_GRAPH;
243 				persecs = 48*60*60;
244 				gtype = strdup(cwalk->value);
245 				glegend = "Last 48 Hours";
246 			}
247 			else if (strcmp(cwalk->value, "daily") == 0) {
248 				period = DAY_GRAPH;
249 				persecs = 12*24*60*60;
250 				gtype = strdup(cwalk->value);
251 				glegend = "Last 12 Days";
252 			}
253 			else if (strcmp(cwalk->value, "weekly") == 0) {
254 				period = WEEK_GRAPH;
255 				persecs = 48*24*60*60;
256 				gtype = strdup(cwalk->value);
257 				glegend = "Last 48 Days";
258 			}
259 			else if (strcmp(cwalk->value, "monthly") == 0) {
260 				period = MONTH_GRAPH;
261 				persecs = 576*24*60*60;
262 				gtype = strdup(cwalk->value);
263 				glegend = "Last 576 Days";
264 			}
265 			else if (strcmp(cwalk->value, "custom") == 0) {
266 				period = NULL;
267 				persecs = 0;
268 				gtype = strdup(cwalk->value);
269 				glegend = "";
270 			}
271 		}
272 		else if (strcmp(cwalk->name, "first") == 0) {
273 			firstidx = atoi(cwalk->value) - 1;
274 		}
275 		else if (strcmp(cwalk->name, "count") == 0) {
276 			idxcount = atoi(cwalk->value);
277 			lastidx = firstidx + idxcount - 1;
278 		}
279 		else if (strcmp(cwalk->name, "action") == 0) {
280 			if (cwalk->value) {
281 				if      (strcmp(cwalk->value, "menu") == 0) action = ACT_MENU;
282 				else if (strcmp(cwalk->value, "selzoom") == 0) action = ACT_SELZOOM;
283 				else if (strcmp(cwalk->value, "view") == 0) action = ACT_VIEW;
284 			}
285 		}
286 		else if (strcmp(cwalk->name, "graph_start") == 0) {
287 			if (cwalk->value) graphstart = atoi(cwalk->value);
288 		}
289 		else if (strcmp(cwalk->name, "graph_end") == 0) {
290 			if (cwalk->value) graphend = atoi(cwalk->value);
291 		}
292 		else if (strcmp(cwalk->name, "upper") == 0) {
293 			if (cwalk->value) { upperlimit = atof(cwalk->value); haveupperlimit = 1; }
294 		}
295 		else if (strcmp(cwalk->name, "lower") == 0) {
296 			if (cwalk->value) { lowerlimit = atof(cwalk->value); havelowerlimit = 1; }
297 		}
298 		else if (strcmp(cwalk->name, "graph_width") == 0) {
299 			if (cwalk->value) graphwidth = atoi(cwalk->value);
300 		}
301 		else if (strcmp(cwalk->name, "graph_height") == 0) {
302 			if (cwalk->value) graphheight = atoi(cwalk->value);
303 		}
304 		else if (strcmp(cwalk->name, "nostale") == 0) {
305 			ignorestalerrds = 1;
306 		}
307 		else if (strcmp(cwalk->name, "color") == 0) {
308 			int color = parse_color(cwalk->value);
309 			if (color != -1) bgcolor = color;
310 		}
311 
312 		cwalk = cwalk->next;
313 	}
314 
315 	if (hostlistsize == 1) {
316 		xfree(hostlist); hostlist = NULL;
317 	}
318 	else {
319 		displayname = hostname = strdup("");
320 	}
321 
322 	if ((hostname == NULL) || (service == NULL)) errormsg("Invalid request - no host or service");
323 	if (displayname == NULL) displayname = hostname;
324 	if (graphstart && graphend) {
325 		char t1[15], t2[15];
326 
327 		persecs = (graphend - graphstart);
328 
329 		strftime(t1, sizeof(t1), "%d/%b/%Y", localtime(&graphstart));
330 		strftime(t2, sizeof(t2), "%d/%b/%Y", localtime(&graphend));
331 		glegend = (char *)malloc(40);
332 		snprintf(glegend, 40, "%s - %s", t1, t2);
333 	}
334 }
335 
336 
load_gdefs(char * fn)337 void load_gdefs(char *fn)
338 {
339 	FILE *fd;
340 	strbuffer_t *inbuf;
341 	char *p;
342 	gdef_t *newitem = NULL;
343 	char **alldefs = NULL;
344 	int alldefcount = 0, alldefidx = 0;
345 
346 	inbuf = newstrbuffer(0);
347 	fd = stackfopen(fn, "r", NULL);
348 	if (fd == NULL) errormsg("Cannot load graph definitions");
349 	while (stackfgets(inbuf, NULL)) {
350 		p = strchr(STRBUF(inbuf), '\n'); if (p) *p = '\0';
351 		p = STRBUF(inbuf); p += strspn(p, " \t");
352 		if ((strlen(p) == 0) || (*p == '#')) continue;
353 
354 		if (*p == '[') {
355 			char *delim;
356 
357 			if (newitem) {
358 				/* Save the current one, and start on the next item */
359 				alldefs[alldefidx] = NULL;
360 				newitem->defs = alldefs;
361 				newitem->next = gdefs;
362 				gdefs = newitem;
363 			}
364 			newitem = calloc(1, sizeof(gdef_t));
365 			delim = strchr(p, ']'); if (delim) *delim = '\0';
366 			newitem->name = strdup(p+1);
367 			alldefcount = 10;
368 			alldefs = (char **)malloc((alldefcount+1) * sizeof(char *));
369 			alldefidx = 0;
370 		}
371 		else if (strncasecmp(p, "FNPATTERN", 9) == 0) {
372 			p += 9; p += strspn(p, " \t");
373 			newitem->fnpat = strdup(p);
374 		}
375 		else if (strncasecmp(p, "EXFNPATTERN", 11) == 0) {
376 			p += 11; p += strspn(p, " \t");
377 			newitem->exfnpat = strdup(p);
378 		}
379 		else if (strncasecmp(p, "TITLE", 5) == 0) {
380 			p += 5; p += strspn(p, " \t");
381 			newitem->title = strdup(p);
382 		}
383 		else if (strncasecmp(p, "YAXIS", 5) == 0) {
384 			p += 5; p += strspn(p, " \t");
385 			newitem->yaxis = strdup(p);
386 		}
387 		else if (strncasecmp(p, "NOVZOOM", 7) == 0) {
388 			newitem->novzoom = 1;
389 		}
390 		else if (strncasecmp(p, "GRAPHOPTIONS", 12) == 0) {
391 			p += 12; p += strspn(p, " \t");
392 			newitem->graphopts = strdup(p);
393 		}
394 		else if (haveupperlimit && (strncmp(p, "-u ", 3) == 0)) {
395 			continue;
396 		}
397 		else if (haveupperlimit && (strncmp(p, "-upper ", 7) == 0)) {
398 			continue;
399 		}
400 		else if (havelowerlimit && (strncmp(p, "-l ", 3) == 0)) {
401 			continue;
402 		}
403 		else if (havelowerlimit && (strncmp(p, "-lower ", 7) == 0)) {
404 			continue;
405 		}
406 		else {
407 			if (alldefidx == alldefcount) {
408 				/* Must expand alldefs */
409 				alldefcount += 5;
410 				alldefs = (char **)realloc(alldefs, (alldefcount+1) * sizeof(char *));
411 			}
412 			alldefs[alldefidx++] = strdup(p);
413 		}
414 	}
415 
416 	/* Pick up the last item */
417 	if (newitem) {
418 		/* Save the current one, and start on the next item */
419 		alldefs[alldefidx] = NULL;
420 		newitem->defs = alldefs;
421 		newitem->next = gdefs;
422 		gdefs = newitem;
423 	}
424 
425 	stackfclose(fd);
426 	freestrbuffer(inbuf);
427 }
428 
lookup_meta(char * keybuf,char * rrdfn)429 char *lookup_meta(char *keybuf, char *rrdfn)
430 {
431 	FILE *fd;
432 	SBUF_DEFINE(metafn);
433 	char *p;
434 	int servicelen = strlen(service);
435 	int keylen = strlen(keybuf);
436 	int found;
437 	static char buf[1024]; /* Must be static since it is returned to caller */
438 
439 	SBUF_MALLOC(metafn, PATH_MAX);
440 
441 	p = strrchr(rrdfn, '/');
442 	if (!p) {
443 		strncpy(metafn, "rrd.meta", metafn_buflen);
444 	}
445 	else {
446 		metafn = (char *)malloc(strlen(rrdfn) + 10);
447 		*p = '\0';
448 		snprintf(metafn, metafn_buflen, "%s/rrd.meta", rrdfn);
449 		*p = '/';
450 	}
451 	fd = fopen(metafn, "r");
452 	xfree(metafn);
453 
454 	if (!fd) return NULL;
455 
456 	/* Find the first line that has our key and then whitespace */
457 	found = 0;
458 	while (!found && fgets(buf, sizeof(buf), fd)) {
459 		found = ( (strncmp(buf, service, servicelen) == 0) &&
460 			  (*(buf+servicelen) == ':') &&
461 			  (strncmp(buf+servicelen+1, keybuf, keylen) == 0) &&
462 			  isspace(*(buf+servicelen+1+keylen)) );
463 	}
464 	fclose(fd);
465 
466 	if (found) {
467 		char *eoln, *val;
468 
469 		val = buf + servicelen + 1 + keylen;
470 		val += strspn(val, " \t");
471 
472 		eoln = strchr(val, '\n');
473 		if (eoln) *eoln = '\0';
474 
475 		if (strlen(val) > 0) return val;
476 	}
477 
478 	return NULL;
479 }
480 
colon_escape(char * buf)481 char *colon_escape(char *buf)
482 {
483 	/* Change all colons to "\:" */
484 	static char *result = NULL;
485 	int count = 0;
486 	char *p, *inp, *outp;
487 
488 	p = buf; while ((p = strchr(p, ':')) != NULL) { count++; p++; }
489 	if (count == 0) return buf;
490 
491 	if (result) xfree(result);
492 	result = (char *) malloc(strlen(buf) + count + 1); /* Add one backslash per colon */
493 	*result = '\0';
494 
495 	inp = buf; outp = result;
496 	while (*inp) {
497 		p = strchr(inp, ':');
498 		if (p == NULL) {
499 			strcat(outp, inp);
500 			inp += strlen(inp);
501 			outp += strlen(outp);
502 		}
503 		else {
504 			*p = '\0';
505 			strcat(outp, inp); strcat(outp, "\\:");
506 			*p = ':';
507 			inp = p+1;
508 			outp = outp + strlen(outp);
509 		}
510 	}
511 
512 	*outp = '\0';
513 	return result;
514 }
515 
expand_tokens(char * tpl)516 char *expand_tokens(char *tpl)
517 {
518 	static strbuffer_t *result = NULL;
519 	char *inp, *p;
520 
521 	if (strchr(tpl, '@') == NULL) return tpl;
522 
523 	if (!result) result = newstrbuffer(2048); else clearstrbuffer(result);
524 
525 	inp = tpl;
526 	while (*inp) {
527 		p = strchr(inp, '@');
528 		if (p == NULL) {
529 			addtobuffer(result, inp);
530 			inp += strlen(inp);
531 			continue;
532 		}
533 
534 		*p = '\0';
535 		if (strlen(inp)) {
536 			addtobuffer(result, inp);
537 			inp = p;
538 		}
539 		*p = '@';
540 
541 		if (strncmp(inp, "@RRDFN@", 7) == 0) {
542 			addtobuffer(result, colon_escape(rrddbs[rrdidx].rrdfn));
543 			inp += 7;
544 		}
545 		else if (strncmp(inp, "@RRDPARAM@", 10) == 0) {
546 			/*
547 			 * We do a colon-escape first, then change all commas to slashes as
548 			 * this is a common mangling used by multiple backends (disk, http, iostat...)
549 			 */
550 			if (rrddbs[rrdidx].rrdparam) {
551 				char *val, *p;
552 				int vallen;
553 				SBUF_DEFINE(resultstr);
554 
555 				val = colon_escape(rrddbs[rrdidx].rrdparam);
556 				p = val; while ((p = strchr(p, ',')) != NULL) *p = '/';
557 
558 				/* rrdparam strings may be very long. */
559 				if (strlen(val) > 100) *(val+100) = '\0';
560 
561 				/*
562 				 * "paramlen" holds the longest string of the any of the matching files' rrdparam.
563 				 * However, because this goes through colon_escape(), the actual string length
564 				 * passed to librrd functions may be longer (since ":" must be escaped as "\:").
565 				 */
566 				vallen = strlen(val);
567 				if (vallen < paramlen) vallen = paramlen;
568 
569 				SBUF_MALLOC(resultstr, vallen + 1);
570 				snprintf(resultstr, resultstr_buflen, "%-*s", paramlen, val);
571 				addtobuffer(result, resultstr);
572 				xfree(resultstr);
573 			}
574 			inp += 10;
575 		}
576 		else if (strncmp(inp, "@RRDMETA@", 9) == 0) {
577 			/*
578 			 * We do a colon-escape first, then change all commas to slashes as
579 			 * this is a common mangling used by multiple backends (disk, http, iostat...)
580 			 */
581 			if (rrddbs[rrdidx].rrdparam) {
582 				char *val, *p, *metaval;
583 
584 				val = colon_escape(rrddbs[rrdidx].rrdparam);
585 				p = val; while ((p = strchr(p, ',')) != NULL) *p = '/';
586 
587 				metaval = lookup_meta(val, rrddbs[rrdidx].rrdfn);
588 				if (metaval) addtobuffer(result, metaval);
589 			}
590 			inp += 9;
591 		}
592 		else if (strncmp(inp, "@RRDIDX@", 8) == 0) {
593 			char numstr[10];
594 
595 			snprintf(numstr, sizeof(numstr), "%d", rrdidx);
596 			addtobuffer(result, numstr);
597 			inp += 8;
598 		}
599 		else if (strncmp(inp, "@STACKIT@", 9) == 0) {
600 			/* Contributed by Gildas Le Nadan <gn1@sanger.ac.uk> */
601 
602 			/* the STACK behavior changed between rrdtool 1.0.x
603 			 * and 1.2.x, hence the ifdef:
604 			 * - in 1.0.x, you replace the graph type (AREA|LINE)
605 			 *  for the graph you want to stack with the  STACK
606 			 *  keyword
607 			 * - in 1.2.x, you add the STACK keyword at the end
608 			 *  of the definition
609 			 *
610 			 * Please note that in both cases the first entry
611 			 * mustn't contain the keyword STACK at all, so
612 			 * we need a different treatment for the first rrdidx
613 			 *
614 			 * examples of graphs.cfg entries:
615 			 *
616 			 * - rrdtool 1.0.x
617 			 * @STACKIT@:la@RRDIDX@#@COLOR@:@RRDPARAM@
618 			 *
619 			 * - rrdtool 1.2.x
620 			 * AREA::la@RRDIDX@#@COLOR@:@RRDPARAM@:@STACKIT@
621 			 */
622 			char numstr[10];
623 
624 			if (rrdidx == 0) {
625 #ifdef RRDTOOL12
626 				strncpy(numstr, "", sizeof(numstr));
627 #else
628 				snprintf(numstr, sizeof(numstr), "AREA");
629 #endif
630 			}
631 			else {
632 				snprintf(numstr, sizeof(numstr), "STACK");
633 			}
634 			addtobuffer(result, numstr);
635 			inp += 9;
636 		}
637 		else if (strncmp(inp, "@SERVICE@", 9) == 0) {
638 			addtobuffer(result, service);
639 			inp += 9;
640 		}
641 		else if (strncmp(inp, "@COLOR@", 7) == 0) {
642 			addtobuffer(result, colorlist[coloridx]);
643 			inp += 7;
644 			coloridx++; if (colorlist[coloridx] == NULL) coloridx = 0;
645 		}
646 		else {
647 			addtobuffer(result, "@");
648 			inp += 1;
649 		}
650 	}
651 
652 	return STRBUF(result);
653 }
654 
rrd_name_compare(const void * v1,const void * v2)655 int rrd_name_compare(const void *v1, const void *v2)
656 {
657 	rrddb_t *r1 = (rrddb_t *)v1;
658 	rrddb_t *r2 = (rrddb_t *)v2;
659 	char *endptr;
660 	long numkey1, numkey2;
661 	int key1isnumber, key2isnumber;
662 
663 	/* See if the keys are all numeric; if yes, then do a numeric sort */
664 	numkey1 = strtol(r1->key, &endptr, 10); key1isnumber = (*endptr == '\0');
665 	numkey2 = strtol(r2->key, &endptr, 10); key2isnumber = (*endptr == '\0');
666 
667 	if (key1isnumber && key2isnumber) {
668 		if (numkey1 < numkey2) return -1;
669 		else if (numkey1 > numkey2) return 1;
670 		else return 0;
671 	}
672 
673 	return strcmp(r1->key, r2->key);
674 }
675 
graph_link(FILE * output,char * uri,char * grtype,time_t seconds)676 void graph_link(FILE *output, char *uri, char *grtype, time_t seconds)
677 {
678 	time_t gstart, gend;
679 	char *grtype_s;
680 
681 	fprintf(output, "<tr>\n");
682 	grtype_s = htmlquoted(grtype);
683 
684 	switch (action) {
685 	  case ACT_MENU:
686 		fprintf(output, "  <td align=\"left\"><img src=\"%s&amp;action=view&amp;graph=%s\" alt=\"%s graph\"></td>\n",
687 			uri, grtype_s, grtype_s);
688 		fprintf(output, "  <td align=\"left\" valign=\"top\"> <a href=\"%s&amp;graph=%s&amp;action=selzoom&amp;color=%s\"> <img src=\"%s/zoom.%s\" border=0 alt=\"Zoom graph\" style='padding: 3px'> </a> </td>\n",
689 			uri, grtype_s, colorname(bgcolor), xgetenv("XYMONSKIN"), xgetenv("IMAGEFILETYPE"));
690 		break;
691 
692 	  case ACT_SELZOOM:
693 		if (graphend == 0) gend = getcurrenttime(NULL); else gend = graphend;
694 		if (graphstart == 0) gstart = gend - persecs; else gstart = graphstart;
695 
696 		fprintf(output, "  <td align=\"left\"><img id='zoomGraphImage' src=\"%s&amp;graph=%s&amp;action=view&amp;graph_start=%u&amp;graph_end=%u&amp;graph_height=%d&amp;graph_width=%d&amp;",
697 			uri, grtype_s, (int) gstart, (int) gend, graphheight, graphwidth);
698 		if (haveupperlimit) fprintf(output, "&amp;upper=%f", upperlimit);
699 		if (havelowerlimit) fprintf(output, "&amp;lower=%f", lowerlimit);
700 		fprintf(output, "\" alt=\"Zoom source image\"></td>\n");
701 		break;
702 
703 	  case ACT_VIEW:
704 		break;
705 	}
706 
707 	fprintf(output, "</tr>\n");
708 }
709 
build_selfURI(void)710 char *build_selfURI(void)
711 {
712 	strbuffer_t *result = newstrbuffer(2048);
713 	char numbuf[40];
714 
715 	addtobuffer(result, xgetenv("SCRIPT_NAME"));
716 
717 	addtobuffer(result, "?host=");
718 	if (hostlist) {
719 		int i;
720 
721 		addtobuffer(result, urlencode(hostlist[0]));
722 		for (i = 1; (i < hostlistsize); i++) {
723 			addtobuffer(result, ",");
724 			addtobuffer(result, urlencode(hostlist[i]));
725 		}
726 	}
727 	else {
728 		addtobuffer(result, urlencode(hostname));
729 	}
730 
731 	addtobuffer(result, "&amp;color="); addtobuffer(result, colorname(bgcolor));
732 	if (service) {
733 		addtobuffer(result, "&amp;service=");
734 		addtobuffer(result, urlencode(service));
735 	}
736 	if (haveupperlimit) {
737 		snprintf(numbuf, sizeof(numbuf)-1, "%f", upperlimit);
738 		addtobuffer(result, "&amp;upper=");
739 		addtobuffer(result, urlencode(numbuf));
740 	}
741 	if (graphheight) {
742 		snprintf(numbuf, sizeof(numbuf)-1, "%d", graphheight);
743 		addtobuffer(result, "&amp;graph_height=");
744 		addtobuffer(result, urlencode(numbuf));
745 	}
746 	if (graphwidth) {
747 		snprintf(numbuf, sizeof(numbuf)-1, "%d", graphwidth);
748 		addtobuffer(result, "&amp;graph_width=");
749 		addtobuffer(result, urlencode(numbuf));
750 	}
751 
752 	if (displayname && (displayname != hostname)) {
753 		addtobuffer(result, "&amp;disp=");
754 		addtobuffer(result, urlencode(displayname));
755 	}
756 
757 	if (firstidx != -1) {
758 		snprintf(numbuf, sizeof(numbuf)-1, "&amp;first=%d", firstidx+1);
759 		addtobuffer(result, numbuf);
760 	}
761 	if (idxcount != -1) {
762 		snprintf(numbuf, sizeof(numbuf)-1, "&amp;count=%d", idxcount);
763 		addtobuffer(result, numbuf);
764 	}
765 	if (ignorestalerrds) addtobuffer(result, "&amp;nostale");
766 
767 	return STRBUF(result);
768 }
769 
770 
build_menu_page(char * selfURI,int backsecs)771 void build_menu_page(char *selfURI, int backsecs)
772 {
773 	/* This is special-handled, because we just want to generate an HTML link page */
774 	fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
775 	sethostenv(displayname, "", service, colorname(bgcolor), hostname);
776 	sethostenv_backsecs(backsecs);
777 
778 	headfoot(stdout, "graphs", "", "header", bgcolor);
779 
780 	fprintf(stdout, "<table align=\"center\" summary=\"Graphs\">\n");
781 
782 	graph_link(stdout, selfURI, "hourly",      48*60*60);
783 	graph_link(stdout, selfURI, "daily",    12*24*60*60);
784 	graph_link(stdout, selfURI, "weekly",   48*24*60*60);
785 	graph_link(stdout, selfURI, "monthly", 576*24*60*60);
786 
787 	fprintf(stdout, "</table>\n");
788 
789 	headfoot(stdout, "graphs", "", "footer", bgcolor);
790 }
791 
792 
generate_graph(char * gdeffn,char * rrddir,char * graphfn)793 void generate_graph(char *gdeffn, char *rrddir, char *graphfn)
794 {
795 	gdef_t *gdef = NULL, *gdefuser = NULL;
796 	int wantsingle = 0;
797 	DIR *dir;
798 	time_t now = getcurrenttime(NULL);
799 
800 	int argi, pcount;
801 
802 	/* Options for rrd_graph() */
803 	int  rrdargcount;
804 	char **rrdargs = NULL;	/* The full argv[] table of string pointers to arguments */
805 	char heightopt[30];	/* -h HEIGHT */
806 	char widthopt[30];	/* -w WIDTH */
807 	char upperopt[30];	/* -u MAX */
808 	char loweropt[30];	/* -l MIN */
809 	char startopt[30];	/* -s STARTTIME */
810 	char endopt[30];	/* -e ENDTIME */
811 	char graphtitle[1024];	/* --title TEXT */
812 	char timestamp[50];	/* COMMENT with timestamp graph was generated */
813 
814 	/* Return variables from rrd_graph() */
815 	int result;
816 	char **calcpr = NULL;
817 	int xsize, ysize;
818 	double ymin, ymax;
819 
820 	char *useroptval = NULL;
821 	char **useropts = NULL;
822 	int useroptcount = 0, useroptidx;
823 
824 	/* Find the graphs.cfg file and load it */
825 	if (gdeffn == NULL) {
826 		char fnam[PATH_MAX];
827 		snprintf(fnam, sizeof(fnam), "%s/etc/graphs.cfg", xgetenv("XYMONHOME"));
828 		gdeffn = strdup(fnam);
829 	}
830 	load_gdefs(gdeffn);
831 
832 
833 	/* Determine the real service name. It might be a multi-service graph */
834 	if (strchr(service, ':') || strchr(service, '.')) {
835 		/*
836 		 * service is "tcp:foo" - so use the "tcp" graph definition, but for a
837 		 * single service (as if service was set to just "foo").
838 		 */
839 		char *delim = service + strcspn(service, ":.");
840 		char *realservice;
841 
842 		*delim = '\0';
843 		realservice = strdup(delim+1);
844 
845 		/* The requested gdef only acts as a fall-back solution so don't set gdef here. */
846 		for (gdefuser = gdefs; (gdefuser && strcmp(service, gdefuser->name)); gdefuser = gdefuser->next) ;
847 		strcpy(service, realservice);
848 		wantsingle = 1;
849 
850 		xfree(realservice);
851 	}
852 
853 	/*
854 	 * Lookup which RRD file corresponds to the service-name, and how we handle this graph.
855 	 * We first lookup the service name in the graph definition list.
856 	 * If that fails, then we try mapping it via the servicename -> RRD map.
857 	 */
858 	for (gdef = gdefs; (gdef && strcmp(service, gdef->name)); gdef = gdef->next) ;
859 	if (gdef == NULL) {
860 		if (gdefuser) {
861 			gdef = gdefuser;
862 		}
863 		else {
864 			xymonrrd_t *ldef = find_xymon_rrd(service, NULL);
865 			if (ldef) {
866 				for (gdef = gdefs; (gdef && strcmp(ldef->xymonrrdname, gdef->name)); gdef = gdef->next) ;
867 				wantsingle = 1;
868 			}
869 		}
870 	}
871 	if (gdef == NULL) errormsg("Unknown graph requested");
872 	if (hostlist && (gdef->fnpat == NULL)) {
873 		SBUF_DEFINE(multiname);
874 
875 		SBUF_MALLOC(multiname, strlen(gdef->name) + 7);
876 		snprintf(multiname, multiname_buflen, "%s-multi", gdef->name);
877 		for (gdef = gdefs; (gdef && strcmp(multiname, gdef->name)); gdef = gdef->next) ;
878 		if (gdef == NULL) errormsg("Unknown multi-graph requested");
879 		xfree(multiname);
880 	}
881 
882 
883 	/*
884 	 * If we're here only to collect the min/max values for the graph but it doesn't
885 	 * allow vertical zoom, then there's no reason to waste anymore time.
886 	 */
887 	if ((action == ACT_SELZOOM) && gdef->novzoom) {
888 		haveupperlimit = havelowerlimit = 0;
889 		return;
890 	}
891 
892 	/* Determine the directory with the host RRD files, and go there. */
893 	if (rrddir == NULL) {
894 		char dnam[PATH_MAX];
895 
896 		if (hostlist) snprintf(dnam, sizeof(dnam), "%s", xgetenv("XYMONRRDS"));
897 		else snprintf(dnam, sizeof(dnam), "%s/%s", xgetenv("XYMONRRDS"), hostname);
898 
899 		rrddir = strdup(dnam);
900 	}
901 	if (chdir(rrddir)) errormsg("Cannot access RRD directory");
902 
903 	/* Request an RRD cache flush from the xymond_rrd update daemon */
904 	if (hostlist) {
905 		int i;
906 		for (i=0; (i < hostlistsize); i++) request_cacheflush(hostlist[i]);
907 	}
908 	else if (hostname) request_cacheflush(hostname);
909 
910 	/* What RRD files do we have matching this request? */
911 	if (hostlist || (gdef->fnpat == NULL)) {
912 		/*
913 		 * No pattern, just a single file. It doesnt matter if it exists, because
914 		 * these types of graphs usually have a hard-coded value for the RRD filename
915 		 * in the graph definition.
916 		 */
917 		rrddbcount = rrddbsize = (hostlist ? hostlistsize : 1);
918 		rrddbs = (rrddb_t *)malloc((rrddbsize + 1) * sizeof(rrddb_t));
919 
920 		if (!hostlist) {
921 			size_t buflen = strlen(gdef->name) + strlen(".rrd") + 1;
922 
923 			rrddbs[0].key = strdup(service);
924 			rrddbs[0].rrdfn = (char *)malloc(buflen);
925 			snprintf(rrddbs[0].rrdfn, buflen, "%s.rrd", gdef->name);
926 			rrddbs[0].rrdparam = NULL;
927 		}
928 		else {
929 			int i, maxlen;
930 			char paramfmt[20];
931 
932 			for (i=0, maxlen=0; (i < hostlistsize); i++) {
933 				if (strlen(hostlist[i]) > maxlen) maxlen = strlen(hostlist[i]);
934 			}
935 			snprintf(paramfmt, sizeof(paramfmt), "%%-%ds", maxlen+1);
936 
937 			for (i=0; (i < hostlistsize); i++) {
938 				size_t buflen;
939 
940 				rrddbs[i].key = strdup(service);
941 				buflen = strlen(hostlist[i]) + strlen(gdef->fnpat) + 2;
942 				rrddbs[i].rrdfn = (char *)malloc(buflen);
943 				snprintf(rrddbs[i].rrdfn, buflen, "%s/%s", hostlist[i], gdef->fnpat);
944 
945 				buflen = maxlen + 2;
946 				rrddbs[i].rrdparam = (char *)malloc(buflen);
947 				snprintf(rrddbs[i].rrdparam, buflen, paramfmt, hostlist[i]);
948 			}
949 		}
950 	}
951 	else {
952 		struct dirent *d;
953 		pcre *pat, *expat = NULL;
954 		const char *errmsg;
955 		int errofs, result;
956 		int ovector[30];
957 		struct stat st;
958 		time_t now = getcurrenttime(NULL);
959 
960 		/* Scan the directory to see what RRD files are there that match */
961 		dir = opendir("."); if (dir == NULL) errormsg("Unexpected error while accessing RRD directory");
962 
963 		/* Setup the pattern to match filenames against */
964 		pat = pcre_compile(gdef->fnpat, PCRE_CASELESS, &errmsg, &errofs, NULL);
965 		if (!pat) {
966 			char msg[8192];
967 
968 			snprintf(msg, sizeof(msg), "graphs.cfg error, PCRE pattern %s invalid: %s, offset %d\n",
969 				 htmlquoted(gdef->fnpat), errmsg, errofs);
970 			errormsg(msg);
971 		}
972 		if (gdef->exfnpat) {
973 			expat = pcre_compile(gdef->exfnpat, PCRE_CASELESS, &errmsg, &errofs, NULL);
974 			if (!expat) {
975 				char msg[8192];
976 
977 				snprintf(msg, sizeof(msg),
978 					 "graphs.cfg error, PCRE pattern %s invalid: %s, offset %d\n",
979 					 htmlquoted(gdef->exfnpat), errmsg, errofs);
980 				errormsg(msg);
981 			}
982 		}
983 
984 		/* Allocate an initial filename table */
985 		rrddbsize = 5;
986 		rrddbs = (rrddb_t *) malloc((rrddbsize+1) * sizeof(rrddb_t));
987 
988 		while ((d = readdir(dir)) != NULL) {
989 			char *ext;
990 			char param[PATH_MAX];
991 
992 			/* Ignore dot-files and files with names shorter than ".rrd" */
993 			if (*(d->d_name) == '.') continue;
994 			ext = d->d_name + strlen(d->d_name) - strlen(".rrd");
995 			if ((ext <= d->d_name) || (strcmp(ext, ".rrd") != 0)) continue;
996 
997 			/* First check the exclude pattern. */
998 			if (expat) {
999 				result = pcre_exec(expat, NULL, d->d_name, strlen(d->d_name), 0, 0,
1000 						   ovector, (sizeof(ovector)/sizeof(int)));
1001 				if (result >= 0) continue;
1002 			}
1003 
1004 			/* Then see if the include pattern matches. */
1005 			result = pcre_exec(pat, NULL, d->d_name, strlen(d->d_name), 0, 0,
1006 					   ovector, (sizeof(ovector)/sizeof(int)));
1007 			if (result < 0) continue;
1008 
1009 			if (wantsingle) {
1010 				/* "Single" graph, i.e. a graph for a service normally included in a bundle (tcp) */
1011 				if (strstr(d->d_name, service) == NULL) continue;
1012 			}
1013 
1014 			/*
1015 			 * Has it been updated recently (within the past 24 hours) ?
1016 			 * We don't want old graphs to mess up multi-displays.
1017 			 */
1018 			if (ignorestalerrds && (stat(d->d_name, &st) == 0) && ((now - st.st_mtime) > 86400)) {
1019 				continue;
1020 			}
1021 
1022 			/* We have a matching file! */
1023 			rrddbs[rrddbcount].rrdfn = strdup(d->d_name);
1024 			if (pcre_copy_substring(d->d_name, ovector, result, 1, param, sizeof(param)) > 0) {
1025 				/*
1026 				 * This is ugly, but I cannot find a pretty way of un-mangling
1027 				 * the disk- and http-data that has been molested by the back-end.
1028 				 */
1029 				if ((strcmp(param, ",root") == 0) &&
1030 				    ((strncmp(gdef->name, "disk", 4) == 0) || (strncmp(gdef->name, "inode", 5) == 0)) ) {
1031 					rrddbs[rrddbcount].rrdparam = strdup(",");
1032 				}
1033 				else if ((strcmp(gdef->name, "http") == 0) && (strncmp(param, "http", 4) != 0)) {
1034 					size_t buflen = strlen("http://")+strlen(param)+1;
1035 					rrddbs[rrddbcount].rrdparam = (char *)malloc(buflen);
1036 					snprintf(rrddbs[rrddbcount].rrdparam, buflen, "http://%s", param);
1037 				}
1038 				else {
1039 					rrddbs[rrddbcount].rrdparam = strdup(param);
1040 				}
1041 
1042 				if (strlen(rrddbs[rrddbcount].rrdparam) > paramlen) {
1043 					/*
1044 					 * "paramlen" holds the longest string of the any of the matching files' rrdparam.
1045 					 */
1046 					paramlen = strlen(rrddbs[rrddbcount].rrdparam);
1047 				}
1048 
1049 				rrddbs[rrddbcount].key = strdup(rrddbs[rrddbcount].rrdparam);
1050 			}
1051 			else {
1052 				rrddbs[rrddbcount].key = strdup(d->d_name);
1053 				rrddbs[rrddbcount].rrdparam = NULL;
1054 			}
1055 
1056 			rrddbcount++;
1057 			if (rrddbcount == rrddbsize) {
1058 				rrddbsize += 5;
1059 				rrddbs = (rrddb_t *)realloc(rrddbs, (rrddbsize+1) * sizeof(rrddb_t));
1060 			}
1061 		}
1062 		pcre_free(pat);
1063 		if (expat) pcre_free(expat);
1064 		closedir(dir);
1065 	}
1066 	rrddbs[rrddbcount].key = rrddbs[rrddbcount].rrdfn = rrddbs[rrddbcount].rrdparam = NULL;
1067 
1068 	/* Sort them so the display looks prettier */
1069 	qsort(&rrddbs[0], rrddbcount, sizeof(rrddb_t), rrd_name_compare);
1070 
1071 	/* Setup the title */
1072 	if (!gdef->title) gdef->title = strdup("");
1073 	if (strncmp(gdef->title, "exec:", 5) == 0) {
1074 		char *pcmd;
1075 		int i, pcmdlen = 7;
1076 		FILE *pfd;
1077 		char *p;
1078 		char *param_str = "%s \"%s\" %s \"%s\"";
1079 
1080 		pcmdlen += (strlen(gdef->title+5) + strlen(displayname) + strlen(service) + strlen(glegend));
1081 		for (i=0; (i<rrddbcount); i++) pcmdlen += (strlen(rrddbs[i].rrdfn) + 3);
1082 
1083 		p = pcmd = (char *)malloc(pcmdlen+1);
1084 		p += snprintf(p, pcmdlen+1, param_str, gdef->title+5, displayname, service, glegend);
1085 		for (i=0; (i<rrddbcount); i++) {
1086 			if ((firstidx == -1) || ((i >= firstidx) && (i <= lastidx))) {
1087 				p += snprintf(p, (pcmdlen - (p - pcmd) + 1), " \"%s\"", rrddbs[i].rrdfn);
1088 			}
1089 		}
1090 		pfd = popen(pcmd, "r");
1091 		if (pfd) {
1092 			if (fgets(graphtitle, sizeof(graphtitle), pfd) == NULL) *graphtitle = '\0';
1093 			pclose(pfd);
1094 		}
1095 
1096 		/* Drop any newline at end of the title */
1097 		p = strchr(graphtitle, '\n'); if (p) *p = '\0';
1098 	}
1099 	else {
1100 		snprintf(graphtitle, sizeof(graphtitle), "%s %s %s", displayname, gdef->title, glegend);
1101 	}
1102 
1103 	snprintf(heightopt, sizeof(heightopt), "-h%d", graphheight);
1104 	snprintf(widthopt, sizeof(widthopt), "-w%d", graphwidth);
1105 
1106 	/*
1107 	 * Grab user-provided additional rrd_graph options from RRDGRAPHOPTS
1108 	 */
1109 	useroptcount = 0;
1110 	useroptval = gdef->graphopts;
1111 	if (!useroptval) useroptval = getenv("RRDGRAPHOPTS");
1112 	if (useroptval) {
1113 		char *tok;
1114 
1115 		useropts = (char **)calloc(1, sizeof(char *));
1116 		useroptval = strdup(useroptval);
1117 		tok = strtok(useroptval, " ");
1118 		while (tok) {
1119 			useroptcount++;
1120 			useropts = (char **)realloc(useropts, (useroptcount+1)*sizeof(char *));
1121 			useropts[useroptcount-1] = tok;
1122 			useropts[useroptcount] = NULL;
1123 			tok = strtok(NULL, " ");
1124 		}
1125 	}
1126 
1127 	/*
1128 	 * Setup the arguments for calling rrd_graph.
1129 	 * There's up to 16 standard arguments, plus the
1130 	 * graph-specific ones (which may be repeated if
1131 	 * there are multiple RRD-files to handle).
1132 	 */
1133 	for (pcount = 0; (gdef->defs[pcount]); pcount++) ;
1134 	rrdargs = (char **) calloc(16 + pcount*rrddbcount + useroptcount + 1, sizeof(char *));
1135 
1136 
1137 	argi = 0;
1138 	rrdargs[argi++]  = "rrdgraph";
1139 	rrdargs[argi++]  = (action == ACT_VIEW) ? graphfn : "/dev/null";
1140 	rrdargs[argi++]  = "--title";
1141 	rrdargs[argi++]  = graphtitle;
1142 	rrdargs[argi++]  = widthopt;
1143 	rrdargs[argi++]  = heightopt;
1144 	rrdargs[argi++]  = "-v";
1145 	rrdargs[argi++]  = gdef->yaxis;
1146 	rrdargs[argi++]  = "-a";
1147 	rrdargs[argi++]  = "PNG";
1148 
1149 	if (haveupperlimit) {
1150 		snprintf(upperopt, sizeof(upperopt), "-u %f", upperlimit);
1151 		rrdargs[argi++] = upperopt;
1152 	}
1153 	if (havelowerlimit) {
1154 		snprintf(loweropt, sizeof(loweropt), "-l %f", lowerlimit);
1155 		rrdargs[argi++] = loweropt;
1156 	}
1157 	if (haveupperlimit || havelowerlimit) rrdargs[argi++] = "--rigid";
1158 
1159 	if (graphstart) snprintf(startopt, sizeof(startopt), "-s %u", (unsigned int) graphstart);
1160 	else snprintf(startopt, sizeof(startopt), "-s %s", period);
1161 	rrdargs[argi++] = startopt;
1162 
1163 	if (graphend) {
1164 		snprintf(endopt, sizeof(endopt), "-e %u", (unsigned int) graphend);
1165 		rrdargs[argi++] = endopt;
1166 	}
1167 
1168 	for (useroptidx=0; (useroptidx < useroptcount); useroptidx++) {
1169 		rrdargs[argi++] = useropts[useroptidx];
1170 	}
1171 
1172 	for (rrdidx=0; (rrdidx < rrddbcount); rrdidx++) {
1173 		if ((firstidx == -1) || ((rrdidx >= firstidx) && (rrdidx <= lastidx))) {
1174 			int i;
1175 			for (i=0; (gdef->defs[i]); i++) {
1176 				rrdargs[argi++] = strdup(expand_tokens(gdef->defs[i]));
1177 			}
1178 		}
1179 	}
1180 
1181 #ifdef RRDTOOL12
1182 	strftime(timestamp, sizeof(timestamp), "COMMENT:Updated\\: %d-%b-%Y %H\\:%M\\:%S", localtime(&now));
1183 #else
1184 	strftime(timestamp, sizeof(timestamp), "COMMENT:Updated: %d-%b-%Y %H:%M:%S", localtime(&now));
1185 #endif
1186 	rrdargs[argi++] = strdup(timestamp);
1187 
1188 
1189 	rrdargcount = argi; rrdargs[argi++] = NULL;
1190 
1191 
1192 	if (debug) { for (argi=0; (argi < rrdargcount); argi++) dbgprintf("%s\n", rrdargs[argi]); }
1193 
1194 	/* If sending to stdout, print the HTTP header first. */
1195 	if ((action == ACT_VIEW) && (strcmp(graphfn, "-") == 0)) {
1196 		time_t expiretime = now + 300;
1197 		char expirehdr[100];
1198 
1199 		printf("Content-type: image/png\n");
1200 		strftime(expirehdr, sizeof(expirehdr), "Expires: %a, %d %b %Y %H:%M:%S GMT", gmtime(&expiretime));
1201 		printf("%s\n", expirehdr);
1202 		printf("\n");
1203 
1204 #ifdef HIDE_EMPTYGRAPH
1205 		/* It works, but we still get the "zoom" magnifying glass which looks odd */
1206 		if (rrddbcount == 0) {
1207 			/* No graph */
1208 			fwrite(blankimg, 1, sizeof(blankimg), stdout);
1209 			return;
1210 		}
1211 #endif
1212 	}
1213 
1214 	/* All set - generate the graph */
1215 	rrd_clear_error();
1216 
1217 #ifdef RRDTOOL12
1218 	result = rrd_graph(rrdargcount, rrdargs, &calcpr, &xsize, &ysize, NULL, &ymin, &ymax);
1219 
1220 	/*
1221 	 * If we have neither the upper- nor lower-limits of the graph, AND we allow vertical
1222 	 * zooming of this graph, then save the upper/lower limit values and flag that we have
1223 	 * them. The values are then used for the zoom URL we construct later on.
1224 	 */
1225 	if (!haveupperlimit && !havelowerlimit) {
1226 		upperlimit = ymax; haveupperlimit = 1;
1227 		lowerlimit = ymin; havelowerlimit = 1;
1228 	}
1229 #else
1230 	result = rrd_graph(rrdargcount, rrdargs, &calcpr, &xsize, &ysize);
1231 #endif
1232 
1233 	/* Was it OK ? */
1234 	if (rrd_test_error() || (result != 0)) {
1235 		if (calcpr) {
1236 			int i;
1237 			for (i=0; (calcpr[i]); i++) xfree(calcpr[i]);
1238 			calcpr = NULL;
1239 		}
1240 
1241 		errormsg(rrd_get_error());
1242 	}
1243 
1244 	if (useroptval) xfree(useroptval);
1245 	if (useropts) xfree(useropts);
1246 }
1247 
generate_zoompage(char * selfURI)1248 void generate_zoompage(char *selfURI)
1249 {
1250 	fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
1251 	sethostenv(displayname, "", service, colorname(bgcolor), hostname);
1252 	headfoot(stdout, "graphs", "", "header", bgcolor);
1253 
1254 
1255 	fprintf(stdout, "  <div id='zoomBox' style='position:absolute; overflow:none; left:0px; top:0px; width:0px; height:0px; visibility:visible; background:red; filter:alpha(opacity=50); -moz-opacity:0.5; opacity:0.5; -khtml-opacity:0.5'></div>\n");
1256 	fprintf(stdout, "  <div id='zoomSensitiveZone' style='position:absolute; overflow:none; left:0px; top:0px; width:0px; height:0px; visibility:visible; cursor:crosshair; background:blue; filter:alpha(opacity=0); opacity:0; -moz-opacity:0; -khtml-opacity:0'></div>\n");
1257 
1258 	fprintf(stdout, "<table align=\"center\" summary=\"Graphs\">\n");
1259 	graph_link(stdout, selfURI, gtype, 0);
1260 	fprintf(stdout, "</table>\n");
1261 
1262 	{
1263 		char zoomjsfn[PATH_MAX];
1264 		struct stat st;
1265 
1266 		snprintf(zoomjsfn, sizeof(zoomjsfn), "%s/web/zoom.js", xgetenv("XYMONHOME"));
1267 		if (stat(zoomjsfn, &st) == 0) {
1268 			FILE *fd;
1269 			char *buf;
1270 			size_t n;
1271 			char *zoomrightoffsetmarker = "var cZoomBoxRightOffset = -";
1272 			char *zoomrightoffsetp;
1273 
1274 			fd = fopen(zoomjsfn, "r");
1275 			if (fd) {
1276 				buf = (char *)malloc(st.st_size+1);
1277 				n = fread(buf, 1, st.st_size, fd);
1278 				fclose(fd);
1279 
1280 #ifdef RRDTOOL12
1281 				zoomrightoffsetp = strstr(buf, zoomrightoffsetmarker);
1282 				if (zoomrightoffsetp) {
1283 					zoomrightoffsetp += strlen(zoomrightoffsetmarker);
1284 					memcpy(zoomrightoffsetp, "30", 2);
1285 				}
1286 #endif
1287 
1288 				fwrite(buf, 1, n, stdout);
1289 			}
1290 		}
1291 	}
1292 
1293 
1294 	headfoot(stdout, "graphs", "", "footer", bgcolor);
1295 }
1296 
1297 
main(int argc,char * argv[])1298 int main(int argc, char *argv[])
1299 {
1300 	/* Command line settings */
1301 	int argi;
1302 	char *envarea = NULL;
1303 	char *rrddir  = NULL;		/* RRD files top-level directory */
1304 	char *gdeffn  = NULL;		/* graphs.cfg file */
1305 	char *graphfn = "-";		/* Output filename, default is stdout */
1306 
1307 	char *selfURI;
1308 
1309 	/* Setup defaults */
1310 	graphwidth = atoi(xgetenv("RRDWIDTH"));
1311 	graphheight = atoi(xgetenv("RRDHEIGHT"));
1312 
1313 	/* See what we want to do - i.e. get hostname, service and graph-type */
1314 	parse_query();
1315 
1316 	/* Handle any command-line args */
1317 	for (argi=1; (argi < argc); argi++) {
1318 		if (strcmp(argv[argi], "--debug") == 0) {
1319 			debug = 1;
1320 		}
1321 		else if (argnmatch(argv[argi], "--env=")) {
1322 			char *p = strchr(argv[argi], '=');
1323 			loadenv(p+1, envarea);
1324 		}
1325 		else if (argnmatch(argv[argi], "--area=")) {
1326 			char *p = strchr(argv[argi], '=');
1327 			envarea = strdup(p+1);
1328 		}
1329 		else if (argnmatch(argv[argi], "--rrddir=")) {
1330 			char *p = strchr(argv[argi], '=');
1331 			rrddir = strdup(p+1);
1332 		}
1333 		else if (argnmatch(argv[argi], "--config=")) {
1334 			char *p = strchr(argv[argi], '=');
1335 			gdeffn = strdup(p+1);
1336 		}
1337 		else if (strcmp(argv[argi], "--save=") == 0) {
1338 			char *p = strchr(argv[argi], '=');
1339 			graphfn = strdup(p+1);
1340 		}
1341 	}
1342 
1343 	redirect_cgilog("showgraph");
1344 
1345 	selfURI = build_selfURI();
1346 
1347 	if (action == ACT_MENU) {
1348 		build_menu_page(selfURI, graphend-graphstart);
1349 		return 0;
1350 	}
1351 
1352 	if ((action == ACT_VIEW) || !(haveupperlimit && havelowerlimit)) {
1353 		generate_graph(gdeffn, rrddir, graphfn);
1354 	}
1355 
1356 	if (action == ACT_SELZOOM) {
1357 		generate_zoompage(selfURI);
1358 	}
1359 
1360 	return 0;
1361 }
1362 
1363