I've made a patch to rrdcgi to add <RRD::TIME::STRFTIME ...> which allows
[rrdtool.git] / src / rrd_cgi.c
1 /*****************************************************************************
2  * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2002
3  *****************************************************************************
4  * rrd_cgi.c  RRD Web Page Generator
5  *****************************************************************************/
6
7 #include "rrd_tool.h"
8 #include <cgi.h>
9 #include <time.h>
10
11
12 #define MEMBLK 1024
13
14 /* global variable for libcgi */
15 s_cgi **cgiArg;
16
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 **));
20
21 /**************************************************/
22 /* tag replacers ... they are called from parse   */
23 /* through function pointers                      */
24 /**************************************************/
25
26 /* return cgi var named arg[0] */ 
27 char* cgiget(long , char **);
28
29 /* return a quoted cgi var named arg[0] */ 
30 char* cgigetq(long , char **);
31
32 /* return a quoted and sanitized cgi variable */
33 char* cgigetqp(long , char **);
34
35 /* call rrd_graph and insert apropriate image tag */
36 char* drawgraph(long, char **);
37
38 /* return PRINT functions from last rrd_graph call */
39 char* drawprint(long, char **);
40
41 /* pretty-print the <last></last> value for some.rrd via strftime() */
42 char* printtimelast(long, char **);
43
44 /* pretty-print current time */
45 char* printtimenow(long,char **);
46
47 /* set an evironment variable */
48 char* rrdsetenv(long, char **);
49
50 /* get an evironment variable */
51 char* rrdgetenv(long, char **);
52
53 /* include the named file at this point */
54 char* includefile(long, char **);
55
56 /* for how long is the output of the cgi valid ? */
57 char* rrdgoodfor(long, char **);
58
59 /* format at-time specified times using strftime */
60 char* printstrftime(long, char**);
61
62 /** http protocol needs special format, and GMT time **/
63 char *http_time(time_t *);
64
65 /* return a pointer to newly alocated copy of this string */
66 char *stralloc(char *);
67
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 }
83
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 }
90
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   }
127
128   if(filter==0) {
129       cgiDebug(0,0);
130       cgiArg = cgiInit ();
131       server_url = getenv("SERVER_URL");
132   }
133
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   }
140    
141   if(rrd_test_error()){
142       fprintf(stderr, "ERROR: %s\n",rrd_get_error());
143       exit(1);
144   }
145
146
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   }
156
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   }
166
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   }
172
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 }
195
196 /* remove occurences of .. this is a general measure to make
197    paths which came in via cgi do not go UP ... */
198
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 }
214
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 }
226
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   }
233    
234   if (goodfor == 0){
235      return stralloc("[ERROR: goodfor value must not be 0]");
236   }
237    
238   return stralloc("");
239 }
240
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;
251
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         }
257
258         /* Init start and end time */
259         parsetime("end-24h", &start_tv);
260         parsetime("now", &end_tv);
261
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         }
274
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         }
286
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 }
296
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 }
315
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 }
330
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;
338
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   }
362
363   return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
364 }
365
366 /* remove occurences of .. this is a general measure to make
367    paths which came in via cgi do not go UP ... */
368
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;
376
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 {
400                 
401                 *(d++) = *(c++);
402             }
403         }
404     }
405     *(d++) = '"';
406     *(d) = '\0';
407     free(buf);
408     return buf2;
409   }
410
411   return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
412
413 }
414
415
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 }
422
423
424
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 }
449
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 }
459
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 }
485
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 }
504
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++;
519   
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       }
558
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   }
574   
575   *putP = '\0';
576   (*argc)++;
577   if (Quote) 
578     return NULL;
579   else
580     return getP+1; /* pointer to next char after parameter */
581 }
582
583
584
585 int parse(char **buf, long i, char *tag, 
586             char *(*func)(long argc, char **args)){
587
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;}
616   
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 }
638
639 char *
640 http_time(time_t *now) {
641         struct tm tmptime;
642         static char buf[60];
643
644         gmtime_r(now, &tmptime);
645         strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT", &tmptime);
646         return(buf);
647 }