use snprintf, strdup, ... where possible to make for safer operation -- Martin Pelikan
[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     if (!str) {
391         return NULL;
392     }
393     return strdup(str);
394 }
395
396 static int readfile(
397     const char *file_name,
398     char **buffer,
399     int skipfirst)
400 {
401     long      writecnt = 0, totalcnt = MEMBLK;
402     long      offset = 0;
403     FILE     *input = NULL;
404     char      c;
405
406     if ((strcmp("-", file_name) == 0)) {
407         input = stdin;
408     } else {
409         if ((input = fopen(file_name, "rb")) == NULL) {
410             rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
411             return (-1);
412         }
413     }
414     if (skipfirst) {
415         do {
416             c = getc(input);
417             offset++;
418         } while (c != '\n' && !feof(input));
419     }
420     if (strcmp("-", file_name)) {
421         fseek(input, 0, SEEK_END);
422         /* have extra space for detecting EOF without realloc */
423         totalcnt = (ftell(input) + 1) / sizeof(char) - offset;
424         if (totalcnt < MEMBLK)
425             totalcnt = MEMBLK;  /* sanitize */
426         fseek(input, offset * sizeof(char), SEEK_SET);
427     }
428     if (((*buffer) = (char *) malloc((totalcnt + 4) * sizeof(char))) == NULL) {
429         perror("Allocate Buffer:");
430         exit(1);
431     };
432     do {
433         writecnt +=
434             fread((*buffer) + writecnt, 1,
435                   (totalcnt - writecnt) * sizeof(char), input);
436         if (writecnt >= totalcnt) {
437             totalcnt += MEMBLK;
438             if (((*buffer) =
439                  rrd_realloc((*buffer),
440                              (totalcnt + 4) * sizeof(char))) == NULL) {
441                 perror("Realloc Buffer:");
442                 exit(1);
443             };
444         }
445     } while (!feof(input));
446     (*buffer)[writecnt] = '\0';
447     if (strcmp("-", file_name) != 0) {
448         fclose(input);
449     };
450     return writecnt;
451 }
452
453 int main(
454     int argc,
455     char *argv[])
456 {
457     long      length;
458     char     *buffer;
459     char     *server_url = NULL;
460     long      i;
461     long      filter = 0;
462     struct option long_options[] = {
463         {"filter", no_argument, 0, 'f'},
464         {0, 0, 0, 0}
465     };
466
467 #ifdef MUST_DISABLE_SIGFPE
468     signal(SIGFPE, SIG_IGN);
469 #endif
470 #ifdef MUST_DISABLE_FPMASK
471     fpsetmask(0);
472 #endif
473     optind = 0;
474     opterr = 0;         /* initialize getopt */
475
476     /* what do we get for cmdline arguments?
477        for (i=0;i<argc;i++)
478        printf("%d-'%s'\n",i,argv[i]); */
479     while (1) {
480         int       option_index = 0;
481         int       opt;
482
483         opt = getopt_long(argc, argv, "f", long_options, &option_index);
484         if (opt == EOF) {
485             break;
486         }
487
488         switch (opt) {
489         case 'f':
490             filter = 1;
491             break;
492         case '?':
493             printf("unknown commandline option '%s'\n", argv[optind - 1]);
494             return -1;
495         }
496     }
497
498     if (!filter) {
499         rrdcgiDebug(0, 0);
500         rrdcgiArg = rrdcgiInit();
501         server_url = getenv("SERVER_URL");
502     }
503
504     /* make sure we have one extra argument, 
505        if there are others, we do not care Apache gives several */
506
507     /* if ( (optind != argc-2 
508        && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) 
509        && optind != argc-1) { */
510
511     if (optind >= argc) {
512         fprintf(stderr, "ERROR: expected a filename\n");
513         exit(1);
514     } else {
515         length = readfile(argv[optind], &buffer, 1);
516     }
517
518     if (rrd_test_error()) {
519         fprintf(stderr, "ERROR: %s\n", rrd_get_error());
520         exit(1);
521     }
522
523     /* initialize variable heap */
524     initvar();
525
526 #ifdef DEBUG_PARSER
527     /* some fake header for testing */
528     printf("Content-Type: text/html\nContent-Length: 10000000\n\n\n");
529 #endif
530
531
532     /* expand rrd directives in buffer recursivly */
533     for (i = 0; buffer[i]; i++) {
534         if (buffer[i] != '<')
535             continue;
536         if (!filter) {
537             parse(&buffer, i, "<RRD::CV", cgiget);
538             parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
539             parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
540             parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
541         }
542         parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
543         parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
544         parse(&buffer, i, "<RRD::GRAPH", drawgraph);
545         parse(&buffer, i, "<RRD::INCLUDE", includefile);
546         parse(&buffer, i, "<RRD::PRINT", drawprint);
547         parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
548         parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
549         parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
550         parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
551         parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
552         parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
553         parse(&buffer, i, "<RRD::INTERNAL", rrdgetinternal);
554     }
555
556     if (!filter) {
557         printf("Content-Type: text/html\n"
558                "Content-Length: %zd\n", strlen(buffer));
559
560         if (labs(goodfor) > 0) {
561             time_t    now;
562
563             now = time(NULL);
564             printf("Last-Modified: %s\n", http_time(&now));
565             now += labs(goodfor);
566             printf("Expires: %s\n", http_time(&now));
567             if (goodfor < 0) {
568                 printf("Refresh: %ld\n", labs(goodfor));
569             }
570         }
571         printf("\n");
572     }
573
574     /* output result */
575     printf("%s", buffer);
576
577     /* cleanup */
578     calfree();
579     if (buffer) {
580         free(buffer);
581     }
582     donevar();
583     exit(0);
584 }
585
586 /* remove occurrences of .. this is a general measure to make
587    paths which came in via cgi do not go UP ... */
588
589 char     *rrdsetenv(
590     long argc,
591     const char **args)
592 {
593     if (argc >= 2) {
594         const size_t len = strlen(args[0]) + strlen(args[1]) + 2;
595         char *xyz = malloc(len);
596
597         if (xyz == NULL) {
598             return stralloc("[ERROR: allocating setenv buffer]");
599         };
600         snprintf(xyz, len, "%s=%s", args[0], args[1]);
601         if (putenv(xyz) == -1) {
602             free(xyz);
603             return stralloc("[ERROR: failed to do putenv]");
604         };
605         return stralloc("");
606     }
607     return stralloc("[ERROR: setenv failed because not enough "
608                     "arguments were defined]");
609 }
610
611 /* rrd interface to the variable function putvar() */
612 char     *rrdsetvar(
613     long argc,
614     const char **args)
615 {
616     if (argc >= 2) {
617         const char *result = putvar(args[0], args[1], 0 /* not const */ );
618
619         if (result) {
620             /* setvar does not return the value set */
621             return stralloc("");
622         }
623         return stralloc("[ERROR: putvar failed]");
624     }
625     return stralloc("[ERROR: putvar failed because not enough arguments "
626                     "were defined]");
627 }
628
629 /* rrd interface to the variable function putvar() */
630 char     *rrdsetvarconst(
631     long argc,
632     const char **args)
633 {
634     if (argc >= 2) {
635         const char *result = putvar(args[0], args[1], 1 /* const */ );
636
637         if (result) {
638             /* setvar does not return the value set */
639             return stralloc("");
640         }
641         return stralloc("[ERROR: putvar failed]");
642     }
643     return stralloc("[ERROR: putvar failed because not enough arguments "
644                     "were defined]");
645 }
646
647 char     *rrdgetenv(
648     long argc,
649     const char **args)
650 {
651     char      buf[128];
652     const char *envvar;
653
654     if (argc != 1) {
655         return stralloc("[ERROR: getenv failed because it did not "
656                         "get 1 argument only]");
657     };
658     envvar = getenv(args[0]);
659     if (envvar) {
660         return stralloc(envvar);
661     } else {
662         snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
663         return stralloc(buf);
664     }
665 }
666
667 char     *rrdgetvar(
668     long argc,
669     const char **args)
670 {
671     char      buf[128];
672     const char *value;
673
674     if (argc != 1) {
675         return stralloc("[ERROR: getvar failed because it did not "
676                         "get 1 argument only]");
677     };
678     value = getvar(args[0]);
679     if (value) {
680         return stralloc(value);
681     } else {
682         snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
683         return stralloc(buf);
684     }
685 }
686
687 char     *rrdgoodfor(
688     long argc,
689     const char **args)
690 {
691     if (argc == 1) {
692         goodfor = atol(args[0]);
693     } else {
694         return stralloc("[ERROR: goodfor expected 1 argument]");
695     }
696
697     if (goodfor == 0) {
698         return stralloc("[ERROR: goodfor value must not be 0]");
699     }
700
701     return stralloc("");
702 }
703
704 char     *rrdgetinternal(
705     long argc,
706     const char **args)
707 {
708     if (argc == 1) {
709         if (strcasecmp(args[0], "VERSION") == 0) {
710             return stralloc(PACKAGE_VERSION);
711         } else if (strcasecmp(args[0], "COMPILETIME") == 0) {
712             return stralloc(__DATE__ " " __TIME__);
713         } else {
714             return stralloc("[ERROR: internal unknown argument]");
715         }
716     } else {
717         return stralloc("[ERROR: internal expected 1 argument]");
718     }
719 }
720
721 /* Format start or end times using strftime.  We always need both the
722  * start and end times, because, either might be relative to the other.
723  * */
724 #define MAX_STRFTIME_SIZE 256
725 char     *printstrftime(
726     long argc,
727     const char **args)
728 {
729     rrd_time_value_t start_tv, end_tv;
730     char     *parsetime_error = NULL;
731     char      formatted[MAX_STRFTIME_SIZE];
732     struct tm *the_tm;
733     time_t    start_tmp, end_tmp;
734
735     /* Make sure that we were given the right number of args */
736     if (argc != 4) {
737         rrd_set_error("wrong number of args %d", argc);
738         return stralloc("");
739     }
740
741     /* Init start and end time */
742     rrd_parsetime("end-24h", &start_tv);
743     rrd_parsetime("now", &end_tv);
744
745     /* Parse the start and end times we were given */
746     if ((parsetime_error = rrd_parsetime(args[1], &start_tv))) {
747         rrd_set_error("start time: %s", parsetime_error);
748         return stralloc("");
749     }
750     if ((parsetime_error = rrd_parsetime(args[2], &end_tv))) {
751         rrd_set_error("end time: %s", parsetime_error);
752         return stralloc("");
753     }
754     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
755         return stralloc("");
756     }
757
758     /* Do we do the start or end */
759     if (strcasecmp(args[0], "START") == 0) {
760         the_tm = localtime(&start_tmp);
761     } else if (strcasecmp(args[0], "END") == 0) {
762         the_tm = localtime(&end_tmp);
763     } else {
764         rrd_set_error("start/end not found in '%s'", args[0]);
765         return stralloc("");
766     }
767
768     /* now format it */
769     if (strftime(formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
770         return (stralloc(formatted));
771     } else {
772         rrd_set_error("strftime failed");
773         return stralloc("");
774     }
775 }
776
777 char     *includefile(
778     long argc,
779     const char **args)
780 {
781     char     *buffer;
782
783     if (argc >= 1) {
784         const char *filename = args[0];
785
786         readfile(filename, &buffer, 0);
787         if (rrd_test_error()) {
788             const size_t len = strlen(rrd_get_error()) + DS_NAM_SIZE;
789             char *err = malloc(len);
790
791             snprintf(err, len, "[ERROR: %s]", rrd_get_error());
792             rrd_clear_error();
793             return err;
794         } else {
795             return buffer;
796         }
797     } else {
798         return stralloc("[ERROR: No Inclue file defined]");
799     }
800 }
801
802 /* make a copy of buf and replace open/close brackets with '_' */
803 char     *rrdstrip(
804     char *buf)
805 {
806     char     *p;
807
808     if (buf == NULL) {
809         return NULL;
810     }
811     /* make a copy of the buffer */
812     buf = stralloc(buf);
813     if (buf == NULL) {
814         return NULL;
815     }
816
817     p = buf;
818     while (*p) {
819         if (*p == '<' || *p == '>') {
820             *p = '_';
821         }
822         p++;
823     }
824     return buf;
825 }
826
827 char     *cgigetq(
828     long argc,
829     const char **args)
830 {
831     if (argc >= 1) {
832         char     *buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
833         char     *buf2;
834         char     *c, *d;
835         int       qc = 0;
836
837         if (buf == NULL)
838             return NULL;
839
840         for (c = buf; *c != '\0'; c++)
841             if (*c == '"')
842                 qc++;
843         if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
844             perror("Malloc Buffer");
845             exit(1);
846         };
847         c = buf;
848         d = buf2;
849         *(d++) = '"';
850         while (*c != '\0') {
851             if (*c == '"') {
852                 *(d++) = '"';
853                 *(d++) = '\'';
854                 *(d++) = '"';
855                 *(d++) = '\'';
856             }
857             *(d++) = *(c++);
858         }
859         *(d++) = '"';
860         *(d) = '\0';
861         free(buf);
862         return buf2;
863     }
864
865     return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
866 }
867
868 /* remove occurrences of .. this is a general measure to make
869    paths which came in via cgi do not go UP ... */
870
871 char     *cgigetqp(
872     long argc,
873     const char **args)
874 {
875     char     *buf;
876     char     *buf2;
877     char     *p;
878     char     *d;
879
880     if (argc < 1) {
881         return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
882     }
883
884     buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
885     if (!buf) {
886         return NULL;
887     }
888
889     buf2 = malloc(strlen(buf) + 1);
890     if (!buf2) {
891         perror("cgigetqp(): Malloc Path Buffer");
892         exit(1);
893     };
894
895     p = buf;
896     d = buf2;
897
898     while (*p) {
899         /* prevent mallicious paths from entering the system */
900         if (p[0] == '.' && p[1] == '.') {
901             p += 2;
902             *d++ = '_';
903             *d++ = '_';
904         } else {
905             *d++ = *p++;
906         }
907     }
908
909     *d = 0;
910     free(buf);
911
912     /* Make sure the path is relative, e.g. does not start with '/' */
913     p = buf2;
914     while ('/' == *p) {
915         *p++ = '_';
916     }
917
918     return buf2;
919 }
920
921
922 char     *cgiget(
923     long argc,
924     const char **args)
925 {
926     if (argc >= 1)
927         return rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
928     else
929         return stralloc("[ERROR: not enough arguments for RRD::CV]");
930 }
931
932
933
934 char     *drawgraph(
935     long argc,
936     const char **args)
937 {
938     int       i, xsize, ysize;
939     double    ymin, ymax;
940
941     for (i = 0; i < argc; i++)
942         if (strcmp(args[i], "--imginfo") == 0 || strcmp(args[i], "-g") == 0)
943             break;
944     if (i == argc) {
945         args[argc++] = "--imginfo";
946         args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
947     }
948     calfree();
949     if (rrd_graph
950         (argc + 1, (char **) args - 1, &calcpr, &xsize, &ysize, NULL, &ymin,
951          &ymax) != -1) {
952         return stralloc(calcpr[0]);
953     } else {
954         if (rrd_test_error()) {
955             const size_t len = strlen(rrd_get_error()) + DS_NAM_SIZE;
956             char *err = malloc(len);
957             snprintf(err, len, "[ERROR: %s]", rrd_get_error());
958             rrd_clear_error();
959             return err;
960         }
961     }
962     return NULL;
963 }
964
965 char     *drawprint(
966     long argc,
967     const char **args)
968 {
969     if (argc == 1 && calcpr) {
970         long      i = 0;
971
972         while (calcpr[i] != NULL)
973             i++;        /*determine number lines in calcpr */
974         if (atol(args[0]) < i - 1)
975             return stralloc(calcpr[atol(args[0]) + 1]);
976     }
977     return stralloc("[ERROR: RRD::PRINT argument error]");
978 }
979
980 char     *printtimelast(
981     long argc,
982     const char **args)
983 {
984     time_t    last;
985     struct tm tm_last;
986     char     *buf;
987
988     if (argc == 2) {
989         buf = malloc(255);
990         if (buf == NULL) {
991             return stralloc("[ERROR: allocating strftime buffer]");
992         };
993         /* not raising argc in step with args - 1 since the last argument
994            will be used below for strftime  */
995
996         last = rrd_last(argc, (char **) args - 1);
997         if (rrd_test_error()) {
998             const size_t len = strlen(rrd_get_error()) + DS_NAM_SIZE;
999             char *err = malloc(len);
1000             snprintf(err, len, "[ERROR: %s]", rrd_get_error());
1001             rrd_clear_error();
1002             return err;
1003         }
1004         tm_last = *localtime(&last);
1005         strftime(buf, 254, args[1], &tm_last);
1006         return buf;
1007     }
1008     return stralloc("[ERROR: expected <RRD::TIME::LAST file.rrd strftime-format>]");
1009 }
1010
1011 char     *printtimenow(
1012     long argc,
1013     const char **args)
1014 {
1015     time_t    now = time(NULL);
1016     struct tm tm_now;
1017     char     *buf;
1018
1019     if (argc == 1) {
1020         buf = malloc(255);
1021         if (buf == NULL) {
1022             return stralloc("[ERROR: allocating strftime buffer]");
1023         };
1024         tm_now = *localtime(&now);
1025         strftime(buf, 254, args[0], &tm_now);
1026         return buf;
1027     }
1028     if (argc < 1) {
1029         return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
1030     }
1031     return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
1032 }
1033
1034 /* Scan buffer until an unescaped '>' arives.
1035  * Update argument array with arguments found.
1036  * Return end cursor where parsing stopped, or NULL in case of failure.
1037  *
1038  * FIXME:
1039  * To allow nested constructs, we call rrd_expand_vars() for arguments
1040  * that contain RRD::x directives. These introduce a small memory leak
1041  * since we have to stralloc the arguments the way parse() works.
1042  */
1043 char     *scanargs(
1044     char *line,
1045     int *argument_count,
1046     char ***arguments)
1047 {
1048     char     *getP;     /* read cursor */
1049     char     *putP;     /* write cursor */
1050     char      Quote;    /* type of quote if in quoted string, 0 otherwise */
1051     int       tagcount; /* open tag count */
1052     int       in_arg;   /* if we currently are parsing an argument or not */
1053     int       argsz;    /* argument array size */
1054     int       curarg_contains_rrd_directives;
1055
1056     /* local array of arguments while parsing */
1057     int       argc = 1;
1058     char    **argv;
1059
1060 #ifdef DEBUG_PARSER
1061     printf("<-- scanargs(%s) -->\n", line);
1062 #endif
1063
1064     *arguments = NULL;
1065     *argument_count = 0;
1066
1067     /* create initial argument array of char pointers */
1068     argsz = 32;
1069     argv = (char **) malloc(argsz * sizeof(char *));
1070     if (!argv) {
1071         return NULL;
1072     }
1073     argv[0] = "rrdcgi";
1074
1075     /* skip leading blanks */
1076     while (isspace((int) *line)) {
1077         line++;
1078     }
1079
1080     getP = line;
1081     putP = line;
1082
1083     Quote = 0;
1084     in_arg = 0;
1085     tagcount = 0;
1086
1087     curarg_contains_rrd_directives = 0;
1088
1089     /* start parsing 'line' for arguments */
1090     while (*getP) {
1091         unsigned char c = *getP++;
1092
1093         if (c == '>' && !Quote && !tagcount) {
1094             /* this is our closing tag, quit scanning */
1095             break;
1096         }
1097
1098         /* remove all special chars */
1099         if (c < ' ') {
1100             c = ' ';
1101         }
1102
1103         switch (c) {
1104         case ' ':
1105             if (Quote || tagcount) {
1106                 /* copy quoted/tagged (=RRD expanded) string */
1107                 *putP++ = c;
1108             } else if (in_arg) {
1109                 /* end argument string */
1110                 *putP++ = 0;
1111                 in_arg = 0;
1112                 if (curarg_contains_rrd_directives) {
1113                     argv[argc - 1] =
1114                         rrd_expand_vars(stralloc(argv[argc - 1]));
1115                     curarg_contains_rrd_directives = 0;
1116                 }
1117             }
1118             break;
1119
1120         case '"':      /* Fall through */
1121         case '\'':
1122             if (Quote != 0) {
1123                 if (Quote == c) {
1124                     Quote = 0;
1125                 } else {
1126                     /* copy quoted string */
1127                     *putP++ = c;
1128                 }
1129             } else {
1130                 if (!in_arg) {
1131                     /* reference start of argument string in argument array */
1132                     argv[argc++] = putP;
1133                     in_arg = 1;
1134                 }
1135                 Quote = c;
1136             }
1137             break;
1138
1139         default:
1140             if (!in_arg) {
1141                 /* start new argument */
1142                 argv[argc++] = putP;
1143                 in_arg = 1;
1144             }
1145             if (c == '>') {
1146                 if (tagcount) {
1147                     tagcount--;
1148                 }
1149             }
1150             if (c == '<') {
1151                 tagcount++;
1152                 if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
1153                     curarg_contains_rrd_directives = 1;
1154                 }
1155             }
1156             *putP++ = c;
1157             break;
1158         }
1159
1160         /* check if our argument array is still large enough */
1161         if (argc == argsz) {
1162             /* resize argument array */
1163             argsz *= 2;
1164             argv = rrd_realloc(argv, argsz * sizeof(char *));
1165             if (*argv == NULL) {
1166                 return NULL;
1167             }
1168         }
1169     }
1170
1171     /* terminate last argument found */
1172     *putP = '\0';
1173     if (curarg_contains_rrd_directives) {
1174         argv[argc - 1] = rrd_expand_vars(stralloc(argv[argc - 1]));
1175     }
1176 #ifdef DEBUG_PARSER
1177     if (argc > 1) {
1178         int       n;
1179
1180         printf("<-- arguments found [%d]\n", argc);
1181         for (n = 0; n < argc; n++) {
1182             printf("arg %02d: '%s'\n", n, argv[n]);
1183         }
1184         printf("-->\n");
1185     } else {
1186         printf("<!-- No arguments found -->\n");
1187     }
1188 #endif
1189
1190     /* update caller's notion of the argument array and it's size */
1191
1192     /* note this is a bit of a hack since the rrd_cgi code used to just put
1193        its arguments into a normal array starting at 0 ... since the rrd_*
1194        commands expect and argc/argv array we used to just shift everything
1195        by -1 ... this in turn exploded when a rrd_* function tried to print
1196        argv[0] ... hence we are now doing everything in argv style but hand
1197        over seemingly the old array ... but doing argv-1 will actually end
1198        up in a 'good' place now. */
1199
1200     *arguments = argv+1;
1201     *argument_count = argc-1;
1202
1203     if (Quote) {
1204         return NULL;
1205     }
1206
1207     /* Return new scanning cursor:
1208        pointer to char after closing bracket */
1209     return getP;
1210 }
1211
1212
1213 /*
1214  * Parse(): scan current portion of buffer for given tag.
1215  * If found, parse tag arguments and call 'func' for it.
1216  * The result of func is inserted at the current position
1217  * in the buffer.
1218  */
1219 int parse(
1220     char **buf,         /* buffer */
1221     long i,             /* offset in buffer */
1222     char *tag,          /* tag to handle  */
1223     char *    (*func) (long,
1224                        const char **)   /* function to call for 'tag' */
1225     )
1226 {
1227     /* the name of the vairable ... */
1228     char     *val;
1229     long      valln;
1230     char    **args;
1231     char     *end;
1232     long      end_offset;
1233     int       argc;
1234     size_t    taglen = strlen(tag);
1235
1236     /* Current position in buffer should start with 'tag' */
1237     if (strncmp((*buf) + i, tag, taglen) != 0) {
1238         return 0;
1239     }
1240     /* .. and match exactly (a whitespace following 'tag') */
1241     if (!isspace(*((*buf) + i + taglen))) {
1242         return 0;
1243     }
1244 #ifdef DEBUG_PARSER
1245     printf("parse(): handling tag '%s'\n", tag);
1246 #endif
1247
1248     /* Scan for arguments following the tag;
1249        scanargs() puts \0 into *buf ... so after scanargs it is probably
1250        not a good time to use strlen on buf */
1251     end = scanargs((*buf) + i + taglen, &argc, &args);
1252     if (end) {
1253         /* got arguments, call function for 'tag' with arguments */
1254         val = func(argc, (const char **) args);
1255         free(args-1);
1256     } else {
1257         /* next call, try parsing at current offset +1 */
1258         end = (*buf) + i + 1;
1259
1260         val = stralloc("[ERROR: Parsing Problem with the following text\n"
1261                        " Check original file. This may have been altered "
1262                        "by parsing.]\n\n");
1263     }
1264
1265     /* remember offset where we have to continue parsing */
1266     end_offset = end - (*buf);
1267
1268     valln = 0;
1269     if (val) {
1270         valln = strlen(val);
1271     }
1272
1273     /* Optionally resize buffer to hold the replacement value:
1274        Calculating the new length of the buffer is simple. add current
1275        buffer pos (i) to length of string after replaced tag to length
1276        of replacement string and add 1 for the final zero ... */
1277     if (end - (*buf) < (i + valln)) {
1278         /* make sure we do not shrink the mallocd block */
1279         size_t    newbufsize = i + strlen(end) + valln + 1;
1280
1281         *buf = rrd_realloc(*buf, newbufsize);
1282
1283         if (*buf == NULL) {
1284             perror("Realoc buf:");
1285             exit(1);
1286         };
1287     }
1288
1289     /* Update new end pointer:
1290        make sure the 'end' pointer gets moved along with the 
1291        buf pointer when realloc moves memory ... */
1292     end = (*buf) + end_offset;
1293
1294     /* splice the variable:
1295        step 1. Shift pending data to make room for 'val' */
1296     memmove((*buf) + i + valln, end, strlen(end) + 1);
1297
1298     /* step 2. Insert val */
1299     if (val) {
1300         memmove((*buf) + i, val, valln);
1301         free(val);
1302     }
1303     return (valln > 0 ? valln - 1 : valln);
1304 }
1305
1306 char     *http_time(
1307     time_t *now)
1308 {
1309     struct tm *tmptime;
1310     static char buf[60];
1311
1312     tmptime = gmtime(now);
1313     strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", tmptime);
1314     return (buf);
1315 }
1316
1317 void rrdcgiHeader(
1318     void)
1319 {
1320     if (rrdcgiType)
1321         printf("Content-type: %s\n", rrdcgiType);
1322     else
1323         printf("Content-type: text/html\n");
1324     if (rrdcgiHeaderString)
1325         printf("%s", rrdcgiHeaderString);
1326     printf("\n");
1327 }
1328
1329 void rrdcgiDebug(
1330     int level,
1331     int where)
1332 {
1333     if (level > 0)
1334         rrdcgiDebugLevel = level;
1335     else
1336         rrdcgiDebugLevel = 0;
1337     if (where)
1338         rrdcgiDebugStderr = 0;
1339     else
1340         rrdcgiDebugStderr = 1;
1341 }
1342
1343 char     *rrdcgiDecodeString(
1344     char *text)
1345 {
1346     char     *cp, *xp;
1347
1348     for (cp = text, xp = text; *cp; cp++) {
1349         if (*cp == '%') {
1350             if (strchr("0123456789ABCDEFabcdef", *(cp + 1))
1351                 && strchr("0123456789ABCDEFabcdef", *(cp + 2))) {
1352                 if (islower(*(cp + 1)))
1353                     *(cp + 1) = toupper(*(cp + 1));
1354                 if (islower(*(cp + 2)))
1355                     *(cp + 2) = toupper(*(cp + 2));
1356                 *(xp) =
1357                     (*(cp + 1) >=
1358                      'A' ? *(cp + 1) - 'A' + 10 : *(cp + 1) - '0') * 16 +
1359                     (*(cp + 2) >=
1360                      'A' ? *(cp + 2) - 'A' + 10 : *(cp + 2) - '0');
1361                 xp++;
1362                 cp += 2;
1363             }
1364         } else {
1365             *(xp++) = *cp;
1366         }
1367     }
1368     memset(xp, 0, cp - xp);
1369     return text;
1370 }
1371
1372 /*  rrdcgiReadVariables()
1373  *
1374  *  Read from stdin if no string is provided via CGI.  Variables that
1375  *  doesn't have a value associated with it doesn't get stored.
1376  */
1377 s_var   **rrdcgiReadVariables(
1378     void)
1379 {
1380     int       length;
1381     char     *line = NULL;
1382     int       numargs;
1383     char     *cp, *ip, *esp, *sptr;
1384     s_var   **result;
1385     int       i, k, len;
1386     char      tmp[101];
1387     size_t    tmplen;
1388
1389     cp = getenv("REQUEST_METHOD");
1390     ip = getenv("CONTENT_LENGTH");
1391
1392     if (cp && !strcmp(cp, "POST")) {
1393         if (ip) {
1394             length = atoi(ip);
1395             if ((line = (char *) malloc(length + 2)) == NULL)
1396                 return NULL;
1397             if (fgets(line, length + 1, stdin) == NULL)
1398                 return NULL;
1399         } else
1400             return NULL;
1401     } else if (cp && !strcmp(cp, "GET")) {
1402         esp = getenv("QUERY_STRING");
1403         if (esp && strlen(esp)) {
1404             if ((line = strdup(esp)) == NULL)
1405                 return NULL;
1406         } else
1407             return NULL;
1408     } else {
1409         length = 0;
1410         printf("(offline mode: enter name=value pairs on standard input)\n");
1411         memset(tmp, 0, sizeof(tmp));
1412         while ((cp = fgets(tmp, 100, stdin)) != NULL) {
1413             if ((tmplen = strlen(tmp)) != 0) {
1414                 if (tmp[tmplen - 1] == '\n')
1415                     tmp[tmplen - 1] = '&';
1416                 length += tmplen;
1417                 len = (length + 1) * sizeof(char);
1418                 if ((unsigned) length > tmplen) {
1419                     if ((line = (char *) realloc(line, len)) == NULL)
1420                         return NULL;
1421                     strncat(line, tmp, tmplen);
1422                 } else {
1423                     if ((line = strdup(tmp)) == NULL)
1424                         return NULL;
1425                 }
1426             }
1427             memset(tmp, 0, sizeof(tmp));
1428         }
1429         if (!line)
1430             return NULL;
1431         if (line[strlen(line) - 1] == '&')
1432             line[strlen(line) - 1] = '\0';
1433     }
1434
1435     /*
1436      *  From now on all cgi variables are stored in the variable line
1437      *  and look like  foo=bar&foobar=barfoo&foofoo=
1438      */
1439
1440     if (rrdcgiDebugLevel > 0) {
1441         if (rrdcgiDebugStderr)
1442             fprintf(stderr, "Received cgi input: %s\n", line);
1443         else
1444             printf
1445                 ("<b>Received cgi input</b><br>\n<pre>\n--\n%s\n--\n</pre>\n\n",
1446                  line);
1447     }
1448
1449     for (cp = line; *cp; cp++)
1450         if (*cp == '+')
1451             *cp = ' ';
1452
1453     if (strlen(line)) {
1454         for (numargs = 1, cp = line; *cp; cp++)
1455             if (*cp == '&')
1456                 numargs++;
1457     } else
1458         numargs = 0;
1459     if (rrdcgiDebugLevel > 0) {
1460         if (rrdcgiDebugStderr)
1461             fprintf(stderr, "%d cgi variables found.\n", numargs);
1462         else
1463             printf("%d cgi variables found.<br>\n", numargs);
1464     }
1465
1466     len = (numargs + 1) * sizeof(s_var *);
1467     if ((result = (s_var **) malloc(len)) == NULL)
1468         return NULL;
1469     memset(result, 0, len);
1470
1471     cp = line;
1472     i = 0;
1473     while (*cp) {
1474         if ((ip = (char *) strchr(cp, '&')) != NULL) {
1475             *ip = '\0';
1476         } else
1477             ip = cp + strlen(cp);
1478
1479         if ((esp = (char *) strchr(cp, '=')) == NULL) {
1480             cp = ++ip;
1481             continue;
1482         }
1483
1484         if (!strlen(esp)) {
1485             cp = ++ip;
1486             continue;
1487         }
1488
1489         if (i < numargs) {
1490
1491             /* try to find out if there's already such a variable */
1492             for (k = 0; k < i && (strncmp(result[k]->name, cp, esp - cp)
1493                                   || !(strlen(result[k]->name) ==
1494                                        (size_t) (esp - cp))); k++);
1495
1496             if (k == i) {   /* No such variable yet */
1497                 if ((result[i] = (s_var *) malloc(sizeof(s_var))) == NULL)
1498                     return NULL;
1499                 if ((result[i]->name =
1500                      (char *) malloc((esp - cp + 1) * sizeof(char))) == NULL)
1501                     return NULL;
1502                 memset(result[i]->name, 0, esp - cp + 1);
1503                 strncpy(result[i]->name, cp, esp - cp);
1504                 cp = ++esp;
1505                 if ((result[i]->value =
1506                      (char *) malloc((ip - esp + 1) * sizeof(char))) == NULL)
1507                     return NULL;
1508                 memset(result[i]->value, 0, ip - esp + 1);
1509                 strncpy(result[i]->value, cp, ip - esp);
1510                 result[i]->value = rrdcgiDecodeString(result[i]->value);
1511                 if (rrdcgiDebugLevel) {
1512                     if (rrdcgiDebugStderr)
1513                         fprintf(stderr, "%s: %s\n", result[i]->name,
1514                                 result[i]->value);
1515                     else
1516                         printf("<h3>Variable %s</h3>\n<pre>\n%s\n</pre>\n\n",
1517                                result[i]->name, result[i]->value);
1518                 }
1519                 i++;
1520             } else {    /* There is already such a name, suppose a mutiple field */
1521                 cp = ++esp;
1522                 len = strlen(result[k]->value) + (ip - esp) + 2;
1523                 if ((sptr = (char *) calloc(len, sizeof(char))) == NULL)
1524                     return NULL;
1525                 snprintf(sptr, len, "%s\n%s", result[k]->value, cp);
1526                 free(result[k]->value);
1527                 result[k]->value = rrdcgiDecodeString(sptr);
1528             }
1529         }
1530         cp = ++ip;
1531     }
1532     return result;
1533 }
1534
1535 /*  rrdcgiInit()
1536  *
1537  *  Read from stdin if no string is provided via CGI.  Variables that
1538  *  doesn't have a value associated with it doesn't get stored.
1539  */
1540 s_cgi    *rrdcgiInit(
1541     void)
1542 {
1543     s_cgi    *res;
1544     s_var   **vars;
1545
1546     vars = rrdcgiReadVariables();
1547
1548     if (!vars)
1549         return NULL;
1550
1551     if ((res = (s_cgi *) malloc(sizeof(s_cgi))) == NULL)
1552         return NULL;
1553     res->vars = vars;
1554
1555     return res;
1556 }
1557
1558 char     *rrdcgiGetValue(
1559     s_cgi * parms,
1560     const char *name)
1561 {
1562     int       i;
1563
1564     if (!parms || !parms->vars)
1565         return NULL;
1566     for (i = 0; parms->vars[i]; i++)
1567         if (!strcmp(name, parms->vars[i]->name)) {
1568             if (rrdcgiDebugLevel > 0) {
1569                 if (rrdcgiDebugStderr)
1570                     fprintf(stderr, "%s found as %s\n", name,
1571                             parms->vars[i]->value);
1572                 else
1573                     printf("%s found as %s<br>\n", name,
1574                            parms->vars[i]->value);
1575             }
1576             return parms->vars[i]->value;
1577         }
1578     if (rrdcgiDebugLevel) {
1579         if (rrdcgiDebugStderr)
1580             fprintf(stderr, "%s not found\n", name);
1581         else
1582             printf("%s not found<br>\n", name);
1583     }
1584     return NULL;
1585 }
1586
1587 void rrdcgiFreeList(
1588     char **list)
1589 {
1590     int       i;
1591
1592     for (i = 0; list[i] != NULL; i++)
1593         free(list[i]);
1594     free(list);
1595 }
1596
1597 void rrdcgiFree(
1598     s_cgi * parms)
1599 {
1600     int       i;
1601
1602     if (!parms)
1603         return;
1604     if (parms->vars) {
1605         for (i = 0; parms->vars[i]; i++) {
1606             if (parms->vars[i]->name)
1607                 free(parms->vars[i]->name);
1608             if (parms->vars[i]->value)
1609                 free(parms->vars[i]->value);
1610             free(parms->vars[i]);
1611         }
1612         free(parms->vars);
1613     }
1614     free(parms);
1615
1616     if (rrdcgiHeaderString) {
1617         free(rrdcgiHeaderString);
1618         rrdcgiHeaderString = NULL;
1619     }
1620     if (rrdcgiType) {
1621         free(rrdcgiType);
1622         rrdcgiType = NULL;
1623     }
1624 }