Code

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