as rrd_cgi fails to parse stuff, do NOT try to fix up the mess ... it is bound to...
[rrdtool.git] / src / rrd_cgi.c
1 /*****************************************************************************
2  * RRDtool 1.4.3  Copyright by Tobi Oetiker, 1997-2010
3  *****************************************************************************
4  * rrd_cgi.c  RRD Web Page Generator
5  *****************************************************************************/
6
7 #include "rrd_tool.h"
8 #ifdef HAVE_STDLIB_H
9 #include <stdlib.h>
10 #endif
11
12 #ifdef WIN32
13    #define strcasecmp stricmp
14    #define strcasencmp strnicmp
15 #endif
16
17 #define MEMBLK 1024
18 /*#define DEBUG_PARSER
19 #define DEBUG_VARS*/
20
21 typedef struct var_s {
22     char     *name, *value;
23 } s_var;
24
25 typedef struct cgi_s {
26     s_var   **vars;
27 } s_cgi;
28
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 **));
37
38 /**************************************************/
39 /* tag replacers ... they are called from parse   */
40 /* through function pointers                      */
41 /**************************************************/
42
43 /* return cgi var named arg[0] */
44 char     *cgiget(
45     long,
46     const char **);
47
48 /* return a quoted cgi var named arg[0] */
49 char     *cgigetq(
50     long,
51     const char **);
52
53 /* return a quoted and sanitized cgi variable */
54 char     *cgigetqp(
55     long,
56     const char **);
57
58 /* call rrd_graph and insert appropriate image tag */
59 char     *drawgraph(
60     long,
61     const char **);
62
63 /* return PRINT functions from last rrd_graph call */
64 char     *drawprint(
65     long,
66     const char **);
67
68 /* pretty-print the <last></last> value for some.rrd via strftime() */
69 char     *printtimelast(
70     long,
71     const char **);
72
73 /* pretty-print current time */
74 char     *printtimenow(
75     long,
76     const char **);
77
78 /* set an environment variable */
79 char     *rrdsetenv(
80     long,
81     const char **);
82
83 /* get an environment variable */
84 char     *rrdgetenv(
85     long,
86     const char **);
87
88 /* include the named file at this point */
89 char     *includefile(
90     long,
91     const char **);
92
93 /* for how long is the output of the cgi valid ? */
94 char     *rrdgoodfor(
95     long,
96     const char **);
97
98 /* return rrdcgi version string */
99 char     *rrdgetinternal(
100     long,
101     const char **);
102
103 char     *rrdstrip(
104     char *buf);
105 char     *scanargs(
106     char *line,
107     int *argc,
108     char ***args);
109
110 /* format at-time specified times using strftime */
111 char     *printstrftime(
112     long,
113     const char **);
114
115 /** HTTP protocol needs special format, and GMT time **/
116 char     *http_time(
117     time_t *);
118
119 /* return a pointer to newly allocated copy of this string */
120 char     *stralloc(
121     const char *);
122
123 /* global variable for rrdcgi */
124 s_cgi    *rrdcgiArg;
125
126 /* rrdcgiHeader
127  * 
128  *  Prints a valid CGI Header (Content-type...) etc.
129  */
130 void      rrdcgiHeader(
131     void);
132
133 /* rrdcgiDecodeString
134  * decode html escapes
135  */
136
137 char     *rrdcgiDecodeString(
138     char *text);
139
140 /* rrdcgiDebug
141  * 
142  *  Set/unsets debugging
143  */
144 void      rrdcgiDebug(
145     int level,
146     int where);
147
148 /* rrdcgiInit
149  *
150  *  Reads in variables set via POST or stdin.
151  */
152 s_cgi    *rrdcgiInit(
153     void);
154
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);
163
164 /* rrdcgiFreeList
165  *
166  * Frees a list as returned by rrdcgiGetVariables()
167  */
168 void      rrdcgiFreeList(
169     char **list);
170
171 /* rrdcgiFree
172  *
173  * Frees the internal data structures
174  */
175 void      rrdcgiFree(
176     s_cgi * parms);
177
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);
185
186
187 int       rrdcgiDebugLevel = 0;
188 int       rrdcgiDebugStderr = 1;
189 char     *rrdcgiHeaderString = NULL;
190 char     *rrdcgiType = NULL;
191
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);
202
203
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);
215
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;
222
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;
228
229 /* allocate and initialize variable heap */
230 static int initvar(
231     void)
232 {
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;
241 }
242
243 /* cleanup: free allocated memory */
244 static void donevar(
245     void)
246 {
247     int       i;
248
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     }
260 }
261
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)
266 {
267     int       i;
268
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;
281 }
282
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)
291 {
292     int       i;
293
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     }
315
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;
320
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);
333
334 #ifdef          DEBUG_VARS
335     printf("<!-- setvar(%s, %s): adding new variable -->\n", name, value);
336 #endif
337     return varheap[i].value;
338 }
339
340 /* expand those RRD:* directives that can be used recursivly */
341 static char *rrd_expand_vars(
342     char *buffer)
343 {
344     int       i;
345
346 #ifdef DEBUG_PARSER
347     printf("expanding variables in '%s'\n", buffer);
348 #endif
349
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;
364 }
365
366 static long goodfor = 0;
367 static char **calcpr = NULL;
368 static void calfree(
369     void)
370 {
371     if (calcpr) {
372         long      i;
373
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     }
384 }
385
386 /* create freeable version of the string */
387 char     *stralloc(
388     const char *str)
389 {
390     char     *nstr;
391
392     if (!str) {
393         return NULL;
394     }
395     nstr = malloc((strlen(str) + 1));
396     strcpy(nstr, str);
397     return (nstr);
398 }
399
400 static int readfile(
401     const char *file_name,
402     char **buffer,
403     int skipfirst)
404 {
405     long      writecnt = 0, totalcnt = MEMBLK;
406     long      offset = 0;
407     FILE     *input = NULL;
408     char      c;
409
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;
455 }
456
457 int main(
458     int argc,
459     char *argv[])
460 {
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     };
470
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 */
479
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;
486
487         opt = getopt_long(argc, argv, "f", long_options, &option_index);
488         if (opt == EOF) {
489             break;
490         }
491
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     }
501
502     if (!filter) {
503         rrdcgiDebug(0, 0);
504         rrdcgiArg = rrdcgiInit();
505         server_url = getenv("SERVER_URL");
506     }
507
508     /* make sure we have one extra argument, 
509        if there are others, we do not care Apache gives several */
510
511     /* if ( (optind != argc-2 
512        && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) 
513        && optind != argc-1) { */
514
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     }
521
522     if (rrd_test_error()) {
523         fprintf(stderr, "ERROR: %s\n", rrd_get_error());
524         exit(1);
525     }
526
527     /* initialize variable heap */
528     initvar();
529
530 #ifdef DEBUG_PARSER
531     /* some fake header for testing */
532     printf("Content-Type: text/html\nContent-Length: 10000000\n\n\n");
533 #endif
534
535
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     }
559
560     if (!filter) {
561         printf("Content-Type: text/html\n"
562                "Content-Length: %zd\n", strlen(buffer));
563
564         if (labs(goodfor) > 0) {
565             time_t    now;
566
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     }
577
578     /* output result */
579     printf("%s", buffer);
580
581     /* cleanup */
582     calfree();
583     if (buffer) {
584         free(buffer);
585     }
586     donevar();
587     exit(0);
588 }
589
590 /* remove occurrences of .. this is a general measure to make
591    paths which came in via cgi do not go UP ... */
592
593 char     *rrdsetenv(
594     long argc,
595     const char **args)
596 {
597     if (argc >= 2) {
598         char     *xyz = malloc((strlen(args[0]) + strlen(args[1]) + 2));
599
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]");
612 }
613
614 /* rrd interface to the variable function putvar() */
615 char     *rrdsetvar(
616     long argc,
617     const char **args)
618 {
619     if (argc >= 2) {
620         const char *result = putvar(args[0], args[1], 0 /* not const */ );
621
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]");
630 }
631
632 /* rrd interface to the variable function putvar() */
633 char     *rrdsetvarconst(
634     long argc,
635     const char **args)
636 {
637     if (argc >= 2) {
638         const char *result = putvar(args[0], args[1], 1 /* const */ );
639
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]");
648 }
649
650 char     *rrdgetenv(
651     long argc,
652     const char **args)
653 {
654     char      buf[128];
655     const char *envvar;
656
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     }
668 }
669
670 char     *rrdgetvar(
671     long argc,
672     const char **args)
673 {
674     char      buf[128];
675     const char *value;
676
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     }
688 }
689
690 char     *rrdgoodfor(
691     long argc,
692     const char **args)
693 {
694     if (argc == 1) {
695         goodfor = atol(args[0]);
696     } else {
697         return stralloc("[ERROR: goodfor expected 1 argument]");
698     }
699
700     if (goodfor == 0) {
701         return stralloc("[ERROR: goodfor value must not be 0]");
702     }
703
704     return stralloc("");
705 }
706
707 char     *rrdgetinternal(
708     long argc,
709     const char **args)
710 {
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     }
722 }
723
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)
731 {
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;
737
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     }
743
744     /* Init start and end time */
745     rrd_parsetime("end-24h", &start_tv);
746     rrd_parsetime("now", &end_tv);
747
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     }
760
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     }
770
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     }
778 }
779
780 char     *includefile(
781     long argc,
782     const char **args)
783 {
784     char     *buffer;
785
786     if (argc >= 1) {
787         const char *filename = args[0];
788
789         readfile(filename, &buffer, 0);
790         if (rrd_test_error()) {
791             char     *err = malloc((strlen(rrd_get_error()) + DS_NAM_SIZE));
792
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     }
802 }
803
804 /* make a copy of buf and replace open/close brackets with '_' */
805 char     *rrdstrip(
806     char *buf)
807 {
808     char     *p;
809
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     }
818
819     p = buf;
820     while (*p) {
821         if (*p == '<' || *p == '>') {
822             *p = '_';
823         }
824         p++;
825     }
826     return buf;
827 }
828
829 char     *cgigetq(
830     long argc,
831     const char **args)
832 {
833     if (argc >= 1) {
834         char     *buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
835         char     *buf2;
836         char     *c, *d;
837         int       qc = 0;
838
839         if (buf == NULL)
840             return NULL;
841
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     }
866
867     return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
868 }
869
870 /* remove occurrences of .. this is a general measure to make
871    paths which came in via cgi do not go UP ... */
872
873 char     *cgigetqp(
874     long argc,
875     const char **args)
876 {
877     char     *buf;
878     char     *buf2;
879     char     *p;
880     char     *d;
881
882     if (argc < 1) {
883         return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
884     }
885
886     buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
887     if (!buf) {
888         return NULL;
889     }
890
891     buf2 = malloc(strlen(buf) + 1);
892     if (!buf2) {
893         perror("cgigetqp(): Malloc Path Buffer");
894         exit(1);
895     };
896
897     p = buf;
898     d = buf2;
899
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     }
910
911     *d = 0;
912     free(buf);
913
914     /* Make sure the path is relative, e.g. does not start with '/' */
915     p = buf2;
916     while ('/' == *p) {
917         *p++ = '_';
918     }
919
920     return buf2;
921 }
922
923
924 char     *cgiget(
925     long argc,
926     const char **args)
927 {
928     if (argc >= 1)
929         return rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
930     else
931         return stralloc("[ERROR: not enough arguments for RRD::CV]");
932 }
933
934
935
936 char     *drawgraph(
937     long argc,
938     const char **args)
939 {
940     int       i, xsize, ysize;
941     double    ymin, ymax;
942
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;
966 }
967
968 char     *drawprint(
969     long argc,
970     const char **args)
971 {
972     if (argc == 1 && calcpr) {
973         long      i = 0;
974
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]");
981 }
982
983 char     *printtimelast(
984     long argc,
985     const char **args)
986 {
987     time_t    last;
988     struct tm tm_last;
989     char     *buf;
990
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  */
998
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>]");
1013 }
1014
1015 char     *printtimenow(
1016     long argc,
1017     const char **args)
1018 {
1019     time_t    now = time(NULL);
1020     struct tm tm_now;
1021     char     *buf;
1022
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]");
1036 }
1037
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)
1051 {
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;
1059
1060     /* local array of arguments while parsing */
1061     int       argc = 1;
1062     char    **argv;
1063
1064 #ifdef DEBUG_PARSER
1065     printf("<-- scanargs(%s) -->\n", line);
1066 #endif
1067
1068     *arguments = NULL;
1069     *argument_count = 0;
1070
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";
1078
1079     /* skip leading blanks */
1080     while (isspace((int) *line)) {
1081         line++;
1082     }
1083
1084     getP = line;
1085     putP = line;
1086
1087     Quote = 0;
1088     in_arg = 0;
1089     tagcount = 0;
1090
1091     curarg_contains_rrd_directives = 0;
1092
1093     /* start parsing 'line' for arguments */
1094     while (*getP) {
1095         unsigned char c = *getP++;
1096
1097         if (c == '>' && !Quote && !tagcount) {
1098             /* this is our closing tag, quit scanning */
1099             break;
1100         }
1101
1102         /* remove all special chars */
1103         if (c < ' ') {
1104             c = ' ';
1105         }
1106
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;
1123
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;
1142
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         }
1163
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     }
1174
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;
1183
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
1193
1194     /* update caller's notion of the argument array and it's size */
1195
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. */
1203
1204     *arguments = argv+1;
1205     *argument_count = argc-1;
1206
1207     if (Quote) {
1208         return NULL;
1209     }
1210
1211     /* Return new scanning cursor:
1212        pointer to char after closing bracket */
1213     return getP;
1214 }
1215
1216
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     )
1230 {
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);
1239
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
1251
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;
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 }