1 /*****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 *****************************************************************************
4 * rrd_cgi.c RRD Web Page Generator
5 *****************************************************************************/
7 #include "rrd_tool.h"
8 #include <cgi.h>
9 #include <time.h>
12 #define MEMBLK 1024
14 /* global variable for libcgi */
15 s_cgi **cgiArg;
17 /* in arg[0] find tags beginning with arg[1] call arg[2] on them
18 and replace by result of arg[2] call */
19 int parse(char **, long, char *, char *(*)(long , char **));
21 /**************************************************/
22 /* tag replacers ... they are called from parse */
23 /* through function pointers */
24 /**************************************************/
26 /* return cgi var named arg[0] */
27 char* cgiget(long , char **);
29 /* return a quoted cgi var named arg[0] */
30 char* cgigetq(long , char **);
32 /* return a quoted and sanitized cgi variable */
33 char* cgigetqp(long , char **);
35 /* call rrd_graph and insert apropriate image tag */
36 char* drawgraph(long, char **);
38 /* return PRINT functions from last rrd_graph call */
39 char* drawprint(long, char **);
41 /* pretty-print the <last></last> value for some.rrd via strftime() */
42 char* printtimelast(long, char **);
44 /* pretty-print current time */
45 char* printtimenow(long,char **);
47 /* set an evironment variable */
48 char* rrdsetenv(long, char **);
50 /* get an evironment variable */
51 char* rrdgetenv(long, char **);
53 /* include the named file at this point */
54 char* includefile(long, char **);
56 /* for how long is the output of the cgi valid ? */
57 char* rrdgoodfor(long, char **);
59 /* format at-time specified times using strftime */
60 char* printstrftime(long, char**);
62 /** http protocol needs special format, and GMT time **/
63 char *http_time(time_t *);
65 /* return a pointer to newly alocated copy of this string */
66 char *stralloc(char *);
68 static long goodfor=0;
69 static char **calcpr=NULL;
70 static void calfree (void){
71 if (calcpr) {
72 long i;
73 for(i=0;calcpr[i];i++){
74 if (calcpr[i]){
75 free(calcpr[i]);
76 }
77 }
78 if (calcpr) {
79 free(calcpr);
80 }
81 }
82 }
84 /* create freeable version of the string */
85 char * stralloc(char *str){
86 char *nstr;
87 if (str == NULL) {
88 return NULL;
89 }
90 nstr = malloc((strlen(str)+1)*sizeof(char));
91 strcpy(nstr,str);
92 return(nstr);
93 }
95 int main(int argc, char *argv[]) {
96 long length;
97 char *buffer;
98 char *server_url = NULL;
99 long i;
100 long filter=0;
101 #ifdef MUST_DISABLE_SIGFPE
102 signal(SIGFPE,SIG_IGN);
103 #endif
104 #ifdef MUST_DISABLE_FPMASK
105 fpsetmask(0);
106 #endif
107 /* what do we get for cmdline arguments?
108 for (i=0;i<argc;i++)
109 printf("%d-'%s'\n",i,argv[i]); */
110 while (1){
111 static struct option long_options[] =
112 {
113 {"filter", no_argument, 0, 'f'},
114 {0,0,0,0}
115 };
116 int option_index = 0;
117 int opt;
118 opt = getopt_long(argc, argv, "f",
119 long_options, &option_index);
120 if (opt == EOF)
121 break;
122 switch(opt) {
123 case 'f':
124 filter=1;
125 break;
126 case '?':
127 printf("unknown commandline option '%s'\n",argv[optind-1]);
128 return -1;
129 }
130 }
132 if(filter==0) {
133 cgiDebug(0,0);
134 cgiArg = cgiInit ();
135 server_url = getenv("SERVER_URL");
136 }
138 if ( (optind != argc-2 && strstr(getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) && optind != argc-1) {
139 fprintf(stderr, "ERROR: expected a filename\n");
140 exit(1);
141 } else {
142 length = readfile(argv[optind], &buffer, 1);
143 }
145 if(rrd_test_error()){
146 fprintf(stderr, "ERROR: %s\n",rrd_get_error());
147 exit(1);
148 }
151 if(filter==0) {
152 /* pass 1 makes only sense in cgi mode */
153 for (i=0;buffer[i] != '\0'; i++){
154 i +=parse(&buffer,i,"<RRD::CV",cgiget);
155 i +=parse(&buffer,i,"<RRD::CV::QUOTE",cgigetq);
156 i +=parse(&buffer,i,"<RRD::CV::PATH",cgigetqp);
157 i +=parse(&buffer,i,"<RRD::GETENV",rrdgetenv);
158 }
159 }
161 /* pass 2 */
162 for (i=0;buffer[i] != '\0'; i++){
163 i += parse(&buffer,i,"<RRD::GOODFOR",rrdgoodfor);
164 i += parse(&buffer,i,"<RRD::SETENV",rrdsetenv);
165 i += parse(&buffer,i,"<RRD::INCLUDE",includefile);
166 i += parse(&buffer,i,"<RRD::TIME::LAST",printtimelast);
167 i += parse(&buffer,i,"<RRD::TIME::NOW",printtimenow);
168 i += parse(&buffer,i,"<RRD::TIME::STRFTIME",printstrftime);
169 }
171 /* pass 3 */
172 for (i=0;buffer[i] != '\0'; i++){
173 i += parse(&buffer,i,"<RRD::GRAPH",drawgraph);
174 i += parse(&buffer,i,"<RRD::PRINT",drawprint);
175 }
177 if (filter==0){
178 printf ("Content-Type: text/html\n"
179 "Content-Length: %d\n", strlen(buffer));
180 if (labs(goodfor) > 0){
181 time_t now;
182 now = time(NULL);
183 printf ("Last-Modified: %s\n",http_time(&now));
184 now += labs(goodfor);
185 printf ("Expires: %s\n",http_time(&now));
186 if (goodfor < 0) {
187 printf("Refresh: %ld\n", labs(goodfor));
188 }
189 }
190 printf ("\n");
191 }
192 printf ("%s", buffer);
193 calfree();
194 if (buffer){
195 free(buffer);
196 }
197 exit(0);
198 }
200 /* remove occurences of .. this is a general measure to make
201 paths which came in via cgi do not go UP ... */
203 char* rrdsetenv(long argc, char **args){
204 if (argc >= 2) {
205 char *xyz=malloc((strlen(args[0])+strlen(args[1])+3)*sizeof(char));
206 if (xyz == NULL){
207 return stralloc("[ERROR: allocating setenv buffer]");
208 };
209 sprintf(xyz,"%s=%s",args[0],args[1]);
210 if( putenv(xyz) == -1) {
211 return stralloc("[ERROR: faild to do putenv]");
212 };
213 } else {
214 return stralloc("[ERROR: setenv faild because not enough arguments were defined]");
215 }
216 return stralloc("");
217 }
219 char* rrdgetenv(long argc, char **args){
220 if (argc != 1) {
221 return stralloc("[ERROR: getenv faild because it did not get 1 argument only]");
222 }
223 else if (getenv(args[0]) == NULL) {
224 return stralloc("");
225 }
226 else {
227 return stralloc(getenv(args[0]));
228 }
229 }
231 char* rrdgoodfor(long argc, char **args){
232 if (argc == 1) {
233 goodfor = atol(args[0]);
234 } else {
235 return stralloc("[ERROR: goodfor expected 1 argument]");
236 }
238 if (goodfor == 0){
239 return stralloc("[ERROR: goodfor value must not be 0]");
240 }
242 return stralloc("");
243 }
245 /* Format start or end times using strftime. We always need both the
246 * start and end times, because, either might be relative to the other.
247 * */
248 #define MAX_STRFTIME_SIZE 256
249 char* printstrftime(long argc, char **args){
250 struct rrd_time_value start_tv, end_tv;
251 char *parsetime_error = NULL;
252 char formatted[MAX_STRFTIME_SIZE];
253 struct tm *the_tm;
254 time_t start_tmp, end_tmp;
256 /* Make sure that we were given the right number of args */
257 if( argc != 4) {
258 rrd_set_error( "wrong number of args %d", argc);
259 return (char *) -1;
260 }
262 /* Init start and end time */
263 parsetime("end-24h", &start_tv);
264 parsetime("now", &end_tv);
266 /* Parse the start and end times we were given */
267 if( (parsetime_error = parsetime( args[1], &start_tv))) {
268 rrd_set_error( "start time: %s", parsetime_error);
269 return (char *) -1;
270 }
271 if( (parsetime_error = parsetime( args[2], &end_tv))) {
272 rrd_set_error( "end time: %s", parsetime_error);
273 return (char *) -1;
274 }
275 if( proc_start_end( &start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
276 return (char *) -1;
277 }
279 /* Do we do the start or end */
280 if( strcasecmp( args[0], "START") == 0) {
281 the_tm = localtime( &start_tmp);
282 }
283 else if( strcasecmp( args[0], "END") == 0) {
284 the_tm = localtime( &end_tmp);
285 }
286 else {
287 rrd_set_error( "start/end not found in '%s'", args[0]);
288 return (char *) -1;
289 }
291 /* now format it */
292 if( strftime( formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
293 return( stralloc( formatted));
294 }
295 else {
296 rrd_set_error( "strftime failed");
297 return (char *) -1;
298 }
299 }
301 char* includefile(long argc, char **args){
302 char *buffer;
303 if (argc >= 1) {
304 readfile(args[0], &buffer, 0);
305 if (rrd_test_error()) {
306 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
307 sprintf(err, "[ERROR: %s]",rrd_get_error());
308 rrd_clear_error();
309 return err;
310 } else {
311 return buffer;
312 }
313 }
314 else
315 {
316 return stralloc("[ERROR: No Inclue file defined]");
317 }
318 }
320 static
321 char* rrdstrip(char *buf){
322 char *start;
323 if (buf == NULL) return NULL;
324 buf = stralloc(buf); /* make a copy of the buffer */
325 if (buf == NULL) return NULL;
326 while ((start = strstr(buf,"<"))){
327 *start = '_';
328 }
329 while ((start = strstr(buf,">"))){
330 *start = '_';
331 }
332 return buf;
333 }
335 char* cgigetq(long argc, char **args){
336 if (argc>= 1){
337 char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
338 char *buf2;
339 char *c,*d;
340 int qc=0;
341 if (buf==NULL) return NULL;
343 for(c=buf;*c != '\0';c++)
344 if (*c == '"') qc++;
345 if((buf2=malloc((strlen(buf) + qc*4 +4) * sizeof(char)))==NULL){
346 perror("Malloc Buffer");
347 exit(1);
348 };
349 c=buf;
350 d=buf2;
351 *(d++) = '"';
352 while(*c != '\0'){
353 if (*c == '"') {
354 *(d++) = '"';
355 *(d++) = '\'';
356 *(d++) = '"';
357 *(d++) = '\'';
358 }
359 *(d++) = *(c++);
360 }
361 *(d++) = '"';
362 *(d) = '\0';
363 free(buf);
364 return buf2;
365 }
367 return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
368 }
370 /* remove occurences of .. this is a general measure to make
371 paths which came in via cgi do not go UP ... */
373 char* cgigetqp(long argc, char **args){
374 if (argc>= 1){
375 char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
376 char *buf2;
377 char *c,*d;
378 int qc=0;
379 if (buf==NULL) return NULL;
381 for(c=buf;*c != '\0';c++)
382 if (*c == '"') qc++;
383 if((buf2=malloc((strlen(buf) + qc*4 +4) * sizeof(char)))==NULL){
384 perror("Malloc Buffer");
385 exit(1);
386 };
387 c=buf;
388 d=buf2;
389 *(d++) = '"';
390 while(*c != '\0'){
391 if (*c == '"') {
392 *(d++) = '"';
393 *(d++) = '\'';
394 *(d++) = '"';
395 *(d++) = '\'';
396 }
397 if(*c == '/') {
398 *(d++) = '_';c++;
399 } else {
400 if (*c=='.' && *(c+1) == '.'){
401 c += 2;
402 *(d++) = '_'; *(d++) ='_';
403 } else {
405 *(d++) = *(c++);
406 }
407 }
408 }
409 *(d++) = '"';
410 *(d) = '\0';
411 free(buf);
412 return buf2;
413 }
415 return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
417 }
420 char* cgiget(long argc, char **args){
421 if (argc>= 1)
422 return rrdstrip(cgiGetValue(cgiArg,args[0]));
423 else
424 return stralloc("[ERROR: not enough arguments for RRD::CV]");
425 }
429 char* drawgraph(long argc, char **args){
430 int i,xsize, ysize;
431 for(i=0;i<argc;i++)
432 if(strcmp(args[i],"--imginfo")==0 || strcmp(args[i],"-g")==0) break;
433 if(i==argc) {
434 args[argc++] = "--imginfo";
435 args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
436 }
437 optind=0; /* reset gnu getopt */
438 opterr=0; /* reset gnu getopt */
439 calfree();
440 if( rrd_graph(argc+1, args-1, &calcpr, &xsize, &ysize, NULL) != -1 ) {
441 return stralloc(calcpr[0]);
442 } else {
443 if (rrd_test_error()) {
444 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
445 sprintf(err, "[ERROR: %s]",rrd_get_error());
446 rrd_clear_error();
447 calfree();
448 return err;
449 }
450 }
451 return NULL;
452 }
454 char* drawprint(long argc, char **args){
455 if (argc==1 && calcpr){
456 long i=0;
457 while (calcpr[i] != NULL) i++; /*determine number lines in calcpr*/
458 if (atol(args[0])<i-1)
459 return stralloc(calcpr[atol(args[0])+1]);
460 }
461 return stralloc("[ERROR: RRD::PRINT argument error]");
462 }
464 char* printtimelast(long argc, char **args) {
465 time_t last;
466 struct tm tm_last;
467 char *buf;
468 if ( argc == 2 ) {
469 buf = malloc(255);
470 if (buf == NULL){
471 return stralloc("[ERROR: allocating strftime buffer]");
472 };
473 last = rrd_last(argc+1, args-1);
474 if (rrd_test_error()) {
475 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
476 sprintf(err, "[ERROR: %s]",rrd_get_error());
477 rrd_clear_error();
478 return err;
479 }
480 localtime_r(&last, &tm_last);
481 strftime(buf,254,args[1],&tm_last);
482 return buf;
483 }
484 if ( argc < 2 ) {
485 return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
486 }
487 return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
488 }
490 char* printtimenow(long argc, char **args) {
491 time_t now = time(NULL);
492 struct tm tm_now;
493 char *buf;
494 if ( argc == 1 ) {
495 buf = malloc(255);
496 if (buf == NULL){
497 return stralloc("[ERROR: allocating strftime buffer]");
498 };
499 localtime_r(&now, &tm_now);
500 strftime(buf,254,args[0],&tm_now);
501 return buf;
502 }
503 if ( argc < 1 ) {
504 return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
505 }
506 return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
507 }
509 /* scan aLine until an unescaped '>' arives */
510 static
511 char* scanargs(char *aLine, long *argc, char ***args)
512 {
513 char *getP, *putP;
514 char Quote = 0;
515 int argal = MEMBLK;
516 int braket = 0;
517 int inArg = 0;
518 if (((*args) = (char **) malloc(MEMBLK*sizeof(char *))) == NULL) {
519 return NULL;
520 }
521 /* sikp leading blanks */
522 while (*aLine && *aLine <= ' ') aLine++;
524 *argc = 0;
525 getP = aLine;
526 putP = aLine;
527 while (*getP && !( !Quote && (braket == 0) && ((*getP) == '>'))){
528 if ((unsigned)*getP < ' ') *getP = ' '; /*remove all special chars*/
529 switch (*getP) {
530 case ' ':
531 if (Quote){
532 *(putP++)=*getP;
533 } else
534 if(inArg) {
535 *(putP++) = 0;
536 inArg = 0;
537 }
538 break;
539 case '"':
540 case '\'':
541 if (Quote != 0) {
542 if (Quote == *getP)
543 Quote = 0;
544 else {
545 *(putP++)=*getP;
546 }
547 } else {
548 if(!inArg){
549 (*args)[++(*argc)] = putP;
550 inArg=1;
551 }
552 Quote = *getP;
553 }
554 break;
555 default:
556 if (Quote == 0 && (*getP) == '<') {
557 braket++;
558 }
559 if (Quote == 0 && (*getP) == '>') {
560 braket--;
561 }
563 if(!inArg){
564 (*args)[++(*argc)] = putP;
565 inArg=1;
566 }
567 *(putP++)=*getP;
568 break;
569 }
570 if ((*argc) >= argal-10 ) {
571 argal += MEMBLK;
572 if (((*args)=rrd_realloc((*args),(argal)*sizeof(char *))) == NULL) {
573 return NULL;
574 }
575 }
576 getP++;
577 }
579 *putP = '\0';
580 (*argc)++;
581 if (Quote)
582 return NULL;
583 else
584 return getP+1; /* pointer to next char after parameter */
585 }
589 int parse(char **buf, long i, char *tag,
590 char *(*func)(long argc, char **args)){
592 /* the name of the vairable ... */
593 char *val;
594 long valln;
595 char **args;
596 char *end;
597 long end_offset;
598 long argc;
599 /* do we like it ? */
600 if (strncmp((*buf)+i, tag, strlen(tag))!=0) return 0;
601 if (! isspace(*( (*buf) + i + strlen(tag) )) ) return 0;
602 /* scanargs puts \0 into *buf ... so after scanargs it is probably
603 not a good time to use strlen on buf */
604 end = scanargs((*buf)+i+strlen(tag),&argc,&args);
605 if (! end) {
606 for (;argc>2;argc--){
607 *((args[argc-1])-1)=' ';
608 }
609 val = stralloc("[ERROR: Parsing Problem with the following text\n"
610 " Check original file. This may have been altered by parsing.]\n\n");
611 end = (*buf)+i+1;
612 } else {
613 val = func(argc-1,args+1);
614 free (args);
615 }
616 /* for (ii=0;ii<argc;ii++) printf("'%s'\n", args[ii]); */
617 if (val != NULL) {
618 valln = strlen(val);
619 } else { valln = 0;}
621 /* make enough room for replacement */
622 end_offset = end - (*buf);
623 if(end-(*buf) < i + valln){ /* make sure we do not shrink the mallocd block */
624 /* calculating the new length of the buffer is simple. add current
625 buffer pos (i) to length of string after replaced tag to length
626 of replacement string and add 1 for the final zero ... */
627 if(((*buf) = rrd_realloc((*buf),
628 (i+strlen(end) + valln +1) * sizeof(char)))==NULL){
629 perror("Realoc buf:");
630 exit(1);
631 };
632 }
633 end = (*buf) + end_offset; /* make sure the 'end' pointer gets moved
634 along with the buf pointer when realoc
635 moves memmory ... */
636 /* splice the variable */
637 memmove ((*buf)+i+valln,end,strlen(end)+1);
638 if (val != NULL ) memmove ((*buf)+i,val, valln);
639 if (val){ free(val);}
640 return valln > 0 ? valln-1: valln;
641 }
643 char *
644 http_time(time_t *now) {
645 struct tm tmptime;
646 static char buf[60];
648 gmtime_r(now, &tmptime);
649 strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT", &tmptime);
650 return(buf);
651 }