Code

1ffe8fd4639559668a2798d2bc208b42985cb14c
[rrdtool.git] / src / rrd_cgi.c
1 /*****************************************************************************
2  * RRDtool 1.4.3  Copyright by Tobi Oetiker, 1997-2010
3  *****************************************************************************
4  * rrd_cgi.c  RRD Web Page Generator
5  *****************************************************************************/
7 #include "rrd_tool.h"
8 #ifdef HAVE_STDLIB_H
9 #include <stdlib.h>
10 #endif
12 #ifdef WIN32
13    #define strcasecmp stricmp
14    #define strcasencmp strnicmp
15 #endif
17 #define MEMBLK 1024
18 /*#define DEBUG_PARSER
19 #define DEBUG_VARS*/
21 typedef struct var_s {
22     char     *name, *value;
23 } s_var;
25 typedef struct cgi_s {
26     s_var   **vars;
27 } s_cgi;
29 /* in arg[0] find tags beginning with arg[1] call arg[2] on them
30    and replace by result of arg[2] call */
31 int       parse(
32     char **,
33     long,
34     char *,
35     char *    (*)(long,
36                   const char **));
38 /**************************************************/
39 /* tag replacers ... they are called from parse   */
40 /* through function pointers                      */
41 /**************************************************/
43 /* return cgi var named arg[0] */
44 char     *cgiget(
45     long,
46     const char **);
48 /* return a quoted cgi var named arg[0] */
49 char     *cgigetq(
50     long,
51     const char **);
53 /* return a quoted and sanitized cgi variable */
54 char     *cgigetqp(
55     long,
56     const char **);
58 /* call rrd_graph and insert appropriate image tag */
59 char     *drawgraph(
60     long,
61     const char **);
63 /* return PRINT functions from last rrd_graph call */
64 char     *drawprint(
65     long,
66     const char **);
68 /* pretty-print the <last></last> value for some.rrd via strftime() */
69 char     *printtimelast(
70     long,
71     const char **);
73 /* pretty-print current time */
74 char     *printtimenow(
75     long,
76     const char **);
78 /* set an environment variable */
79 char     *rrdsetenv(
80     long,
81     const char **);
83 /* get an environment variable */
84 char     *rrdgetenv(
85     long,
86     const char **);
88 /* include the named file at this point */
89 char     *includefile(
90     long,
91     const char **);
93 /* for how long is the output of the cgi valid ? */
94 char     *rrdgoodfor(
95     long,
96     const char **);
98 /* return rrdcgi version string */
99 char     *rrdgetinternal(
100     long,
101     const char **);
103 char     *rrdstrip(
104     char *buf);
105 char     *scanargs(
106     char *line,
107     int *argc,
108     char ***args);
110 /* format at-time specified times using strftime */
111 char     *printstrftime(
112     long,
113     const char **);
115 /** HTTP protocol needs special format, and GMT time **/
116 char     *http_time(
117     time_t *);
119 /* return a pointer to newly allocated copy of this string */
120 char     *stralloc(
121     const char *);
123 /* global variable for rrdcgi */
124 s_cgi    *rrdcgiArg;
126 /* rrdcgiHeader
127  * 
128  *  Prints a valid CGI Header (Content-type...) etc.
129  */
130 void      rrdcgiHeader(
131     void);
133 /* rrdcgiDecodeString
134  * decode html escapes
135  */
137 char     *rrdcgiDecodeString(
138     char *text);
140 /* rrdcgiDebug
141  * 
142  *  Set/unsets debugging
143  */
144 void      rrdcgiDebug(
145     int level,
146     int where);
148 /* rrdcgiInit
149  *
150  *  Reads in variables set via POST or stdin.
151  */
152 s_cgi    *rrdcgiInit(
153     void);
155 /* rrdcgiGetValue
156  *
157  *  Returns the value of the specified variable or NULL if it's empty
158  *  or doesn't exist.
159  */
160 char     *rrdcgiGetValue(
161     s_cgi * parms,
162     const char *name);
164 /* rrdcgiFreeList
165  *
166  * Frees a list as returned by rrdcgiGetVariables()
167  */
168 void      rrdcgiFreeList(
169     char **list);
171 /* rrdcgiFree
172  *
173  * Frees the internal data structures
174  */
175 void      rrdcgiFree(
176     s_cgi * parms);
178 /*  rrdcgiReadVariables()
179  *
180  *  Read from stdin if no string is provided via CGI.  Variables that
181  *  doesn't have a value associated with it doesn't get stored.
182  */
183 s_var   **rrdcgiReadVariables(
184     void);
187 int       rrdcgiDebugLevel = 0;
188 int       rrdcgiDebugStderr = 1;
189 char     *rrdcgiHeaderString = NULL;
190 char     *rrdcgiType = NULL;
192 /* rrd interface to the variable functions {put,get}var() */
193 char     *rrdgetvar(
194     long argc,
195     const char **args);
196 char     *rrdsetvar(
197     long argc,
198     const char **args);
199 char     *rrdsetvarconst(
200     long argc,
201     const char **args);
204 /* variable store: put/get key-value pairs */
205 static int initvar(
206     );
207 static void donevar(
208     );
209 static const char *getvar(
210     const char *varname);
211 static const char *putvar(
212     const char *name,
213     const char *value,
214     int is_const);
216 /* key value pair that makes up an entry in the variable store */
217 typedef struct {
218     int       is_const; /* const variable or not */
219     const char *name;   /* variable name */
220     const char *value;  /* variable value */
221 } vardata;
223 /* the variable heap: 
224    start with a heapsize of 10 variables */
225 #define INIT_VARSTORE_SIZE      10
226 static vardata *varheap = NULL;
227 static size_t varheap_size = 0;
229 /* allocate and initialize variable heap */
230 static int initvar(
231     void)
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     long      length;
462     char     *buffer;
463     char     *server_url = NULL;
464     long      i;
465     long      filter = 0;
466     struct option long_options[] = {
467         {"filter", no_argument, 0, 'f'},
468         {0, 0, 0, 0}
469     };
471 #ifdef MUST_DISABLE_SIGFPE
472     signal(SIGFPE, SIG_IGN);
473 #endif
474 #ifdef MUST_DISABLE_FPMASK
475     fpsetmask(0);
476 #endif
477     optind = 0;
478     opterr = 0;         /* initialize getopt */
480     /* what do we get for cmdline arguments?
481        for (i=0;i<argc;i++)
482        printf("%d-'%s'\n",i,argv[i]); */
483     while (1) {
484         int       option_index = 0;
485         int       opt;
487         opt = getopt_long(argc, argv, "f", long_options, &option_index);
488         if (opt == EOF) {
489             break;
490         }
492         switch (opt) {
493         case 'f':
494             filter = 1;
495             break;
496         case '?':
497             printf("unknown commandline option '%s'\n", argv[optind - 1]);
498             return -1;
499         }
500     }
502     if (!filter) {
503         rrdcgiDebug(0, 0);
504         rrdcgiArg = rrdcgiInit();
505         server_url = getenv("SERVER_URL");
506     }
508     /* make sure we have one extra argument, 
509        if there are others, we do not care Apache gives several */
511     /* if ( (optind != argc-2 
512        && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) 
513        && optind != argc-1) { */
515     if (optind >= argc) {
516         fprintf(stderr, "ERROR: expected a filename\n");
517         exit(1);
518     } else {
519         length = readfile(argv[optind], &buffer, 1);
520     }
522     if (rrd_test_error()) {
523         fprintf(stderr, "ERROR: %s\n", rrd_get_error());
524         exit(1);
525     }
527     /* initialize variable heap */
528     initvar();
530 #ifdef DEBUG_PARSER
531     /* some fake header for testing */
532     printf("Content-Type: text/html\nContent-Length: 10000000\n\n\n");
533 #endif
536     /* expand rrd directives in buffer recursivly */
537     for (i = 0; buffer[i]; i++) {
538         if (buffer[i] != '<')
539             continue;
540         if (!filter) {
541             parse(&buffer, i, "<RRD::CV", cgiget);
542             parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
543             parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
544             parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
545         }
546         parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
547         parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
548         parse(&buffer, i, "<RRD::GRAPH", drawgraph);
549         parse(&buffer, i, "<RRD::INCLUDE", includefile);
550         parse(&buffer, i, "<RRD::PRINT", drawprint);
551         parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
552         parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
553         parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
554         parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
555         parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
556         parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
557         parse(&buffer, i, "<RRD::INTERNAL", rrdgetinternal);
558     }
560     if (!filter) {
561         printf("Content-Type: text/html\n"
562                "Content-Length: %zd\n", strlen(buffer));
564         if (labs(goodfor) > 0) {
565             time_t    now;
567             now = time(NULL);
568             printf("Last-Modified: %s\n", http_time(&now));
569             now += labs(goodfor);
570             printf("Expires: %s\n", http_time(&now));
571             if (goodfor < 0) {
572                 printf("Refresh: %ld\n", labs(goodfor));
573             }
574         }
575         printf("\n");
576     }
578     /* output result */
579     printf("%s", buffer);
581     /* cleanup */
582     calfree();
583     if (buffer) {
584         free(buffer);
585     }
586     donevar();
587     exit(0);
590 /* remove occurrences of .. this is a general measure to make
591    paths which came in via cgi do not go UP ... */
593 char     *rrdsetenv(
594     long argc,
595     const char **args)
597     if (argc >= 2) {
598         char     *xyz = malloc((strlen(args[0]) + strlen(args[1]) + 2));
600         if (xyz == NULL) {
601             return stralloc("[ERROR: allocating setenv buffer]");
602         };
603         sprintf(xyz, "%s=%s", args[0], args[1]);
604         if (putenv(xyz) == -1) {
605             free(xyz);
606             return stralloc("[ERROR: failed to do putenv]");
607         };
608         return stralloc("");
609     }
610     return stralloc("[ERROR: setenv failed because not enough "
611                     "arguments were defined]");
614 /* rrd interface to the variable function putvar() */
615 char     *rrdsetvar(
616     long argc,
617     const char **args)
619     if (argc >= 2) {
620         const char *result = putvar(args[0], args[1], 0 /* not const */ );
622         if (result) {
623             /* setvar does not return the value set */
624             return stralloc("");
625         }
626         return stralloc("[ERROR: putvar failed]");
627     }
628     return stralloc("[ERROR: putvar failed because not enough arguments "
629                     "were defined]");
632 /* rrd interface to the variable function putvar() */
633 char     *rrdsetvarconst(
634     long argc,
635     const char **args)
637     if (argc >= 2) {
638         const char *result = putvar(args[0], args[1], 1 /* const */ );
640         if (result) {
641             /* setvar does not return the value set */
642             return stralloc("");
643         }
644         return stralloc("[ERROR: putvar failed]");
645     }
646     return stralloc("[ERROR: putvar failed because not enough arguments "
647                     "were defined]");
650 char     *rrdgetenv(
651     long argc,
652     const char **args)
654     char      buf[128];
655     const char *envvar;
657     if (argc != 1) {
658         return stralloc("[ERROR: getenv failed because it did not "
659                         "get 1 argument only]");
660     };
661     envvar = getenv(args[0]);
662     if (envvar) {
663         return stralloc(envvar);
664     } else {
665         snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
666         return stralloc(buf);
667     }
670 char     *rrdgetvar(
671     long argc,
672     const char **args)
674     char      buf[128];
675     const char *value;
677     if (argc != 1) {
678         return stralloc("[ERROR: getvar failed because it did not "
679                         "get 1 argument only]");
680     };
681     value = getvar(args[0]);
682     if (value) {
683         return stralloc(value);
684     } else {
685         snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
686         return stralloc(buf);
687     }
690 char     *rrdgoodfor(
691     long argc,
692     const char **args)
694     if (argc == 1) {
695         goodfor = atol(args[0]);
696     } else {
697         return stralloc("[ERROR: goodfor expected 1 argument]");
698     }
700     if (goodfor == 0) {
701         return stralloc("[ERROR: goodfor value must not be 0]");
702     }
704     return stralloc("");
707 char     *rrdgetinternal(
708     long argc,
709     const char **args)
711     if (argc == 1) {
712         if (strcasecmp(args[0], "VERSION") == 0) {
713             return stralloc(PACKAGE_VERSION);
714         } else if (strcasecmp(args[0], "COMPILETIME") == 0) {
715             return stralloc(__DATE__ " " __TIME__);
716         } else {
717             return stralloc("[ERROR: internal unknown argument]");
718         }
719     } else {
720         return stralloc("[ERROR: internal expected 1 argument]");
721     }
724 /* Format start or end times using strftime.  We always need both the
725  * start and end times, because, either might be relative to the other.
726  * */
727 #define MAX_STRFTIME_SIZE 256
728 char     *printstrftime(
729     long argc,
730     const char **args)
732     rrd_time_value_t start_tv, end_tv;
733     char     *parsetime_error = NULL;
734     char      formatted[MAX_STRFTIME_SIZE];
735     struct tm *the_tm;
736     time_t    start_tmp, end_tmp;
738     /* Make sure that we were given the right number of args */
739     if (argc != 4) {
740         rrd_set_error("wrong number of args %d", argc);
741         return stralloc("");
742     }
744     /* Init start and end time */
745     rrd_parsetime("end-24h", &start_tv);
746     rrd_parsetime("now", &end_tv);
748     /* Parse the start and end times we were given */
749     if ((parsetime_error = rrd_parsetime(args[1], &start_tv))) {
750         rrd_set_error("start time: %s", parsetime_error);
751         return stralloc("");
752     }
753     if ((parsetime_error = rrd_parsetime(args[2], &end_tv))) {
754         rrd_set_error("end time: %s", parsetime_error);
755         return stralloc("");
756     }
757     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
758         return stralloc("");
759     }
761     /* Do we do the start or end */
762     if (strcasecmp(args[0], "START") == 0) {
763         the_tm = localtime(&start_tmp);
764     } else if (strcasecmp(args[0], "END") == 0) {
765         the_tm = localtime(&end_tmp);
766     } else {
767         rrd_set_error("start/end not found in '%s'", args[0]);
768         return stralloc("");
769     }
771     /* now format it */
772     if (strftime(formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
773         return (stralloc(formatted));
774     } else {
775         rrd_set_error("strftime failed");
776         return stralloc("");
777     }
780 char     *includefile(
781     long argc,
782     const char **args)
784     char     *buffer;
786     if (argc >= 1) {
787         const char *filename = args[0];
789         readfile(filename, &buffer, 0);
790         if (rrd_test_error()) {
791             char     *err = malloc((strlen(rrd_get_error()) + DS_NAM_SIZE));
793             sprintf(err, "[ERROR: %s]", rrd_get_error());
794             rrd_clear_error();
795             return err;
796         } else {
797             return buffer;
798         }
799     } else {
800         return stralloc("[ERROR: No Inclue file defined]");
801     }
804 /* make a copy of buf and replace open/close brackets with '_' */
805 char     *rrdstrip(
806     char *buf)
808     char     *p;
810     if (buf == NULL) {
811         return NULL;
812     }
813     /* make a copy of the buffer */
814     buf = stralloc(buf);
815     if (buf == NULL) {
816         return NULL;
817     }
819     p = buf;
820     while (*p) {
821         if (*p == '<' || *p == '>') {
822             *p = '_';
823         }
824         p++;
825     }
826     return buf;
829 char     *cgigetq(
830     long argc,
831     const char **args)
833     if (argc >= 1) {
834         char     *buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
835         char     *buf2;
836         char     *c, *d;
837         int       qc = 0;
839         if (buf == NULL)
840             return NULL;
842         for (c = buf; *c != '\0'; c++)
843             if (*c == '"')
844                 qc++;
845         if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
846             perror("Malloc Buffer");
847             exit(1);
848         };
849         c = buf;
850         d = buf2;
851         *(d++) = '"';
852         while (*c != '\0') {
853             if (*c == '"') {
854                 *(d++) = '"';
855                 *(d++) = '\'';
856                 *(d++) = '"';
857                 *(d++) = '\'';
858             }
859             *(d++) = *(c++);
860         }
861         *(d++) = '"';
862         *(d) = '\0';
863         free(buf);
864         return buf2;
865     }
867     return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
870 /* remove occurrences of .. this is a general measure to make
871    paths which came in via cgi do not go UP ... */
873 char     *cgigetqp(
874     long argc,
875     const char **args)
877     char     *buf;
878     char     *buf2;
879     char     *p;
880     char     *d;
882     if (argc < 1) {
883         return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
884     }
886     buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
887     if (!buf) {
888         return NULL;
889     }
891     buf2 = malloc(strlen(buf) + 1);
892     if (!buf2) {
893         perror("cgigetqp(): Malloc Path Buffer");
894         exit(1);
895     };
897     p = buf;
898     d = buf2;
900     while (*p) {
901         /* prevent mallicious paths from entering the system */
902         if (p[0] == '.' && p[1] == '.') {
903             p += 2;
904             *d++ = '_';
905             *d++ = '_';
906         } else {
907             *d++ = *p++;
908         }
909     }
911     *d = 0;
912     free(buf);
914     /* Make sure the path is relative, e.g. does not start with '/' */
915     p = buf2;
916     while ('/' == *p) {
917         *p++ = '_';
918     }
920     return buf2;
924 char     *cgiget(
925     long argc,
926     const char **args)
928     if (argc >= 1)
929         return rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
930     else
931         return stralloc("[ERROR: not enough arguments for RRD::CV]");
936 char     *drawgraph(
937     long argc,
938     const char **args)
940     int       i, xsize, ysize;
941     double    ymin, ymax;
943     for (i = 0; i < argc; i++)
944         if (strcmp(args[i], "--imginfo") == 0 || strcmp(args[i], "-g") == 0)
945             break;
946     if (i == argc) {
947         args[argc++] = "--imginfo";
948         args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
949     }
950     calfree();
951     if (rrd_graph
952         (argc + 1, (char **) args - 1, &calcpr, &xsize, &ysize, NULL, &ymin,
953          &ymax) != -1) {
954         return stralloc(calcpr[0]);
955     } else {
956         if (rrd_test_error()) {
957             char     *err =
958                 malloc((strlen(rrd_get_error()) +
959                         DS_NAM_SIZE) * sizeof(char));
960             sprintf(err, "[ERROR: %s]", rrd_get_error());
961             rrd_clear_error();
962             return err;
963         }
964     }
965     return NULL;
968 char     *drawprint(
969     long argc,
970     const char **args)
972     if (argc == 1 && calcpr) {
973         long      i = 0;
975         while (calcpr[i] != NULL)
976             i++;        /*determine number lines in calcpr */
977         if (atol(args[0]) < i - 1)
978             return stralloc(calcpr[atol(args[0]) + 1]);
979     }
980     return stralloc("[ERROR: RRD::PRINT argument error]");
983 char     *printtimelast(
984     long argc,
985     const char **args)
987     time_t    last;
988     struct tm tm_last;
989     char     *buf;
991     if (argc == 2) {
992         buf = malloc(255);
993         if (buf == NULL) {
994             return stralloc("[ERROR: allocating strftime buffer]");
995         };
996         /* not raising argc in step with args - 1 since the last argument
997            will be used below for strftime  */
999         last = rrd_last(argc, (char **) args - 1);
1000         if (rrd_test_error()) {
1001             char     *err =
1002                 malloc((strlen(rrd_get_error()) +
1003                         DS_NAM_SIZE) * sizeof(char));
1004             sprintf(err, "[ERROR: %s]", rrd_get_error());
1005             rrd_clear_error();
1006             return err;
1007         }
1008         tm_last = *localtime(&last);
1009         strftime(buf, 254, args[1], &tm_last);
1010         return buf;
1011     }
1012     return stralloc("[ERROR: expected <RRD::TIME::LAST file.rrd strftime-format>]");
1015 char     *printtimenow(
1016     long argc,
1017     const char **args)
1019     time_t    now = time(NULL);
1020     struct tm tm_now;
1021     char     *buf;
1023     if (argc == 1) {
1024         buf = malloc(255);
1025         if (buf == NULL) {
1026             return stralloc("[ERROR: allocating strftime buffer]");
1027         };
1028         tm_now = *localtime(&now);
1029         strftime(buf, 254, args[0], &tm_now);
1030         return buf;
1031     }
1032     if (argc < 1) {
1033         return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
1034     }
1035     return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
1038 /* Scan buffer until an unescaped '>' arives.
1039  * Update argument array with arguments found.
1040  * Return end cursor where parsing stopped, or NULL in case of failure.
1041  *
1042  * FIXME:
1043  * To allow nested constructs, we call rrd_expand_vars() for arguments
1044  * that contain RRD::x directives. These introduce a small memory leak
1045  * since we have to stralloc the arguments the way parse() works.
1046  */
1047 char     *scanargs(
1048     char *line,
1049     int *argument_count,
1050     char ***arguments)
1052     char     *getP;     /* read cursor */
1053     char     *putP;     /* write cursor */
1054     char      Quote;    /* type of quote if in quoted string, 0 otherwise */
1055     int       tagcount; /* open tag count */
1056     int       in_arg;   /* if we currently are parsing an argument or not */
1057     int       argsz;    /* argument array size */
1058     int       curarg_contains_rrd_directives;
1060     /* local array of arguments while parsing */
1061     int       argc = 1;
1062     char    **argv;
1064 #ifdef DEBUG_PARSER
1065     printf("<-- scanargs(%s) -->\n", line);
1066 #endif
1068     *arguments = NULL;
1069     *argument_count = 0;
1071     /* create initial argument array of char pointers */
1072     argsz = 32;
1073     argv = (char **) malloc(argsz * sizeof(char *));
1074     if (!argv) {
1075         return NULL;
1076     }
1077     argv[0] = "rrdcgi";
1079     /* skip leading blanks */
1080     while (isspace((int) *line)) {
1081         line++;
1082     }
1084     getP = line;
1085     putP = line;
1087     Quote = 0;
1088     in_arg = 0;
1089     tagcount = 0;
1091     curarg_contains_rrd_directives = 0;
1093     /* start parsing 'line' for arguments */
1094     while (*getP) {
1095         unsigned char c = *getP++;
1097         if (c == '>' && !Quote && !tagcount) {
1098             /* this is our closing tag, quit scanning */
1099             break;
1100         }
1102         /* remove all special chars */
1103         if (c < ' ') {
1104             c = ' ';
1105         }
1107         switch (c) {
1108         case ' ':
1109             if (Quote || tagcount) {
1110                 /* copy quoted/tagged (=RRD expanded) string */
1111                 *putP++ = c;
1112             } else if (in_arg) {
1113                 /* end argument string */
1114                 *putP++ = 0;
1115                 in_arg = 0;
1116                 if (curarg_contains_rrd_directives) {
1117                     argv[argc - 1] =
1118                         rrd_expand_vars(stralloc(argv[argc - 1]));
1119                     curarg_contains_rrd_directives = 0;
1120                 }
1121             }
1122             break;
1124         case '"':      /* Fall through */
1125         case '\'':
1126             if (Quote != 0) {
1127                 if (Quote == c) {
1128                     Quote = 0;
1129                 } else {
1130                     /* copy quoted string */
1131                     *putP++ = c;
1132                 }
1133             } else {
1134                 if (!in_arg) {
1135                     /* reference start of argument string in argument array */
1136                     argv[argc++] = putP;
1137                     in_arg = 1;
1138                 }
1139                 Quote = c;
1140             }
1141             break;
1143         default:
1144             if (!in_arg) {
1145                 /* start new argument */
1146                 argv[argc++] = putP;
1147                 in_arg = 1;
1148             }
1149             if (c == '>') {
1150                 if (tagcount) {
1151                     tagcount--;
1152                 }
1153             }
1154             if (c == '<') {
1155                 tagcount++;
1156                 if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
1157                     curarg_contains_rrd_directives = 1;
1158                 }
1159             }
1160             *putP++ = c;
1161             break;
1162         }
1164         /* check if our argument array is still large enough */
1165         if (argc == argsz) {
1166             /* resize argument array */
1167             argsz *= 2;
1168             argv = rrd_realloc(argv, argsz * sizeof(char *));
1169             if (*argv == NULL) {
1170                 return NULL;
1171             }
1172         }
1173     }
1175     /* terminate last argument found */
1176     *putP = '\0';
1177     if (curarg_contains_rrd_directives) {
1178         argv[argc - 1] = rrd_expand_vars(stralloc(argv[argc - 1]));
1179     }
1180 #ifdef DEBUG_PARSER
1181     if (argc > 1) {
1182         int       n;
1184         printf("<-- arguments found [%d]\n", argc);
1185         for (n = 0; n < argc; n++) {
1186             printf("arg %02d: '%s'\n", n, argv[n]);
1187         }
1188         printf("-->\n");
1189     } else {
1190         printf("<!-- No arguments found -->\n");
1191     }
1192 #endif
1194     /* update caller's notion of the argument array and it's size */
1196     /* note this is a bit of a hack since the rrd_cgi code used to just put
1197        its arguments into a normal array starting at 0 ... since the rrd_*
1198        commands expect and argc/argv array we used to just shift everything
1199        by -1 ... this in turn exploded when a rrd_* function tried to print
1200        argv[0] ... hence we are now doing everything in argv style but hand
1201        over seemingly the old array ... but doing argv-1 will actually end
1202        up in a 'good' place now. */
1204     *arguments = argv+1;
1205     *argument_count = argc-1;
1207     if (Quote) {
1208         return NULL;
1209     }
1211     /* Return new scanning cursor:
1212        pointer to char after closing bracket */
1213     return getP;
1217 /*
1218  * Parse(): scan current portion of buffer for given tag.
1219  * If found, parse tag arguments and call 'func' for it.
1220  * The result of func is inserted at the current position
1221  * in the buffer.
1222  */
1223 int parse(
1224     char **buf,         /* buffer */
1225     long i,             /* offset in buffer */
1226     char *tag,          /* tag to handle  */
1227     char *    (*func) (long,
1228                        const char **)   /* function to call for 'tag' */
1229     )
1231     /* the name of the vairable ... */
1232     char     *val;
1233     long      valln;
1234     char    **args;
1235     char     *end;
1236     long      end_offset;
1237     int       argc;
1238     size_t    taglen = strlen(tag);
1240     /* Current position in buffer should start with 'tag' */
1241     if (strncmp((*buf) + i, tag, taglen) != 0) {
1242         return 0;
1243     }
1244     /* .. and match exactly (a whitespace following 'tag') */
1245     if (!isspace(*((*buf) + i + taglen))) {
1246         return 0;
1247     }
1248 #ifdef DEBUG_PARSER
1249     printf("parse(): handling tag '%s'\n", tag);
1250 #endif
1252     /* Scan for arguments following the tag;
1253        scanargs() puts \0 into *buf ... so after scanargs it is probably
1254        not a good time to use strlen on buf */
1255     end = scanargs((*buf) + i + taglen, &argc, &args);
1256     if (end) {
1257         /* got arguments, call function for 'tag' with arguments */
1258         val = func(argc, (const char **) args);
1259         free(args-1);
1260     } else {
1261         /* unable to parse arguments, undo 0-termination by scanargs */
1262         for (; argc > 0; argc--) {
1263             *((args[argc - 1]) - 1) = ' ';
1264         }
1266         /* next call, try parsing at current offset +1 */
1267         end = (*buf) + i + 1;
1269         val = stralloc("[ERROR: Parsing Problem with the following text\n"
1270                        " Check original file. This may have been altered "
1271                        "by parsing.]\n\n");
1272     }
1274     /* remember offset where we have to continue parsing */
1275     end_offset = end - (*buf);
1277     valln = 0;
1278     if (val) {
1279         valln = strlen(val);
1280     }
1282     /* Optionally resize buffer to hold the replacement value:
1283        Calculating the new length of the buffer is simple. add current
1284        buffer pos (i) to length of string after replaced tag to length
1285        of replacement string and add 1 for the final zero ... */
1286     if (end - (*buf) < (i + valln)) {
1287         /* make sure we do not shrink the mallocd block */
1288         size_t    newbufsize = i + strlen(end) + valln + 1;
1290         *buf = rrd_realloc(*buf, newbufsize);
1292         if (*buf == NULL) {
1293             perror("Realoc buf:");
1294             exit(1);
1295         };
1296     }
1298     /* Update new end pointer:
1299        make sure the 'end' pointer gets moved along with the 
1300        buf pointer when realloc moves memory ... */
1301     end = (*buf) + end_offset;
1303     /* splice the variable:
1304        step 1. Shift pending data to make room for 'val' */
1305     memmove((*buf) + i + valln, end, strlen(end) + 1);
1307     /* step 2. Insert val */
1308     if (val) {
1309         memmove((*buf) + i, val, valln);
1310         free(val);
1311     }
1312     return (valln > 0 ? valln - 1 : valln);
1315 char     *http_time(
1316     time_t *now)
1318     struct tm *tmptime;
1319     static char buf[60];
1321     tmptime = gmtime(now);
1322     strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", tmptime);
1323     return (buf);
1326 void rrdcgiHeader(
1327     void)
1329     if (rrdcgiType)
1330         printf("Content-type: %s\n", rrdcgiType);
1331     else
1332         printf("Content-type: text/html\n");
1333     if (rrdcgiHeaderString)
1334         printf("%s", rrdcgiHeaderString);
1335     printf("\n");
1338 void rrdcgiDebug(
1339     int level,
1340     int where)
1342     if (level > 0)
1343         rrdcgiDebugLevel = level;
1344     else
1345         rrdcgiDebugLevel = 0;
1346     if (where)
1347         rrdcgiDebugStderr = 0;
1348     else
1349         rrdcgiDebugStderr = 1;
1352 char     *rrdcgiDecodeString(
1353     char *text)
1355     char     *cp, *xp;
1357     for (cp = text, xp = text; *cp; cp++) {
1358         if (*cp == '%') {
1359             if (strchr("0123456789ABCDEFabcdef", *(cp + 1))
1360                 && strchr("0123456789ABCDEFabcdef", *(cp + 2))) {
1361                 if (islower(*(cp + 1)))
1362                     *(cp + 1) = toupper(*(cp + 1));
1363                 if (islower(*(cp + 2)))
1364                     *(cp + 2) = toupper(*(cp + 2));
1365                 *(xp) =
1366                     (*(cp + 1) >=
1367                      'A' ? *(cp + 1) - 'A' + 10 : *(cp + 1) - '0') * 16 +
1368                     (*(cp + 2) >=
1369                      'A' ? *(cp + 2) - 'A' + 10 : *(cp + 2) - '0');
1370                 xp++;
1371                 cp += 2;
1372             }
1373         } else {
1374             *(xp++) = *cp;
1375         }
1376     }
1377     memset(xp, 0, cp - xp);
1378     return text;
1381 /*  rrdcgiReadVariables()
1382  *
1383  *  Read from stdin if no string is provided via CGI.  Variables that
1384  *  doesn't have a value associated with it doesn't get stored.
1385  */
1386 s_var   **rrdcgiReadVariables(
1387     void)
1389     int       length;
1390     char     *line = NULL;
1391     int       numargs;
1392     char     *cp, *ip, *esp, *sptr;
1393     s_var   **result;
1394     int       i, k, len;
1395     char      tmp[101];
1397     cp = getenv("REQUEST_METHOD");
1398     ip = getenv("CONTENT_LENGTH");
1400     if (cp && !strcmp(cp, "POST")) {
1401         if (ip) {
1402             length = atoi(ip);
1403             if ((line = (char *) malloc(length + 2)) == NULL)
1404                 return NULL;
1405             if (fgets(line, length + 1, stdin) == NULL)
1406                 return NULL;
1407         } else
1408             return NULL;
1409     } else if (cp && !strcmp(cp, "GET")) {
1410         esp = getenv("QUERY_STRING");
1411         if (esp && strlen(esp)) {
1412             if ((line = (char *) malloc(strlen(esp) + 2)) == NULL)
1413                 return NULL;
1414             sprintf(line, "%s", esp);
1415         } else
1416             return NULL;
1417     } else {
1418         length = 0;
1419         printf("(offline mode: enter name=value pairs on standard input)\n");
1420         memset(tmp, 0, sizeof(tmp));
1421         while ((cp = fgets(tmp, 100, stdin)) != NULL) {
1422             if (strlen(tmp)) {
1423                 if (tmp[strlen(tmp) - 1] == '\n')
1424                     tmp[strlen(tmp) - 1] = '&';
1425                 if (length) {
1426                     length += strlen(tmp);
1427                     len = (length + 1) * sizeof(char);
1428                     if ((line = (char *) realloc(line, len)) == NULL)
1429                         return NULL;
1430                     strcat(line, tmp);
1431                 } else {
1432                     length = strlen(tmp);
1433                     len = (length + 1) * sizeof(char);
1434                     if ((line = (char *) malloc(len)) == NULL)
1435                         return NULL;
1436                     memset(line, 0, len);
1437                     strcpy(line, tmp);
1438                 }
1439             }
1440             memset(tmp, 0, sizeof(tmp));
1441         }
1442         if (!line)
1443             return NULL;
1444         if (line[strlen(line) - 1] == '&')
1445             line[strlen(line) - 1] = '\0';
1446     }
1448     /*
1449      *  From now on all cgi variables are stored in the variable line
1450      *  and look like  foo=bar&foobar=barfoo&foofoo=
1451      */
1453     if (rrdcgiDebugLevel > 0) {
1454         if (rrdcgiDebugStderr)
1455             fprintf(stderr, "Received cgi input: %s\n", line);
1456         else
1457             printf
1458                 ("<b>Received cgi input</b><br>\n<pre>\n--\n%s\n--\n</pre>\n\n",
1459                  line);
1460     }
1462     for (cp = line; *cp; cp++)
1463         if (*cp == '+')
1464             *cp = ' ';
1466     if (strlen(line)) {
1467         for (numargs = 1, cp = line; *cp; cp++)
1468             if (*cp == '&')
1469                 numargs++;
1470     } else
1471         numargs = 0;
1472     if (rrdcgiDebugLevel > 0) {
1473         if (rrdcgiDebugStderr)
1474             fprintf(stderr, "%d cgi variables found.\n", numargs);
1475         else
1476             printf("%d cgi variables found.<br>\n", numargs);
1477     }
1479     len = (numargs + 1) * sizeof(s_var *);
1480     if ((result = (s_var **) malloc(len)) == NULL)
1481         return NULL;
1482     memset(result, 0, len);
1484     cp = line;
1485     i = 0;
1486     while (*cp) {
1487         if ((ip = (char *) strchr(cp, '&')) != NULL) {
1488             *ip = '\0';
1489         } else
1490             ip = cp + strlen(cp);
1492         if ((esp = (char *) strchr(cp, '=')) == NULL) {
1493             cp = ++ip;
1494             continue;
1495         }
1497         if (!strlen(esp)) {
1498             cp = ++ip;
1499             continue;
1500         }
1502         if (i < numargs) {
1504             /* try to find out if there's already such a variable */
1505             for (k = 0; k < i && (strncmp(result[k]->name, cp, esp - cp)
1506                                   || !(strlen(result[k]->name) ==
1507                                        (size_t) (esp - cp))); k++);
1509             if (k == i) {   /* No such variable yet */
1510                 if ((result[i] = (s_var *) malloc(sizeof(s_var))) == NULL)
1511                     return NULL;
1512                 if ((result[i]->name =
1513                      (char *) malloc((esp - cp + 1) * sizeof(char))) == NULL)
1514                     return NULL;
1515                 memset(result[i]->name, 0, esp - cp + 1);
1516                 strncpy(result[i]->name, cp, esp - cp);
1517                 cp = ++esp;
1518                 if ((result[i]->value =
1519                      (char *) malloc((ip - esp + 1) * sizeof(char))) == NULL)
1520                     return NULL;
1521                 memset(result[i]->value, 0, ip - esp + 1);
1522                 strncpy(result[i]->value, cp, ip - esp);
1523                 result[i]->value = rrdcgiDecodeString(result[i]->value);
1524                 if (rrdcgiDebugLevel) {
1525                     if (rrdcgiDebugStderr)
1526                         fprintf(stderr, "%s: %s\n", result[i]->name,
1527                                 result[i]->value);
1528                     else
1529                         printf("<h3>Variable %s</h3>\n<pre>\n%s\n</pre>\n\n",
1530                                result[i]->name, result[i]->value);
1531                 }
1532                 i++;
1533             } else {    /* There is already such a name, suppose a mutiple field */
1534                 cp = ++esp;
1535                 len =
1536                     (strlen(result[k]->value) + (ip - esp) +
1537                      2) * sizeof(char);
1538                 if ((sptr = (char *) malloc(len)) == NULL)
1539                     return NULL;
1540                 memset(sptr, 0, len);
1541                 sprintf(sptr, "%s\n", result[k]->value);
1542                 strncat(sptr, cp, ip - esp);
1543                 free(result[k]->value);
1544                 result[k]->value = rrdcgiDecodeString(sptr);
1545             }
1546         }
1547         cp = ++ip;
1548     }
1549     return result;
1552 /*  rrdcgiInit()
1553  *
1554  *  Read from stdin if no string is provided via CGI.  Variables that
1555  *  doesn't have a value associated with it doesn't get stored.
1556  */
1557 s_cgi    *rrdcgiInit(
1558     void)
1560     s_cgi    *res;
1561     s_var   **vars;
1563     vars = rrdcgiReadVariables();
1565     if (!vars)
1566         return NULL;
1568     if ((res = (s_cgi *) malloc(sizeof(s_cgi))) == NULL)
1569         return NULL;
1570     res->vars = vars;
1572     return res;
1575 char     *rrdcgiGetValue(
1576     s_cgi * parms,
1577     const char *name)
1579     int       i;
1581     if (!parms || !parms->vars)
1582         return NULL;
1583     for (i = 0; parms->vars[i]; i++)
1584         if (!strcmp(name, parms->vars[i]->name)) {
1585             if (rrdcgiDebugLevel > 0) {
1586                 if (rrdcgiDebugStderr)
1587                     fprintf(stderr, "%s found as %s\n", name,
1588                             parms->vars[i]->value);
1589                 else
1590                     printf("%s found as %s<br>\n", name,
1591                            parms->vars[i]->value);
1592             }
1593             return parms->vars[i]->value;
1594         }
1595     if (rrdcgiDebugLevel) {
1596         if (rrdcgiDebugStderr)
1597             fprintf(stderr, "%s not found\n", name);
1598         else
1599             printf("%s not found<br>\n", name);
1600     }
1601     return NULL;
1604 void rrdcgiFreeList(
1605     char **list)
1607     int       i;
1609     for (i = 0; list[i] != NULL; i++)
1610         free(list[i]);
1611     free(list);
1614 void rrdcgiFree(
1615     s_cgi * parms)
1617     int       i;
1619     if (!parms)
1620         return;
1621     if (parms->vars) {
1622         for (i = 0; parms->vars[i]; i++) {
1623             if (parms->vars[i]->name)
1624                 free(parms->vars[i]->name);
1625             if (parms->vars[i]->value)
1626                 free(parms->vars[i]->value);
1627             free(parms->vars[i]);
1628         }
1629         free(parms->vars);
1630     }
1631     free(parms);
1633     if (rrdcgiHeaderString) {
1634         free(rrdcgiHeaderString);
1635         rrdcgiHeaderString = NULL;
1636     }
1637     if (rrdcgiType) {
1638         free(rrdcgiType);
1639         rrdcgiType = NULL;
1640     }