1 /*****************************************************************************
2 * RRDtool 1.2.6 Copyright by Tobi Oetiker, 1997-2005
3 *****************************************************************************
4 * rrd_cgi.c RRD Web Page Generator
5 *****************************************************************************/
13 /*#define DEBUG_PARSER
16 /* global variable for libcgi */
19 /* in arg[0] find tags beginning with arg[1] call arg[2] on them
20 and replace by result of arg[2] call */
21 int parse(char **, long, char *, char *(*)(long , const char **));
23 /**************************************************/
24 /* tag replacers ... they are called from parse */
25 /* through function pointers */
26 /**************************************************/
28 /* return cgi var named arg[0] */
29 char* cgiget(long , const char **);
31 /* return a quoted cgi var named arg[0] */
32 char* cgigetq(long , const char **);
34 /* return a quoted and sanitized cgi variable */
35 char* cgigetqp(long , const char **);
37 /* call rrd_graph and insert appropriate image tag */
38 char* drawgraph(long, char **);
40 /* return PRINT functions from last rrd_graph call */
41 char* drawprint(long, const char **);
43 /* pretty-print the <last></last> value for some.rrd via strftime() */
44 char* printtimelast(long, const char **);
46 /* pretty-print current time */
47 char* printtimenow(long, const char **);
49 /* set an environment variable */
50 char* rrdsetenv(long, const char **);
52 /* get an environment variable */
53 char* rrdgetenv(long, const char **);
55 /* include the named file at this point */
56 char* includefile(long, const char **);
58 /* for how long is the output of the cgi valid ? */
59 char* rrdgoodfor(long, const char **);
61 char* rrdstrip(char *buf);
62 char* scanargs(char *line, int *argc, char ***args);
64 /* format at-time specified times using strftime */
65 char* printstrftime(long, const char**);
67 /** HTTP protocol needs special format, and GMT time **/
68 char *http_time(time_t *);
70 /* return a pointer to newly allocated copy of this string */
71 char *stralloc(const char *);
74 /* rrd interface to the variable functions {put,get}var() */
75 char* rrdgetvar(long argc, const char **args);
76 char* rrdsetvar(long argc, const char **args);
77 char* rrdsetvarconst(long argc, const char **args);
80 /* variable store: put/get key-value pairs */
82 static void donevar();
83 static const char* getvar(const char* varname);
84 static const char* putvar(const char* name, const char* value, int is_const);
86 /* key value pair that makes up an entry in the variable store */
89 int is_const; /* const variable or not */
90 const char* name; /* variable name */
91 const char* value; /* variable value */
95 start with a heapsize of 10 variables */
96 #define INIT_VARSTORE_SIZE 10
97 static vardata* varheap = NULL;
98 static size_t varheap_size = 0;
100 /* allocate and initialize variable heap */
104 varheap = (vardata*)malloc(sizeof(vardata) * INIT_VARSTORE_SIZE);
105 if (varheap == NULL) {
106 fprintf(stderr, "ERROR: unable to initialize variable store\n");
109 memset(varheap, 0, sizeof(vardata) * INIT_VARSTORE_SIZE);
110 varheap_size = INIT_VARSTORE_SIZE;
114 /* cleanup: free allocated memory */
120 for (i=0; i<(int)varheap_size; i++) {
121 if (varheap[i].name) {
122 free((char*)varheap[i].name);
124 if (varheap[i].value) {
125 free((char*)varheap[i].value);
132 /* Get a variable from the variable store.
133 Return NULL in case the requested variable was not found. */
135 getvar(const char* name)
138 for (i=0; i<(int)varheap_size && varheap[i].name; i++) {
139 if (0 == strcmp(name, varheap[i].name)) {
141 printf("<!-- getvar(%s) -> %s -->\n", name, varheap[i].value);
143 return varheap[i].value;
147 printf("<!-- getvar(%s) -> Not found-->\n", name);
152 /* Put a variable into the variable store. If a variable by that
153 name exists, it's value is overwritten with the new value unless it was
154 marked as 'const' (initialized by RRD::SETCONSTVAR).
155 Returns a copy the newly allocated value on success, NULL on error. */
157 putvar(const char* name, const char* value, int is_const)
160 for (i=0; i < (int)varheap_size && varheap[i].name; i++) {
161 if (0 == strcmp(name, varheap[i].name)) {
162 /* overwrite existing entry */
163 if (varheap[i].is_const) {
165 printf("<!-- setver(%s, %s): not assigning: "
166 "const variable -->\n", name, value);
168 return varheap[i].value;
171 printf("<!-- setvar(%s, %s): overwriting old value (%s) -->\n",
172 name, value, varheap[i].value);
174 /* make it possible to promote a variable to readonly */
175 varheap[i].is_const = is_const;
176 free((char*)varheap[i].value);
177 varheap[i].value = stralloc(value);
178 return varheap[i].value;
182 /* no existing variable found by that name, add it */
183 if (i == (int)varheap_size) {
184 /* ran out of heap: resize heap to double size */
185 size_t new_size = varheap_size * 2;
186 varheap = (vardata*)(realloc(varheap, sizeof(vardata) * new_size));
188 fprintf(stderr, "ERROR: Unable to realloc variable heap\n");
191 /* initialize newly allocated memory */;
192 memset(&varheap[varheap_size], 0, sizeof(vardata) * varheap_size);
193 varheap_size = new_size;
195 varheap[i].is_const = is_const;
196 varheap[i].name = stralloc(name);
197 varheap[i].value = stralloc(value);
200 printf("<!-- setvar(%s, %s): adding new variable -->\n", name, value);
202 return varheap[i].value;
205 /* expand those RRD:* directives that can be used recursivly */
207 rrd_expand_vars(char* buffer)
212 printf("expanding variables in '%s'\n", buffer);
215 for (i=0; buffer[i]; i++) {
216 if (buffer[i] != '<')
218 parse(&buffer, i, "<RRD::CV", cgiget);
219 parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
220 parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
221 parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
222 parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
223 parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
224 parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
225 parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
230 static long goodfor=0;
231 static char **calcpr=NULL;
232 static void calfree (void){
235 for(i=0;calcpr[i];i++){
246 /* create freeable version of the string */
247 char * stralloc(const char *str){
252 nstr = malloc((strlen(str)+1));
257 int main(int argc, char *argv[]) {
260 char *server_url = NULL;
263 #ifdef MUST_DISABLE_SIGFPE
264 signal(SIGFPE,SIG_IGN);
266 #ifdef MUST_DISABLE_FPMASK
269 optind = 0; opterr = 0; /* initialize getopt */
271 /* what do we get for cmdline arguments?
273 printf("%d-'%s'\n",i,argv[i]); */
275 static struct option long_options[] = {
276 { "filter", no_argument, 0, 'f' },
279 int option_index = 0;
281 opt = getopt_long(argc, argv, "f", long_options, &option_index);
291 printf("unknown commandline option '%s'\n",argv[optind-1]);
299 server_url = getenv("SERVER_URL");
302 /* make sure we have one extra argument,
303 if there are others, we do not care Apache gives several */
305 /* if ( (optind != argc-2
306 && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL)
307 && optind != argc-1) { */
309 if ( optind >= argc ) {
310 fprintf(stderr, "ERROR: expected a filename\n");
313 length = readfile(argv[optind], &buffer, 1);
316 if(rrd_test_error()) {
317 fprintf(stderr, "ERROR: %s\n",rrd_get_error());
321 /* initialize variable heap */
325 /* some fake header for testing */
326 printf ("Content-Type: text/html\nContent-Length: 10000000\n\n\n");
330 /* expand rrd directives in buffer recursivly */
331 for (i=0; buffer[i]; i++) {
332 if (buffer[i] != '<')
335 parse(&buffer, i, "<RRD::CV", cgiget);
336 parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
337 parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
338 parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
340 parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
341 parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
342 parse(&buffer, i, "<RRD::GRAPH", drawgraph);
343 parse(&buffer, i, "<RRD::INCLUDE", includefile);
344 parse(&buffer, i, "<RRD::PRINT", drawprint);
345 parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
346 parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
347 parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
348 parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
349 parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
350 parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
354 printf ("Content-Type: text/html\n"
355 "Content-Length: %d\n",
358 if (labs(goodfor) > 0) {
361 printf("Last-Modified: %s\n", http_time(&now));
362 now += labs(goodfor);
363 printf("Expires: %s\n", http_time(&now));
365 printf("Refresh: %ld\n", labs(goodfor));
372 printf("%s", buffer);
383 /* remove occurrences of .. this is a general measure to make
384 paths which came in via cgi do not go UP ... */
386 char* rrdsetenv(long argc, const char **args) {
388 char *xyz = malloc((strlen(args[0]) + strlen(args[1]) + 2));
390 return stralloc("[ERROR: allocating setenv buffer]");
392 sprintf(xyz, "%s=%s", args[0], args[1]);
393 if(putenv(xyz) == -1) {
395 return stralloc("[ERROR: failed to do putenv]");
399 return stralloc("[ERROR: setenv failed because not enough "
400 "arguments were defined]");
403 /* rrd interface to the variable function putvar() */
405 rrdsetvar(long argc, const char **args)
409 const char* result = putvar(args[0], args[1], 0 /* not const */);
411 /* setvar does not return the value set */
414 return stralloc("[ERROR: putvar failed]");
416 return stralloc("[ERROR: putvar failed because not enough arguments "
420 /* rrd interface to the variable function putvar() */
422 rrdsetvarconst(long argc, const char **args)
426 const char* result = putvar(args[0], args[1], 1 /* const */);
428 /* setvar does not return the value set */
431 return stralloc("[ERROR: putvar failed]");
433 return stralloc("[ERROR: putvar failed because not enough arguments "
437 char* rrdgetenv(long argc, const char **args) {
441 return stralloc("[ERROR: getenv failed because it did not "
442 "get 1 argument only]");
444 envvar = getenv(args[0]);
446 return stralloc(envvar);
448 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
449 _snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
451 snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
453 return stralloc(buf);
457 char* rrdgetvar(long argc, const char **args) {
461 return stralloc("[ERROR: getvar failed because it did not "
462 "get 1 argument only]");
464 value = getvar(args[0]);
466 return stralloc(value);
468 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
469 _snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
471 snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
473 return stralloc(buf);
477 char* rrdgoodfor(long argc, const char **args){
479 goodfor = atol(args[0]);
481 return stralloc("[ERROR: goodfor expected 1 argument]");
485 return stralloc("[ERROR: goodfor value must not be 0]");
491 /* Format start or end times using strftime. We always need both the
492 * start and end times, because, either might be relative to the other.
494 #define MAX_STRFTIME_SIZE 256
495 char* printstrftime(long argc, const char **args){
496 struct rrd_time_value start_tv, end_tv;
497 char *parsetime_error = NULL;
498 char formatted[MAX_STRFTIME_SIZE];
500 time_t start_tmp, end_tmp;
502 /* Make sure that we were given the right number of args */
504 rrd_set_error( "wrong number of args %d", argc);
508 /* Init start and end time */
509 parsetime("end-24h", &start_tv);
510 parsetime("now", &end_tv);
512 /* Parse the start and end times we were given */
513 if( (parsetime_error = parsetime( args[1], &start_tv))) {
514 rrd_set_error( "start time: %s", parsetime_error);
517 if( (parsetime_error = parsetime( args[2], &end_tv))) {
518 rrd_set_error( "end time: %s", parsetime_error);
521 if( proc_start_end( &start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
525 /* Do we do the start or end */
526 if( strcasecmp( args[0], "START") == 0) {
527 the_tm = localtime( &start_tmp);
529 else if( strcasecmp( args[0], "END") == 0) {
530 the_tm = localtime( &end_tmp);
533 rrd_set_error( "start/end not found in '%s'", args[0]);
538 if( strftime( formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
539 return( stralloc( formatted));
542 rrd_set_error( "strftime failed");
547 char* includefile(long argc, const char **args){
550 char* filename = args[0];
551 readfile(filename, &buffer, 0);
552 if (rrd_test_error()) {
553 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE));
554 sprintf(err, "[ERROR: %s]",rrd_get_error());
563 return stralloc("[ERROR: No Inclue file defined]");
567 /* make a copy of buf and replace open/close brackets with '_' */
568 char* rrdstrip(char *buf) {
573 /* make a copy of the buffer */
581 if (*p == '<' || *p == '>') {
589 char* cgigetq(long argc, const char **args){
591 char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
595 if (buf==NULL) return NULL;
597 for(c=buf;*c != '\0';c++)
599 if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
600 perror("Malloc Buffer");
621 return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
624 /* remove occurrences of .. this is a general measure to make
625 paths which came in via cgi do not go UP ... */
627 char* cgigetqp(long argc, const char **args){
635 return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
638 buf = rrdstrip(cgiGetValue(cgiArg, args[0]));
644 buf2 = malloc(strlen(buf)+1);
647 perror("cgigetqp(): Malloc Path Buffer");
656 /* prevent mallicious paths from entering the system */
657 if (p[0] == '.' && p[1] == '.')
672 /* Make sure the path is relative, e.g. does not start with '/' */
683 char* cgiget(long argc, const char **args){
685 return rrdstrip(cgiGetValue(cgiArg,args[0]));
687 return stralloc("[ERROR: not enough arguments for RRD::CV]");
692 char* drawgraph(long argc, char **args){
696 if(strcmp(args[i],"--imginfo")==0 || strcmp(args[i],"-g")==0) break;
698 args[argc++] = "--imginfo";
699 args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
702 if( rrd_graph(argc+1, args-1, &calcpr, &xsize, &ysize,NULL,&ymin,&ymax) != -1 ) {
703 return stralloc(calcpr[0]);
705 if (rrd_test_error()) {
706 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
707 sprintf(err, "[ERROR: %s]",rrd_get_error());
716 char* drawprint(long argc, const char **args){
717 if (argc==1 && calcpr){
719 while (calcpr[i] != NULL) i++; /*determine number lines in calcpr*/
720 if (atol(args[0])<i-1)
721 return stralloc(calcpr[atol(args[0])+1]);
723 return stralloc("[ERROR: RRD::PRINT argument error]");
726 char* printtimelast(long argc, const char **args) {
733 return stralloc("[ERROR: allocating strftime buffer]");
735 last = rrd_last(argc+1, args-1);
736 if (rrd_test_error()) {
737 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
738 sprintf(err, "[ERROR: %s]",rrd_get_error());
742 tm_last = *localtime(&last);
743 strftime(buf,254,args[1],&tm_last);
747 return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
749 return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
752 char* printtimenow(long argc, const char **args) {
753 time_t now = time(NULL);
759 return stralloc("[ERROR: allocating strftime buffer]");
761 tm_now = *localtime(&now);
762 strftime(buf,254,args[0],&tm_now);
766 return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
768 return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
771 /* Scan buffer until an unescaped '>' arives.
772 * Update argument array with arguments found.
773 * Return end cursor where parsing stopped, or NULL in case of failure.
776 * To allow nested constructs, we call rrd_expand_vars() for arguments
777 * that contain RRD::x directives. These introduce a small memory leak
778 * since we have to stralloc the arguments the way parse() works.
781 scanargs(char *line, int *argument_count, char ***arguments)
783 char *getP; /* read cursor */
784 char *putP; /* write cursor */
785 char Quote; /* type of quote if in quoted string, 0 otherwise */
786 int tagcount; /* open tag count */
787 int in_arg; /* if we currently are parsing an argument or not */
788 int argsz; /* argument array size */
789 int curarg_contains_rrd_directives;
791 /* local array of arguments while parsing */
796 printf("<-- scanargs(%s) -->\n", line);
802 /* create initial argument array of char pointers */
804 argv = (char **)malloc(argsz * sizeof(char *));
809 /* skip leading blanks */
810 while (isspace((int)*line)) {
821 curarg_contains_rrd_directives = 0;
823 /* start parsing 'line' for arguments */
826 unsigned char c = *getP++;
828 if (c == '>' && !Quote && !tagcount) {
829 /* this is our closing tag, quit scanning */
833 /* remove all special chars */
841 if (Quote || tagcount) {
842 /* copy quoted/tagged (=RRD expanded) string */
847 /* end argument string */
850 if (curarg_contains_rrd_directives) {
851 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
852 curarg_contains_rrd_directives = 0;
857 case '"': /* Fall through */
863 /* copy quoted string */
868 /* reference start of argument string in argument array */
878 /* start new argument */
889 if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
890 curarg_contains_rrd_directives = 1;
897 /* check if our argument array is still large enough */
899 /* resize argument array */
901 argv = rrd_realloc(argv, argsz * sizeof(char *));
908 /* terminate last argument found */
910 if (curarg_contains_rrd_directives) {
911 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
917 printf("<-- arguments found [%d]\n", argc);
918 for (n=0; n<argc; n++) {
919 printf("arg %02d: '%s'\n", n, argv[n]);
923 printf("<!-- No arguments found -->\n");
927 /* update caller's notion of the argument array and it's size */
929 *argument_count = argc;
935 /* Return new scanning cursor:
936 pointer to char after closing bracket */
942 * Parse(): scan current portion of buffer for given tag.
943 * If found, parse tag arguments and call 'func' for it.
944 * The result of func is inserted at the current position
949 char **buf, /* buffer */
950 long i, /* offset in buffer */
951 char *tag, /* tag to handle */
952 char *(*func)(long , const char **) /* function to call for 'tag' */
955 /* the name of the vairable ... */
962 size_t taglen = strlen(tag);
964 /* Current position in buffer should start with 'tag' */
965 if (strncmp((*buf)+i, tag, taglen) != 0) {
968 /* .. and match exactly (a whitespace following 'tag') */
969 if (! isspace(*((*buf) + i + taglen)) ) {
974 printf("parse(): handling tag '%s'\n", tag);
977 /* Scan for arguments following the tag;
978 scanargs() puts \0 into *buf ... so after scanargs it is probably
979 not a good time to use strlen on buf */
980 end = scanargs((*buf) + i + taglen, &argc, &args);
983 /* got arguments, call function for 'tag' with arguments */
984 val = func(argc, args);
989 /* unable to parse arguments, undo 0-termination by scanargs */
990 for (; argc > 0; argc--) {
991 *((args[argc-1])-1) = ' ';
994 /* next call, try parsing at current offset +1 */
995 end = (*buf) + i + 1;
997 val = stralloc("[ERROR: Parsing Problem with the following text\n"
998 " Check original file. This may have been altered "
1002 /* remember offset where we have to continue parsing */
1003 end_offset = end - (*buf);
1007 valln = strlen(val);
1010 /* Optionally resize buffer to hold the replacement value:
1011 Calculating the new length of the buffer is simple. add current
1012 buffer pos (i) to length of string after replaced tag to length
1013 of replacement string and add 1 for the final zero ... */
1014 if (end - (*buf) < (i + valln)) {
1015 /* make sure we do not shrink the mallocd block */
1016 size_t newbufsize = i + strlen(end) + valln + 1;
1017 *buf = rrd_realloc(*buf, newbufsize);
1020 perror("Realoc buf:");
1025 /* Update new end pointer:
1026 make sure the 'end' pointer gets moved along with the
1027 buf pointer when realloc moves memory ... */
1028 end = (*buf) + end_offset;
1030 /* splice the variable:
1031 step 1. Shift pending data to make room for 'val' */
1032 memmove((*buf) + i + valln, end, strlen(end) + 1);
1034 /* step 2. Insert val */
1036 memmove((*buf)+i, val, valln);
1039 return (valln > 0 ? valln-1: valln);
1043 http_time(time_t *now) {
1045 static char buf[60];
1047 tmptime=gmtime(now);
1048 strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT",tmptime);