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