45bf1609d1ae835fa0e0405ae3913fc38fffce02
[rrdtool.git] / src / rrd_cgi.c
1 /*****************************************************************************
2  * RRDtool 1.4.2  Copyright by Tobi Oetiker, 1997-2009
3  *****************************************************************************
4  * rrd_cgi.c  RRD Web Page Generator
5  *****************************************************************************/
6
7 #include "rrd_tool.h"
8 #ifdef HAVE_STDLIB_H
9 #include <stdlib.h>
10 #endif
11
12 #define MEMBLK 1024
13 /*#define DEBUG_PARSER
14 #define DEBUG_VARS*/
15
16 typedef struct var_s {
17     char     *name, *value;
18 } s_var;
19
20 typedef struct cgi_s {
21     s_var   **vars;
22 } s_cgi;
23
24 /* in arg[0] find tags beginning with arg[1] call arg[2] on them
25    and replace by result of arg[2] call */
26 int       parse(
27     char **,
28     long,
29     char *,
30     char *    (*)(long,
31                   const char **));
32
33 /**************************************************/
34 /* tag replacers ... they are called from parse   */
35 /* through function pointers                      */
36 /**************************************************/
37
38 /* return cgi var named arg[0] */
39 char     *cgiget(
40     long,
41     const char **);
42
43 /* return a quoted cgi var named arg[0] */
44 char     *cgigetq(
45     long,
46     const char **);
47
48 /* return a quoted and sanitized cgi variable */
49 char     *cgigetqp(
50     long,
51     const char **);
52
53 /* call rrd_graph and insert appropriate image tag */
54 char     *drawgraph(
55     long,
56     const char **);
57
58 /* return PRINT functions from last rrd_graph call */
59 char     *drawprint(
60     long,
61     const char **);
62
63 /* pretty-print the <last></last> value for some.rrd via strftime() */
64 char     *printtimelast(
65     long,
66     const char **);
67
68 /* pretty-print current time */
69 char     *printtimenow(
70     long,
71     const char **);
72
73 /* set an environment variable */
74 char     *rrdsetenv(
75     long,
76     const char **);
77
78 /* get an environment variable */
79 char     *rrdgetenv(
80     long,
81     const char **);
82
83 /* include the named file at this point */
84 char     *includefile(
85     long,
86     const char **);
87
88 /* for how long is the output of the cgi valid ? */
89 char     *rrdgoodfor(
90     long,
91     const char **);
92
93 /* return rrdcgi version string */
94 char     *rrdgetinternal(
95     long,
96     const char **);
97
98 char     *rrdstrip(
99     char *buf);
100 char     *scanargs(
101     char *line,
102     int *argc,
103     char ***args);
104
105 /* format at-time specified times using strftime */
106 char     *printstrftime(
107     long,
108     const char **);
109
110 /** HTTP protocol needs special format, and GMT time **/
111 char     *http_time(
112     time_t *);
113
114 /* return a pointer to newly allocated copy of this string */
115 char     *stralloc(
116     const char *);
117
118 /* global variable for rrdcgi */
119 s_cgi    *rrdcgiArg;
120
121 /* rrdcgiHeader
122  * 
123  *  Prints a valid CGI Header (Content-type...) etc.
124  */
125 void      rrdcgiHeader(
126     void);
127
128 /* rrdcgiDecodeString
129  * decode html escapes
130  */
131
132 char     *rrdcgiDecodeString(
133     char *text);
134
135 /* rrdcgiDebug
136  * 
137  *  Set/unsets debugging
138  */
139 void      rrdcgiDebug(
140     int level,
141     int where);
142
143 /* rrdcgiInit
144  *
145  *  Reads in variables set via POST or stdin.
146  */
147 s_cgi    *rrdcgiInit(
148     void);
149
150 /* rrdcgiGetValue
151  *
152  *  Returns the value of the specified variable or NULL if it's empty
153  *  or doesn't exist.
154  */
155 char     *rrdcgiGetValue(
156     s_cgi * parms,
157     const char *name);
158
159 /* rrdcgiFreeList
160  *
161  * Frees a list as returned by rrdcgiGetVariables()
162  */
163 void      rrdcgiFreeList(
164     char **list);
165
166 /* rrdcgiFree
167  *
168  * Frees the internal data structures
169  */
170 void      rrdcgiFree(
171     s_cgi * parms);
172
173 /*  rrdcgiReadVariables()
174  *
175  *  Read from stdin if no string is provided via CGI.  Variables that
176  *  doesn't have a value associated with it doesn't get stored.
177  */
178 s_var   **rrdcgiReadVariables(
179     void);
180
181
182 int       rrdcgiDebugLevel = 0;
183 int       rrdcgiDebugStderr = 1;
184 char     *rrdcgiHeaderString = NULL;
185 char     *rrdcgiType = NULL;
186
187 /* rrd interface to the variable functions {put,get}var() */
188 char     *rrdgetvar(
189     long argc,
190     const char **args);
191 char     *rrdsetvar(
192     long argc,
193     const char **args);
194 char     *rrdsetvarconst(
195     long argc,
196     const char **args);
197
198
199 /* variable store: put/get key-value pairs */
200 static int initvar(
201     );
202 static void donevar(
203     );
204 static const char *getvar(
205     const char *varname);
206 static const char *putvar(
207     const char *name,
208     const char *value,
209     int is_const);
210
211 /* key value pair that makes up an entry in the variable store */
212 typedef struct {
213     int       is_const; /* const variable or not */
214     const char *name;   /* variable name */
215     const char *value;  /* variable value */
216 } vardata;
217
218 /* the variable heap: 
219    start with a heapsize of 10 variables */
220 #define INIT_VARSTORE_SIZE      10
221 static vardata *varheap = NULL;
222 static size_t varheap_size = 0;
223
224 /* allocate and initialize variable heap */
225 static int initvar(
226     void)
227 {
228     varheap = (vardata *) malloc(sizeof(vardata) * INIT_VARSTORE_SIZE);
229     if (varheap == NULL) {
230         fprintf(stderr, "ERROR: unable to initialize variable store\n");
231         return -1;
232     }
233     memset(varheap, 0, sizeof(vardata) * INIT_VARSTORE_SIZE);
234     varheap_size = INIT_VARSTORE_SIZE;
235     return 0;
236 }
237
238 /* cleanup: free allocated memory */
239 static void donevar(
240     void)
241 {
242     int       i;
243
244     if (varheap) {
245         for (i = 0; i < (int) varheap_size; i++) {
246             if (varheap[i].name) {
247                 free((char *) varheap[i].name);
248             }
249             if (varheap[i].value) {
250                 free((char *) varheap[i].value);
251             }
252         }
253         free(varheap);
254     }
255 }
256
257 /* Get a variable from the variable store.
258    Return NULL in case the requested variable was not found. */
259 static const char *getvar(
260     const char *name)
261 {
262     int       i;
263
264     for (i = 0; i < (int) varheap_size && varheap[i].name; i++) {
265         if (0 == strcmp(name, varheap[i].name)) {
266 #ifdef          DEBUG_VARS
267             printf("<!-- getvar(%s) -> %s -->\n", name, varheap[i].value);
268 #endif
269             return varheap[i].value;
270         }
271     }
272 #ifdef DEBUG_VARS
273     printf("<!-- getvar(%s) -> Not found-->\n", name);
274 #endif
275     return NULL;
276 }
277
278 /* Put a variable into the variable store. If a variable by that
279    name exists, it's value is overwritten with the new value unless it was
280    marked as 'const' (initialized by RRD::SETCONSTVAR).
281    Returns a copy the newly allocated value on success, NULL on error. */
282 static const char *putvar(
283     const char *name,
284     const char *value,
285     int is_const)
286 {
287     int       i;
288
289     for (i = 0; i < (int) varheap_size && varheap[i].name; i++) {
290         if (0 == strcmp(name, varheap[i].name)) {
291             /* overwrite existing entry */
292             if (varheap[i].is_const) {
293 #ifdef  DEBUG_VARS
294                 printf("<!-- setver(%s, %s): not assigning: "
295                        "const variable -->\n", name, value);
296 #endif
297                 return varheap[i].value;
298             }
299 #ifdef  DEBUG_VARS
300             printf("<!-- setvar(%s, %s): overwriting old value (%s) -->\n",
301                    name, value, varheap[i].value);
302 #endif
303             /* make it possible to promote a variable to readonly */
304             varheap[i].is_const = is_const;
305             free((char *) varheap[i].value);
306             varheap[i].value = stralloc(value);
307             return varheap[i].value;
308         }
309     }
310
311     /* no existing variable found by that name, add it */
312     if (i == (int) varheap_size) {
313         /* ran out of heap: resize heap to double size */
314         size_t    new_size = varheap_size * 2;
315
316         varheap = (vardata *) (realloc(varheap, sizeof(vardata) * new_size));
317         if (!varheap) {
318             fprintf(stderr, "ERROR: Unable to realloc variable heap\n");
319             return NULL;
320         }
321         /* initialize newly allocated memory */ ;
322         memset(&varheap[varheap_size], 0, sizeof(vardata) * varheap_size);
323         varheap_size = new_size;
324     }
325     varheap[i].is_const = is_const;
326     varheap[i].name = stralloc(name);
327     varheap[i].value = stralloc(value);
328
329 #ifdef          DEBUG_VARS
330     printf("<!-- setvar(%s, %s): adding new variable -->\n", name, value);
331 #endif
332     return varheap[i].value;
333 }
334
335 /* expand those RRD:* directives that can be used recursivly */
336 static char *rrd_expand_vars(
337     char *buffer)
338 {
339     int       i;
340
341 #ifdef DEBUG_PARSER
342     printf("expanding variables in '%s'\n", buffer);
343 #endif
344
345     for (i = 0; buffer[i]; i++) {
346         if (buffer[i] != '<')
347             continue;
348         parse(&buffer, i, "<RRD::CV", cgiget);
349         parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
350         parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
351         parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
352         parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
353         parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
354         parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
355         parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
356         parse(&buffer, i, "<RRD::INTERNAL", rrdgetinternal);
357     }
358     return buffer;
359 }
360
361 static long goodfor = 0;
362 static char **calcpr = NULL;
363 static void calfree(
364     void)
365 {
366     if (calcpr) {
367         long      i;
368
369         for (i = 0; calcpr[i]; i++) {
370             if (calcpr[i]) {
371                 free(calcpr[i]);
372             }
373         }
374         if (calcpr) {
375             free(calcpr);
376         }
377         calcpr = NULL;
378     }
379 }
380
381 /* create freeable version of the string */
382 char     *stralloc(
383     const char *str)
384 {
385     char     *nstr;
386
387     if (!str) {
388         return NULL;
389     }
390     nstr = malloc((strlen(str) + 1));
391     strcpy(nstr, str);
392     return (nstr);
393 }
394
395 static int readfile(
396     const char *file_name,
397     char **buffer,
398     int skipfirst)
399 {
400     long      writecnt = 0, totalcnt = MEMBLK;
401     long      offset = 0;
402     FILE     *input = NULL;
403     char      c;
404
405     if ((strcmp("-", file_name) == 0)) {
406         input = stdin;
407     } else {
408         if ((input = fopen(file_name, "rb")) == NULL) {
409             rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
410             return (-1);
411         }
412     }
413     if (skipfirst) {
414         do {
415             c = getc(input);
416             offset++;
417         } while (c != '\n' && !feof(input));
418     }
419     if (strcmp("-", file_name)) {
420         fseek(input, 0, SEEK_END);
421         /* have extra space for detecting EOF without realloc */
422         totalcnt = (ftell(input) + 1) / sizeof(char) - offset;
423         if (totalcnt < MEMBLK)
424             totalcnt = MEMBLK;  /* sanitize */
425         fseek(input, offset * sizeof(char), SEEK_SET);
426     }
427     if (((*buffer) = (char *) malloc((totalcnt + 4) * sizeof(char))) == NULL) {
428         perror("Allocate Buffer:");
429         exit(1);
430     };
431     do {
432         writecnt +=
433             fread((*buffer) + writecnt, 1,
434                   (totalcnt - writecnt) * sizeof(char), input);
435         if (writecnt >= totalcnt) {
436             totalcnt += MEMBLK;
437             if (((*buffer) =
438                  rrd_realloc((*buffer),
439                              (totalcnt + 4) * sizeof(char))) == NULL) {
440                 perror("Realloc Buffer:");
441                 exit(1);
442             };
443         }
444     } while (!feof(input));
445     (*buffer)[writecnt] = '\0';
446     if (strcmp("-", file_name) != 0) {
447         fclose(input);
448     };
449     return writecnt;
450 }
451
452 int main(
453     int argc,
454     char *argv[])
455 {
456     long      length;
457     char     *buffer;
458     char     *server_url = NULL;
459     long      i;
460     long      filter = 0;
461     struct option long_options[] = {
462         {"filter", no_argument, 0, 'f'},
463         {0, 0, 0, 0}
464     };
465
466 #ifdef MUST_DISABLE_SIGFPE
467     signal(SIGFPE, SIG_IGN);
468 #endif
469 #ifdef MUST_DISABLE_FPMASK
470     fpsetmask(0);
471 #endif
472     optind = 0;
473     opterr = 0;         /* initialize getopt */
474
475     /* what do we get for cmdline arguments?
476        for (i=0;i<argc;i++)
477        printf("%d-'%s'\n",i,argv[i]); */
478     while (1) {
479         int       option_index = 0;
480         int       opt;
481
482         opt = getopt_long(argc, argv, "f", long_options, &option_index);
483         if (opt == EOF) {
484             break;
485         }
486
487         switch (opt) {
488         case 'f':
489             filter = 1;
490             break;
491         case '?':
492             printf("unknown commandline option '%s'\n", argv[optind - 1]);
493             return -1;
494         }
495     }
496
497     if (!filter) {
498         rrdcgiDebug(0, 0);
499         rrdcgiArg = rrdcgiInit();
500         server_url = getenv("SERVER_URL");
501     }
502
503     /* make sure we have one extra argument, 
504        if there are others, we do not care Apache gives several */
505
506     /* if ( (optind != argc-2 
507        && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) 
508        && optind != argc-1) { */
509
510     if (optind >= argc) {
511         fprintf(stderr, "ERROR: expected a filename\n");
512         exit(1);
513     } else {
514         length = readfile(argv[optind], &buffer, 1);
515     }
516
517     if (rrd_test_error()) {
518         fprintf(stderr, "ERROR: %s\n", rrd_get_error());
519         exit(1);
520     }
521
522     /* initialize variable heap */
523     initvar();
524
525 #ifdef DEBUG_PARSER
526     /* some fake header for testing */
527     printf("Content-Type: text/html\nContent-Length: 10000000\n\n\n");
528 #endif
529
530
531     /* expand rrd directives in buffer recursivly */
532     for (i = 0; buffer[i]; i++) {
533         if (buffer[i] != '<')
534             continue;
535         if (!filter) {
536             parse(&buffer, i, "<RRD::CV", cgiget);
537             parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
538             parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
539             parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
540         }
541         parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
542         parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
543         parse(&buffer, i, "<RRD::GRAPH", drawgraph);
544         parse(&buffer, i, "<RRD::INCLUDE", includefile);
545         parse(&buffer, i, "<RRD::PRINT", drawprint);
546         parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
547         parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
548         parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
549         parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
550         parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
551         parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
552         parse(&buffer, i, "<RRD::INTERNAL", rrdgetinternal);
553     }
554
555     if (!filter) {
556         printf("Content-Type: text/html\n"
557                "Content-Length: %zd\n", strlen(buffer));
558
559         if (labs(goodfor) > 0) {
560             time_t    now;
561
562             now = time(NULL);
563             printf("Last-Modified: %s\n", http_time(&now));
564             now += labs(goodfor);
565             printf("Expires: %s\n", http_time(&now));
566             if (goodfor < 0) {
567                 printf("Refresh: %ld\n", labs(goodfor));
568             }
569         }
570         printf("\n");
571     }
572
573     /* output result */
574     printf("%s", buffer);
575
576     /* cleanup */
577     calfree();
578     if (buffer) {
579         free(buffer);
580     }
581     donevar();
582     exit(0);
583 }
584
585 /* remove occurrences of .. this is a general measure to make
586    paths which came in via cgi do not go UP ... */
587
588 char     *rrdsetenv(
589     long argc,
590     const char **args)
591 {
592     if (argc >= 2) {
593         char     *xyz = malloc((strlen(args[0]) + strlen(args[1]) + 2));
594
595         if (xyz == NULL) {
596             return stralloc("[ERROR: allocating setenv buffer]");
597         };
598         sprintf(xyz, "%s=%s", args[0], args[1]);
599         if (putenv(xyz) == -1) {
600             free(xyz);
601             return stralloc("[ERROR: failed to do putenv]");
602         };
603         return stralloc("");
604     }
605     return stralloc("[ERROR: setenv failed because not enough "
606                     "arguments were defined]");
607 }
608
609 /* rrd interface to the variable function putvar() */
610 char     *rrdsetvar(
611     long argc,
612     const char **args)
613 {
614     if (argc >= 2) {
615         const char *result = putvar(args[0], args[1], 0 /* not const */ );
616
617         if (result) {
618             /* setvar does not return the value set */
619             return stralloc("");
620         }
621         return stralloc("[ERROR: putvar failed]");
622     }
623     return stralloc("[ERROR: putvar failed because not enough arguments "
624                     "were defined]");
625 }
626
627 /* rrd interface to the variable function putvar() */
628 char     *rrdsetvarconst(
629     long argc,
630     const char **args)
631 {
632     if (argc >= 2) {
633         const char *result = putvar(args[0], args[1], 1 /* const */ );
634
635         if (result) {
636             /* setvar does not return the value set */
637             return stralloc("");
638         }
639         return stralloc("[ERROR: putvar failed]");
640     }
641     return stralloc("[ERROR: putvar failed because not enough arguments "
642                     "were defined]");
643 }
644
645 char     *rrdgetenv(
646     long argc,
647     const char **args)
648 {
649     char      buf[128];
650     const char *envvar;
651
652     if (argc != 1) {
653         return stralloc("[ERROR: getenv failed because it did not "
654                         "get 1 argument only]");
655     };
656     envvar = getenv(args[0]);
657     if (envvar) {
658         return stralloc(envvar);
659     } else {
660         snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
661         return stralloc(buf);
662     }
663 }
664
665 char     *rrdgetvar(
666     long argc,
667     const char **args)
668 {
669     char      buf[128];
670     const char *value;
671
672     if (argc != 1) {
673         return stralloc("[ERROR: getvar failed because it did not "
674                         "get 1 argument only]");
675     };
676     value = getvar(args[0]);
677     if (value) {
678         return stralloc(value);
679     } else {
680         snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
681         return stralloc(buf);
682     }
683 }
684
685 char     *rrdgoodfor(
686     long argc,
687     const char **args)
688 {
689     if (argc == 1) {
690         goodfor = atol(args[0]);
691     } else {
692         return stralloc("[ERROR: goodfor expected 1 argument]");
693     }
694
695     if (goodfor == 0) {
696         return stralloc("[ERROR: goodfor value must not be 0]");
697     }
698
699     return stralloc("");
700 }
701
702 char     *rrdgetinternal(
703     long argc,
704     const char **args)
705 {
706     if (argc == 1) {
707         if (strcasecmp(args[0], "VERSION") == 0) {
708             return stralloc(PACKAGE_VERSION);
709         } else if (strcasecmp(args[0], "COMPILETIME") == 0) {
710             return stralloc(__DATE__ " " __TIME__);
711         } else {
712             return stralloc("[ERROR: internal unknown argument]");
713         }
714     } else {
715         return stralloc("[ERROR: internal expected 1 argument]");
716     }
717 }
718
719 /* Format start or end times using strftime.  We always need both the
720  * start and end times, because, either might be relative to the other.
721  * */
722 #define MAX_STRFTIME_SIZE 256
723 char     *printstrftime(
724     long argc,
725     const char **args)
726 {
727     rrd_time_value_t start_tv, end_tv;
728     char     *parsetime_error = NULL;
729     char      formatted[MAX_STRFTIME_SIZE];
730     struct tm *the_tm;
731     time_t    start_tmp, end_tmp;
732
733     /* Make sure that we were given the right number of args */
734     if (argc != 4) {
735         rrd_set_error("wrong number of args %d", argc);
736         return stralloc("");
737     }
738
739     /* Init start and end time */
740     rrd_parsetime("end-24h", &start_tv);
741     rrd_parsetime("now", &end_tv);
742
743     /* Parse the start and end times we were given */
744     if ((parsetime_error = rrd_parsetime(args[1], &start_tv))) {
745         rrd_set_error("start time: %s", parsetime_error);
746         return stralloc("");
747     }
748     if ((parsetime_error = rrd_parsetime(args[2], &end_tv))) {
749         rrd_set_error("end time: %s", parsetime_error);
750         return stralloc("");
751     }
752     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
753         return stralloc("");
754     }
755
756     /* Do we do the start or end */
757     if (strcasecmp(args[0], "START") == 0) {
758         the_tm = localtime(&start_tmp);
759     } else if (strcasecmp(args[0], "END") == 0) {
760         the_tm = localtime(&end_tmp);
761     } else {
762         rrd_set_error("start/end not found in '%s'", args[0]);
763         return stralloc("");
764     }
765
766     /* now format it */
767     if (strftime(formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
768         return (stralloc(formatted));
769     } else {
770         rrd_set_error("strftime failed");
771         return stralloc("");
772     }
773 }
774
775 char     *includefile(
776     long argc,
777     const char **args)
778 {
779     char     *buffer;
780
781     if (argc >= 1) {
782         const char *filename = args[0];
783
784         readfile(filename, &buffer, 0);
785         if (rrd_test_error()) {
786             char     *err = malloc((strlen(rrd_get_error()) + DS_NAM_SIZE));
787
788             sprintf(err, "[ERROR: %s]", rrd_get_error());
789             rrd_clear_error();
790             return err;
791         } else {
792             return buffer;
793         }
794     } else {
795         return stralloc("[ERROR: No Inclue file defined]");
796     }
797 }
798
799 /* make a copy of buf and replace open/close brackets with '_' */
800 char     *rrdstrip(
801     char *buf)
802 {
803     char     *p;
804
805     if (buf == NULL) {
806         return NULL;
807     }
808     /* make a copy of the buffer */
809     buf = stralloc(buf);
810     if (buf == NULL) {
811         return NULL;
812     }
813
814     p = buf;
815     while (*p) {
816         if (*p == '<' || *p == '>') {
817             *p = '_';
818         }
819         p++;
820     }
821     return buf;
822 }
823
824 char     *cgigetq(
825     long argc,
826     const char **args)
827 {
828     if (argc >= 1) {
829         char     *buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
830         char     *buf2;
831         char     *c, *d;
832         int       qc = 0;
833
834         if (buf == NULL)
835             return NULL;
836
837         for (c = buf; *c != '\0'; c++)
838             if (*c == '"')
839                 qc++;
840         if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
841             perror("Malloc Buffer");
842             exit(1);
843         };
844         c = buf;
845         d = buf2;
846         *(d++) = '"';
847         while (*c != '\0') {
848             if (*c == '"') {
849                 *(d++) = '"';
850                 *(d++) = '\'';
851                 *(d++) = '"';
852                 *(d++) = '\'';
853             }
854             *(d++) = *(c++);
855         }
856         *(d++) = '"';
857         *(d) = '\0';
858         free(buf);
859         return buf2;
860     }
861
862     return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
863 }
864
865 /* remove occurrences of .. this is a general measure to make
866    paths which came in via cgi do not go UP ... */
867
868 char     *cgigetqp(
869     long argc,
870     const char **args)
871 {
872     char     *buf;
873     char     *buf2;
874     char     *p;
875     char     *d;
876
877     if (argc < 1) {
878         return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
879     }
880
881     buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
882     if (!buf) {
883         return NULL;
884     }
885
886     buf2 = malloc(strlen(buf) + 1);
887     if (!buf2) {
888         perror("cgigetqp(): Malloc Path Buffer");
889         exit(1);
890     };
891
892     p = buf;
893     d = buf2;
894
895     while (*p) {
896         /* prevent mallicious paths from entering the system */
897         if (p[0] == '.' && p[1] == '.') {
898             p += 2;
899             *d++ = '_';
900             *d++ = '_';
901         } else {
902             *d++ = *p++;
903         }
904     }
905
906     *d = 0;
907     free(buf);
908
909     /* Make sure the path is relative, e.g. does not start with '/' */
910     p = buf2;
911     while ('/' == *p) {
912         *p++ = '_';
913     }
914
915     return buf2;
916 }
917
918
919 char     *cgiget(
920     long argc,
921     const char **args)
922 {
923     if (argc >= 1)
924         return rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
925     else
926         return stralloc("[ERROR: not enough arguments for RRD::CV]");
927 }
928
929
930
931 char     *drawgraph(
932     long argc,
933     const char **args)
934 {
935     int       i, xsize, ysize;
936     double    ymin, ymax;
937
938     for (i = 0; i < argc; i++)
939         if (strcmp(args[i], "--imginfo") == 0 || strcmp(args[i], "-g") == 0)
940             break;
941     if (i == argc) {
942         args[argc++] = "--imginfo";
943         args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
944     }
945     calfree();
946     if (rrd_graph
947         (argc + 1, (char **) args - 1, &calcpr, &xsize, &ysize, NULL, &ymin,
948          &ymax) != -1) {
949         return stralloc(calcpr[0]);
950     } else {
951         if (rrd_test_error()) {
952             char     *err =
953                 malloc((strlen(rrd_get_error()) +
954                         DS_NAM_SIZE) * sizeof(char));
955             sprintf(err, "[ERROR: %s]", rrd_get_error());
956             rrd_clear_error();
957             return err;
958         }
959     }
960     return NULL;
961 }
962
963 char     *drawprint(
964     long argc,
965     const char **args)
966 {
967     if (argc == 1 && calcpr) {
968         long      i = 0;
969
970         while (calcpr[i] != NULL)
971             i++;        /*determine number lines in calcpr */
972         if (atol(args[0]) < i - 1)
973             return stralloc(calcpr[atol(args[0]) + 1]);
974     }
975     return stralloc("[ERROR: RRD::PRINT argument error]");
976 }
977
978 char     *printtimelast(
979     long argc,
980     const char **args)
981 {
982     time_t    last;
983     struct tm tm_last;
984     char     *buf;
985
986     if (argc == 2) {
987         buf = malloc(255);
988         if (buf == NULL) {
989             return stralloc("[ERROR: allocating strftime buffer]");
990         };
991         last = rrd_last(argc + 1, (char **) args - 1);
992         if (rrd_test_error()) {
993             char     *err =
994                 malloc((strlen(rrd_get_error()) +
995                         DS_NAM_SIZE) * sizeof(char));
996             sprintf(err, "[ERROR: %s]", rrd_get_error());
997             rrd_clear_error();
998             return err;
999         }
1000         tm_last = *localtime(&last);
1001         strftime(buf, 254, args[1], &tm_last);
1002         return buf;
1003     }
1004     if (argc < 2) {
1005         return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
1006     }
1007     return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
1008 }
1009
1010 char     *printtimenow(
1011     long argc,
1012     const char **args)
1013 {
1014     time_t    now = time(NULL);
1015     struct tm tm_now;
1016     char     *buf;
1017
1018     if (argc == 1) {
1019         buf = malloc(255);
1020         if (buf == NULL) {
1021             return stralloc("[ERROR: allocating strftime buffer]");
1022         };
1023         tm_now = *localtime(&now);
1024         strftime(buf, 254, args[0], &tm_now);
1025         return buf;
1026     }
1027     if (argc < 1) {
1028         return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
1029     }
1030     return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
1031 }
1032
1033 /* Scan buffer until an unescaped '>' arives.
1034  * Update argument array with arguments found.
1035  * Return end cursor where parsing stopped, or NULL in case of failure.
1036  *
1037  * FIXME:
1038  * To allow nested constructs, we call rrd_expand_vars() for arguments
1039  * that contain RRD::x directives. These introduce a small memory leak
1040  * since we have to stralloc the arguments the way parse() works.
1041  */
1042 char     *scanargs(
1043     char *line,
1044     int *argument_count,
1045     char ***arguments)
1046 {
1047     char     *getP;     /* read cursor */
1048     char     *putP;     /* write cursor */
1049     char      Quote;    /* type of quote if in quoted string, 0 otherwise */
1050     int       tagcount; /* open tag count */
1051     int       in_arg;   /* if we currently are parsing an argument or not */
1052     int       argsz;    /* argument array size */
1053     int       curarg_contains_rrd_directives;
1054
1055     /* local array of arguments while parsing */
1056     int       argc = 0;
1057     char    **argv;
1058
1059 #ifdef DEBUG_PARSER
1060     printf("<-- scanargs(%s) -->\n", line);
1061 #endif
1062
1063     *arguments = NULL;
1064     *argument_count = 0;
1065
1066     /* create initial argument array of char pointers */
1067     argsz = 32;
1068     argv = (char **) malloc(argsz * sizeof(char *));
1069     if (!argv) {
1070         return NULL;
1071     }
1072
1073     /* skip leading blanks */
1074     while (isspace((int) *line)) {
1075         line++;
1076     }
1077
1078     getP = line;
1079     putP = line;
1080
1081     Quote = 0;
1082     in_arg = 0;
1083     tagcount = 0;
1084
1085     curarg_contains_rrd_directives = 0;
1086
1087     /* start parsing 'line' for arguments */
1088     while (*getP) {
1089         unsigned char c = *getP++;
1090
1091         if (c == '>' && !Quote && !tagcount) {
1092             /* this is our closing tag, quit scanning */
1093             break;
1094         }
1095
1096         /* remove all special chars */
1097         if (c < ' ') {
1098             c = ' ';
1099         }
1100
1101         switch (c) {
1102         case ' ':
1103             if (Quote || tagcount) {
1104                 /* copy quoted/tagged (=RRD expanded) string */
1105                 *putP++ = c;
1106             } else if (in_arg) {
1107                 /* end argument string */
1108                 *putP++ = 0;
1109                 in_arg = 0;
1110                 if (curarg_contains_rrd_directives) {
1111                     argv[argc - 1] =
1112                         rrd_expand_vars(stralloc(argv[argc - 1]));
1113                     curarg_contains_rrd_directives = 0;
1114                 }
1115             }
1116             break;
1117
1118         case '"':      /* Fall through */
1119         case '\'':
1120             if (Quote != 0) {
1121                 if (Quote == c) {
1122                     Quote = 0;
1123                 } else {
1124                     /* copy quoted string */
1125                     *putP++ = c;
1126                 }
1127             } else {
1128                 if (!in_arg) {
1129                     /* reference start of argument string in argument array */
1130                     argv[argc++] = putP;
1131                     in_arg = 1;
1132                 }
1133                 Quote = c;
1134             }
1135             break;
1136
1137         default:
1138             if (!in_arg) {
1139                 /* start new argument */
1140                 argv[argc++] = putP;
1141                 in_arg = 1;
1142             }
1143             if (c == '>') {
1144                 if (tagcount) {
1145                     tagcount--;
1146                 }
1147             }
1148             if (c == '<') {
1149                 tagcount++;
1150                 if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
1151                     curarg_contains_rrd_directives = 1;
1152                 }
1153             }
1154             *putP++ = c;
1155             break;
1156         }
1157
1158         /* check if our argument array is still large enough */
1159         if (argc == argsz) {
1160             /* resize argument array */
1161             argsz *= 2;
1162             argv = rrd_realloc(argv, argsz * sizeof(char *));
1163             if (*argv == NULL) {
1164                 return NULL;
1165             }
1166         }
1167     }
1168
1169     /* terminate last argument found */
1170     *putP = '\0';
1171     if (curarg_contains_rrd_directives) {
1172         argv[argc - 1] = rrd_expand_vars(stralloc(argv[argc - 1]));
1173     }
1174 #ifdef DEBUG_PARSER
1175     if (argc > 0) {
1176         int       n;
1177
1178         printf("<-- arguments found [%d]\n", argc);
1179         for (n = 0; n < argc; n++) {
1180             printf("arg %02d: '%s'\n", n, argv[n]);
1181         }
1182         printf("-->\n");
1183     } else {
1184         printf("<!-- No arguments found -->\n");
1185     }
1186 #endif
1187
1188     /* update caller's notion of the argument array and it's size */
1189     *arguments = argv;
1190     *argument_count = argc;
1191
1192     if (Quote) {
1193         return NULL;
1194     }
1195
1196     /* Return new scanning cursor:
1197        pointer to char after closing bracket */
1198     return getP;
1199 }
1200
1201
1202 /*
1203  * Parse(): scan current portion of buffer for given tag.
1204  * If found, parse tag arguments and call 'func' for it.
1205  * The result of func is inserted at the current position
1206  * in the buffer.
1207  */
1208 int parse(
1209     char **buf,         /* buffer */
1210     long i,             /* offset in buffer */
1211     char *tag,          /* tag to handle  */
1212     char *    (*func) (long,
1213                        const char **)   /* function to call for 'tag' */
1214     )
1215 {
1216     /* the name of the vairable ... */
1217     char     *val;
1218     long      valln;
1219     char    **args;
1220     char     *end;
1221     long      end_offset;
1222     int       argc;
1223     size_t    taglen = strlen(tag);
1224
1225     /* Current position in buffer should start with 'tag' */
1226     if (strncmp((*buf) + i, tag, taglen) != 0) {
1227         return 0;
1228     }
1229     /* .. and match exactly (a whitespace following 'tag') */
1230     if (!isspace(*((*buf) + i + taglen))) {
1231         return 0;
1232     }
1233 #ifdef DEBUG_PARSER
1234     printf("parse(): handling tag '%s'\n", tag);
1235 #endif
1236
1237     /* Scan for arguments following the tag;
1238        scanargs() puts \0 into *buf ... so after scanargs it is probably
1239        not a good time to use strlen on buf */
1240     end = scanargs((*buf) + i + taglen, &argc, &args);
1241     if (end) {
1242         /* got arguments, call function for 'tag' with arguments */
1243         val = func(argc, (const char **) args);
1244         free(args);
1245     } else {
1246         /* unable to parse arguments, undo 0-termination by scanargs */
1247         for (; argc > 0; argc--) {
1248             *((args[argc - 1]) - 1) = ' ';
1249         }
1250
1251         /* next call, try parsing at current offset +1 */
1252         end = (*buf) + i + 1;
1253
1254         val = stralloc("[ERROR: Parsing Problem with the following text\n"
1255                        " Check original file. This may have been altered "
1256                        "by parsing.]\n\n");
1257     }
1258
1259     /* remember offset where we have to continue parsing */
1260     end_offset = end - (*buf);
1261
1262     valln = 0;
1263     if (val) {
1264         valln = strlen(val);
1265     }
1266
1267     /* Optionally resize buffer to hold the replacement value:
1268        Calculating the new length of the buffer is simple. add current
1269        buffer pos (i) to length of string after replaced tag to length
1270        of replacement string and add 1 for the final zero ... */
1271     if (end - (*buf) < (i + valln)) {
1272         /* make sure we do not shrink the mallocd block */
1273         size_t    newbufsize = i + strlen(end) + valln + 1;
1274
1275         *buf = rrd_realloc(*buf, newbufsize);
1276
1277         if (*buf == NULL) {
1278             perror("Realoc buf:");
1279             exit(1);
1280         };
1281     }
1282
1283     /* Update new end pointer:
1284        make sure the 'end' pointer gets moved along with the 
1285        buf pointer when realloc moves memory ... */
1286     end = (*buf) + end_offset;
1287
1288     /* splice the variable:
1289        step 1. Shift pending data to make room for 'val' */
1290     memmove((*buf) + i + valln, end, strlen(end) + 1);
1291
1292     /* step 2. Insert val */
1293     if (val) {
1294         memmove((*buf) + i, val, valln);
1295         free(val);
1296     }
1297     return (valln > 0 ? valln - 1 : valln);
1298 }
1299
1300 char     *http_time(
1301     time_t *now)
1302 {
1303     struct tm *tmptime;
1304     static char buf[60];
1305
1306     tmptime = gmtime(now);
1307     strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", tmptime);
1308     return (buf);
1309 }
1310
1311 void rrdcgiHeader(
1312     void)
1313 {
1314     if (rrdcgiType)
1315         printf("Content-type: %s\n", rrdcgiType);
1316     else
1317         printf("Content-type: text/html\n");
1318     if (rrdcgiHeaderString)
1319         printf("%s", rrdcgiHeaderString);
1320     printf("\n");
1321 }
1322
1323 void rrdcgiDebug(
1324     int level,
1325     int where)
1326 {
1327     if (level > 0)
1328         rrdcgiDebugLevel = level;
1329     else
1330         rrdcgiDebugLevel = 0;
1331     if (where)
1332         rrdcgiDebugStderr = 0;
1333     else
1334         rrdcgiDebugStderr = 1;
1335 }
1336
1337 char     *rrdcgiDecodeString(
1338     char *text)
1339 {
1340     char     *cp, *xp;
1341
1342     for (cp = text, xp = text; *cp; cp++) {
1343         if (*cp == '%') {
1344             if (strchr("0123456789ABCDEFabcdef", *(cp + 1))
1345                 && strchr("0123456789ABCDEFabcdef", *(cp + 2))) {
1346                 if (islower(*(cp + 1)))
1347                     *(cp + 1) = toupper(*(cp + 1));
1348                 if (islower(*(cp + 2)))
1349                     *(cp + 2) = toupper(*(cp + 2));
1350                 *(xp) =
1351                     (*(cp + 1) >=
1352                      'A' ? *(cp + 1) - 'A' + 10 : *(cp + 1) - '0') * 16 +
1353                     (*(cp + 2) >=
1354                      'A' ? *(cp + 2) - 'A' + 10 : *(cp + 2) - '0');
1355                 xp++;
1356                 cp += 2;
1357             }
1358         } else {
1359             *(xp++) = *cp;
1360         }
1361     }
1362     memset(xp, 0, cp - xp);
1363     return text;
1364 }
1365
1366 /*  rrdcgiReadVariables()
1367  *
1368  *  Read from stdin if no string is provided via CGI.  Variables that
1369  *  doesn't have a value associated with it doesn't get stored.
1370  */
1371 s_var   **rrdcgiReadVariables(
1372     void)
1373 {
1374     int       length;
1375     char     *line = NULL;
1376     int       numargs;
1377     char     *cp, *ip, *esp, *sptr;
1378     s_var   **result;
1379     int       i, k, len;
1380     char      tmp[101];
1381
1382     cp = getenv("REQUEST_METHOD");
1383     ip = getenv("CONTENT_LENGTH");
1384
1385     if (cp && !strcmp(cp, "POST")) {
1386         if (ip) {
1387             length = atoi(ip);
1388             if ((line = (char *) malloc(length + 2)) == NULL)
1389                 return NULL;
1390             if (fgets(line, length + 1, stdin) == NULL)
1391                 return NULL;
1392         } else
1393             return NULL;
1394     } else if (cp && !strcmp(cp, "GET")) {
1395         esp = getenv("QUERY_STRING");
1396         if (esp && strlen(esp)) {
1397             if ((line = (char *) malloc(strlen(esp) + 2)) == NULL)
1398                 return NULL;
1399             sprintf(line, "%s", esp);
1400         } else
1401             return NULL;
1402     } else {
1403         length = 0;
1404         printf("(offline mode: enter name=value pairs on standard input)\n");
1405         memset(tmp, 0, sizeof(tmp));
1406         while ((cp = fgets(tmp, 100, stdin)) != NULL) {
1407             if (strlen(tmp)) {
1408                 if (tmp[strlen(tmp) - 1] == '\n')
1409                     tmp[strlen(tmp) - 1] = '&';
1410                 if (length) {
1411                     length += strlen(tmp);
1412                     len = (length + 1) * sizeof(char);
1413                     if ((line = (char *) realloc(line, len)) == NULL)
1414                         return NULL;
1415                     strcat(line, tmp);
1416                 } else {
1417                     length = strlen(tmp);
1418                     len = (length + 1) * sizeof(char);
1419                     if ((line = (char *) malloc(len)) == NULL)
1420                         return NULL;
1421                     memset(line, 0, len);
1422                     strcpy(line, tmp);
1423                 }
1424             }
1425             memset(tmp, 0, sizeof(tmp));
1426         }
1427         if (!line)
1428             return NULL;
1429         if (line[strlen(line) - 1] == '&')
1430             line[strlen(line) - 1] = '\0';
1431     }
1432
1433     /*
1434      *  From now on all cgi variables are stored in the variable line
1435      *  and look like  foo=bar&foobar=barfoo&foofoo=
1436      */
1437
1438     if (rrdcgiDebugLevel > 0) {
1439         if (rrdcgiDebugStderr)
1440             fprintf(stderr, "Received cgi input: %s\n", line);
1441         else
1442             printf
1443                 ("<b>Received cgi input</b><br>\n<pre>\n--\n%s\n--\n</pre>\n\n",
1444                  line);
1445     }
1446
1447     for (cp = line; *cp; cp++)
1448         if (*cp == '+')
1449             *cp = ' ';
1450
1451     if (strlen(line)) {
1452         for (numargs = 1, cp = line; *cp; cp++)
1453             if (*cp == '&')
1454                 numargs++;
1455     } else
1456         numargs = 0;
1457     if (rrdcgiDebugLevel > 0) {
1458         if (rrdcgiDebugStderr)
1459             fprintf(stderr, "%d cgi variables found.\n", numargs);
1460         else
1461             printf("%d cgi variables found.<br>\n", numargs);
1462     }
1463
1464     len = (numargs + 1) * sizeof(s_var *);
1465     if ((result = (s_var **) malloc(len)) == NULL)
1466         return NULL;
1467     memset(result, 0, len);
1468
1469     cp = line;
1470     i = 0;
1471     while (*cp) {
1472         if ((ip = (char *) strchr(cp, '&')) != NULL) {
1473             *ip = '\0';
1474         } else
1475             ip = cp + strlen(cp);
1476
1477         if ((esp = (char *) strchr(cp, '=')) == NULL) {
1478             cp = ++ip;
1479             continue;
1480         }
1481
1482         if (!strlen(esp)) {
1483             cp = ++ip;
1484             continue;
1485         }
1486
1487         if (i < numargs) {
1488
1489             /* try to find out if there's already such a variable */
1490             for (k = 0; k < i && (strncmp(result[k]->name, cp, esp - cp)
1491                                   || !(strlen(result[k]->name) ==
1492                                        (size_t) (esp - cp))); k++);
1493
1494             if (k == i) {   /* No such variable yet */
1495                 if ((result[i] = (s_var *) malloc(sizeof(s_var))) == NULL)
1496                     return NULL;
1497                 if ((result[i]->name =
1498                      (char *) malloc((esp - cp + 1) * sizeof(char))) == NULL)
1499                     return NULL;
1500                 memset(result[i]->name, 0, esp - cp + 1);
1501                 strncpy(result[i]->name, cp, esp - cp);
1502                 cp = ++esp;
1503                 if ((result[i]->value =
1504                      (char *) malloc((ip - esp + 1) * sizeof(char))) == NULL)
1505                     return NULL;
1506                 memset(result[i]->value, 0, ip - esp + 1);
1507                 strncpy(result[i]->value, cp, ip - esp);
1508                 result[i]->value = rrdcgiDecodeString(result[i]->value);
1509                 if (rrdcgiDebugLevel) {
1510                     if (rrdcgiDebugStderr)
1511                         fprintf(stderr, "%s: %s\n", result[i]->name,
1512                                 result[i]->value);
1513                     else
1514                         printf("<h3>Variable %s</h3>\n<pre>\n%s\n</pre>\n\n",
1515                                result[i]->name, result[i]->value);
1516                 }
1517                 i++;
1518             } else {    /* There is already such a name, suppose a mutiple field */
1519                 cp = ++esp;
1520                 len =
1521                     (strlen(result[k]->value) + (ip - esp) +
1522                      2) * sizeof(char);
1523                 if ((sptr = (char *) malloc(len)) == NULL)
1524                     return NULL;
1525                 memset(sptr, 0, len);
1526                 sprintf(sptr, "%s\n", result[k]->value);
1527                 strncat(sptr, cp, ip - esp);
1528                 free(result[k]->value);
1529                 result[k]->value = rrdcgiDecodeString(sptr);
1530             }
1531         }
1532         cp = ++ip;
1533     }
1534     return result;
1535 }
1536
1537 /*  rrdcgiInit()
1538  *
1539  *  Read from stdin if no string is provided via CGI.  Variables that
1540  *  doesn't have a value associated with it doesn't get stored.
1541  */
1542 s_cgi    *rrdcgiInit(
1543     void)
1544 {
1545     s_cgi    *res;
1546     s_var   **vars;
1547
1548     vars = rrdcgiReadVariables();
1549
1550     if (!vars)
1551         return NULL;
1552
1553     if ((res = (s_cgi *) malloc(sizeof(s_cgi))) == NULL)
1554         return NULL;
1555     res->vars = vars;
1556
1557     return res;
1558 }
1559
1560 char     *rrdcgiGetValue(
1561     s_cgi * parms,
1562     const char *name)
1563 {
1564     int       i;
1565
1566     if (!parms || !parms->vars)
1567         return NULL;
1568     for (i = 0; parms->vars[i]; i++)
1569         if (!strcmp(name, parms->vars[i]->name)) {
1570             if (rrdcgiDebugLevel > 0) {
1571                 if (rrdcgiDebugStderr)
1572                     fprintf(stderr, "%s found as %s\n", name,
1573                             parms->vars[i]->value);
1574                 else
1575                     printf("%s found as %s<br>\n", name,
1576                            parms->vars[i]->value);
1577             }
1578             return parms->vars[i]->value;
1579         }
1580     if (rrdcgiDebugLevel) {
1581         if (rrdcgiDebugStderr)
1582             fprintf(stderr, "%s not found\n", name);
1583         else
1584             printf("%s not found<br>\n", name);
1585     }
1586     return NULL;
1587 }
1588
1589 void rrdcgiFreeList(
1590     char **list)
1591 {
1592     int       i;
1593
1594     for (i = 0; list[i] != NULL; i++)
1595         free(list[i]);
1596     free(list);
1597 }
1598
1599 void rrdcgiFree(
1600     s_cgi * parms)
1601 {
1602     int       i;
1603
1604     if (!parms)
1605         return;
1606     if (parms->vars) {
1607         for (i = 0; parms->vars[i]; i++) {
1608             if (parms->vars[i]->name)
1609                 free(parms->vars[i]->name);
1610             if (parms->vars[i]->value)
1611                 free(parms->vars[i]->value);
1612             free(parms->vars[i]);
1613         }
1614         free(parms->vars);
1615     }
1616     free(parms);
1617
1618     if (rrdcgiHeaderString) {
1619         free(rrdcgiHeaderString);
1620         rrdcgiHeaderString = NULL;
1621     }
1622     if (rrdcgiType) {
1623         free(rrdcgiType);
1624         rrdcgiType = NULL;
1625     }
1626 }