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