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