1 /*****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2004
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 , 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 , char **);
31 /* return a quoted cgi var named arg[0] */
32 char* cgigetq(long , char **);
34 /* return a quoted and sanitized cgi variable */
35 char* cgigetqp(long , 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, char **);
43 /* pretty-print the <last></last> value for some.rrd via strftime() */
44 char* printtimelast(long, char **);
46 /* pretty-print current time */
47 char* printtimenow(long,char **);
49 /* set an environment variable */
50 char* rrdsetenv(long, char **);
52 /* get an environment variable */
53 char* rrdgetenv(long, char **);
55 /* include the named file at this point */
56 char* includefile(long, char **);
58 /* for how long is the output of the cgi valid ? */
59 char* rrdgoodfor(long, 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, 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, char **args);
76 char* rrdsetvar(long argc, char **args);
77 char* rrdsetvarconst(long argc, 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<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<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 < 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 == 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);
227 static long goodfor=0;
228 static char **calcpr=NULL;
229 static void calfree (void){
232 for(i=0;calcpr[i];i++){
243 /* create freeable version of the string */
244 char * stralloc(const char *str){
249 nstr = malloc((strlen(str)+1));
254 int main(int argc, char *argv[]) {
257 char *server_url = NULL;
260 #ifdef MUST_DISABLE_SIGFPE
261 signal(SIGFPE,SIG_IGN);
263 #ifdef MUST_DISABLE_FPMASK
266 /* what do we get for cmdline arguments?
268 printf("%d-'%s'\n",i,argv[i]); */
270 static struct option long_options[] = {
271 { "filter", no_argument, 0, 'f' },
274 int option_index = 0;
276 opt = getopt_long(argc, argv, "f", long_options, &option_index);
286 printf("unknown commandline option '%s'\n",argv[optind-1]);
294 server_url = getenv("SERVER_URL");
297 /* make sure we have one extra argument,
298 if there are others, we do not care Apache gives several */
300 /* if ( (optind != argc-2
301 && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL)
302 && optind != argc-1) { */
304 if ( optind >= argc ) {
305 fprintf(stderr, "ERROR: expected a filename\n");
308 length = readfile(argv[optind], &buffer, 1);
311 if(rrd_test_error()) {
312 fprintf(stderr, "ERROR: %s\n",rrd_get_error());
316 /* initialize variable heap */
319 /* expand rrd directives in buffer recursivly */
320 for (i=0; buffer[i]; i++) {
321 if (buffer[i] != '<')
324 parse(&buffer, i, "<RRD::CV", cgiget);
325 parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
326 parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
327 parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
329 parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
330 parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
331 parse(&buffer, i, "<RRD::GRAPH", drawgraph);
332 parse(&buffer, i, "<RRD::INCLUDE", includefile);
333 parse(&buffer, i, "<RRD::PRINT", drawprint);
334 parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
335 parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
336 parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
337 parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
338 parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
339 parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
343 printf ("Content-Type: text/html\n"
344 "Content-Length: %d\n",
347 if (labs(goodfor) > 0) {
350 printf("Last-Modified: %s\n", http_time(&now));
351 now += labs(goodfor);
352 printf("Expires: %s\n", http_time(&now));
354 printf("Refresh: %ld\n", labs(goodfor));
361 printf("%s", buffer);
372 /* remove occurrences of .. this is a general measure to make
373 paths which came in via cgi do not go UP ... */
375 char* rrdsetenv(long argc, char **args) {
377 char *xyz = malloc((strlen(args[0]) + strlen(args[1]) + 2));
379 return stralloc("[ERROR: allocating setenv buffer]");
381 sprintf(xyz, "%s=%s", args[0], args[1]);
382 if(putenv(xyz) == -1) {
384 return stralloc("[ERROR: failed to do putenv]");
387 return stralloc("[ERROR: setenv failed because not enough "
388 "arguments were defined]");
391 /* rrd interface to the variable function putvar() */
393 rrdsetvar(long argc, char **args)
397 const char* result = putvar(args[0], args[1], 0 /* not const */);
399 /* setvar does not return the value set */
402 return stralloc("[ERROR: putvar failed]");
404 return stralloc("[ERROR: putvar failed because not enough arguments "
408 /* rrd interface to the variable function putvar() */
410 rrdsetvarconst(long argc, char **args)
414 const char* result = putvar(args[0], args[1], 1 /* const */);
416 /* setvar does not return the value set */
419 return stralloc("[ERROR: putvar failed]");
421 return stralloc("[ERROR: putvar failed because not enough arguments "
425 char* rrdgetenv(long argc, char **args) {
429 return stralloc("[ERROR: getenv failed because it did not "
430 "get 1 argument only]");
432 envvar = getenv(args[0]);
434 return stralloc(envvar);
436 snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
437 return stralloc(buf);
441 char* rrdgetvar(long argc, char **args) {
445 return stralloc("[ERROR: getvar failed because it did not "
446 "get 1 argument only]");
448 value = getvar(args[0]);
450 return stralloc(value);
452 snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
453 return stralloc(buf);
457 char* rrdgoodfor(long argc, char **args){
459 goodfor = atol(args[0]);
461 return stralloc("[ERROR: goodfor expected 1 argument]");
465 return stralloc("[ERROR: goodfor value must not be 0]");
471 /* Format start or end times using strftime. We always need both the
472 * start and end times, because, either might be relative to the other.
474 #define MAX_STRFTIME_SIZE 256
475 char* printstrftime(long argc, char **args){
476 struct rrd_time_value start_tv, end_tv;
477 char *parsetime_error = NULL;
478 char formatted[MAX_STRFTIME_SIZE];
480 time_t start_tmp, end_tmp;
482 /* Make sure that we were given the right number of args */
484 rrd_set_error( "wrong number of args %d", argc);
488 /* Init start and end time */
489 parsetime("end-24h", &start_tv);
490 parsetime("now", &end_tv);
492 /* Parse the start and end times we were given */
493 if( (parsetime_error = parsetime( args[1], &start_tv))) {
494 rrd_set_error( "start time: %s", parsetime_error);
497 if( (parsetime_error = parsetime( args[2], &end_tv))) {
498 rrd_set_error( "end time: %s", parsetime_error);
501 if( proc_start_end( &start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
505 /* Do we do the start or end */
506 if( strcasecmp( args[0], "START") == 0) {
507 the_tm = localtime( &start_tmp);
509 else if( strcasecmp( args[0], "END") == 0) {
510 the_tm = localtime( &end_tmp);
513 rrd_set_error( "start/end not found in '%s'", args[0]);
518 if( strftime( formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
519 return( stralloc( formatted));
522 rrd_set_error( "strftime failed");
527 char* includefile(long argc, char **args){
530 char* filename = args[0];
531 readfile(filename, &buffer, 0);
532 if (rrd_test_error()) {
533 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE));
534 sprintf(err, "[ERROR: %s]",rrd_get_error());
543 return stralloc("[ERROR: No Inclue file defined]");
547 /* make a copy of buf and replace open/close brackets with '_' */
548 char* rrdstrip(char *buf) {
553 /* make a copy of the buffer */
561 if (*p == '<' || *p == '>') {
569 char* cgigetq(long argc, char **args){
571 char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
575 if (buf==NULL) return NULL;
577 for(c=buf;*c != '\0';c++)
579 if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
580 perror("Malloc Buffer");
601 return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
604 /* remove occurrences of .. this is a general measure to make
605 paths which came in via cgi do not go UP ... */
607 char* cgigetqp(long argc, char **args){
609 char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
617 for(c=buf;*c != '\0';c++) {
623 if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
624 perror("Malloc Buffer");
643 if (*c=='.' && *(c+1) == '.') {
645 *(d++) = '_'; *(d++) ='_';
656 return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
660 char* cgiget(long argc, char **args){
662 return rrdstrip(cgiGetValue(cgiArg,args[0]));
664 return stralloc("[ERROR: not enough arguments for RRD::CV]");
669 char* drawgraph(long argc, char **args){
672 if(strcmp(args[i],"--imginfo")==0 || strcmp(args[i],"-g")==0) break;
674 args[argc++] = "--imginfo";
675 args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
677 optind=0; /* reset gnu getopt */
678 opterr=0; /* reset gnu getopt */
680 if( rrd_graph(argc+1, args-1, &calcpr, &xsize, &ysize,NULL) != -1 ) {
681 return stralloc(calcpr[0]);
683 if (rrd_test_error()) {
684 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
685 sprintf(err, "[ERROR: %s]",rrd_get_error());
694 char* drawprint(long argc, char **args){
695 if (argc==1 && calcpr){
697 while (calcpr[i] != NULL) i++; /*determine number lines in calcpr*/
698 if (atol(args[0])<i-1)
699 return stralloc(calcpr[atol(args[0])+1]);
701 return stralloc("[ERROR: RRD::PRINT argument error]");
704 char* printtimelast(long argc, char **args) {
711 return stralloc("[ERROR: allocating strftime buffer]");
713 last = rrd_last(argc+1, args-1);
714 if (rrd_test_error()) {
715 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
716 sprintf(err, "[ERROR: %s]",rrd_get_error());
720 tm_last = *localtime(&last);
721 strftime(buf,254,args[1],&tm_last);
725 return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
727 return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
730 char* printtimenow(long argc, char **args) {
731 time_t now = time(NULL);
737 return stralloc("[ERROR: allocating strftime buffer]");
739 tm_now = *localtime(&now);
740 strftime(buf,254,args[0],&tm_now);
744 return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
746 return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
749 /* Scan buffer until an unescaped '>' arives.
750 * Update argument array with arguments found.
751 * Return end cursor where parsing stopped, or NULL in case of failure.
754 * To allow nested constructs, we call rrd_expand_vars() for arguments
755 * that contain RRD::x directives. These introduce a small memory leak
756 * since we have to stralloc the arguments the way parse() works.
759 scanargs(char *line, int *argument_count, char ***arguments)
761 char *getP; /* read cursor */
762 char *putP; /* write cursor */
763 char Quote; /* type of quote if in quoted string, 0 otherwise */
764 int tagcount; /* open tag count */
765 int in_arg; /* if we currently are parsing an argument or not */
766 int argsz; /* argument array size */
767 int curarg_contains_rrd_directives;
769 /* local array of arguments while parsing */
774 printf("<-- scanargs(%s) -->\n", line);
780 /* create initial argument array of char pointers */
782 argv = (char **)malloc(argsz * sizeof(char *));
787 /* skip leading blanks */
788 while (isspace((int)*line)) {
799 curarg_contains_rrd_directives = 0;
801 /* start parsing 'line' for arguments */
804 unsigned char c = *getP++;
806 if (c == '>' && !Quote && !tagcount) {
807 /* this is our closing tag, quit scanning */
811 /* remove all special chars */
819 if (Quote || tagcount) {
820 /* copy quoted/tagged string */
825 /* end argument string */
828 if (curarg_contains_rrd_directives) {
829 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
830 curarg_contains_rrd_directives = 0;
835 case '"': /* Fall through */
841 /* copy quoted string */
846 /* reference argument string in argument array */
857 /* start new argument */
868 if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
869 curarg_contains_rrd_directives = 1;
877 /* check if our argument array is still large enough */
879 /* resize argument array */
881 argv = rrd_realloc(argv, argsz * sizeof(char *));
888 /* terminate last argument found */
890 if (curarg_contains_rrd_directives) {
891 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
897 printf("<-- arguments found [%d]\n", argc);
898 for (n=0; n<argc; n++) {
899 printf("arg %02d: '%s'\n", n, argv[n]);
903 printf("<!-- No arguments found -->\n");
907 /* update caller's notion of the argument array and it's size */
909 *argument_count = argc;
915 /* Return new scanning cursor:
916 pointer to char after closing bracket */
922 * Parse(): scan current portion of buffer for given tag.
923 * If found, parse tag arguments and call 'func' for it.
924 * The result of func is inserted at the current position
929 char **buf, /* buffer */
930 long i, /* offset in buffer */
931 char *tag, /* tag to handle */
932 char *(*func)(long argc, char **args) /* function to call for 'tag' */
935 /* the name of the vairable ... */
942 size_t taglen = strlen(tag);
944 /* Current position in buffer should start with 'tag' */
945 if (strncmp((*buf)+i, tag, taglen) != 0) {
948 /* .. and match exactly (a whitespace following 'tag') */
949 if (! isspace(*((*buf) + i + taglen)) ) {
954 printf("parse(): handeling tag '%s'\n", tag);
957 /* Scan for arguments following the tag;
958 scanargs() puts \0 into *buf ... so after scanargs it is probably
959 not a good time to use strlen on buf */
960 end = scanargs((*buf) + i + taglen, &argc, &args);
963 /* got arguments, call function for 'tag' with arguments */
964 val = func(argc, args);
969 /* unable to parse arguments, undo 0-termination by scanargs */
970 for (; argc > 0; argc--) {
971 *((args[argc-1])-1) = ' ';
974 /* next call, try parsing at current offset +1 */
975 end = (*buf) + i + 1;
977 val = stralloc("[ERROR: Parsing Problem with the following text\n"
978 " Check original file. This may have been altered "
982 /* remember offset where we have to continue parsing */
983 end_offset = end - (*buf);
990 /* Optionally resize buffer to hold the replacement value:
991 Calculating the new length of the buffer is simple. add current
992 buffer pos (i) to length of string after replaced tag to length
993 of replacement string and add 1 for the final zero ... */
994 if (end - (*buf) < (i + valln)) {
995 /* make sure we do not shrink the mallocd block */
996 size_t newbufsize = i + strlen(end) + valln + 1;
997 *buf = rrd_realloc(*buf, newbufsize);
1000 perror("Realoc buf:");
1005 /* Update new end pointer:
1006 make sure the 'end' pointer gets moved along with the
1007 buf pointer when realloc moves memory ... */
1008 end = (*buf) + end_offset;
1010 /* splice the variable:
1011 step 1. Shift pending data to make room for 'val' */
1012 memmove((*buf) + i + valln, end, strlen(end) + 1);
1014 /* step 2. Insert val */
1016 memmove((*buf)+i, val, valln);
1019 return (valln > 0 ? valln-1: valln);
1023 http_time(time_t *now) {
1025 static char buf[60];
1027 tmptime=gmtime(now);
1028 strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT",tmptime);