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 = malloc((strlen(str)+1)*sizeof(char));
87 strcpy(nstr,str);
88 return(nstr);
89 }
91 int main(int argc, char *argv[]) {
92 long length;
93 char *buffer;
94 char *server_url = NULL;
95 long i;
96 long filter=0;
97 #ifdef MUST_DISABLE_SIGFPE
98 signal(SIGFPE,SIG_IGN);
99 #endif
100 #ifdef MUST_DISABLE_FPMASK
101 fpsetmask(0);
102 #endif
103 /* what do we get for cmdline arguments?
104 for (i=0;i<argc;i++)
105 printf("%d-'%s'\n",i,argv[i]); */
106 while (1){
107 static struct option long_options[] =
108 {
109 {"filter", no_argument, 0, 'f'},
110 {0,0,0,0}
111 };
112 int option_index = 0;
113 int opt;
114 opt = getopt_long(argc, argv, "f",
115 long_options, &option_index);
116 if (opt == EOF)
117 break;
118 switch(opt) {
119 case 'f':
120 filter=1;
121 break;
122 case '?':
123 printf("unknown commandline option '%s'\n",argv[optind-1]);
124 return -1;
125 }
126 }
128 if(filter==0) {
129 cgiDebug(0,0);
130 cgiArg = cgiInit ();
131 server_url = getenv("SERVER_URL");
132 }
134 if ( (optind != argc-2 && strstr(getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) && optind != argc-1) {
135 fprintf(stderr, "ERROR: expected a filename\n");
136 exit(1);
137 } else {
138 length = readfile(argv[optind], &buffer, 1);
139 }
141 if(rrd_test_error()){
142 fprintf(stderr, "ERROR: %s\n",rrd_get_error());
143 exit(1);
144 }
147 if(filter==0) {
148 /* pass 1 makes only sense in cgi mode */
149 for (i=0;buffer[i] != '\0'; i++){
150 i +=parse(&buffer,i,"<RRD::CV",cgiget);
151 i +=parse(&buffer,i,"<RRD::CV::QUOTE",cgigetq);
152 i +=parse(&buffer,i,"<RRD::CV::PATH",cgigetqp);
153 i +=parse(&buffer,i,"<RRD::GETENV",rrdgetenv);
154 }
155 }
157 /* pass 2 */
158 for (i=0;buffer[i] != '\0'; i++){
159 i += parse(&buffer,i,"<RRD::GOODFOR",rrdgoodfor);
160 i += parse(&buffer,i,"<RRD::SETENV",rrdsetenv);
161 i += parse(&buffer,i,"<RRD::INCLUDE",includefile);
162 i += parse(&buffer,i,"<RRD::TIME::LAST",printtimelast);
163 i += parse(&buffer,i,"<RRD::TIME::NOW",printtimenow);
164 i += parse(&buffer,i,"<RRD::TIME::STRFTIME",printstrftime);
165 }
167 /* pass 3 */
168 for (i=0;buffer[i] != '\0'; i++){
169 i += parse(&buffer,i,"<RRD::GRAPH",drawgraph);
170 i += parse(&buffer,i,"<RRD::PRINT",drawprint);
171 }
173 if (filter==0){
174 printf ("Content-Type: text/html\n"
175 "Content-Length: %d\n", strlen(buffer));
176 if (labs(goodfor) > 0){
177 time_t now;
178 now = time(NULL);
179 printf ("Last-Modified: %s\n",http_time(&now));
180 now += labs(goodfor);
181 printf ("Expires: %s\n",http_time(&now));
182 if (goodfor < 0) {
183 printf("Refresh: %ld\n", labs(goodfor));
184 }
185 }
186 printf ("\n");
187 }
188 printf ("%s", buffer);
189 calfree();
190 if (buffer){
191 free(buffer);
192 }
193 exit(0);
194 }
196 /* remove occurences of .. this is a general measure to make
197 paths which came in via cgi do not go UP ... */
199 char* rrdsetenv(long argc, char **args){
200 if (argc >= 2) {
201 char *xyz=malloc((strlen(args[0])+strlen(args[1])+3)*sizeof(char));
202 if (xyz == NULL){
203 return stralloc("[ERROR: allocating setenv buffer]");
204 };
205 sprintf(xyz,"%s=%s",args[0],args[1]);
206 if( putenv(xyz) == -1) {
207 return stralloc("[ERROR: faild to do putenv]");
208 };
209 } else {
210 return stralloc("[ERROR: setenv faild because not enough arguments were defined]");
211 }
212 return stralloc("");
213 }
215 char* rrdgetenv(long argc, char **args){
216 if (argc != 1) {
217 return stralloc("[ERROR: getenv faild because it did not get 1 argument only]");
218 }
219 else if (getenv(args[0]) == NULL) {
220 return stralloc("");
221 }
222 else {
223 return stralloc(getenv(args[0]));
224 }
225 }
227 char* rrdgoodfor(long argc, char **args){
228 if (argc == 1) {
229 goodfor = atol(args[0]);
230 } else {
231 return stralloc("[ERROR: goodfor expected 1 argument]");
232 }
234 if (goodfor == 0){
235 return stralloc("[ERROR: goodfor value must not be 0]");
236 }
238 return stralloc("");
239 }
241 /* Format start or end times using strftime. We always need both the
242 * start and end times, because, either might be relative to the other.
243 * */
244 #define MAX_STRFTIME_SIZE 256
245 char* printstrftime(long argc, char **args){
246 struct time_value start_tv, end_tv;
247 char *parsetime_error = NULL;
248 char formatted[MAX_STRFTIME_SIZE];
249 struct tm *the_tm;
250 time_t start_tmp, end_tmp;
252 /* Make sure that we were given the right number of args */
253 if( argc != 4) {
254 rrd_set_error( "wrong number of args %d", argc);
255 return (char *) -1;
256 }
258 /* Init start and end time */
259 parsetime("end-24h", &start_tv);
260 parsetime("now", &end_tv);
262 /* Parse the start and end times we were given */
263 if( (parsetime_error = parsetime( args[1], &start_tv))) {
264 rrd_set_error( "start time: %s", parsetime_error);
265 return (char *) -1;
266 }
267 if( (parsetime_error = parsetime( args[2], &end_tv))) {
268 rrd_set_error( "end time: %s", parsetime_error);
269 return (char *) -1;
270 }
271 if( proc_start_end( &start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
272 return (char *) -1;
273 }
275 /* Do we do the start or end */
276 if( strcasecmp( args[0], "START") == 0) {
277 the_tm = localtime( &start_tmp);
278 }
279 else if( strcasecmp( args[0], "END") == 0) {
280 the_tm = localtime( &end_tmp);
281 }
282 else {
283 rrd_set_error( "start/end not found in '%s'", args[0]);
284 return (char *) -1;
285 }
287 /* now format it */
288 if( strftime( formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
289 return( stralloc( formatted));
290 }
291 else {
292 rrd_set_error( "strftime failed");
293 return (char *) -1;
294 }
295 }
297 char* includefile(long argc, char **args){
298 char *buffer;
299 if (argc >= 1) {
300 readfile(args[0], &buffer, 0);
301 if (rrd_test_error()) {
302 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
303 sprintf(err, "[ERROR: %s]",rrd_get_error());
304 rrd_clear_error();
305 return err;
306 } else {
307 return buffer;
308 }
309 }
310 else
311 {
312 return stralloc("[ERROR: No Inclue file defined]");
313 }
314 }
316 static
317 char* rrdstrip(char *buf){
318 char *start;
319 if (buf == NULL) return NULL;
320 buf = stralloc(buf); /* make a copy of the buffer */
321 if (buf == NULL) return NULL;
322 while ((start = strstr(buf,"<"))){
323 *start = '_';
324 }
325 while ((start = strstr(buf,">"))){
326 *start = '_';
327 }
328 return buf;
329 }
331 char* cgigetq(long argc, char **args){
332 if (argc>= 1){
333 char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
334 char *buf2;
335 char *c,*d;
336 int qc=0;
337 if (buf==NULL) return NULL;
339 for(c=buf;*c != '\0';c++)
340 if (*c == '"') qc++;
341 if((buf2=malloc((strlen(buf) + qc*4 +4) * sizeof(char)))==NULL){
342 perror("Malloc Buffer");
343 exit(1);
344 };
345 c=buf;
346 d=buf2;
347 *(d++) = '"';
348 while(*c != '\0'){
349 if (*c == '"') {
350 *(d++) = '"';
351 *(d++) = '\'';
352 *(d++) = '"';
353 *(d++) = '\'';
354 }
355 *(d++) = *(c++);
356 }
357 *(d++) = '"';
358 *(d) = '\0';
359 free(buf);
360 return buf2;
361 }
363 return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
364 }
366 /* remove occurences of .. this is a general measure to make
367 paths which came in via cgi do not go UP ... */
369 char* cgigetqp(long argc, char **args){
370 if (argc>= 1){
371 char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
372 char *buf2;
373 char *c,*d;
374 int qc=0;
375 if (buf==NULL) return NULL;
377 for(c=buf;*c != '\0';c++)
378 if (*c == '"') qc++;
379 if((buf2=malloc((strlen(buf) + qc*4 +4) * sizeof(char)))==NULL){
380 perror("Malloc Buffer");
381 exit(1);
382 };
383 c=buf;
384 d=buf2;
385 *(d++) = '"';
386 while(*c != '\0'){
387 if (*c == '"') {
388 *(d++) = '"';
389 *(d++) = '\'';
390 *(d++) = '"';
391 *(d++) = '\'';
392 }
393 if(*c == '/') {
394 *(d++) = '_';c++;
395 } else {
396 if (*c=='.' && *(c+1) == '.'){
397 c += 2;
398 *(d++) = '_'; *(d++) ='_';
399 } else {
401 *(d++) = *(c++);
402 }
403 }
404 }
405 *(d++) = '"';
406 *(d) = '\0';
407 free(buf);
408 return buf2;
409 }
411 return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
413 }
416 char* cgiget(long argc, char **args){
417 if (argc>= 1)
418 return rrdstrip(cgiGetValue(cgiArg,args[0]));
419 else
420 return stralloc("[ERROR: not enough arguments for RRD::CV]");
421 }
425 char* drawgraph(long argc, char **args){
426 int i,xsize, ysize;
427 for(i=0;i<argc;i++)
428 if(strcmp(args[i],"--imginfo")==0 || strcmp(args[i],"-g")==0) break;
429 if(i==argc) {
430 args[argc++] = "--imginfo";
431 args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
432 }
433 optind=0; /* reset gnu getopt */
434 opterr=0; /* reset gnu getopt */
435 calfree();
436 if( rrd_graph(argc+1, args-1, &calcpr, &xsize, &ysize) != -1 ) {
437 return stralloc(calcpr[0]);
438 } else {
439 if (rrd_test_error()) {
440 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
441 sprintf(err, "[ERROR: %s]",rrd_get_error());
442 rrd_clear_error();
443 calfree();
444 return err;
445 }
446 }
447 return NULL;
448 }
450 char* drawprint(long argc, char **args){
451 if (argc==1 && calcpr){
452 long i=0;
453 while (calcpr[i] != NULL) i++; /*determine number lines in calcpr*/
454 if (atol(args[0])<i-1)
455 return stralloc(calcpr[atol(args[0])+1]);
456 }
457 return stralloc("[ERROR: RRD::PRINT argument error]");
458 }
460 char* printtimelast(long argc, char **args) {
461 time_t last;
462 struct tm tm_last;
463 char *buf;
464 if ( argc == 2 ) {
465 buf = malloc(255);
466 if (buf == NULL){
467 return stralloc("[ERROR: allocating strftime buffer]");
468 };
469 last = rrd_last(argc+1, args-1);
470 if (rrd_test_error()) {
471 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
472 sprintf(err, "[ERROR: %s]",rrd_get_error());
473 rrd_clear_error();
474 return err;
475 }
476 localtime_r(&last, &tm_last);
477 strftime(buf,254,args[1],&tm_last);
478 return buf;
479 }
480 if ( argc < 2 ) {
481 return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
482 }
483 return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
484 }
486 char* printtimenow(long argc, char **args) {
487 time_t now = time(NULL);
488 struct tm tm_now;
489 char *buf;
490 if ( argc == 1 ) {
491 buf = malloc(255);
492 if (buf == NULL){
493 return stralloc("[ERROR: allocating strftime buffer]");
494 };
495 localtime_r(&now, &tm_now);
496 strftime(buf,254,args[0],&tm_now);
497 return buf;
498 }
499 if ( argc < 1 ) {
500 return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
501 }
502 return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
503 }
505 /* scan aLine until an unescaped '>' arives */
506 static
507 char* scanargs(char *aLine, long *argc, char ***args)
508 {
509 char *getP, *putP;
510 char Quote = 0;
511 int argal = MEMBLK;
512 int braket = 0;
513 int inArg = 0;
514 if (((*args) = (char **) malloc(MEMBLK*sizeof(char *))) == NULL) {
515 return NULL;
516 }
517 /* sikp leading blanks */
518 while (*aLine && *aLine <= ' ') aLine++;
520 *argc = 0;
521 getP = aLine;
522 putP = aLine;
523 while (*getP && !( !Quote && (braket == 0) && ((*getP) == '>'))){
524 if ((unsigned)*getP < ' ') *getP = ' '; /*remove all special chars*/
525 switch (*getP) {
526 case ' ':
527 if (Quote){
528 *(putP++)=*getP;
529 } else
530 if(inArg) {
531 *(putP++) = 0;
532 inArg = 0;
533 }
534 break;
535 case '"':
536 case '\'':
537 if (Quote != 0) {
538 if (Quote == *getP)
539 Quote = 0;
540 else {
541 *(putP++)=*getP;
542 }
543 } else {
544 if(!inArg){
545 (*args)[++(*argc)] = putP;
546 inArg=1;
547 }
548 Quote = *getP;
549 }
550 break;
551 default:
552 if (Quote == 0 && (*getP) == '<') {
553 braket++;
554 }
555 if (Quote == 0 && (*getP) == '>') {
556 braket--;
557 }
559 if(!inArg){
560 (*args)[++(*argc)] = putP;
561 inArg=1;
562 }
563 *(putP++)=*getP;
564 break;
565 }
566 if ((*argc) >= argal-10 ) {
567 argal += MEMBLK;
568 if (((*args)=rrd_realloc((*args),(argal)*sizeof(char *))) == NULL) {
569 return NULL;
570 }
571 }
572 getP++;
573 }
575 *putP = '\0';
576 (*argc)++;
577 if (Quote)
578 return NULL;
579 else
580 return getP+1; /* pointer to next char after parameter */
581 }
585 int parse(char **buf, long i, char *tag,
586 char *(*func)(long argc, char **args)){
588 /* the name of the vairable ... */
589 char *val;
590 long valln;
591 char **args;
592 char *end;
593 long end_offset;
594 long argc;
595 /* do we like it ? */
596 if (strncmp((*buf)+i, tag, strlen(tag))!=0) return 0;
597 if (! isspace(*( (*buf) + i + strlen(tag) )) ) return 0;
598 /* scanargs puts \0 into *buf ... so after scanargs it is probably
599 not a good time to use strlen on buf */
600 end = scanargs((*buf)+i+strlen(tag),&argc,&args);
601 if (! end) {
602 for (;argc>2;argc--){
603 *((args[argc-1])-1)=' ';
604 }
605 val = stralloc("[ERROR: Parsing Problem with the following text\n"
606 " Check original file. This may have been altered by parsing.]\n\n");
607 end = (*buf)+i+1;
608 } else {
609 val = func(argc-1,args+1);
610 free (args);
611 }
612 /* for (ii=0;ii<argc;ii++) printf("'%s'\n", args[ii]); */
613 if (val != NULL) {
614 valln = strlen(val);
615 } else { valln = 0;}
617 /* make enough room for replacement */
618 end_offset = end - (*buf);
619 if(end-(*buf) < i + valln){ /* make sure we do not shrink the mallocd block */
620 /* calculating the new length of the buffer is simple. add current
621 buffer pos (i) to length of string after replaced tag to length
622 of replacement string and add 1 for the final zero ... */
623 if(((*buf) = rrd_realloc((*buf),
624 (i+strlen(end) + valln +1) * sizeof(char)))==NULL){
625 perror("Realoc buf:");
626 exit(1);
627 };
628 }
629 end = (*buf) + end_offset; /* make sure the 'end' pointer gets moved
630 along with the buf pointer when realoc
631 moves memmory ... */
632 /* splice the variable */
633 memmove ((*buf)+i+valln,end,strlen(end)+1);
634 if (val != NULL ) memmove ((*buf)+i,val, valln);
635 if (val){ free(val);}
636 return valln > 0 ? valln-1: valln;
637 }
639 char *
640 http_time(time_t *now) {
641 struct tm tmptime;
642 static char buf[60];
644 gmtime_r(now, &tmptime);
645 strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT", &tmptime);
646 return(buf);
647 }