in rrd_cgi RRD::TIME::LAST do not raise argc when calling rrd_last since the strftime...
[rrdtool.git] / src / rrd_cgi.c
1 /*****************************************************************************
2  * RRDtool 1.4.2  Copyright by Tobi Oetiker, 1997-2009
3  *****************************************************************************
4  * rrd_cgi.c  RRD Web Page Generator
5  *****************************************************************************/
6
7 #include "rrd_tool.h"
8 #ifdef HAVE_STDLIB_H
9 #include <stdlib.h>
10 #endif
11
12 #define MEMBLK 1024
13 /*#define DEBUG_PARSER
14 #define DEBUG_VARS*/
15
16 typedef struct var_s {
17     char     *name, *value;
18 } s_var;
19
20 typedef struct cgi_s {
21     s_var   **vars;
22 } s_cgi;
23
24 /* in arg[0] find tags beginning with arg[1] call arg[2] on them
25    and replace by result of arg[2] call */
26 int       parse(
27     char **,
28     long,
29     char *,
30     char *    (*)(long,
31                   const char **));
32
33 /**************************************************/
34 /* tag replacers ... they are called from parse   */
35 /* through function pointers                      */
36 /**************************************************/
37
38 /* return cgi var named arg[0] */
39 char     *cgiget(
40     long,
41     const char **);
42
43 /* return a quoted cgi var named arg[0] */
44 char     *cgigetq(
45     long,
46     const char **);
47
48 /* return a quoted and sanitized cgi variable */
49 char     *cgigetqp(
50     long,
51     const char **);
52
53 /* call rrd_graph and insert appropriate image tag */
54 char     *drawgraph(
55     long,
56     const char **);
57
58 /* return PRINT functions from last rrd_graph call */
59 char     *drawprint(
60     long,
61     const char **);
62
63 /* pretty-print the <last></last> value for some.rrd via strftime() */
64 char     *printtimelast(
65     long,
66     const char **);
67
68 /* pretty-print current time */
69 char     *printtimenow(
70     long,
71     const char **);
72
73 /* set an environment variable */
74 char     *rrdsetenv(
75     long,
76     const char **);
77
78 /* get an environment variable */
79 char     *rrdgetenv(
80     long,
81     const char **);
82
83 /* include the named file at this point */
84 char     *includefile(
85     long,
86     const char **);
87
88 /* for how long is the output of the cgi valid ? */
89 char     *rrdgoodfor(
90     long,
91     const char **);
92
93 /* return rrdcgi version string */
94 char     *rrdgetinternal(
95     long,
96     const char **);
97
98 char     *rrdstrip(
99     char *buf);
100 char     *scanargs(
101     char *line,
102     int *argc,
103     char ***args);
104
105 /* format at-time specified times using strftime */
106 char     *printstrftime(
107     long,
108     const char **);
109
110 /** HTTP protocol needs special format, and GMT time **/
111 char     *http_time(
112     time_t *);
113
114 /* return a pointer to newly allocated copy of this string */
115 char     *stralloc(
116     const char *);
117
118 /* global variable for rrdcgi */
119 s_cgi    *rrdcgiArg;
120
121 /* rrdcgiHeader
122  * 
123  *  Prints a valid CGI Header (Content-type...) etc.
124  */
125 void      rrdcgiHeader(
126     void);
127
128 /* rrdcgiDecodeString
129  * decode html escapes
130  */
131
132 char     *rrdcgiDecodeString(
133     char *text);
134
135 /* rrdcgiDebug
136  * 
137  *  Set/unsets debugging
138  */
139 void      rrdcgiDebug(
140     int level,
141     int where);
142
143 /* rrdcgiInit
144  *
145  *  Reads in variables set via POST or stdin.
146  */
147 s_cgi    *rrdcgiInit(
148     void);
149
150 /* rrdcgiGetValue
151  *
152  *  Returns the value of the specified variable or NULL if it's empty
153  *  or doesn't exist.
154  */
155 char     *rrdcgiGetValue(
156     s_cgi * parms,
157     const char *name);
158
159 /* rrdcgiFreeList
160  *
161  * Frees a list as returned by rrdcgiGetVariables()
162  */
163 void      rrdcgiFreeList(
164     char **list);
165
166 /* rrdcgiFree
167  *
168  * Frees the internal data structures
169  */
170 void      rrdcgiFree(
171     s_cgi * parms);
172
173 /*  rrdcgiReadVariables()
174  *
175  *  Read from stdin if no string is provided via CGI.  Variables that
176  *  doesn't have a value associated with it doesn't get stored.
177  */
178 s_var   **rrdcgiReadVariables(
179     void);
180
181
182 int       rrdcgiDebugLevel = 0;
183 int       rrdcgiDebugStderr = 1;
184 char     *rrdcgiHeaderString = NULL;
185 char     *rrdcgiType = NULL;
186
187 /* rrd interface to the variable functions {put,get}var() */
188 char     *rrdgetvar(
189     long argc,
190     const char **args);
191 char     *rrdsetvar(
192     long argc,
193     const char **args);
194 char     *rrdsetvarconst(
195     long argc,
196     const char **args);
197
198
199 /* variable store: put/get key-value pairs */
200 static int initvar(
201     );
202 static void donevar(
203     );
204 static const char *getvar(
205     const char *varname);
206 static const char *putvar(
207     const char *name,
208     const char *value,
209     int is_const);
210
211 /* key value pair that makes up an entry in the variable store */
212 typedef struct {
213     int       is_const; /* const variable or not */
214     const char *name;   /* variable name */
215     const char *value;  /* variable value */
216 } vardata;
217
218 /* the variable heap: 
219    start with a heapsize of 10 variables */
220 #define INIT_VARSTORE_SIZE      10
221 static vardata *varheap = NULL;
222 static size_t varheap_size = 0;
223
224 /* allocate and initialize variable heap */
225 static int initvar(
226     void)
227 {
228     varheap = (vardata *) malloc(sizeof(vardata) * INIT_VARSTORE_SIZE);
229     if (varheap == NULL) {
230         fprintf(stderr, "ERROR: unable to initialize variable store\n");
231         return -1;
232     }
233     memset(varheap, 0, sizeof(vardata) * INIT_VARSTORE_SIZE);
234     varheap_size = INIT_VARSTORE_SIZE;
235     return 0;
236 }
237
238 /* cleanup: free allocated memory */
239 static void donevar(
240     void)
241 {
242     int       i;
243
244     if (varheap) {
245         for (i = 0; i < (int) varheap_size; i++) {
246             if (varheap[i].name) {
247                 free((char *) varheap[i].name);
248             }
249             if (varheap[i].value) {
250                 free((char *) varheap[i].value);
251             }
252         }
253         free(varheap);
254     }
255 }
256
257 /* Get a variable from the variable store.
258    Return NULL in case the requested variable was not found. */
259 static const char *getvar(
260     const char *name)
261 {
262     int       i;
263
264     for (i = 0; i < (int) varheap_size && varheap[i].name; i++) {
265         if (0 == strcmp(name, varheap[i].name)) {
266 #ifdef          DEBUG_VARS
267             printf("<!-- getvar(%s) -> %s -->\n", name, varheap[i].value);
268 #endif
269             return varheap[i].value;
270         }
271     }
272 #ifdef DEBUG_VARS
273     printf("<!-- getvar(%s) -> Not found-->\n", name);
274 #endif
275     return NULL;
276 }
277
278 /* Put a variable into the variable store. If a variable by that
279    name exists, it's value is overwritten with the new value unless it was
280    marked as 'const' (initialized by RRD::SETCONSTVAR).
281    Returns a copy the newly allocated value on success, NULL on error. */
282 static const char *putvar(
283     const char *name,
284     const char *value,
285     int is_const)
286 {
287     int       i;
288
289     for (i = 0; i < (int) varheap_size && varheap[i].name; i++) {
290         if (0 == strcmp(name, varheap[i].name)) {
291             /* overwrite existing entry */
292             if (varheap[i].is_const) {
293 #ifdef  DEBUG_VARS
294                 printf("<!-- setver(%s, %s): not assigning: "
295                        "const variable -->\n", name, value);
296 #endif
297                 return varheap[i].value;
298             }
299 #ifdef  DEBUG_VARS
300             printf("<!-- setvar(%s, %s): overwriting old value (%s) -->\n",
301                    name, value, varheap[i].value);
302 #endif
303             /* make it possible to promote a variable to readonly */
304             varheap[i].is_const = is_const;
305             free((char *) varheap[i].value);
306             varheap[i].value = stralloc(value);
307             return varheap[i].value;
308         }
309     }
310
311     /* no existing variable found by that name, add it */
312     if (i == (int) varheap_size) {
313         /* ran out of heap: resize heap to double size */
314         size_t    new_size = varheap_size * 2;
315
316         varheap = (vardata *) (realloc(varheap, sizeof(vardata) * new_size));
317         if (!varheap) {
318             fprintf(stderr, "ERROR: Unable to realloc variable heap\n");
319             return NULL;
320         }
321         /* initialize newly allocated memory */ ;
322         memset(&varheap[varheap_size], 0, sizeof(vardata) * varheap_size);
323         varheap_size = new_size;
324     }
325     varheap[i].is_const = is_const;
326     varheap[i].name = stralloc(name);
327     varheap[i].value = stralloc(value);
328
329 #ifdef          DEBUG_VARS
330     printf("<!-- setvar(%s, %s): adding new variable -->\n", name, value);
331 #endif
332     return varheap[i].value;
333 }
334
335 /* expand those RRD:* directives that can be used recursivly */
336 static char *rrd_expand_vars(
337     char *buffer)
338 {
339     int       i;
340
341 #ifdef DEBUG_PARSER
342     printf("expanding variables in '%s'\n", buffer);
343 #endif
344
345     for (i = 0; buffer[i]; i++) {
346         if (buffer[i] != '<')
347             continue;
348         parse(&buffer, i, "<RRD::CV", cgiget);
349         parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
350         parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
351         parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
352         parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
353         parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
354         parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
355         parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
356         parse(&buffer, i, "<RRD::INTERNAL", rrdgetinternal);
357     }
358     return buffer;
359 }
360
361 static long goodfor = 0;
362 static char **calcpr = NULL;
363 static void calfree(
364     void)
365 {
366     if (calcpr) {
367         long      i;
368
369         for (i = 0; calcpr[i]; i++) {
370             if (calcpr[i]) {
371                 free(calcpr[i]);
372             }
373         }
374         if (calcpr) {
375             free(calcpr);
376         }
377         calcpr = NULL;
378     }
379 }
380
381 /* create freeable version of the string */
382 char     *stralloc(
383     const char *str)
384 {
385     char     *nstr;
386
387     if (!str) {
388         return NULL;
389     }
390     nstr = malloc((strlen(str) + 1));
391     strcpy(nstr, str);
392     return (nstr);
393 }
394
395 static int readfile(
396     const char *file_name,
397     char **buffer,
398     int skipfirst)
399 {
400     long      writecnt = 0, totalcnt = MEMBLK;
401     long      offset = 0;
402     FILE     *input = NULL;
403     char      c;
404
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;
450 }
451
452 int main(
453     int argc,
454     char *argv[])
455 {
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     };
465
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 */
474
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;
481
482         opt = getopt_long(argc, argv, "f", long_options, &option_index);
483         if (opt == EOF) {
484             break;
485         }
486
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     }
496
497     if (!filter) {
498         rrdcgiDebug(0, 0);
499         rrdcgiArg = rrdcgiInit();
500         server_url = getenv("SERVER_URL");
501     }
502
503     /* make sure we have one extra argument, 
504        if there are others, we do not care Apache gives several */
505
506     /* if ( (optind != argc-2 
507        && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) 
508        && optind != argc-1) { */
509
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     }
516
517     if (rrd_test_error()) {
518         fprintf(stderr, "ERROR: %s\n", rrd_get_error());
519         exit(1);
520     }
521
522     /* initialize variable heap */
523     initvar();
524
525 #ifdef DEBUG_PARSER
526     /* some fake header for testing */
527     printf("Content-Type: text/html\nContent-Length: 10000000\n\n\n");
528 #endif
529
530
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     }
554
555     if (!filter) {
556         printf("Content-Type: text/html\n"
557                "Content-Length: %zd\n", strlen(buffer));
558
559         if (labs(goodfor) > 0) {
560             time_t    now;
561
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     }
572
573     /* output result */
574     printf("%s", buffer);
575
576     /* cleanup */
577     calfree();
578     if (buffer) {
579         free(buffer);
580     }
581     donevar();
582     exit(0);
583 }
584
585 /* remove occurrences of .. this is a general measure to make
586    paths which came in via cgi do not go UP ... */
587
588 char     *rrdsetenv(
589     long argc,
590     const char **args)
591 {
592     if (argc >= 2) {
593         char     *xyz = malloc((strlen(args[0]) + strlen(args[1]) + 2));
594
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]");
607 }
608
609 /* rrd interface to the variable function putvar() */
610 char     *rrdsetvar(
611     long argc,
612     const char **args)
613 {
614     if (argc >= 2) {
615         const char *result = putvar(args[0], args[1], 0 /* not const */ );
616
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]");
625 }
626
627 /* rrd interface to the variable function putvar() */
628 char     *rrdsetvarconst(
629     long argc,
630     const char **args)
631 {
632     if (argc >= 2) {
633         const char *result = putvar(args[0], args[1], 1 /* const */ );
634
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]");
643 }
644
645 char     *rrdgetenv(
646     long argc,
647     const char **args)
648 {
649     char      buf[128];
650     const char *envvar;
651
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     }
663 }
664
665 char     *rrdgetvar(
666     long argc,
667     const char **args)
668 {
669     char      buf[128];
670     const char *value;
671
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     }
683 }
684
685 char     *rrdgoodfor(
686     long argc,
687     const char **args)
688 {
689     if (argc == 1) {
690         goodfor = atol(args[0]);
691     } else {
692         return stralloc("[ERROR: goodfor expected 1 argument]");
693     }
694
695     if (goodfor == 0) {
696         return stralloc("[ERROR: goodfor value must not be 0]");
697     }
698
699     return stralloc("");
700 }
701
702 char     *rrdgetinternal(
703     long argc,
704     const char **args)
705 {
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     }
717 }
718
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)
726 {
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;
732
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     }
738
739     /* Init start and end time */
740     rrd_parsetime("end-24h", &start_tv);
741     rrd_parsetime("now", &end_tv);
742
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     }
755
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     }
765
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     }
773 }
774
775 char     *includefile(
776     long argc,
777     const char **args)
778 {
779     char     *buffer;
780
781     if (argc >= 1) {
782         const char *filename = args[0];
783
784         readfile(filename, &buffer, 0);
785         if (rrd_test_error()) {
786             char     *err = malloc((strlen(rrd_get_error()) + DS_NAM_SIZE));
787
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     }
797 }
798
799 /* make a copy of buf and replace open/close brackets with '_' */
800 char     *rrdstrip(
801     char *buf)
802 {
803     char     *p;
804
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     }
813
814     p = buf;
815     while (*p) {
816         if (*p == '<' || *p == '>') {
817             *p = '_';
818         }
819         p++;
820     }
821     return buf;
822 }
823
824 char     *cgigetq(
825     long argc,
826     const char **args)
827 {
828     if (argc >= 1) {
829         char     *buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
830         char     *buf2;
831         char     *c, *d;
832         int       qc = 0;
833
834         if (buf == NULL)
835             return NULL;
836
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     }
861
862     return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
863 }
864
865 /* remove occurrences of .. this is a general measure to make
866    paths which came in via cgi do not go UP ... */
867
868 char     *cgigetqp(
869     long argc,
870     const char **args)
871 {
872     char     *buf;
873     char     *buf2;
874     char     *p;
875     char     *d;
876
877     if (argc < 1) {
878         return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
879     }
880
881     buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
882     if (!buf) {
883         return NULL;
884     }
885
886     buf2 = malloc(strlen(buf) + 1);
887     if (!buf2) {
888         perror("cgigetqp(): Malloc Path Buffer");
889         exit(1);
890     };
891
892     p = buf;
893     d = buf2;
894
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     }
905
906     *d = 0;
907     free(buf);
908
909     /* Make sure the path is relative, e.g. does not start with '/' */
910     p = buf2;
911     while ('/' == *p) {
912         *p++ = '_';
913     }
914
915     return buf2;
916 }
917
918
919 char     *cgiget(
920     long argc,
921     const char **args)
922 {
923     if (argc >= 1)
924         return rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
925     else
926         return stralloc("[ERROR: not enough arguments for RRD::CV]");
927 }
928
929
930
931 char     *drawgraph(
932     long argc,
933     const char **args)
934 {
935     int       i, xsize, ysize;
936     double    ymin, ymax;
937
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             return err;
958         }
959     }
960     return NULL;
961 }
962
963 char     *drawprint(
964     long argc,
965     const char **args)
966 {
967     if (argc == 1 && calcpr) {
968         long      i = 0;
969
970         while (calcpr[i] != NULL)
971             i++;        /*determine number lines in calcpr */
972         if (atol(args[0]) < i - 1)
973             return stralloc(calcpr[atol(args[0]) + 1]);
974     }
975     return stralloc("[ERROR: RRD::PRINT argument error]");
976 }
977
978 char     *printtimelast(
979     long argc,
980     const char **args)
981 {
982     time_t    last;
983     struct tm tm_last;
984     char     *buf;
985
986     if (argc == 2) {
987         buf = malloc(255);
988         if (buf == NULL) {
989             return stralloc("[ERROR: allocating strftime buffer]");
990         };
991         /* not raising argc in step with args - 1 since the last argument
992            will be used below for strftime  */
993
994         last = rrd_last(argc, (char **) args - 1);
995         if (rrd_test_error()) {
996             char     *err =
997                 malloc((strlen(rrd_get_error()) +
998                         DS_NAM_SIZE) * sizeof(char));
999             sprintf(err, "[ERROR: %s]", rrd_get_error());
1000             rrd_clear_error();
1001             return err;
1002         }
1003         tm_last = *localtime(&last);
1004         strftime(buf, 254, args[1], &tm_last);
1005         return buf;
1006     }
1007     return stralloc("[ERROR: expected <RRD::TIME::LAST file.rrd strftime-format>]");
1008 }
1009
1010 char     *printtimenow(
1011     long argc,
1012     const char **args)
1013 {
1014     time_t    now = time(NULL);
1015     struct tm tm_now;
1016     char     *buf;
1017
1018     if (argc == 1) {
1019         buf = malloc(255);
1020         if (buf == NULL) {
1021             return stralloc("[ERROR: allocating strftime buffer]");
1022         };
1023         tm_now = *localtime(&now);
1024         strftime(buf, 254, args[0], &tm_now);
1025         return buf;
1026     }
1027     if (argc < 1) {
1028         return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
1029     }
1030     return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
1031 }
1032
1033 /* Scan buffer until an unescaped '>' arives.
1034  * Update argument array with arguments found.
1035  * Return end cursor where parsing stopped, or NULL in case of failure.
1036  *
1037  * FIXME:
1038  * To allow nested constructs, we call rrd_expand_vars() for arguments
1039  * that contain RRD::x directives. These introduce a small memory leak
1040  * since we have to stralloc the arguments the way parse() works.
1041  */
1042 char     *scanargs(
1043     char *line,
1044     int *argument_count,
1045     char ***arguments)
1046 {
1047     char     *getP;     /* read cursor */
1048     char     *putP;     /* write cursor */
1049     char      Quote;    /* type of quote if in quoted string, 0 otherwise */
1050     int       tagcount; /* open tag count */
1051     int       in_arg;   /* if we currently are parsing an argument or not */
1052     int       argsz;    /* argument array size */
1053     int       curarg_contains_rrd_directives;
1054
1055     /* local array of arguments while parsing */
1056     int       argc = 1;
1057     char    **argv;
1058
1059 #ifdef DEBUG_PARSER
1060     printf("<-- scanargs(%s) -->\n", line);
1061 #endif
1062
1063     *arguments = NULL;
1064     *argument_count = 0;
1065
1066     /* create initial argument array of char pointers */
1067     argsz = 32;
1068     argv = (char **) malloc(argsz * sizeof(char *));
1069     if (!argv) {
1070         return NULL;
1071     }
1072     argv[0] = "rrdcgi";
1073
1074     /* skip leading blanks */
1075     while (isspace((int) *line)) {
1076         line++;
1077     }
1078
1079     getP = line;
1080     putP = line;
1081
1082     Quote = 0;
1083     in_arg = 0;
1084     tagcount = 0;
1085
1086     curarg_contains_rrd_directives = 0;
1087
1088     /* start parsing 'line' for arguments */
1089     while (*getP) {
1090         unsigned char c = *getP++;
1091
1092         if (c == '>' && !Quote && !tagcount) {
1093             /* this is our closing tag, quit scanning */
1094             break;
1095         }
1096
1097         /* remove all special chars */
1098         if (c < ' ') {
1099             c = ' ';
1100         }
1101
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;
1118
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;
1137
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         }
1158
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     }
1169
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 > 1) {
1177         int       n;
1178
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
1188
1189     /* update caller's notion of the argument array and it's size */
1190
1191     /* note this is a bit of a hack since the rrd_cgi code used to just put
1192        its arguments into a normal array starting at 0 ... since the rrd_*
1193        commands expect and argc/argv array we used to just shift everything
1194        by -1 ... this in turn exploded when a rrd_* function tried to print
1195        argv[0] ... hence we are now doing everything in argv style but hand
1196        over seemingly the old array ... but doing argv-1 will actually end
1197        up in a 'good' place now. */
1198
1199     *arguments = argv+1;
1200     *argument_count = argc-1;
1201
1202     if (Quote) {
1203         return NULL;
1204     }
1205
1206     /* Return new scanning cursor:
1207        pointer to char after closing bracket */
1208     return getP;
1209 }
1210
1211
1212 /*
1213  * Parse(): scan current portion of buffer for given tag.
1214  * If found, parse tag arguments and call 'func' for it.
1215  * The result of func is inserted at the current position
1216  * in the buffer.
1217  */
1218 int parse(
1219     char **buf,         /* buffer */
1220     long i,             /* offset in buffer */
1221     char *tag,          /* tag to handle  */
1222     char *    (*func) (long,
1223                        const char **)   /* function to call for 'tag' */
1224     )
1225 {
1226     /* the name of the vairable ... */
1227     char     *val;
1228     long      valln;
1229     char    **args;
1230     char     *end;
1231     long      end_offset;
1232     int       argc;
1233     size_t    taglen = strlen(tag);
1234
1235     /* Current position in buffer should start with 'tag' */
1236     if (strncmp((*buf) + i, tag, taglen) != 0) {
1237         return 0;
1238     }
1239     /* .. and match exactly (a whitespace following 'tag') */
1240     if (!isspace(*((*buf) + i + taglen))) {
1241         return 0;
1242     }
1243 #ifdef DEBUG_PARSER
1244     printf("parse(): handling tag '%s'\n", tag);
1245 #endif
1246
1247     /* Scan for arguments following the tag;
1248        scanargs() puts \0 into *buf ... so after scanargs it is probably
1249        not a good time to use strlen on buf */
1250     end = scanargs((*buf) + i + taglen, &argc, &args);
1251     if (end) {
1252         /* got arguments, call function for 'tag' with arguments */
1253         val = func(argc, (const char **) args);
1254         free(args-1);
1255     } else {
1256         /* unable to parse arguments, undo 0-termination by scanargs */
1257         for (; argc > 0; argc--) {
1258             *((args[argc - 1]) - 1) = ' ';
1259         }
1260
1261         /* next call, try parsing at current offset +1 */
1262         end = (*buf) + i + 1;
1263
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     }
1268
1269     /* remember offset where we have to continue parsing */
1270     end_offset = end - (*buf);
1271
1272     valln = 0;
1273     if (val) {
1274         valln = strlen(val);
1275     }
1276
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;
1284
1285         *buf = rrd_realloc(*buf, newbufsize);
1286
1287         if (*buf == NULL) {
1288             perror("Realoc buf:");
1289             exit(1);
1290         };
1291     }
1292
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;
1297
1298     /* splice the variable:
1299        step 1. Shift pending data to make room for 'val' */
1300     memmove((*buf) + i + valln, end, strlen(end) + 1);
1301
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);
1308 }
1309
1310 char     *http_time(
1311     time_t *now)
1312 {
1313     struct tm *tmptime;
1314     static char buf[60];
1315
1316     tmptime = gmtime(now);
1317     strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", tmptime);
1318     return (buf);
1319 }
1320
1321 void rrdcgiHeader(
1322     void)
1323 {
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");
1331 }
1332
1333 void rrdcgiDebug(
1334     int level,
1335     int where)
1336 {
1337     if (level > 0)
1338         rrdcgiDebugLevel = level;
1339     else
1340         rrdcgiDebugLevel = 0;
1341     if (where)
1342         rrdcgiDebugStderr = 0;
1343     else
1344         rrdcgiDebugStderr = 1;
1345 }
1346
1347 char     *rrdcgiDecodeString(
1348     char *text)
1349 {
1350     char     *cp, *xp;
1351
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;
1374 }
1375
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)
1383 {
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];
1391
1392     cp = getenv("REQUEST_METHOD");
1393     ip = getenv("CONTENT_LENGTH");
1394
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     }
1442
1443     /*
1444      *  From now on all cgi variables are stored in the variable line
1445      *  and look like  foo=bar&foobar=barfoo&foofoo=
1446      */
1447
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     }
1456
1457     for (cp = line; *cp; cp++)
1458         if (*cp == '+')
1459             *cp = ' ';
1460
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     }
1473
1474     len = (numargs + 1) * sizeof(s_var *);
1475     if ((result = (s_var **) malloc(len)) == NULL)
1476         return NULL;
1477     memset(result, 0, len);
1478
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);
1486
1487         if ((esp = (char *) strchr(cp, '=')) == NULL) {
1488             cp = ++ip;
1489             continue;
1490         }
1491
1492         if (!strlen(esp)) {
1493             cp = ++ip;
1494             continue;
1495         }
1496
1497         if (i < numargs) {
1498
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++);
1503
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;
1545 }
1546
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)
1554 {
1555     s_cgi    *res;
1556     s_var   **vars;
1557
1558     vars = rrdcgiReadVariables();
1559
1560     if (!vars)
1561         return NULL;
1562
1563     if ((res = (s_cgi *) malloc(sizeof(s_cgi))) == NULL)
1564         return NULL;
1565     res->vars = vars;
1566
1567     return res;
1568 }
1569
1570 char     *rrdcgiGetValue(
1571     s_cgi * parms,
1572     const char *name)
1573 {
1574     int       i;
1575
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;
1597 }
1598
1599 void rrdcgiFreeList(
1600     char **list)
1601 {
1602     int       i;
1603
1604     for (i = 0; list[i] != NULL; i++)
1605         free(list[i]);
1606     free(list);
1607 }
1608
1609 void rrdcgiFree(
1610     s_cgi * parms)
1611 {
1612     int       i;
1613
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);
1627
1628     if (rrdcgiHeaderString) {
1629         free(rrdcgiHeaderString);
1630         rrdcgiHeaderString = NULL;
1631     }
1632     if (rrdcgiType) {
1633         free(rrdcgiType);
1634         rrdcgiType = NULL;
1635     }
1636 }