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