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