handle NULL pointers gracefully
[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;
87   if (str == NULL) {  
88         return NULL;
89   }
90   nstr = malloc((strlen(str)+1)*sizeof(char));
91   strcpy(nstr,str);
92   return(nstr);
93 }
94
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   }
131
132   if(filter==0) {
133       cgiDebug(0,0);
134       cgiArg = cgiInit ();
135       server_url = getenv("SERVER_URL");
136   }
137
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   }
144    
145   if(rrd_test_error()){
146       fprintf(stderr, "ERROR: %s\n",rrd_get_error());
147       exit(1);
148   }
149
150
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   }
160
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   }
170
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   }
176
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 }
199
200 /* remove occurences of .. this is a general measure to make
201    paths which came in via cgi do not go UP ... */
202
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 }
218
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 }
230
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   }
237    
238   if (goodfor == 0){
239      return stralloc("[ERROR: goodfor value must not be 0]");
240   }
241    
242   return stralloc("");
243 }
244
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;
255
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         }
261
262         /* Init start and end time */
263         parsetime("end-24h", &start_tv);
264         parsetime("now", &end_tv);
265
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         }
278
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         }
290
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 }
300
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 }
319
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 }
334
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;
342
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   }
366
367   return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
368 }
369
370 /* remove occurences of .. this is a general measure to make
371    paths which came in via cgi do not go UP ... */
372
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;
380
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 {
404                 
405                 *(d++) = *(c++);
406             }
407         }
408     }
409     *(d++) = '"';
410     *(d) = '\0';
411     free(buf);
412     return buf2;
413   }
414
415   return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
416
417 }
418
419
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 }
426
427
428
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 }
453
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 }
463
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 }
489
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 }
508
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++;
523   
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       }
562
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   }
578   
579   *putP = '\0';
580   (*argc)++;
581   if (Quote) 
582     return NULL;
583   else
584     return getP+1; /* pointer to next char after parameter */
585 }
586
587
588
589 int parse(char **buf, long i, char *tag, 
590             char *(*func)(long argc, char **args)){
591
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;}
620   
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 }
642
643 char *
644 http_time(time_t *now) {
645         struct tm tmptime;
646         static char buf[60];
647
648         gmtime_r(now, &tmptime);
649         strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT", &tmptime);
650         return(buf);
651 }