1 /*****************************************************************************
2 * RRDtool 1.4.3 Copyright by Tobi Oetiker, 1997-2010
3 *****************************************************************************
4 * rrd_cgi.c RRD Web Page Generator
5 *****************************************************************************/
7 #include "rrd_tool.h"
8 #ifdef HAVE_STDLIB_H
9 #include <stdlib.h>
10 #endif
12 #ifdef WIN32
13 #define strcasecmp stricmp
14 #define strcasencmp strnicmp
15 #endif
17 #define MEMBLK 1024
18 /*#define DEBUG_PARSER
19 #define DEBUG_VARS*/
21 typedef struct var_s {
22 char *name, *value;
23 } s_var;
25 typedef struct cgi_s {
26 s_var **vars;
27 } s_cgi;
29 /* in arg[0] find tags beginning with arg[1] call arg[2] on them
30 and replace by result of arg[2] call */
31 int parse(
32 char **,
33 long,
34 char *,
35 char * (*)(long,
36 const char **));
38 /**************************************************/
39 /* tag replacers ... they are called from parse */
40 /* through function pointers */
41 /**************************************************/
43 /* return cgi var named arg[0] */
44 char *cgiget(
45 long,
46 const char **);
48 /* return a quoted cgi var named arg[0] */
49 char *cgigetq(
50 long,
51 const char **);
53 /* return a quoted and sanitized cgi variable */
54 char *cgigetqp(
55 long,
56 const char **);
58 /* call rrd_graph and insert appropriate image tag */
59 char *drawgraph(
60 long,
61 const char **);
63 /* return PRINT functions from last rrd_graph call */
64 char *drawprint(
65 long,
66 const char **);
68 /* pretty-print the <last></last> value for some.rrd via strftime() */
69 char *printtimelast(
70 long,
71 const char **);
73 /* pretty-print current time */
74 char *printtimenow(
75 long,
76 const char **);
78 /* set an environment variable */
79 char *rrdsetenv(
80 long,
81 const char **);
83 /* get an environment variable */
84 char *rrdgetenv(
85 long,
86 const char **);
88 /* include the named file at this point */
89 char *includefile(
90 long,
91 const char **);
93 /* for how long is the output of the cgi valid ? */
94 char *rrdgoodfor(
95 long,
96 const char **);
98 /* return rrdcgi version string */
99 char *rrdgetinternal(
100 long,
101 const char **);
103 char *rrdstrip(
104 char *buf);
105 char *scanargs(
106 char *line,
107 int *argc,
108 char ***args);
110 /* format at-time specified times using strftime */
111 char *printstrftime(
112 long,
113 const char **);
115 /** HTTP protocol needs special format, and GMT time **/
116 char *http_time(
117 time_t *);
119 /* return a pointer to newly allocated copy of this string */
120 char *stralloc(
121 const char *);
123 /* global variable for rrdcgi */
124 s_cgi *rrdcgiArg;
126 /* rrdcgiHeader
127 *
128 * Prints a valid CGI Header (Content-type...) etc.
129 */
130 void rrdcgiHeader(
131 void);
133 /* rrdcgiDecodeString
134 * decode html escapes
135 */
137 char *rrdcgiDecodeString(
138 char *text);
140 /* rrdcgiDebug
141 *
142 * Set/unsets debugging
143 */
144 void rrdcgiDebug(
145 int level,
146 int where);
148 /* rrdcgiInit
149 *
150 * Reads in variables set via POST or stdin.
151 */
152 s_cgi *rrdcgiInit(
153 void);
155 /* rrdcgiGetValue
156 *
157 * Returns the value of the specified variable or NULL if it's empty
158 * or doesn't exist.
159 */
160 char *rrdcgiGetValue(
161 s_cgi * parms,
162 const char *name);
164 /* rrdcgiFreeList
165 *
166 * Frees a list as returned by rrdcgiGetVariables()
167 */
168 void rrdcgiFreeList(
169 char **list);
171 /* rrdcgiFree
172 *
173 * Frees the internal data structures
174 */
175 void rrdcgiFree(
176 s_cgi * parms);
178 /* rrdcgiReadVariables()
179 *
180 * Read from stdin if no string is provided via CGI. Variables that
181 * doesn't have a value associated with it doesn't get stored.
182 */
183 s_var **rrdcgiReadVariables(
184 void);
187 int rrdcgiDebugLevel = 0;
188 int rrdcgiDebugStderr = 1;
189 char *rrdcgiHeaderString = NULL;
190 char *rrdcgiType = NULL;
192 /* rrd interface to the variable functions {put,get}var() */
193 char *rrdgetvar(
194 long argc,
195 const char **args);
196 char *rrdsetvar(
197 long argc,
198 const char **args);
199 char *rrdsetvarconst(
200 long argc,
201 const char **args);
204 /* variable store: put/get key-value pairs */
205 static int initvar(
206 );
207 static void donevar(
208 );
209 static const char *getvar(
210 const char *varname);
211 static const char *putvar(
212 const char *name,
213 const char *value,
214 int is_const);
216 /* key value pair that makes up an entry in the variable store */
217 typedef struct {
218 int is_const; /* const variable or not */
219 const char *name; /* variable name */
220 const char *value; /* variable value */
221 } vardata;
223 /* the variable heap:
224 start with a heapsize of 10 variables */
225 #define INIT_VARSTORE_SIZE 10
226 static vardata *varheap = NULL;
227 static size_t varheap_size = 0;
229 /* allocate and initialize variable heap */
230 static int initvar(
231 void)
232 {
233 varheap = (vardata *) malloc(sizeof(vardata) * INIT_VARSTORE_SIZE);
234 if (varheap == NULL) {
235 fprintf(stderr, "ERROR: unable to initialize variable store\n");
236 return -1;
237 }
238 memset(varheap, 0, sizeof(vardata) * INIT_VARSTORE_SIZE);
239 varheap_size = INIT_VARSTORE_SIZE;
240 return 0;
241 }
243 /* cleanup: free allocated memory */
244 static void donevar(
245 void)
246 {
247 int i;
249 if (varheap) {
250 for (i = 0; i < (int) varheap_size; i++) {
251 if (varheap[i].name) {
252 free((char *) varheap[i].name);
253 }
254 if (varheap[i].value) {
255 free((char *) varheap[i].value);
256 }
257 }
258 free(varheap);
259 }
260 }
262 /* Get a variable from the variable store.
263 Return NULL in case the requested variable was not found. */
264 static const char *getvar(
265 const char *name)
266 {
267 int i;
269 for (i = 0; i < (int) varheap_size && varheap[i].name; i++) {
270 if (0 == strcmp(name, varheap[i].name)) {
271 #ifdef DEBUG_VARS
272 printf("<!-- getvar(%s) -> %s -->\n", name, varheap[i].value);
273 #endif
274 return varheap[i].value;
275 }
276 }
277 #ifdef DEBUG_VARS
278 printf("<!-- getvar(%s) -> Not found-->\n", name);
279 #endif
280 return NULL;
281 }
283 /* Put a variable into the variable store. If a variable by that
284 name exists, it's value is overwritten with the new value unless it was
285 marked as 'const' (initialized by RRD::SETCONSTVAR).
286 Returns a copy the newly allocated value on success, NULL on error. */
287 static const char *putvar(
288 const char *name,
289 const char *value,
290 int is_const)
291 {
292 int i;
294 for (i = 0; i < (int) varheap_size && varheap[i].name; i++) {
295 if (0 == strcmp(name, varheap[i].name)) {
296 /* overwrite existing entry */
297 if (varheap[i].is_const) {
298 #ifdef DEBUG_VARS
299 printf("<!-- setver(%s, %s): not assigning: "
300 "const variable -->\n", name, value);
301 #endif
302 return varheap[i].value;
303 }
304 #ifdef DEBUG_VARS
305 printf("<!-- setvar(%s, %s): overwriting old value (%s) -->\n",
306 name, value, varheap[i].value);
307 #endif
308 /* make it possible to promote a variable to readonly */
309 varheap[i].is_const = is_const;
310 free((char *) varheap[i].value);
311 varheap[i].value = stralloc(value);
312 return varheap[i].value;
313 }
314 }
316 /* no existing variable found by that name, add it */
317 if (i == (int) varheap_size) {
318 /* ran out of heap: resize heap to double size */
319 size_t new_size = varheap_size * 2;
321 varheap = (vardata *) (realloc(varheap, sizeof(vardata) * new_size));
322 if (!varheap) {
323 fprintf(stderr, "ERROR: Unable to realloc variable heap\n");
324 return NULL;
325 }
326 /* initialize newly allocated memory */ ;
327 memset(&varheap[varheap_size], 0, sizeof(vardata) * varheap_size);
328 varheap_size = new_size;
329 }
330 varheap[i].is_const = is_const;
331 varheap[i].name = stralloc(name);
332 varheap[i].value = stralloc(value);
334 #ifdef DEBUG_VARS
335 printf("<!-- setvar(%s, %s): adding new variable -->\n", name, value);
336 #endif
337 return varheap[i].value;
338 }
340 /* expand those RRD:* directives that can be used recursivly */
341 static char *rrd_expand_vars(
342 char *buffer)
343 {
344 int i;
346 #ifdef DEBUG_PARSER
347 printf("expanding variables in '%s'\n", buffer);
348 #endif
350 for (i = 0; buffer[i]; i++) {
351 if (buffer[i] != '<')
352 continue;
353 parse(&buffer, i, "<RRD::CV", cgiget);
354 parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
355 parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
356 parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
357 parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
358 parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
359 parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
360 parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
361 parse(&buffer, i, "<RRD::INTERNAL", rrdgetinternal);
362 }
363 return buffer;
364 }
366 static long goodfor = 0;
367 static char **calcpr = NULL;
368 static void calfree(
369 void)
370 {
371 if (calcpr) {
372 long i;
374 for (i = 0; calcpr[i]; i++) {
375 if (calcpr[i]) {
376 free(calcpr[i]);
377 }
378 }
379 if (calcpr) {
380 free(calcpr);
381 }
382 calcpr = NULL;
383 }
384 }
386 /* create freeable version of the string */
387 char *stralloc(
388 const char *str)
389 {
390 if (!str) {
391 return NULL;
392 }
393 return strdup(str);
394 }
396 static int readfile(
397 const char *file_name,
398 char **buffer,
399 int skipfirst)
400 {
401 long writecnt = 0, totalcnt = MEMBLK;
402 long offset = 0;
403 FILE *input = NULL;
404 char c;
406 if ((strcmp("-", file_name) == 0)) {
407 input = stdin;
408 } else {
409 if ((input = fopen(file_name, "rb")) == NULL) {
410 rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
411 return (-1);
412 }
413 }
414 if (skipfirst) {
415 do {
416 c = getc(input);
417 offset++;
418 } while (c != '\n' && !feof(input));
419 }
420 if (strcmp("-", file_name)) {
421 fseek(input, 0, SEEK_END);
422 /* have extra space for detecting EOF without realloc */
423 totalcnt = (ftell(input) + 1) / sizeof(char) - offset;
424 if (totalcnt < MEMBLK)
425 totalcnt = MEMBLK; /* sanitize */
426 fseek(input, offset * sizeof(char), SEEK_SET);
427 }
428 if (((*buffer) = (char *) malloc((totalcnt + 4) * sizeof(char))) == NULL) {
429 perror("Allocate Buffer:");
430 exit(1);
431 };
432 do {
433 writecnt +=
434 fread((*buffer) + writecnt, 1,
435 (totalcnt - writecnt) * sizeof(char), input);
436 if (writecnt >= totalcnt) {
437 totalcnt += MEMBLK;
438 if (((*buffer) =
439 rrd_realloc((*buffer),
440 (totalcnt + 4) * sizeof(char))) == NULL) {
441 perror("Realloc Buffer:");
442 exit(1);
443 };
444 }
445 } while (!feof(input));
446 (*buffer)[writecnt] = '\0';
447 if (strcmp("-", file_name) != 0) {
448 fclose(input);
449 };
450 return writecnt;
451 }
453 int main(
454 int argc,
455 char *argv[])
456 {
457 char *buffer;
458 long i;
459 long filter = 0;
460 struct option long_options[] = {
461 {"filter", no_argument, 0, 'f'},
462 {0, 0, 0, 0}
463 };
465 #ifdef MUST_DISABLE_SIGFPE
466 signal(SIGFPE, SIG_IGN);
467 #endif
468 #ifdef MUST_DISABLE_FPMASK
469 fpsetmask(0);
470 #endif
471 optind = 0;
472 opterr = 0; /* initialize getopt */
474 /* what do we get for cmdline arguments?
475 for (i=0;i<argc;i++)
476 printf("%d-'%s'\n",i,argv[i]); */
477 while (1) {
478 int option_index = 0;
479 int opt;
481 opt = getopt_long(argc, argv, "f", long_options, &option_index);
482 if (opt == EOF) {
483 break;
484 }
486 switch (opt) {
487 case 'f':
488 filter = 1;
489 break;
490 case '?':
491 printf("unknown commandline option '%s'\n", argv[optind - 1]);
492 return -1;
493 }
494 }
496 if (!filter) {
497 rrdcgiDebug(0, 0);
498 rrdcgiArg = rrdcgiInit();
499 }
501 /* make sure we have one extra argument,
502 if there are others, we do not care Apache gives several */
504 /* if ( (optind != argc-2
505 && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL)
506 && optind != argc-1) { */
508 if (optind >= argc) {
509 fprintf(stderr, "ERROR: expected a filename\n");
510 exit(1);
511 } else {
512 readfile(argv[optind], &buffer, 1);
513 }
515 if (rrd_test_error()) {
516 fprintf(stderr, "ERROR: %s\n", rrd_get_error());
517 exit(1);
518 }
520 /* initialize variable heap */
521 initvar();
523 #ifdef DEBUG_PARSER
524 /* some fake header for testing */
525 printf("Content-Type: text/html\nContent-Length: 10000000\n\n\n");
526 #endif
529 /* expand rrd directives in buffer recursivly */
530 for (i = 0; buffer[i]; i++) {
531 if (buffer[i] != '<')
532 continue;
533 if (!filter) {
534 parse(&buffer, i, "<RRD::CV", cgiget);
535 parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
536 parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
537 parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
538 }
539 parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
540 parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
541 parse(&buffer, i, "<RRD::GRAPH", drawgraph);
542 parse(&buffer, i, "<RRD::INCLUDE", includefile);
543 parse(&buffer, i, "<RRD::PRINT", drawprint);
544 parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
545 parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
546 parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
547 parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
548 parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
549 parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
550 parse(&buffer, i, "<RRD::INTERNAL", rrdgetinternal);
551 }
553 if (!filter) {
554 printf("Content-Type: text/html\n"
555 "Content-Length: %zd\n", strlen(buffer));
557 if (labs(goodfor) > 0) {
558 time_t now;
560 now = time(NULL);
561 printf("Last-Modified: %s\n", http_time(&now));
562 now += labs(goodfor);
563 printf("Expires: %s\n", http_time(&now));
564 if (goodfor < 0) {
565 printf("Refresh: %ld\n", labs(goodfor));
566 }
567 }
568 printf("\n");
569 }
571 /* output result */
572 printf("%s", buffer);
574 /* cleanup */
575 calfree();
576 if (buffer) {
577 free(buffer);
578 }
579 donevar();
580 exit(0);
581 }
583 /* remove occurrences of .. this is a general measure to make
584 paths which came in via cgi do not go UP ... */
586 char *rrdsetenv(
587 long argc,
588 const char **args)
589 {
590 if (argc >= 2) {
591 const size_t len = strlen(args[0]) + strlen(args[1]) + 2;
592 char *xyz = malloc(len);
594 if (xyz == NULL) {
595 return stralloc("[ERROR: allocating setenv buffer]");
596 };
597 snprintf(xyz, len, "%s=%s", args[0], args[1]);
598 if (putenv(xyz) == -1) {
599 free(xyz);
600 return stralloc("[ERROR: failed to do putenv]");
601 };
602 return stralloc("");
603 }
604 return stralloc("[ERROR: setenv failed because not enough "
605 "arguments were defined]");
606 }
608 /* rrd interface to the variable function putvar() */
609 char *rrdsetvar(
610 long argc,
611 const char **args)
612 {
613 if (argc >= 2) {
614 const char *result = putvar(args[0], args[1], 0 /* not const */ );
616 if (result) {
617 /* setvar does not return the value set */
618 return stralloc("");
619 }
620 return stralloc("[ERROR: putvar failed]");
621 }
622 return stralloc("[ERROR: putvar failed because not enough arguments "
623 "were defined]");
624 }
626 /* rrd interface to the variable function putvar() */
627 char *rrdsetvarconst(
628 long argc,
629 const char **args)
630 {
631 if (argc >= 2) {
632 const char *result = putvar(args[0], args[1], 1 /* const */ );
634 if (result) {
635 /* setvar does not return the value set */
636 return stralloc("");
637 }
638 return stralloc("[ERROR: putvar failed]");
639 }
640 return stralloc("[ERROR: putvar failed because not enough arguments "
641 "were defined]");
642 }
644 char *rrdgetenv(
645 long argc,
646 const char **args)
647 {
648 char buf[128];
649 const char *envvar;
651 if (argc != 1) {
652 return stralloc("[ERROR: getenv failed because it did not "
653 "get 1 argument only]");
654 };
655 envvar = getenv(args[0]);
656 if (envvar) {
657 return stralloc(envvar);
658 } else {
659 snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
660 return stralloc(buf);
661 }
662 }
664 char *rrdgetvar(
665 long argc,
666 const char **args)
667 {
668 char buf[128];
669 const char *value;
671 if (argc != 1) {
672 return stralloc("[ERROR: getvar failed because it did not "
673 "get 1 argument only]");
674 };
675 value = getvar(args[0]);
676 if (value) {
677 return stralloc(value);
678 } else {
679 snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
680 return stralloc(buf);
681 }
682 }
684 char *rrdgoodfor(
685 long argc,
686 const char **args)
687 {
688 if (argc == 1) {
689 goodfor = atol(args[0]);
690 } else {
691 return stralloc("[ERROR: goodfor expected 1 argument]");
692 }
694 if (goodfor == 0) {
695 return stralloc("[ERROR: goodfor value must not be 0]");
696 }
698 return stralloc("");
699 }
701 char *rrdgetinternal(
702 long argc,
703 const char **args)
704 {
705 if (argc == 1) {
706 if (strcasecmp(args[0], "VERSION") == 0) {
707 return stralloc(PACKAGE_VERSION);
708 } else if (strcasecmp(args[0], "COMPILETIME") == 0) {
709 return stralloc(__DATE__ " " __TIME__);
710 } else {
711 return stralloc("[ERROR: internal unknown argument]");
712 }
713 } else {
714 return stralloc("[ERROR: internal expected 1 argument]");
715 }
716 }
718 /* Format start or end times using strftime. We always need both the
719 * start and end times, because, either might be relative to the other.
720 * */
721 #define MAX_STRFTIME_SIZE 256
722 char *printstrftime(
723 long argc,
724 const char **args)
725 {
726 rrd_time_value_t start_tv, end_tv;
727 char *parsetime_error = NULL;
728 char formatted[MAX_STRFTIME_SIZE];
729 struct tm *the_tm;
730 time_t start_tmp, end_tmp;
732 /* Make sure that we were given the right number of args */
733 if (argc != 4) {
734 rrd_set_error("wrong number of args %d", argc);
735 return stralloc("");
736 }
738 /* Init start and end time */
739 rrd_parsetime("end-24h", &start_tv);
740 rrd_parsetime("now", &end_tv);
742 /* Parse the start and end times we were given */
743 if ((parsetime_error = rrd_parsetime(args[1], &start_tv))) {
744 rrd_set_error("start time: %s", parsetime_error);
745 return stralloc("");
746 }
747 if ((parsetime_error = rrd_parsetime(args[2], &end_tv))) {
748 rrd_set_error("end time: %s", parsetime_error);
749 return stralloc("");
750 }
751 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
752 return stralloc("");
753 }
755 /* Do we do the start or end */
756 if (strcasecmp(args[0], "START") == 0) {
757 the_tm = localtime(&start_tmp);
758 } else if (strcasecmp(args[0], "END") == 0) {
759 the_tm = localtime(&end_tmp);
760 } else {
761 rrd_set_error("start/end not found in '%s'", args[0]);
762 return stralloc("");
763 }
765 /* now format it */
766 if (strftime(formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
767 return (stralloc(formatted));
768 } else {
769 rrd_set_error("strftime failed");
770 return stralloc("");
771 }
772 }
774 char *includefile(
775 long argc,
776 const char **args)
777 {
778 char *buffer;
780 if (argc >= 1) {
781 const char *filename = args[0];
783 readfile(filename, &buffer, 0);
784 if (rrd_test_error()) {
785 const size_t len = strlen(rrd_get_error()) + DS_NAM_SIZE;
786 char *err = malloc(len);
788 snprintf(err, len, "[ERROR: %s]", rrd_get_error());
789 rrd_clear_error();
790 return err;
791 } else {
792 return buffer;
793 }
794 } else {
795 return stralloc("[ERROR: No Inclue file defined]");
796 }
797 }
799 /* make a copy of buf and replace open/close brackets with '_' */
800 char *rrdstrip(
801 char *buf)
802 {
803 char *p;
805 if (buf == NULL) {
806 return NULL;
807 }
808 /* make a copy of the buffer */
809 buf = stralloc(buf);
810 if (buf == NULL) {
811 return NULL;
812 }
814 p = buf;
815 while (*p) {
816 if (*p == '<' || *p == '>') {
817 *p = '_';
818 }
819 p++;
820 }
821 return buf;
822 }
824 char *cgigetq(
825 long argc,
826 const char **args)
827 {
828 if (argc >= 1) {
829 char *buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
830 char *buf2;
831 char *c, *d;
832 int qc = 0;
834 if (buf == NULL)
835 return NULL;
837 for (c = buf; *c != '\0'; c++)
838 if (*c == '"')
839 qc++;
840 if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
841 perror("Malloc Buffer");
842 exit(1);
843 };
844 c = buf;
845 d = buf2;
846 *(d++) = '"';
847 while (*c != '\0') {
848 if (*c == '"') {
849 *(d++) = '"';
850 *(d++) = '\'';
851 *(d++) = '"';
852 *(d++) = '\'';
853 }
854 *(d++) = *(c++);
855 }
856 *(d++) = '"';
857 *(d) = '\0';
858 free(buf);
859 return buf2;
860 }
862 return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
863 }
865 /* remove occurrences of .. this is a general measure to make
866 paths which came in via cgi do not go UP ... */
868 char *cgigetqp(
869 long argc,
870 const char **args)
871 {
872 char *buf;
873 char *buf2;
874 char *p;
875 char *d;
877 if (argc < 1) {
878 return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
879 }
881 buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
882 if (!buf) {
883 return NULL;
884 }
886 buf2 = malloc(strlen(buf) + 1);
887 if (!buf2) {
888 perror("cgigetqp(): Malloc Path Buffer");
889 exit(1);
890 };
892 p = buf;
893 d = buf2;
895 while (*p) {
896 /* prevent mallicious paths from entering the system */
897 if (p[0] == '.' && p[1] == '.') {
898 p += 2;
899 *d++ = '_';
900 *d++ = '_';
901 } else {
902 *d++ = *p++;
903 }
904 }
906 *d = 0;
907 free(buf);
909 /* Make sure the path is relative, e.g. does not start with '/' */
910 p = buf2;
911 while ('/' == *p) {
912 *p++ = '_';
913 }
915 return buf2;
916 }
919 char *cgiget(
920 long argc,
921 const char **args)
922 {
923 if (argc >= 1)
924 return rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
925 else
926 return stralloc("[ERROR: not enough arguments for RRD::CV]");
927 }
931 char *drawgraph(
932 long argc,
933 const char **args)
934 {
935 int i, xsize, ysize;
936 double ymin, ymax;
938 for (i = 0; i < argc; i++)
939 if (strcmp(args[i], "--imginfo") == 0 || strcmp(args[i], "-g") == 0)
940 break;
941 if (i == argc) {
942 args[argc++] = "--imginfo";
943 args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
944 }
945 calfree();
946 if (rrd_graph
947 (argc + 1, (char **) args - 1, &calcpr, &xsize, &ysize, NULL, &ymin,
948 &ymax) != -1) {
949 return stralloc(calcpr[0]);
950 } else {
951 if (rrd_test_error()) {
952 const size_t len = strlen(rrd_get_error()) + DS_NAM_SIZE;
953 char *err = malloc(len);
954 snprintf(err, len, "[ERROR: %s]", rrd_get_error());
955 rrd_clear_error();
956 return err;
957 }
958 }
959 return NULL;
960 }
962 char *drawprint(
963 long argc,
964 const char **args)
965 {
966 if (argc == 1 && calcpr) {
967 long i = 0;
969 while (calcpr[i] != NULL)
970 i++; /*determine number lines in calcpr */
971 if (atol(args[0]) < i - 1)
972 return stralloc(calcpr[atol(args[0]) + 1]);
973 }
974 return stralloc("[ERROR: RRD::PRINT argument error]");
975 }
977 char *printtimelast(
978 long argc,
979 const char **args)
980 {
981 time_t last;
982 struct tm tm_last;
983 char *buf;
985 if (argc == 2) {
986 buf = malloc(255);
987 if (buf == NULL) {
988 return stralloc("[ERROR: allocating strftime buffer]");
989 };
990 /* not raising argc in step with args - 1 since the last argument
991 will be used below for strftime */
993 last = rrd_last(argc, (char **) args - 1);
994 if (rrd_test_error()) {
995 const size_t len = strlen(rrd_get_error()) + DS_NAM_SIZE;
996 char *err = malloc(len);
997 snprintf(err, len, "[ERROR: %s]", rrd_get_error());
998 rrd_clear_error();
999 return err;
1000 }
1001 tm_last = *localtime(&last);
1002 strftime(buf, 254, args[1], &tm_last);
1003 return buf;
1004 }
1005 return stralloc("[ERROR: expected <RRD::TIME::LAST file.rrd strftime-format>]");
1006 }
1008 char *printtimenow(
1009 long argc,
1010 const char **args)
1011 {
1012 time_t now = time(NULL);
1013 struct tm tm_now;
1014 char *buf;
1016 if (argc == 1) {
1017 buf = malloc(255);
1018 if (buf == NULL) {
1019 return stralloc("[ERROR: allocating strftime buffer]");
1020 };
1021 tm_now = *localtime(&now);
1022 strftime(buf, 254, args[0], &tm_now);
1023 return buf;
1024 }
1025 if (argc < 1) {
1026 return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
1027 }
1028 return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
1029 }
1031 /* Scan buffer until an unescaped '>' arives.
1032 * Update argument array with arguments found.
1033 * Return end cursor where parsing stopped, or NULL in case of failure.
1034 *
1035 * FIXME:
1036 * To allow nested constructs, we call rrd_expand_vars() for arguments
1037 * that contain RRD::x directives. These introduce a small memory leak
1038 * since we have to stralloc the arguments the way parse() works.
1039 */
1040 char *scanargs(
1041 char *line,
1042 int *argument_count,
1043 char ***arguments)
1044 {
1045 char *getP; /* read cursor */
1046 char *putP; /* write cursor */
1047 char Quote; /* type of quote if in quoted string, 0 otherwise */
1048 int tagcount; /* open tag count */
1049 int in_arg; /* if we currently are parsing an argument or not */
1050 int argsz; /* argument array size */
1051 int curarg_contains_rrd_directives;
1053 /* local array of arguments while parsing */
1054 int argc = 1;
1055 char **argv;
1057 #ifdef DEBUG_PARSER
1058 printf("<-- scanargs(%s) -->\n", line);
1059 #endif
1061 *arguments = NULL;
1062 *argument_count = 0;
1064 /* create initial argument array of char pointers */
1065 argsz = 32;
1066 argv = (char **) malloc(argsz * sizeof(char *));
1067 if (!argv) {
1068 return NULL;
1069 }
1070 argv[0] = "rrdcgi";
1072 /* skip leading blanks */
1073 while (isspace((int) *line)) {
1074 line++;
1075 }
1077 getP = line;
1078 putP = line;
1080 Quote = 0;
1081 in_arg = 0;
1082 tagcount = 0;
1084 curarg_contains_rrd_directives = 0;
1086 /* start parsing 'line' for arguments */
1087 while (*getP) {
1088 unsigned char c = *getP++;
1090 if (c == '>' && !Quote && !tagcount) {
1091 /* this is our closing tag, quit scanning */
1092 break;
1093 }
1095 /* remove all special chars */
1096 if (c < ' ') {
1097 c = ' ';
1098 }
1100 switch (c) {
1101 case ' ':
1102 if (Quote || tagcount) {
1103 /* copy quoted/tagged (=RRD expanded) string */
1104 *putP++ = c;
1105 } else if (in_arg) {
1106 /* end argument string */
1107 *putP++ = 0;
1108 in_arg = 0;
1109 if (curarg_contains_rrd_directives) {
1110 argv[argc - 1] =
1111 rrd_expand_vars(stralloc(argv[argc - 1]));
1112 curarg_contains_rrd_directives = 0;
1113 }
1114 }
1115 break;
1117 case '"': /* Fall through */
1118 case '\'':
1119 if (Quote != 0) {
1120 if (Quote == c) {
1121 Quote = 0;
1122 } else {
1123 /* copy quoted string */
1124 *putP++ = c;
1125 }
1126 } else {
1127 if (!in_arg) {
1128 /* reference start of argument string in argument array */
1129 argv[argc++] = putP;
1130 in_arg = 1;
1131 }
1132 Quote = c;
1133 }
1134 break;
1136 default:
1137 if (!in_arg) {
1138 /* start new argument */
1139 argv[argc++] = putP;
1140 in_arg = 1;
1141 }
1142 if (c == '>') {
1143 if (tagcount) {
1144 tagcount--;
1145 }
1146 }
1147 if (c == '<') {
1148 tagcount++;
1149 if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
1150 curarg_contains_rrd_directives = 1;
1151 }
1152 }
1153 *putP++ = c;
1154 break;
1155 }
1157 /* check if our argument array is still large enough */
1158 if (argc == argsz) {
1159 /* resize argument array */
1160 argsz *= 2;
1161 argv = rrd_realloc(argv, argsz * sizeof(char *));
1162 if (*argv == NULL) {
1163 return NULL;
1164 }
1165 }
1166 }
1168 /* terminate last argument found */
1169 *putP = '\0';
1170 if (curarg_contains_rrd_directives) {
1171 argv[argc - 1] = rrd_expand_vars(stralloc(argv[argc - 1]));
1172 }
1173 #ifdef DEBUG_PARSER
1174 if (argc > 1) {
1175 int n;
1177 printf("<-- arguments found [%d]\n", argc);
1178 for (n = 0; n < argc; n++) {
1179 printf("arg %02d: '%s'\n", n, argv[n]);
1180 }
1181 printf("-->\n");
1182 } else {
1183 printf("<!-- No arguments found -->\n");
1184 }
1185 #endif
1187 /* update caller's notion of the argument array and it's size */
1189 /* note this is a bit of a hack since the rrd_cgi code used to just put
1190 its arguments into a normal array starting at 0 ... since the rrd_*
1191 commands expect and argc/argv array we used to just shift everything
1192 by -1 ... this in turn exploded when a rrd_* function tried to print
1193 argv[0] ... hence we are now doing everything in argv style but hand
1194 over seemingly the old array ... but doing argv-1 will actually end
1195 up in a 'good' place now. */
1197 *arguments = argv+1;
1198 *argument_count = argc-1;
1200 if (Quote) {
1201 return NULL;
1202 }
1204 /* Return new scanning cursor:
1205 pointer to char after closing bracket */
1206 return getP;
1207 }
1210 /*
1211 * Parse(): scan current portion of buffer for given tag.
1212 * If found, parse tag arguments and call 'func' for it.
1213 * The result of func is inserted at the current position
1214 * in the buffer.
1215 */
1216 int parse(
1217 char **buf, /* buffer */
1218 long i, /* offset in buffer */
1219 char *tag, /* tag to handle */
1220 char * (*func) (long,
1221 const char **) /* function to call for 'tag' */
1222 )
1223 {
1224 /* the name of the vairable ... */
1225 char *val;
1226 long valln;
1227 char **args;
1228 char *end;
1229 long end_offset;
1230 int argc;
1231 size_t taglen = strlen(tag);
1233 /* Current position in buffer should start with 'tag' */
1234 if (strncmp((*buf) + i, tag, taglen) != 0) {
1235 return 0;
1236 }
1237 /* .. and match exactly (a whitespace following 'tag') */
1238 if (!isspace(*((*buf) + i + taglen))) {
1239 return 0;
1240 }
1241 #ifdef DEBUG_PARSER
1242 printf("parse(): handling tag '%s'\n", tag);
1243 #endif
1245 /* Scan for arguments following the tag;
1246 scanargs() puts \0 into *buf ... so after scanargs it is probably
1247 not a good time to use strlen on buf */
1248 end = scanargs((*buf) + i + taglen, &argc, &args);
1249 if (end) {
1250 /* got arguments, call function for 'tag' with arguments */
1251 val = func(argc, (const char **) args);
1252 free(args-1);
1253 } else {
1254 /* next call, try parsing at current offset +1 */
1255 end = (*buf) + i + 1;
1257 val = stralloc("[ERROR: Parsing Problem with the following text\n"
1258 " Check original file. This may have been altered "
1259 "by parsing.]\n\n");
1260 }
1262 /* remember offset where we have to continue parsing */
1263 end_offset = end - (*buf);
1265 valln = 0;
1266 if (val) {
1267 valln = strlen(val);
1268 }
1270 /* Optionally resize buffer to hold the replacement value:
1271 Calculating the new length of the buffer is simple. add current
1272 buffer pos (i) to length of string after replaced tag to length
1273 of replacement string and add 1 for the final zero ... */
1274 if (end - (*buf) < (i + valln)) {
1275 /* make sure we do not shrink the mallocd block */
1276 size_t newbufsize = i + strlen(end) + valln + 1;
1278 *buf = rrd_realloc(*buf, newbufsize);
1280 if (*buf == NULL) {
1281 perror("Realoc buf:");
1282 exit(1);
1283 };
1284 }
1286 /* Update new end pointer:
1287 make sure the 'end' pointer gets moved along with the
1288 buf pointer when realloc moves memory ... */
1289 end = (*buf) + end_offset;
1291 /* splice the variable:
1292 step 1. Shift pending data to make room for 'val' */
1293 memmove((*buf) + i + valln, end, strlen(end) + 1);
1295 /* step 2. Insert val */
1296 if (val) {
1297 memmove((*buf) + i, val, valln);
1298 free(val);
1299 }
1300 return (valln > 0 ? valln - 1 : valln);
1301 }
1303 char *http_time(
1304 time_t *now)
1305 {
1306 struct tm *tmptime;
1307 static char buf[60];
1309 tmptime = gmtime(now);
1310 strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", tmptime);
1311 return (buf);
1312 }
1314 void rrdcgiHeader(
1315 void)
1316 {
1317 if (rrdcgiType)
1318 printf("Content-type: %s\n", rrdcgiType);
1319 else
1320 printf("Content-type: text/html\n");
1321 if (rrdcgiHeaderString)
1322 printf("%s", rrdcgiHeaderString);
1323 printf("\n");
1324 }
1326 void rrdcgiDebug(
1327 int level,
1328 int where)
1329 {
1330 if (level > 0)
1331 rrdcgiDebugLevel = level;
1332 else
1333 rrdcgiDebugLevel = 0;
1334 if (where)
1335 rrdcgiDebugStderr = 0;
1336 else
1337 rrdcgiDebugStderr = 1;
1338 }
1340 char *rrdcgiDecodeString(
1341 char *text)
1342 {
1343 char *cp, *xp;
1345 for (cp = text, xp = text; *cp; cp++) {
1346 if (*cp == '%') {
1347 if (strchr("0123456789ABCDEFabcdef", *(cp + 1))
1348 && strchr("0123456789ABCDEFabcdef", *(cp + 2))) {
1349 if (islower(*(cp + 1)))
1350 *(cp + 1) = toupper(*(cp + 1));
1351 if (islower(*(cp + 2)))
1352 *(cp + 2) = toupper(*(cp + 2));
1353 *(xp) =
1354 (*(cp + 1) >=
1355 'A' ? *(cp + 1) - 'A' + 10 : *(cp + 1) - '0') * 16 +
1356 (*(cp + 2) >=
1357 'A' ? *(cp + 2) - 'A' + 10 : *(cp + 2) - '0');
1358 xp++;
1359 cp += 2;
1360 }
1361 } else {
1362 *(xp++) = *cp;
1363 }
1364 }
1365 memset(xp, 0, cp - xp);
1366 return text;
1367 }
1369 /* rrdcgiReadVariables()
1370 *
1371 * Read from stdin if no string is provided via CGI. Variables that
1372 * doesn't have a value associated with it doesn't get stored.
1373 */
1374 s_var **rrdcgiReadVariables(
1375 void)
1376 {
1377 int length;
1378 char *line = NULL;
1379 int numargs;
1380 char *cp, *ip, *esp, *sptr;
1381 s_var **result;
1382 int i, k, len;
1383 char tmp[101];
1384 size_t tmplen;
1386 cp = getenv("REQUEST_METHOD");
1387 ip = getenv("CONTENT_LENGTH");
1389 if (cp && !strcmp(cp, "POST")) {
1390 if (ip) {
1391 length = atoi(ip);
1392 if ((line = (char *) malloc(length + 2)) == NULL)
1393 return NULL;
1394 if (fgets(line, length + 1, stdin) == NULL)
1395 return NULL;
1396 } else
1397 return NULL;
1398 } else if (cp && !strcmp(cp, "GET")) {
1399 esp = getenv("QUERY_STRING");
1400 if (esp && strlen(esp)) {
1401 if ((line = strdup(esp)) == NULL)
1402 return NULL;
1403 } else
1404 return NULL;
1405 } else {
1406 length = 0;
1407 printf("(offline mode: enter name=value pairs on standard input)\n");
1408 memset(tmp, 0, sizeof(tmp));
1409 while ((cp = fgets(tmp, 100, stdin)) != NULL) {
1410 if ((tmplen = strlen(tmp)) != 0) {
1411 if (tmp[tmplen - 1] == '\n')
1412 tmp[tmplen - 1] = '&';
1413 length += tmplen;
1414 len = (length + 1) * sizeof(char);
1415 if ((unsigned) length > tmplen) {
1416 if ((line = (char *) realloc(line, len)) == NULL)
1417 return NULL;
1418 strncat(line, tmp, tmplen);
1419 } else {
1420 if ((line = strdup(tmp)) == NULL)
1421 return NULL;
1422 }
1423 }
1424 memset(tmp, 0, sizeof(tmp));
1425 }
1426 if (!line)
1427 return NULL;
1428 if (line[strlen(line) - 1] == '&')
1429 line[strlen(line) - 1] = '\0';
1430 }
1432 /*
1433 * From now on all cgi variables are stored in the variable line
1434 * and look like foo=bar&foobar=barfoo&foofoo=
1435 */
1437 if (rrdcgiDebugLevel > 0) {
1438 if (rrdcgiDebugStderr)
1439 fprintf(stderr, "Received cgi input: %s\n", line);
1440 else
1441 printf
1442 ("<b>Received cgi input</b><br>\n<pre>\n--\n%s\n--\n</pre>\n\n",
1443 line);
1444 }
1446 for (cp = line; *cp; cp++)
1447 if (*cp == '+')
1448 *cp = ' ';
1450 if (strlen(line)) {
1451 for (numargs = 1, cp = line; *cp; cp++)
1452 if (*cp == '&')
1453 numargs++;
1454 } else
1455 numargs = 0;
1456 if (rrdcgiDebugLevel > 0) {
1457 if (rrdcgiDebugStderr)
1458 fprintf(stderr, "%d cgi variables found.\n", numargs);
1459 else
1460 printf("%d cgi variables found.<br>\n", numargs);
1461 }
1463 len = (numargs + 1) * sizeof(s_var *);
1464 if ((result = (s_var **) malloc(len)) == NULL)
1465 return NULL;
1466 memset(result, 0, len);
1468 cp = line;
1469 i = 0;
1470 while (*cp) {
1471 if ((ip = (char *) strchr(cp, '&')) != NULL) {
1472 *ip = '\0';
1473 } else
1474 ip = cp + strlen(cp);
1476 if ((esp = (char *) strchr(cp, '=')) == NULL) {
1477 cp = ++ip;
1478 continue;
1479 }
1481 if (!strlen(esp)) {
1482 cp = ++ip;
1483 continue;
1484 }
1486 if (i < numargs) {
1488 /* try to find out if there's already such a variable */
1489 for (k = 0; k < i && (strncmp(result[k]->name, cp, esp - cp)
1490 || !(strlen(result[k]->name) ==
1491 (size_t) (esp - cp))); k++);
1493 if (k == i) { /* No such variable yet */
1494 if ((result[i] = (s_var *) malloc(sizeof(s_var))) == NULL)
1495 return NULL;
1496 if ((result[i]->name =
1497 (char *) malloc((esp - cp + 1) * sizeof(char))) == NULL)
1498 return NULL;
1499 memset(result[i]->name, 0, esp - cp + 1);
1500 strncpy(result[i]->name, cp, esp - cp);
1501 cp = ++esp;
1502 if ((result[i]->value =
1503 (char *) malloc((ip - esp + 1) * sizeof(char))) == NULL)
1504 return NULL;
1505 memset(result[i]->value, 0, ip - esp + 1);
1506 strncpy(result[i]->value, cp, ip - esp);
1507 result[i]->value = rrdcgiDecodeString(result[i]->value);
1508 if (rrdcgiDebugLevel) {
1509 if (rrdcgiDebugStderr)
1510 fprintf(stderr, "%s: %s\n", result[i]->name,
1511 result[i]->value);
1512 else
1513 printf("<h3>Variable %s</h3>\n<pre>\n%s\n</pre>\n\n",
1514 result[i]->name, result[i]->value);
1515 }
1516 i++;
1517 } else { /* There is already such a name, suppose a mutiple field */
1518 cp = ++esp;
1519 len = strlen(result[k]->value) + (ip - esp) + 2;
1520 if ((sptr = (char *) calloc(len, sizeof(char))) == NULL)
1521 return NULL;
1522 snprintf(sptr, len, "%s\n%s", result[k]->value, cp);
1523 free(result[k]->value);
1524 result[k]->value = rrdcgiDecodeString(sptr);
1525 }
1526 }
1527 cp = ++ip;
1528 }
1529 return result;
1530 }
1532 /* rrdcgiInit()
1533 *
1534 * Read from stdin if no string is provided via CGI. Variables that
1535 * doesn't have a value associated with it doesn't get stored.
1536 */
1537 s_cgi *rrdcgiInit(
1538 void)
1539 {
1540 s_cgi *res;
1541 s_var **vars;
1543 vars = rrdcgiReadVariables();
1545 if (!vars)
1546 return NULL;
1548 if ((res = (s_cgi *) malloc(sizeof(s_cgi))) == NULL)
1549 return NULL;
1550 res->vars = vars;
1552 return res;
1553 }
1555 char *rrdcgiGetValue(
1556 s_cgi * parms,
1557 const char *name)
1558 {
1559 int i;
1561 if (!parms || !parms->vars)
1562 return NULL;
1563 for (i = 0; parms->vars[i]; i++)
1564 if (!strcmp(name, parms->vars[i]->name)) {
1565 if (rrdcgiDebugLevel > 0) {
1566 if (rrdcgiDebugStderr)
1567 fprintf(stderr, "%s found as %s\n", name,
1568 parms->vars[i]->value);
1569 else
1570 printf("%s found as %s<br>\n", name,
1571 parms->vars[i]->value);
1572 }
1573 return parms->vars[i]->value;
1574 }
1575 if (rrdcgiDebugLevel) {
1576 if (rrdcgiDebugStderr)
1577 fprintf(stderr, "%s not found\n", name);
1578 else
1579 printf("%s not found<br>\n", name);
1580 }
1581 return NULL;
1582 }
1584 void rrdcgiFreeList(
1585 char **list)
1586 {
1587 int i;
1589 for (i = 0; list[i] != NULL; i++)
1590 free(list[i]);
1591 free(list);
1592 }
1594 void rrdcgiFree(
1595 s_cgi * parms)
1596 {
1597 int i;
1599 if (!parms)
1600 return;
1601 if (parms->vars) {
1602 for (i = 0; parms->vars[i]; i++) {
1603 if (parms->vars[i]->name)
1604 free(parms->vars[i]->name);
1605 if (parms->vars[i]->value)
1606 free(parms->vars[i]->value);
1607 free(parms->vars[i]);
1608 }
1609 free(parms->vars);
1610 }
1611 free(parms);
1613 if (rrdcgiHeaderString) {
1614 free(rrdcgiHeaderString);
1615 rrdcgiHeaderString = NULL;
1616 }
1617 if (rrdcgiType) {
1618 free(rrdcgiType);
1619 rrdcgiType = NULL;
1620 }
1621 }