Code

Imported upstream version 1.4.8
[pkg-rrdtool.git] / src / rrd_cgi.c
1 /*****************************************************************************
2  * RRDtool 1.4.8  Copyright by Tobi Oetiker, 1997-2013
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)
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;
243 /* cleanup: free allocated memory */
244 static void donevar(
245     void)
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     }
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)
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;
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)
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;
340 /* expand those RRD:* directives that can be used recursivly */
341 static char *rrd_expand_vars(
342     char *buffer)
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;
366 static long goodfor = 0;
367 static char **calcpr = NULL;
368 static void calfree(
369     void)
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     }
386 /* create freeable version of the string */
387 char     *stralloc(
388     const char *str)
390     char     *nstr;
392     if (!str) {
393         return NULL;
394     }
395     nstr = malloc((strlen(str) + 1));
396     strcpy(nstr, str);
397     return (nstr);
400 static int readfile(
401     const char *file_name,
402     char **buffer,
403     int skipfirst)
405     long      writecnt = 0, totalcnt = MEMBLK;
406     long      offset = 0;
407     FILE     *input = NULL;
408     char      c;
410     if ((strcmp("-", file_name) == 0)) {
411         input = stdin;
412     } else {
413         if ((input = fopen(file_name, "rb")) == NULL) {
414             rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
415             return (-1);
416         }
417     }
418     if (skipfirst) {
419         do {
420             c = getc(input);
421             offset++;
422         } while (c != '\n' && !feof(input));
423     }
424     if (strcmp("-", file_name)) {
425         fseek(input, 0, SEEK_END);
426         /* have extra space for detecting EOF without realloc */
427         totalcnt = (ftell(input) + 1) / sizeof(char) - offset;
428         if (totalcnt < MEMBLK)
429             totalcnt = MEMBLK;  /* sanitize */
430         fseek(input, offset * sizeof(char), SEEK_SET);
431     }
432     if (((*buffer) = (char *) malloc((totalcnt + 4) * sizeof(char))) == NULL) {
433         perror("Allocate Buffer:");
434         exit(1);
435     };
436     do {
437         writecnt +=
438             fread((*buffer) + writecnt, 1,
439                   (totalcnt - writecnt) * sizeof(char), input);
440         if (writecnt >= totalcnt) {
441             totalcnt += MEMBLK;
442             if (((*buffer) =
443                  rrd_realloc((*buffer),
444                              (totalcnt + 4) * sizeof(char))) == NULL) {
445                 perror("Realloc Buffer:");
446                 exit(1);
447             };
448         }
449     } while (!feof(input));
450     (*buffer)[writecnt] = '\0';
451     if (strcmp("-", file_name) != 0) {
452         fclose(input);
453     };
454     return writecnt;
457 int main(
458     int argc,
459     char *argv[])
461     char     *buffer;
462     long      i;
463     long      filter = 0;
464     struct option long_options[] = {
465         {"filter", no_argument, 0, 'f'},
466         {0, 0, 0, 0}
467     };
469 #ifdef MUST_DISABLE_SIGFPE
470     signal(SIGFPE, SIG_IGN);
471 #endif
472 #ifdef MUST_DISABLE_FPMASK
473     fpsetmask(0);
474 #endif
475     optind = 0;
476     opterr = 0;         /* initialize getopt */
478     /* what do we get for cmdline arguments?
479        for (i=0;i<argc;i++)
480        printf("%d-'%s'\n",i,argv[i]); */
481     while (1) {
482         int       option_index = 0;
483         int       opt;
485         opt = getopt_long(argc, argv, "f", long_options, &option_index);
486         if (opt == EOF) {
487             break;
488         }
490         switch (opt) {
491         case 'f':
492             filter = 1;
493             break;
494         case '?':
495             printf("unknown commandline option '%s'\n", argv[optind - 1]);
496             return -1;
497         }
498     }
500     if (!filter) {
501         rrdcgiDebug(0, 0);
502         rrdcgiArg = rrdcgiInit();
503     }
505     /* make sure we have one extra argument, 
506        if there are others, we do not care Apache gives several */
508     /* if ( (optind != argc-2 
509        && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) 
510        && optind != argc-1) { */
512     if (optind >= argc) {
513         fprintf(stderr, "ERROR: expected a filename\n");
514         exit(1);
515     } else {
516         readfile(argv[optind], &buffer, 1);
517     }
519     if (rrd_test_error()) {
520         fprintf(stderr, "ERROR: %s\n", rrd_get_error());
521         exit(1);
522     }
524     /* initialize variable heap */
525     initvar();
527 #ifdef DEBUG_PARSER
528     /* some fake header for testing */
529     printf("Content-Type: text/html\nContent-Length: 10000000\n\n\n");
530 #endif
533     /* expand rrd directives in buffer recursivly */
534     for (i = 0; buffer[i]; i++) {
535         if (buffer[i] != '<')
536             continue;
537         if (!filter) {
538             parse(&buffer, i, "<RRD::CV", cgiget);
539             parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
540             parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
541             parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
542         }
543         parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
544         parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
545         parse(&buffer, i, "<RRD::GRAPH", drawgraph);
546         parse(&buffer, i, "<RRD::INCLUDE", includefile);
547         parse(&buffer, i, "<RRD::PRINT", drawprint);
548         parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
549         parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
550         parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
551         parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
552         parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
553         parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
554         parse(&buffer, i, "<RRD::INTERNAL", rrdgetinternal);
555     }
557     if (!filter) {
558         printf("Content-Type: text/html\n"
559                "Content-Length: %zd\n", strlen(buffer));
561         if (labs(goodfor) > 0) {
562             time_t    now;
564             now = time(NULL);
565             printf("Last-Modified: %s\n", http_time(&now));
566             now += labs(goodfor);
567             printf("Expires: %s\n", http_time(&now));
568             if (goodfor < 0) {
569                 printf("Refresh: %ld\n", labs(goodfor));
570             }
571         }
572         printf("\n");
573     }
575     /* output result */
576     printf("%s", buffer);
578     /* cleanup */
579     calfree();
580     if (buffer) {
581         free(buffer);
582     }
583     donevar();
584     exit(0);
587 /* remove occurrences of .. this is a general measure to make
588    paths which came in via cgi do not go UP ... */
590 char     *rrdsetenv(
591     long argc,
592     const char **args)
594     if (argc >= 2) {
595         char     *xyz = malloc((strlen(args[0]) + strlen(args[1]) + 2));
597         if (xyz == NULL) {
598             return stralloc("[ERROR: allocating setenv buffer]");
599         };
600         sprintf(xyz, "%s=%s", args[0], args[1]);
601         if (putenv(xyz) == -1) {
602             free(xyz);
603             return stralloc("[ERROR: failed to do putenv]");
604         };
605         return stralloc("");
606     }
607     return stralloc("[ERROR: setenv failed because not enough "
608                     "arguments were defined]");
611 /* rrd interface to the variable function putvar() */
612 char     *rrdsetvar(
613     long argc,
614     const char **args)
616     if (argc >= 2) {
617         const char *result = putvar(args[0], args[1], 0 /* not const */ );
619         if (result) {
620             /* setvar does not return the value set */
621             return stralloc("");
622         }
623         return stralloc("[ERROR: putvar failed]");
624     }
625     return stralloc("[ERROR: putvar failed because not enough arguments "
626                     "were defined]");
629 /* rrd interface to the variable function putvar() */
630 char     *rrdsetvarconst(
631     long argc,
632     const char **args)
634     if (argc >= 2) {
635         const char *result = putvar(args[0], args[1], 1 /* const */ );
637         if (result) {
638             /* setvar does not return the value set */
639             return stralloc("");
640         }
641         return stralloc("[ERROR: putvar failed]");
642     }
643     return stralloc("[ERROR: putvar failed because not enough arguments "
644                     "were defined]");
647 char     *rrdgetenv(
648     long argc,
649     const char **args)
651     char      buf[128];
652     const char *envvar;
654     if (argc != 1) {
655         return stralloc("[ERROR: getenv failed because it did not "
656                         "get 1 argument only]");
657     };
658     envvar = getenv(args[0]);
659     if (envvar) {
660         return stralloc(envvar);
661     } else {
662         snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
663         return stralloc(buf);
664     }
667 char     *rrdgetvar(
668     long argc,
669     const char **args)
671     char      buf[128];
672     const char *value;
674     if (argc != 1) {
675         return stralloc("[ERROR: getvar failed because it did not "
676                         "get 1 argument only]");
677     };
678     value = getvar(args[0]);
679     if (value) {
680         return stralloc(value);
681     } else {
682         snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
683         return stralloc(buf);
684     }
687 char     *rrdgoodfor(
688     long argc,
689     const char **args)
691     if (argc == 1) {
692         goodfor = atol(args[0]);
693     } else {
694         return stralloc("[ERROR: goodfor expected 1 argument]");
695     }
697     if (goodfor == 0) {
698         return stralloc("[ERROR: goodfor value must not be 0]");
699     }
701     return stralloc("");
704 char     *rrdgetinternal(
705     long argc,
706     const char **args)
708     if (argc == 1) {
709         if (strcasecmp(args[0], "VERSION") == 0) {
710             return stralloc(PACKAGE_VERSION);
711         } else if (strcasecmp(args[0], "COMPILETIME") == 0) {
712             return stralloc(__DATE__ " " __TIME__);
713         } else {
714             return stralloc("[ERROR: internal unknown argument]");
715         }
716     } else {
717         return stralloc("[ERROR: internal expected 1 argument]");
718     }
721 /* Format start or end times using strftime.  We always need both the
722  * start and end times, because, either might be relative to the other.
723  * */
724 #define MAX_STRFTIME_SIZE 256
725 char     *printstrftime(
726     long argc,
727     const char **args)
729     rrd_time_value_t start_tv, end_tv;
730     char     *parsetime_error = NULL;
731     char      formatted[MAX_STRFTIME_SIZE];
732     struct tm *the_tm;
733     time_t    start_tmp, end_tmp;
735     /* Make sure that we were given the right number of args */
736     if (argc != 4) {
737         rrd_set_error("wrong number of args %d", argc);
738         return stralloc("");
739     }
741     /* Init start and end time */
742     rrd_parsetime("end-24h", &start_tv);
743     rrd_parsetime("now", &end_tv);
745     /* Parse the start and end times we were given */
746     if ((parsetime_error = rrd_parsetime(args[1], &start_tv))) {
747         rrd_set_error("start time: %s", parsetime_error);
748         return stralloc("");
749     }
750     if ((parsetime_error = rrd_parsetime(args[2], &end_tv))) {
751         rrd_set_error("end time: %s", parsetime_error);
752         return stralloc("");
753     }
754     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
755         return stralloc("");
756     }
758     /* Do we do the start or end */
759     if (strcasecmp(args[0], "START") == 0) {
760         the_tm = localtime(&start_tmp);
761     } else if (strcasecmp(args[0], "END") == 0) {
762         the_tm = localtime(&end_tmp);
763     } else {
764         rrd_set_error("start/end not found in '%s'", args[0]);
765         return stralloc("");
766     }
768     /* now format it */
769     if (strftime(formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
770         return (stralloc(formatted));
771     } else {
772         rrd_set_error("strftime failed");
773         return stralloc("");
774     }
777 char     *includefile(
778     long argc,
779     const char **args)
781     char     *buffer;
783     if (argc >= 1) {
784         const char *filename = args[0];
786         readfile(filename, &buffer, 0);
787         if (rrd_test_error()) {
788             char     *err = malloc((strlen(rrd_get_error()) + DS_NAM_SIZE));
790             sprintf(err, "[ERROR: %s]", rrd_get_error());
791             rrd_clear_error();
792             return err;
793         } else {
794             return buffer;
795         }
796     } else {
797         return stralloc("[ERROR: No Inclue file defined]");
798     }
801 /* make a copy of buf and replace open/close brackets with '_' */
802 char     *rrdstrip(
803     char *buf)
805     char     *p;
807     if (buf == NULL) {
808         return NULL;
809     }
810     /* make a copy of the buffer */
811     buf = stralloc(buf);
812     if (buf == NULL) {
813         return NULL;
814     }
816     p = buf;
817     while (*p) {
818         if (*p == '<' || *p == '>') {
819             *p = '_';
820         }
821         p++;
822     }
823     return buf;
826 char     *cgigetq(
827     long argc,
828     const char **args)
830     if (argc >= 1) {
831         char     *buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
832         char     *buf2;
833         char     *c, *d;
834         int       qc = 0;
836         if (buf == NULL)
837             return NULL;
839         for (c = buf; *c != '\0'; c++)
840             if (*c == '"')
841                 qc++;
842         if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
843             perror("Malloc Buffer");
844             exit(1);
845         };
846         c = buf;
847         d = buf2;
848         *(d++) = '"';
849         while (*c != '\0') {
850             if (*c == '"') {
851                 *(d++) = '"';
852                 *(d++) = '\'';
853                 *(d++) = '"';
854                 *(d++) = '\'';
855             }
856             *(d++) = *(c++);
857         }
858         *(d++) = '"';
859         *(d) = '\0';
860         free(buf);
861         return buf2;
862     }
864     return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
867 /* remove occurrences of .. this is a general measure to make
868    paths which came in via cgi do not go UP ... */
870 char     *cgigetqp(
871     long argc,
872     const char **args)
874     char     *buf;
875     char     *buf2;
876     char     *p;
877     char     *d;
879     if (argc < 1) {
880         return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
881     }
883     buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
884     if (!buf) {
885         return NULL;
886     }
888     buf2 = malloc(strlen(buf) + 1);
889     if (!buf2) {
890         perror("cgigetqp(): Malloc Path Buffer");
891         exit(1);
892     };
894     p = buf;
895     d = buf2;
897     while (*p) {
898         /* prevent mallicious paths from entering the system */
899         if (p[0] == '.' && p[1] == '.') {
900             p += 2;
901             *d++ = '_';
902             *d++ = '_';
903         } else {
904             *d++ = *p++;
905         }
906     }
908     *d = 0;
909     free(buf);
911     /* Make sure the path is relative, e.g. does not start with '/' */
912     p = buf2;
913     while ('/' == *p) {
914         *p++ = '_';
915     }
917     return buf2;
921 char     *cgiget(
922     long argc,
923     const char **args)
925     if (argc >= 1)
926         return rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
927     else
928         return stralloc("[ERROR: not enough arguments for RRD::CV]");
933 char     *drawgraph(
934     long argc,
935     const char **args)
937     int       i, xsize, ysize;
938     double    ymin, ymax;
940     for (i = 0; i < argc; i++)
941         if (strcmp(args[i], "--imginfo") == 0 || strcmp(args[i], "-g") == 0)
942             break;
943     if (i == argc) {
944         args[argc++] = "--imginfo";
945         args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
946     }
947     calfree();
948     if (rrd_graph
949         (argc + 1, (char **) args - 1, &calcpr, &xsize, &ysize, NULL, &ymin,
950          &ymax) != -1) {
951         return stralloc(calcpr[0]);
952     } else {
953         if (rrd_test_error()) {
954             char     *err =
955                 malloc((strlen(rrd_get_error()) +
956                         DS_NAM_SIZE) * sizeof(char));
957             sprintf(err, "[ERROR: %s]", rrd_get_error());
958             rrd_clear_error();
959             return err;
960         }
961     }
962     return NULL;
965 char     *drawprint(
966     long argc,
967     const char **args)
969     if (argc == 1 && calcpr) {
970         long      i = 0;
972         while (calcpr[i] != NULL)
973             i++;        /*determine number lines in calcpr */
974         if (atol(args[0]) < i - 1)
975             return stralloc(calcpr[atol(args[0]) + 1]);
976     }
977     return stralloc("[ERROR: RRD::PRINT argument error]");
980 char     *printtimelast(
981     long argc,
982     const char **args)
984     time_t    last;
985     struct tm tm_last;
986     char     *buf;
988     if (argc == 2) {
989         buf = malloc(255);
990         if (buf == NULL) {
991             return stralloc("[ERROR: allocating strftime buffer]");
992         };
993         /* not raising argc in step with args - 1 since the last argument
994            will be used below for strftime  */
996         last = rrd_last(argc, (char **) args - 1);
997         if (rrd_test_error()) {
998             char     *err =
999                 malloc((strlen(rrd_get_error()) +
1000                         DS_NAM_SIZE) * sizeof(char));
1001             sprintf(err, "[ERROR: %s]", rrd_get_error());
1002             rrd_clear_error();
1003             return err;
1004         }
1005         tm_last = *localtime(&last);
1006         strftime(buf, 254, args[1], &tm_last);
1007         return buf;
1008     }
1009     return stralloc("[ERROR: expected <RRD::TIME::LAST file.rrd strftime-format>]");
1012 char     *printtimenow(
1013     long argc,
1014     const char **args)
1016     time_t    now = time(NULL);
1017     struct tm tm_now;
1018     char     *buf;
1020     if (argc == 1) {
1021         buf = malloc(255);
1022         if (buf == NULL) {
1023             return stralloc("[ERROR: allocating strftime buffer]");
1024         };
1025         tm_now = *localtime(&now);
1026         strftime(buf, 254, args[0], &tm_now);
1027         return buf;
1028     }
1029     if (argc < 1) {
1030         return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
1031     }
1032     return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
1035 /* Scan buffer until an unescaped '>' arives.
1036  * Update argument array with arguments found.
1037  * Return end cursor where parsing stopped, or NULL in case of failure.
1038  *
1039  * FIXME:
1040  * To allow nested constructs, we call rrd_expand_vars() for arguments
1041  * that contain RRD::x directives. These introduce a small memory leak
1042  * since we have to stralloc the arguments the way parse() works.
1043  */
1044 char     *scanargs(
1045     char *line,
1046     int *argument_count,
1047     char ***arguments)
1049     char     *getP;     /* read cursor */
1050     char     *putP;     /* write cursor */
1051     char      Quote;    /* type of quote if in quoted string, 0 otherwise */
1052     int       tagcount; /* open tag count */
1053     int       in_arg;   /* if we currently are parsing an argument or not */
1054     int       argsz;    /* argument array size */
1055     int       curarg_contains_rrd_directives;
1057     /* local array of arguments while parsing */
1058     int       argc = 1;
1059     char    **argv;
1061 #ifdef DEBUG_PARSER
1062     printf("<-- scanargs(%s) -->\n", line);
1063 #endif
1065     *arguments = NULL;
1066     *argument_count = 0;
1068     /* create initial argument array of char pointers */
1069     argsz = 32;
1070     argv = (char **) malloc(argsz * sizeof(char *));
1071     if (!argv) {
1072         return NULL;
1073     }
1074     argv[0] = "rrdcgi";
1076     /* skip leading blanks */
1077     while (isspace((int) *line)) {
1078         line++;
1079     }
1081     getP = line;
1082     putP = line;
1084     Quote = 0;
1085     in_arg = 0;
1086     tagcount = 0;
1088     curarg_contains_rrd_directives = 0;
1090     /* start parsing 'line' for arguments */
1091     while (*getP) {
1092         unsigned char c = *getP++;
1094         if (c == '>' && !Quote && !tagcount) {
1095             /* this is our closing tag, quit scanning */
1096             break;
1097         }
1099         /* remove all special chars */
1100         if (c < ' ') {
1101             c = ' ';
1102         }
1104         switch (c) {
1105         case ' ':
1106             if (Quote || tagcount) {
1107                 /* copy quoted/tagged (=RRD expanded) string */
1108                 *putP++ = c;
1109             } else if (in_arg) {
1110                 /* end argument string */
1111                 *putP++ = 0;
1112                 in_arg = 0;
1113                 if (curarg_contains_rrd_directives) {
1114                     argv[argc - 1] =
1115                         rrd_expand_vars(stralloc(argv[argc - 1]));
1116                     curarg_contains_rrd_directives = 0;
1117                 }
1118             }
1119             break;
1121         case '"':      /* Fall through */
1122         case '\'':
1123             if (Quote != 0) {
1124                 if (Quote == c) {
1125                     Quote = 0;
1126                 } else {
1127                     /* copy quoted string */
1128                     *putP++ = c;
1129                 }
1130             } else {
1131                 if (!in_arg) {
1132                     /* reference start of argument string in argument array */
1133                     argv[argc++] = putP;
1134                     in_arg = 1;
1135                 }
1136                 Quote = c;
1137             }
1138             break;
1140         default:
1141             if (!in_arg) {
1142                 /* start new argument */
1143                 argv[argc++] = putP;
1144                 in_arg = 1;
1145             }
1146             if (c == '>') {
1147                 if (tagcount) {
1148                     tagcount--;
1149                 }
1150             }
1151             if (c == '<') {
1152                 tagcount++;
1153                 if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
1154                     curarg_contains_rrd_directives = 1;
1155                 }
1156             }
1157             *putP++ = c;
1158             break;
1159         }
1161         /* check if our argument array is still large enough */
1162         if (argc == argsz) {
1163             /* resize argument array */
1164             argsz *= 2;
1165             argv = rrd_realloc(argv, argsz * sizeof(char *));
1166             if (*argv == NULL) {
1167                 return NULL;
1168             }
1169         }
1170     }
1172     /* terminate last argument found */
1173     *putP = '\0';
1174     if (curarg_contains_rrd_directives) {
1175         argv[argc - 1] = rrd_expand_vars(stralloc(argv[argc - 1]));
1176     }
1177 #ifdef DEBUG_PARSER
1178     if (argc > 1) {
1179         int       n;
1181         printf("<-- arguments found [%d]\n", argc);
1182         for (n = 0; n < argc; n++) {
1183             printf("arg %02d: '%s'\n", n, argv[n]);
1184         }
1185         printf("-->\n");
1186     } else {
1187         printf("<!-- No arguments found -->\n");
1188     }
1189 #endif
1191     /* update caller's notion of the argument array and it's size */
1193     /* note this is a bit of a hack since the rrd_cgi code used to just put
1194        its arguments into a normal array starting at 0 ... since the rrd_*
1195        commands expect and argc/argv array we used to just shift everything
1196        by -1 ... this in turn exploded when a rrd_* function tried to print
1197        argv[0] ... hence we are now doing everything in argv style but hand
1198        over seemingly the old array ... but doing argv-1 will actually end
1199        up in a 'good' place now. */
1201     *arguments = argv+1;
1202     *argument_count = argc-1;
1204     if (Quote) {
1205         return NULL;
1206     }
1208     /* Return new scanning cursor:
1209        pointer to char after closing bracket */
1210     return getP;
1214 /*
1215  * Parse(): scan current portion of buffer for given tag.
1216  * If found, parse tag arguments and call 'func' for it.
1217  * The result of func is inserted at the current position
1218  * in the buffer.
1219  */
1220 int parse(
1221     char **buf,         /* buffer */
1222     long i,             /* offset in buffer */
1223     char *tag,          /* tag to handle  */
1224     char *    (*func) (long,
1225                        const char **)   /* function to call for 'tag' */
1226     )
1228     /* the name of the vairable ... */
1229     char     *val;
1230     long      valln;
1231     char    **args;
1232     char     *end;
1233     long      end_offset;
1234     int       argc;
1235     size_t    taglen = strlen(tag);
1237     /* Current position in buffer should start with 'tag' */
1238     if (strncmp((*buf) + i, tag, taglen) != 0) {
1239         return 0;
1240     }
1241     /* .. and match exactly (a whitespace following 'tag') */
1242     if (!isspace(*((*buf) + i + taglen))) {
1243         return 0;
1244     }
1245 #ifdef DEBUG_PARSER
1246     printf("parse(): handling tag '%s'\n", tag);
1247 #endif
1249     /* Scan for arguments following the tag;
1250        scanargs() puts \0 into *buf ... so after scanargs it is probably
1251        not a good time to use strlen on buf */
1252     end = scanargs((*buf) + i + taglen, &argc, &args);
1253     if (end) {
1254         /* got arguments, call function for 'tag' with arguments */
1255         val = func(argc, (const char **) args);
1256         free(args-1);
1257     } else {
1258         /* next call, try parsing at current offset +1 */
1259         end = (*buf) + i + 1;
1261         val = stralloc("[ERROR: Parsing Problem with the following text\n"
1262                        " Check original file. This may have been altered "
1263                        "by parsing.]\n\n");
1264     }
1266     /* remember offset where we have to continue parsing */
1267     end_offset = end - (*buf);
1269     valln = 0;
1270     if (val) {
1271         valln = strlen(val);
1272     }
1274     /* Optionally resize buffer to hold the replacement value:
1275        Calculating the new length of the buffer is simple. add current
1276        buffer pos (i) to length of string after replaced tag to length
1277        of replacement string and add 1 for the final zero ... */
1278     if (end - (*buf) < (i + valln)) {
1279         /* make sure we do not shrink the mallocd block */
1280         size_t    newbufsize = i + strlen(end) + valln + 1;
1282         *buf = rrd_realloc(*buf, newbufsize);
1284         if (*buf == NULL) {
1285             perror("Realoc buf:");
1286             exit(1);
1287         };
1288     }
1290     /* Update new end pointer:
1291        make sure the 'end' pointer gets moved along with the 
1292        buf pointer when realloc moves memory ... */
1293     end = (*buf) + end_offset;
1295     /* splice the variable:
1296        step 1. Shift pending data to make room for 'val' */
1297     memmove((*buf) + i + valln, end, strlen(end) + 1);
1299     /* step 2. Insert val */
1300     if (val) {
1301         memmove((*buf) + i, val, valln);
1302         free(val);
1303     }
1304     return (valln > 0 ? valln - 1 : valln);
1307 char     *http_time(
1308     time_t *now)
1310     struct tm *tmptime;
1311     static char buf[60];
1313     tmptime = gmtime(now);
1314     strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", tmptime);
1315     return (buf);
1318 void rrdcgiHeader(
1319     void)
1321     if (rrdcgiType)
1322         printf("Content-type: %s\n", rrdcgiType);
1323     else
1324         printf("Content-type: text/html\n");
1325     if (rrdcgiHeaderString)
1326         printf("%s", rrdcgiHeaderString);
1327     printf("\n");
1330 void rrdcgiDebug(
1331     int level,
1332     int where)
1334     if (level > 0)
1335         rrdcgiDebugLevel = level;
1336     else
1337         rrdcgiDebugLevel = 0;
1338     if (where)
1339         rrdcgiDebugStderr = 0;
1340     else
1341         rrdcgiDebugStderr = 1;
1344 char     *rrdcgiDecodeString(
1345     char *text)
1347     char     *cp, *xp;
1349     for (cp = text, xp = text; *cp; cp++) {
1350         if (*cp == '%') {
1351             if (strchr("0123456789ABCDEFabcdef", *(cp + 1))
1352                 && strchr("0123456789ABCDEFabcdef", *(cp + 2))) {
1353                 if (islower(*(cp + 1)))
1354                     *(cp + 1) = toupper(*(cp + 1));
1355                 if (islower(*(cp + 2)))
1356                     *(cp + 2) = toupper(*(cp + 2));
1357                 *(xp) =
1358                     (*(cp + 1) >=
1359                      'A' ? *(cp + 1) - 'A' + 10 : *(cp + 1) - '0') * 16 +
1360                     (*(cp + 2) >=
1361                      'A' ? *(cp + 2) - 'A' + 10 : *(cp + 2) - '0');
1362                 xp++;
1363                 cp += 2;
1364             }
1365         } else {
1366             *(xp++) = *cp;
1367         }
1368     }
1369     memset(xp, 0, cp - xp);
1370     return text;
1373 /*  rrdcgiReadVariables()
1374  *
1375  *  Read from stdin if no string is provided via CGI.  Variables that
1376  *  doesn't have a value associated with it doesn't get stored.
1377  */
1378 s_var   **rrdcgiReadVariables(
1379     void)
1381     int       length;
1382     char     *line = NULL;
1383     int       numargs;
1384     char     *cp, *ip, *esp, *sptr;
1385     s_var   **result;
1386     int       i, k, len;
1387     char      tmp[101];
1389     cp = getenv("REQUEST_METHOD");
1390     ip = getenv("CONTENT_LENGTH");
1392     if (cp && !strcmp(cp, "POST")) {
1393         if (ip) {
1394             length = atoi(ip);
1395             if ((line = (char *) malloc(length + 2)) == NULL)
1396                 return NULL;
1397             if (fgets(line, length + 1, stdin) == NULL)
1398                 return NULL;
1399         } else
1400             return NULL;
1401     } else if (cp && !strcmp(cp, "GET")) {
1402         esp = getenv("QUERY_STRING");
1403         if (esp && strlen(esp)) {
1404             if ((line = (char *) malloc(strlen(esp) + 2)) == NULL)
1405                 return NULL;
1406             sprintf(line, "%s", esp);
1407         } else
1408             return NULL;
1409     } else {
1410         length = 0;
1411         printf("(offline mode: enter name=value pairs on standard input)\n");
1412         memset(tmp, 0, sizeof(tmp));
1413         while ((cp = fgets(tmp, 100, stdin)) != NULL) {
1414             if (strlen(tmp)) {
1415                 if (tmp[strlen(tmp) - 1] == '\n')
1416                     tmp[strlen(tmp) - 1] = '&';
1417                 if (length) {
1418                     length += strlen(tmp);
1419                     len = (length + 1) * sizeof(char);
1420                     if ((line = (char *) realloc(line, len)) == NULL)
1421                         return NULL;
1422                     strcat(line, tmp);
1423                 } else {
1424                     length = strlen(tmp);
1425                     len = (length + 1) * sizeof(char);
1426                     if ((line = (char *) malloc(len)) == NULL)
1427                         return NULL;
1428                     memset(line, 0, len);
1429                     strcpy(line, tmp);
1430                 }
1431             }
1432             memset(tmp, 0, sizeof(tmp));
1433         }
1434         if (!line)
1435             return NULL;
1436         if (line[strlen(line) - 1] == '&')
1437             line[strlen(line) - 1] = '\0';
1438     }
1440     /*
1441      *  From now on all cgi variables are stored in the variable line
1442      *  and look like  foo=bar&foobar=barfoo&foofoo=
1443      */
1445     if (rrdcgiDebugLevel > 0) {
1446         if (rrdcgiDebugStderr)
1447             fprintf(stderr, "Received cgi input: %s\n", line);
1448         else
1449             printf
1450                 ("<b>Received cgi input</b><br>\n<pre>\n--\n%s\n--\n</pre>\n\n",
1451                  line);
1452     }
1454     for (cp = line; *cp; cp++)
1455         if (*cp == '+')
1456             *cp = ' ';
1458     if (strlen(line)) {
1459         for (numargs = 1, cp = line; *cp; cp++)
1460             if (*cp == '&')
1461                 numargs++;
1462     } else
1463         numargs = 0;
1464     if (rrdcgiDebugLevel > 0) {
1465         if (rrdcgiDebugStderr)
1466             fprintf(stderr, "%d cgi variables found.\n", numargs);
1467         else
1468             printf("%d cgi variables found.<br>\n", numargs);
1469     }
1471     len = (numargs + 1) * sizeof(s_var *);
1472     if ((result = (s_var **) malloc(len)) == NULL)
1473         return NULL;
1474     memset(result, 0, len);
1476     cp = line;
1477     i = 0;
1478     while (*cp) {
1479         if ((ip = (char *) strchr(cp, '&')) != NULL) {
1480             *ip = '\0';
1481         } else
1482             ip = cp + strlen(cp);
1484         if ((esp = (char *) strchr(cp, '=')) == NULL) {
1485             cp = ++ip;
1486             continue;
1487         }
1489         if (!strlen(esp)) {
1490             cp = ++ip;
1491             continue;
1492         }
1494         if (i < numargs) {
1496             /* try to find out if there's already such a variable */
1497             for (k = 0; k < i && (strncmp(result[k]->name, cp, esp - cp)
1498                                   || !(strlen(result[k]->name) ==
1499                                        (size_t) (esp - cp))); k++);
1501             if (k == i) {   /* No such variable yet */
1502                 if ((result[i] = (s_var *) malloc(sizeof(s_var))) == NULL)
1503                     return NULL;
1504                 if ((result[i]->name =
1505                      (char *) malloc((esp - cp + 1) * sizeof(char))) == NULL)
1506                     return NULL;
1507                 memset(result[i]->name, 0, esp - cp + 1);
1508                 strncpy(result[i]->name, cp, esp - cp);
1509                 cp = ++esp;
1510                 if ((result[i]->value =
1511                      (char *) malloc((ip - esp + 1) * sizeof(char))) == NULL)
1512                     return NULL;
1513                 memset(result[i]->value, 0, ip - esp + 1);
1514                 strncpy(result[i]->value, cp, ip - esp);
1515                 result[i]->value = rrdcgiDecodeString(result[i]->value);
1516                 if (rrdcgiDebugLevel) {
1517                     if (rrdcgiDebugStderr)
1518                         fprintf(stderr, "%s: %s\n", result[i]->name,
1519                                 result[i]->value);
1520                     else
1521                         printf("<h3>Variable %s</h3>\n<pre>\n%s\n</pre>\n\n",
1522                                result[i]->name, result[i]->value);
1523                 }
1524                 i++;
1525             } else {    /* There is already such a name, suppose a mutiple field */
1526                 cp = ++esp;
1527                 len =
1528                     (strlen(result[k]->value) + (ip - esp) +
1529                      2) * sizeof(char);
1530                 if ((sptr = (char *) malloc(len)) == NULL)
1531                     return NULL;
1532                 memset(sptr, 0, len);
1533                 sprintf(sptr, "%s\n", result[k]->value);
1534                 strncat(sptr, cp, ip - esp);
1535                 free(result[k]->value);
1536                 result[k]->value = rrdcgiDecodeString(sptr);
1537             }
1538         }
1539         cp = ++ip;
1540     }
1541     return result;
1544 /*  rrdcgiInit()
1545  *
1546  *  Read from stdin if no string is provided via CGI.  Variables that
1547  *  doesn't have a value associated with it doesn't get stored.
1548  */
1549 s_cgi    *rrdcgiInit(
1550     void)
1552     s_cgi    *res;
1553     s_var   **vars;
1555     vars = rrdcgiReadVariables();
1557     if (!vars)
1558         return NULL;
1560     if ((res = (s_cgi *) malloc(sizeof(s_cgi))) == NULL)
1561         return NULL;
1562     res->vars = vars;
1564     return res;
1567 char     *rrdcgiGetValue(
1568     s_cgi * parms,
1569     const char *name)
1571     int       i;
1573     if (!parms || !parms->vars)
1574         return NULL;
1575     for (i = 0; parms->vars[i]; i++)
1576         if (!strcmp(name, parms->vars[i]->name)) {
1577             if (rrdcgiDebugLevel > 0) {
1578                 if (rrdcgiDebugStderr)
1579                     fprintf(stderr, "%s found as %s\n", name,
1580                             parms->vars[i]->value);
1581                 else
1582                     printf("%s found as %s<br>\n", name,
1583                            parms->vars[i]->value);
1584             }
1585             return parms->vars[i]->value;
1586         }
1587     if (rrdcgiDebugLevel) {
1588         if (rrdcgiDebugStderr)
1589             fprintf(stderr, "%s not found\n", name);
1590         else
1591             printf("%s not found<br>\n", name);
1592     }
1593     return NULL;
1596 void rrdcgiFreeList(
1597     char **list)
1599     int       i;
1601     for (i = 0; list[i] != NULL; i++)
1602         free(list[i]);
1603     free(list);
1606 void rrdcgiFree(
1607     s_cgi * parms)
1609     int       i;
1611     if (!parms)
1612         return;
1613     if (parms->vars) {
1614         for (i = 0; parms->vars[i]; i++) {
1615             if (parms->vars[i]->name)
1616                 free(parms->vars[i]->name);
1617             if (parms->vars[i]->value)
1618                 free(parms->vars[i]->value);
1619             free(parms->vars[i]);
1620         }
1621         free(parms->vars);
1622     }
1623     free(parms);
1625     if (rrdcgiHeaderString) {
1626         free(rrdcgiHeaderString);
1627         rrdcgiHeaderString = NULL;
1628     }
1629     if (rrdcgiType) {
1630         free(rrdcgiType);
1631         rrdcgiType = NULL;
1632     }