Code

df4a9dcdfbd5b14dccaa4b9eb7875c22d89d970b
[rrdtool-all.git] / program / 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         /* next call, try parsing at current offset +1 */
1262         end = (*buf) + i + 1;
1264         val = stralloc("[ERROR: Parsing Problem with the following text\n"
1265                        " Check original file. This may have been altered "
1266                        "by parsing.]\n\n");
1267     }
1269     /* remember offset where we have to continue parsing */
1270     end_offset = end - (*buf);
1272     valln = 0;
1273     if (val) {
1274         valln = strlen(val);
1275     }
1277     /* Optionally resize buffer to hold the replacement value:
1278        Calculating the new length of the buffer is simple. add current
1279        buffer pos (i) to length of string after replaced tag to length
1280        of replacement string and add 1 for the final zero ... */
1281     if (end - (*buf) < (i + valln)) {
1282         /* make sure we do not shrink the mallocd block */
1283         size_t    newbufsize = i + strlen(end) + valln + 1;
1285         *buf = rrd_realloc(*buf, newbufsize);
1287         if (*buf == NULL) {
1288             perror("Realoc buf:");
1289             exit(1);
1290         };
1291     }
1293     /* Update new end pointer:
1294        make sure the 'end' pointer gets moved along with the 
1295        buf pointer when realloc moves memory ... */
1296     end = (*buf) + end_offset;
1298     /* splice the variable:
1299        step 1. Shift pending data to make room for 'val' */
1300     memmove((*buf) + i + valln, end, strlen(end) + 1);
1302     /* step 2. Insert val */
1303     if (val) {
1304         memmove((*buf) + i, val, valln);
1305         free(val);
1306     }
1307     return (valln > 0 ? valln - 1 : valln);
1310 char     *http_time(
1311     time_t *now)
1313     struct tm *tmptime;
1314     static char buf[60];
1316     tmptime = gmtime(now);
1317     strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", tmptime);
1318     return (buf);
1321 void rrdcgiHeader(
1322     void)
1324     if (rrdcgiType)
1325         printf("Content-type: %s\n", rrdcgiType);
1326     else
1327         printf("Content-type: text/html\n");
1328     if (rrdcgiHeaderString)
1329         printf("%s", rrdcgiHeaderString);
1330     printf("\n");
1333 void rrdcgiDebug(
1334     int level,
1335     int where)
1337     if (level > 0)
1338         rrdcgiDebugLevel = level;
1339     else
1340         rrdcgiDebugLevel = 0;
1341     if (where)
1342         rrdcgiDebugStderr = 0;
1343     else
1344         rrdcgiDebugStderr = 1;
1347 char     *rrdcgiDecodeString(
1348     char *text)
1350     char     *cp, *xp;
1352     for (cp = text, xp = text; *cp; cp++) {
1353         if (*cp == '%') {
1354             if (strchr("0123456789ABCDEFabcdef", *(cp + 1))
1355                 && strchr("0123456789ABCDEFabcdef", *(cp + 2))) {
1356                 if (islower(*(cp + 1)))
1357                     *(cp + 1) = toupper(*(cp + 1));
1358                 if (islower(*(cp + 2)))
1359                     *(cp + 2) = toupper(*(cp + 2));
1360                 *(xp) =
1361                     (*(cp + 1) >=
1362                      'A' ? *(cp + 1) - 'A' + 10 : *(cp + 1) - '0') * 16 +
1363                     (*(cp + 2) >=
1364                      'A' ? *(cp + 2) - 'A' + 10 : *(cp + 2) - '0');
1365                 xp++;
1366                 cp += 2;
1367             }
1368         } else {
1369             *(xp++) = *cp;
1370         }
1371     }
1372     memset(xp, 0, cp - xp);
1373     return text;
1376 /*  rrdcgiReadVariables()
1377  *
1378  *  Read from stdin if no string is provided via CGI.  Variables that
1379  *  doesn't have a value associated with it doesn't get stored.
1380  */
1381 s_var   **rrdcgiReadVariables(
1382     void)
1384     int       length;
1385     char     *line = NULL;
1386     int       numargs;
1387     char     *cp, *ip, *esp, *sptr;
1388     s_var   **result;
1389     int       i, k, len;
1390     char      tmp[101];
1392     cp = getenv("REQUEST_METHOD");
1393     ip = getenv("CONTENT_LENGTH");
1395     if (cp && !strcmp(cp, "POST")) {
1396         if (ip) {
1397             length = atoi(ip);
1398             if ((line = (char *) malloc(length + 2)) == NULL)
1399                 return NULL;
1400             if (fgets(line, length + 1, stdin) == NULL)
1401                 return NULL;
1402         } else
1403             return NULL;
1404     } else if (cp && !strcmp(cp, "GET")) {
1405         esp = getenv("QUERY_STRING");
1406         if (esp && strlen(esp)) {
1407             if ((line = (char *) malloc(strlen(esp) + 2)) == NULL)
1408                 return NULL;
1409             sprintf(line, "%s", esp);
1410         } else
1411             return NULL;
1412     } else {
1413         length = 0;
1414         printf("(offline mode: enter name=value pairs on standard input)\n");
1415         memset(tmp, 0, sizeof(tmp));
1416         while ((cp = fgets(tmp, 100, stdin)) != NULL) {
1417             if (strlen(tmp)) {
1418                 if (tmp[strlen(tmp) - 1] == '\n')
1419                     tmp[strlen(tmp) - 1] = '&';
1420                 if (length) {
1421                     length += strlen(tmp);
1422                     len = (length + 1) * sizeof(char);
1423                     if ((line = (char *) realloc(line, len)) == NULL)
1424                         return NULL;
1425                     strcat(line, tmp);
1426                 } else {
1427                     length = strlen(tmp);
1428                     len = (length + 1) * sizeof(char);
1429                     if ((line = (char *) malloc(len)) == NULL)
1430                         return NULL;
1431                     memset(line, 0, len);
1432                     strcpy(line, tmp);
1433                 }
1434             }
1435             memset(tmp, 0, sizeof(tmp));
1436         }
1437         if (!line)
1438             return NULL;
1439         if (line[strlen(line) - 1] == '&')
1440             line[strlen(line) - 1] = '\0';
1441     }
1443     /*
1444      *  From now on all cgi variables are stored in the variable line
1445      *  and look like  foo=bar&foobar=barfoo&foofoo=
1446      */
1448     if (rrdcgiDebugLevel > 0) {
1449         if (rrdcgiDebugStderr)
1450             fprintf(stderr, "Received cgi input: %s\n", line);
1451         else
1452             printf
1453                 ("<b>Received cgi input</b><br>\n<pre>\n--\n%s\n--\n</pre>\n\n",
1454                  line);
1455     }
1457     for (cp = line; *cp; cp++)
1458         if (*cp == '+')
1459             *cp = ' ';
1461     if (strlen(line)) {
1462         for (numargs = 1, cp = line; *cp; cp++)
1463             if (*cp == '&')
1464                 numargs++;
1465     } else
1466         numargs = 0;
1467     if (rrdcgiDebugLevel > 0) {
1468         if (rrdcgiDebugStderr)
1469             fprintf(stderr, "%d cgi variables found.\n", numargs);
1470         else
1471             printf("%d cgi variables found.<br>\n", numargs);
1472     }
1474     len = (numargs + 1) * sizeof(s_var *);
1475     if ((result = (s_var **) malloc(len)) == NULL)
1476         return NULL;
1477     memset(result, 0, len);
1479     cp = line;
1480     i = 0;
1481     while (*cp) {
1482         if ((ip = (char *) strchr(cp, '&')) != NULL) {
1483             *ip = '\0';
1484         } else
1485             ip = cp + strlen(cp);
1487         if ((esp = (char *) strchr(cp, '=')) == NULL) {
1488             cp = ++ip;
1489             continue;
1490         }
1492         if (!strlen(esp)) {
1493             cp = ++ip;
1494             continue;
1495         }
1497         if (i < numargs) {
1499             /* try to find out if there's already such a variable */
1500             for (k = 0; k < i && (strncmp(result[k]->name, cp, esp - cp)
1501                                   || !(strlen(result[k]->name) ==
1502                                        (size_t) (esp - cp))); k++);
1504             if (k == i) {   /* No such variable yet */
1505                 if ((result[i] = (s_var *) malloc(sizeof(s_var))) == NULL)
1506                     return NULL;
1507                 if ((result[i]->name =
1508                      (char *) malloc((esp - cp + 1) * sizeof(char))) == NULL)
1509                     return NULL;
1510                 memset(result[i]->name, 0, esp - cp + 1);
1511                 strncpy(result[i]->name, cp, esp - cp);
1512                 cp = ++esp;
1513                 if ((result[i]->value =
1514                      (char *) malloc((ip - esp + 1) * sizeof(char))) == NULL)
1515                     return NULL;
1516                 memset(result[i]->value, 0, ip - esp + 1);
1517                 strncpy(result[i]->value, cp, ip - esp);
1518                 result[i]->value = rrdcgiDecodeString(result[i]->value);
1519                 if (rrdcgiDebugLevel) {
1520                     if (rrdcgiDebugStderr)
1521                         fprintf(stderr, "%s: %s\n", result[i]->name,
1522                                 result[i]->value);
1523                     else
1524                         printf("<h3>Variable %s</h3>\n<pre>\n%s\n</pre>\n\n",
1525                                result[i]->name, result[i]->value);
1526                 }
1527                 i++;
1528             } else {    /* There is already such a name, suppose a mutiple field */
1529                 cp = ++esp;
1530                 len =
1531                     (strlen(result[k]->value) + (ip - esp) +
1532                      2) * sizeof(char);
1533                 if ((sptr = (char *) malloc(len)) == NULL)
1534                     return NULL;
1535                 memset(sptr, 0, len);
1536                 sprintf(sptr, "%s\n", result[k]->value);
1537                 strncat(sptr, cp, ip - esp);
1538                 free(result[k]->value);
1539                 result[k]->value = rrdcgiDecodeString(sptr);
1540             }
1541         }
1542         cp = ++ip;
1543     }
1544     return result;
1547 /*  rrdcgiInit()
1548  *
1549  *  Read from stdin if no string is provided via CGI.  Variables that
1550  *  doesn't have a value associated with it doesn't get stored.
1551  */
1552 s_cgi    *rrdcgiInit(
1553     void)
1555     s_cgi    *res;
1556     s_var   **vars;
1558     vars = rrdcgiReadVariables();
1560     if (!vars)
1561         return NULL;
1563     if ((res = (s_cgi *) malloc(sizeof(s_cgi))) == NULL)
1564         return NULL;
1565     res->vars = vars;
1567     return res;
1570 char     *rrdcgiGetValue(
1571     s_cgi * parms,
1572     const char *name)
1574     int       i;
1576     if (!parms || !parms->vars)
1577         return NULL;
1578     for (i = 0; parms->vars[i]; i++)
1579         if (!strcmp(name, parms->vars[i]->name)) {
1580             if (rrdcgiDebugLevel > 0) {
1581                 if (rrdcgiDebugStderr)
1582                     fprintf(stderr, "%s found as %s\n", name,
1583                             parms->vars[i]->value);
1584                 else
1585                     printf("%s found as %s<br>\n", name,
1586                            parms->vars[i]->value);
1587             }
1588             return parms->vars[i]->value;
1589         }
1590     if (rrdcgiDebugLevel) {
1591         if (rrdcgiDebugStderr)
1592             fprintf(stderr, "%s not found\n", name);
1593         else
1594             printf("%s not found<br>\n", name);
1595     }
1596     return NULL;
1599 void rrdcgiFreeList(
1600     char **list)
1602     int       i;
1604     for (i = 0; list[i] != NULL; i++)
1605         free(list[i]);
1606     free(list);
1609 void rrdcgiFree(
1610     s_cgi * parms)
1612     int       i;
1614     if (!parms)
1615         return;
1616     if (parms->vars) {
1617         for (i = 0; parms->vars[i]; i++) {
1618             if (parms->vars[i]->name)
1619                 free(parms->vars[i]->name);
1620             if (parms->vars[i]->value)
1621                 free(parms->vars[i]->value);
1622             free(parms->vars[i]);
1623         }
1624         free(parms->vars);
1625     }
1626     free(parms);
1628     if (rrdcgiHeaderString) {
1629         free(rrdcgiHeaderString);
1630         rrdcgiHeaderString = NULL;
1631     }
1632     if (rrdcgiType) {
1633         free(rrdcgiType);
1634         rrdcgiType = NULL;
1635     }