]> git.gir.st - ttxd.git/blob - src/thttpd-2.27/cgi-src/ssi.c
initial code import
[ttxd.git] / src / thttpd-2.27 / cgi-src / ssi.c
1 /* ssi - server-side-includes CGI program
2 **
3 ** Copyright © 1995 by Jef Poskanzer <jef@mail.acme.com>.
4 ** All rights reserved.
5 **
6 ** Redistribution and use in source and binary forms, with or without
7 ** modification, are permitted provided that the following conditions
8 ** are met:
9 ** 1. Redistributions of source code must retain the above copyright
10 ** notice, this list of conditions and the following disclaimer.
11 ** 2. Redistributions in binary form must reproduce the above copyright
12 ** notice, this list of conditions and the following disclaimer in the
13 ** documentation and/or other materials provided with the distribution.
14 **
15 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 ** SUCH DAMAGE.
26 */
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34
35 #include "config.h"
36 #include "match.h"
37
38
39 #define ST_GROUND 0
40 #define ST_LESSTHAN 1
41 #define ST_BANG 2
42 #define ST_MINUS1 3
43 #define ST_MINUS2 4
44
45
46 static void read_file( char* vfilename, char* filename, FILE* fp );
47
48
49 static char* argv0;
50 static char* url;
51
52 static char timefmt[100];
53 static int sizefmt;
54 #define SF_BYTES 0
55 #define SF_ABBREV 1
56 static struct stat sb;
57
58
59 static void
60 internal_error( char* reason )
61 {
62 char* title = "500 Internal Error";
63
64 (void) printf( "\
65 <HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
66 <BODY><H2>%s</H2>\n\
67 Something unusual went wrong during a server-side-includes request:\n\
68 <BLOCKQUOTE>\n\
69 %s\n\
70 </BLOCKQUOTE>\n\
71 </BODY></HTML>\n", title, title, reason );
72 }
73
74
75 static void
76 not_found( char* filename )
77 {
78 char* title = "404 Not Found";
79
80 (void) printf( "\
81 <HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
82 <BODY><H2>%s</H2>\n\
83 The requested server-side-includes filename, %s,\n\
84 does not seem to exist.\n\
85 </BODY></HTML>\n", title, title, filename );
86 }
87
88
89 static void
90 not_found2( char* directive, char* tag, char* filename2 )
91 {
92 char* title = "Not Found";
93
94 (void) printf( "\
95 <HR><H2>%s</H2>\n\
96 The filename requested in a %s %s directive, %s,\n\
97 does not seem to exist.\n\
98 <HR>\n", title, directive, tag, filename2 );
99 }
100
101
102 static void
103 not_permitted( char* directive, char* tag, char* val )
104 {
105 char* title = "Not Permitted";
106
107 (void) printf( "\
108 <HR><H2>%s</H2>\n\
109 The filename requested in the %s %s=%s directive\n\
110 may not be fetched.\n\
111 <HR>\n", title, directive, tag, val );
112 }
113
114
115 static void
116 unknown_directive( char* filename, char* directive )
117 {
118 char* title = "Unknown Directive";
119
120 (void) printf( "\
121 <HR><H2>%s</H2>\n\
122 The requested server-side-includes filename, %s,\n\
123 tried to use an unknown directive, %s.\n\
124 <HR>\n", title, filename, directive );
125 }
126
127
128 static void
129 unknown_tag( char* filename, char* directive, char* tag )
130 {
131 char* title = "Unknown Tag";
132
133 (void) printf( "\
134 <HR><H2>%s</H2>\n\
135 The requested server-side-includes filename, %s,\n\
136 tried to use the directive %s with an unknown tag, %s.\n\
137 <HR>\n", title, filename, directive, tag );
138 }
139
140
141 static void
142 unknown_value( char* filename, char* directive, char* tag, char* val )
143 {
144 char* title = "Unknown Value";
145
146 (void) printf( "\
147 <HR><H2>%s</H2>\n\
148 The requested server-side-includes filename, %s,\n\
149 tried to use the directive %s %s with an unknown value, %s.\n\
150 <HR>\n", title, filename, directive, tag, val );
151 }
152
153
154 static int
155 get_filename( char* vfilename, char* filename, char* directive, char* tag, char* val, char* fn, int fnsize )
156 {
157 int vl, fl;
158 char* cp;
159
160 /* Used for the various commands that accept a file name.
161 ** These commands accept two tags:
162 ** virtual
163 ** Gives a virtual path to a document on the server.
164 ** file
165 ** Gives a pathname relative to the current directory. ../ cannot
166 ** be used in this pathname, nor can absolute paths be used.
167 */
168 vl = strlen( vfilename );
169 fl = strlen( filename );
170 if ( strcmp( tag, "virtual" ) == 0 )
171 {
172 if ( strstr( val, "../" ) != (char*) 0 )
173 {
174 not_permitted( directive, tag, val );
175 return -1;
176 }
177 /* Figure out root using difference between vfilename and filename. */
178 if ( vl > fl ||
179 strcmp( vfilename, &filename[fl - vl] ) != 0 )
180 return -1;
181 if ( fl - vl + strlen( val ) >= fnsize )
182 return -1;
183 (void) strncpy( fn, filename, fl - vl );
184 (void) strcpy( &fn[fl - vl], val );
185 }
186 else if ( strcmp( tag, "file" ) == 0 )
187 {
188 if ( val[0] == '/' || strstr( val, "../" ) != (char*) 0 )
189 {
190 not_permitted( directive, tag, val );
191 return -1;
192 }
193 if ( fl + 1 + strlen( val ) >= fnsize )
194 return -1;
195 (void) strcpy( fn, filename );
196 cp = strrchr( fn, '/' );
197 if ( cp == (char*) 0 )
198 {
199 cp = &fn[strlen( fn )];
200 *cp = '/';
201 }
202 (void) strcpy( ++cp, val );
203 }
204 else
205 {
206 unknown_tag( filename, directive, tag );
207 return -1;
208 }
209 return 0;
210 }
211
212
213 static int
214 check_filename( char* filename )
215 {
216 static int inited = 0;
217 static char* cgi_pattern;
218 int fnl;
219 char* cp;
220 char* dirname;
221 char* authname;
222 struct stat sb2;
223 int r;
224
225 if ( ! inited )
226 {
227 /* Get the cgi pattern. */
228 cgi_pattern = getenv( "CGI_PATTERN" );
229 #ifdef CGI_PATTERN
230 if ( cgi_pattern == (char*) 0 )
231 cgi_pattern = CGI_PATTERN;
232 #endif /* CGI_PATTERN */
233 inited = 1;
234 }
235
236 /* ../ is not permitted. */
237 if ( strstr( filename, "../" ) != (char*) 0 )
238 return 0;
239
240 #ifdef AUTH_FILE
241 /* Ensure that we are not reading a basic auth password file. */
242 fnl = strlen(filename);
243 if ( strcmp( filename, AUTH_FILE ) == 0 ||
244 ( fnl >= sizeof(AUTH_FILE) &&
245 strcmp( &filename[fnl - sizeof(AUTH_FILE) + 1], AUTH_FILE ) == 0 &&
246 filename[fnl - sizeof(AUTH_FILE)] == '/' ) )
247 return 0;
248
249 /* Check for an auth file in the same directory. We can't do an actual
250 ** auth password check here because CGI programs are not given the
251 ** authorization header, for security reasons. So instead we just
252 ** prohibit access to all auth-protected files.
253 */
254 dirname = strdup( filename );
255 if ( dirname == (char*) 0 )
256 return 0; /* out of memory */
257 cp = strrchr( dirname, '/' );
258 if ( cp == (char*) 0 )
259 (void) strcpy( dirname, "." );
260 else
261 *cp = '\0';
262 authname = malloc( strlen( dirname ) + 1 + sizeof(AUTH_FILE) );
263 if ( authname == (char*) 0 )
264 return 0; /* out of memory */
265 (void) sprintf( authname, "%s/%s", dirname, AUTH_FILE );
266 r = stat( authname, &sb2 );
267 free( dirname );
268 free( authname );
269 if ( r == 0 )
270 return 0;
271 #endif /* AUTH_FILE */
272
273 /* Ensure that we are not reading a CGI file. */
274 if ( cgi_pattern != (char*) 0 && match( cgi_pattern, filename ) )
275 return 0;
276
277 return 1;
278 }
279
280
281 static void
282 show_time( time_t t, int gmt )
283 {
284 struct tm* tmP;
285 char tbuf[500];
286
287 if ( gmt )
288 tmP = gmtime( &t );
289 else
290 tmP = localtime( &t );
291 if ( strftime( tbuf, sizeof(tbuf), timefmt, tmP ) > 0 )
292 (void) fputs( tbuf, stdout );
293 }
294
295
296 static void
297 show_size( off_t size )
298 {
299 switch ( sizefmt )
300 {
301 case SF_BYTES:
302 (void) printf( "%ld", (long) size ); /* spec says should have commas */
303 break;
304 case SF_ABBREV:
305 if ( size < 1024 )
306 (void) printf( "%ld", (long) size );
307 else if ( size < 1024 )
308 (void) printf( "%ldK", (long) size / 1024L );
309 else if ( size < 1024*1024 )
310 (void) printf( "%ldM", (long) size / (1024L*1024L) );
311 else
312 (void) printf( "%ldG", (long) size / (1024L*1024L*1024L) );
313 break;
314 }
315 }
316
317
318 static void
319 do_config( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
320 {
321 /* The config directive controls various aspects of the file parsing.
322 ** There are two valid tags:
323 ** timefmt
324 ** Gives the server a new format to use when providing dates. This
325 ** is a string compatible with the strftime library call.
326 ** sizefmt
327 ** Determines the formatting to be used when displaying the size of
328 ** a file. Valid choices are bytes, for a formatted byte count
329 ** (formatted as 1,234,567), or abbrev for an abbreviated version
330 ** displaying the number of kilobytes or megabytes the file occupies.
331 */
332
333 if ( strcmp( tag, "timefmt" ) == 0 )
334 {
335 (void) strncpy( timefmt, val, sizeof(timefmt) - 1 );
336 timefmt[sizeof(timefmt) - 1] = '\0';
337 }
338 else if ( strcmp( tag, "sizefmt" ) == 0 )
339 {
340 if ( strcmp( val, "bytes" ) == 0 )
341 sizefmt = SF_BYTES;
342 else if ( strcmp( val, "abbrev" ) == 0 )
343 sizefmt = SF_ABBREV;
344 else
345 unknown_value( filename, directive, tag, val );
346 }
347 else
348 unknown_tag( filename, directive, tag );
349 }
350
351
352 static void
353 do_include( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
354 {
355 char vfilename2[1000];
356 char filename2[1000];
357 FILE* fp2;
358
359 /* Inserts the text of another document into the parsed document. */
360
361 if ( get_filename(
362 vfilename, filename, directive, tag, val, filename2,
363 sizeof(filename2) ) < 0 )
364 return;
365
366 if ( ! check_filename( filename2 ) )
367 {
368 not_permitted( directive, tag, filename2 );
369 return;
370 }
371
372 fp2 = fopen( filename2, "r" );
373 if ( fp2 == (FILE*) 0 )
374 {
375 not_found2( directive, tag, filename2 );
376 return;
377 }
378
379 if ( strcmp( tag, "virtual" ) == 0 )
380 {
381 if ( strlen( val ) < sizeof( vfilename2 ) )
382 (void) strcpy( vfilename2, val );
383 else
384 (void) strcpy( vfilename2, filename2 ); /* same size, has to fit */
385 }
386 else
387 {
388 if ( strlen( vfilename ) + 1 + strlen( val ) < sizeof(vfilename2) )
389 {
390 char* cp;
391 (void) strcpy( vfilename2, vfilename );
392 cp = strrchr( vfilename2, '/' );
393 if ( cp == (char*) 0 )
394 {
395 cp = &vfilename2[strlen( vfilename2 )];
396 *cp = '/';
397 }
398 (void) strcpy( ++cp, val );
399 }
400 else
401 (void) strcpy( vfilename2, filename2 ); /* same size, has to fit */
402 }
403
404 read_file( vfilename2, filename2, fp2 );
405 (void) fclose( fp2 );
406 }
407
408
409 static void
410 do_echo( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
411 {
412 char* cp;
413 time_t t;
414
415 /* Prints the value of one of the include variables. Any dates are
416 ** printed subject to the currently configured timefmt. The only valid
417 ** tag is var, whose value is the name of the variable you wish to echo.
418 */
419
420 if ( strcmp( tag, "var" ) != 0 )
421 unknown_tag( filename, directive, tag );
422 else
423 {
424 if ( strcmp( val, "DOCUMENT_NAME" ) == 0 )
425 {
426 /* The current filename. */
427 (void) fputs( filename, stdout );
428 }
429 else if ( strcmp( val, "DOCUMENT_URI" ) == 0 )
430 {
431 /* The virtual path to this file (such as /~robm/foo.shtml). */
432 (void) fputs( vfilename, stdout );
433 }
434 else if ( strcmp( val, "QUERY_STRING_UNESCAPED" ) == 0 )
435 {
436 /* The unescaped version of any search query the client sent. */
437 cp = getenv( "QUERY_STRING" );
438 if ( cp != (char*) 0 )
439 (void) fputs( cp, stdout );
440 }
441 else if ( strcmp( val, "DATE_LOCAL" ) == 0 )
442 {
443 /* The current date, local time zone. */
444 t = time( (time_t*) 0 );
445 show_time( t, 0 );
446 }
447 else if ( strcmp( val, "DATE_GMT" ) == 0 )
448 {
449 /* Same as DATE_LOCAL but in Greenwich mean time. */
450 t = time( (time_t*) 0 );
451 show_time( t, 1 );
452 }
453 else if ( strcmp( val, "LAST_MODIFIED" ) == 0 )
454 {
455 /* The last modification date of the current document. */
456 if ( fstat( fileno( fp ), &sb ) >= 0 )
457 show_time( sb.st_mtime, 0 );
458 }
459 else
460 {
461 /* Try an environment variable. */
462 cp = getenv( val );
463 if ( cp == (char*) 0 )
464 unknown_value( filename, directive, tag, val );
465 else
466 (void) fputs( cp, stdout );
467 }
468 }
469 }
470
471
472 static void
473 do_fsize( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
474 {
475 char filename2[1000];
476
477 /* Prints the size of the specified file. */
478
479 if ( get_filename(
480 vfilename, filename, directive, tag, val, filename2,
481 sizeof(filename2) ) < 0 )
482 return;
483 if ( stat( filename2, &sb ) < 0 )
484 {
485 not_found2( directive, tag, filename2 );
486 return;
487 }
488 show_size( sb.st_size );
489 }
490
491
492 static void
493 do_flastmod( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
494 {
495 char filename2[1000];
496
497 /* Prints the last modification date of the specified file. */
498
499 if ( get_filename(
500 vfilename, filename, directive, tag, val, filename2,
501 sizeof(filename2) ) < 0 )
502 return;
503 if ( stat( filename2, &sb ) < 0 )
504 {
505 not_found2( directive, tag, filename2 );
506 return;
507 }
508 show_time( sb.st_mtime, 0 );
509 }
510
511
512 static void
513 parse( char* vfilename, char* filename, FILE* fp, char* str )
514 {
515 char* directive;
516 char* cp;
517 int ntags;
518 char* tags[200];
519 int dirn;
520 #define DI_CONFIG 0
521 #define DI_INCLUDE 1
522 #define DI_ECHO 2
523 #define DI_FSIZE 3
524 #define DI_FLASTMOD 4
525 int i;
526 char* val;
527
528 directive = str;
529 directive += strspn( directive, " \t\n\r" );
530
531 ntags = 0;
532 cp = directive;
533 for (;;)
534 {
535 cp = strpbrk( cp, " \t\n\r\"" );
536 if ( cp == (char*) 0 )
537 break;
538 if ( *cp == '"' )
539 {
540 cp = strpbrk( cp + 1, "\"" );
541 ++cp;
542 if ( *cp == '\0' )
543 break;
544 }
545 *cp++ = '\0';
546 cp += strspn( cp, " \t\n\r" );
547 if ( *cp == '\0' )
548 break;
549 if ( ntags < sizeof(tags)/sizeof(*tags) )
550 tags[ntags++] = cp;
551 }
552
553 if ( strcmp( directive, "config" ) == 0 )
554 dirn = DI_CONFIG;
555 else if ( strcmp( directive, "include" ) == 0 )
556 dirn = DI_INCLUDE;
557 else if ( strcmp( directive, "echo" ) == 0 )
558 dirn = DI_ECHO;
559 else if ( strcmp( directive, "fsize" ) == 0 )
560 dirn = DI_FSIZE;
561 else if ( strcmp( directive, "flastmod" ) == 0 )
562 dirn = DI_FLASTMOD;
563 else
564 {
565 unknown_directive( filename, directive );
566 return;
567 }
568
569 for ( i = 0; i < ntags; ++i )
570 {
571 if ( i > 0 )
572 putchar( ' ' );
573 val = strchr( tags[i], '=' );
574 if ( val == (char*) 0 )
575 val = "";
576 else
577 *val++ = '\0';
578 if ( *val == '"' && val[strlen( val ) - 1] == '"' )
579 {
580 val[strlen( val ) - 1] = '\0';
581 ++val;
582 }
583 switch( dirn )
584 {
585 case DI_CONFIG:
586 do_config( vfilename, filename, fp, directive, tags[i], val );
587 break;
588 case DI_INCLUDE:
589 do_include( vfilename, filename, fp, directive, tags[i], val );
590 break;
591 case DI_ECHO:
592 do_echo( vfilename, filename, fp, directive, tags[i], val );
593 break;
594 case DI_FSIZE:
595 do_fsize( vfilename, filename, fp, directive, tags[i], val );
596 break;
597 case DI_FLASTMOD:
598 do_flastmod( vfilename, filename, fp, directive, tags[i], val );
599 break;
600 }
601 }
602 }
603
604
605 static void
606 slurp( char* vfilename, char* filename, FILE* fp )
607 {
608 char buf[1000];
609 int i;
610 int state;
611 int ich;
612
613 /* Now slurp in the rest of the comment from the input file. */
614 i = 0;
615 state = ST_GROUND;
616 while ( ( ich = getc( fp ) ) != EOF )
617 {
618 switch ( state )
619 {
620 case ST_GROUND:
621 if ( ich == '-' )
622 state = ST_MINUS1;
623 break;
624 case ST_MINUS1:
625 if ( ich == '-' )
626 state = ST_MINUS2;
627 else
628 state = ST_GROUND;
629 break;
630 case ST_MINUS2:
631 if ( ich == '>' )
632 {
633 buf[i - 2] = '\0';
634 parse( vfilename, filename, fp, buf );
635 return;
636 }
637 else if ( ich != '-' )
638 state = ST_GROUND;
639 break;
640 }
641 if ( i < sizeof(buf) - 1 )
642 buf[i++] = (char) ich;
643 }
644 }
645
646
647 static void
648 read_file( char* vfilename, char* filename, FILE* fp )
649 {
650 int ich;
651 int state;
652
653 /* Copy it to output, while running a state-machine to look for
654 ** SSI directives.
655 */
656 state = ST_GROUND;
657 while ( ( ich = getc( fp ) ) != EOF )
658 {
659 switch ( state )
660 {
661 case ST_GROUND:
662 if ( ich == '<' )
663 { state = ST_LESSTHAN; continue; }
664 break;
665 case ST_LESSTHAN:
666 if ( ich == '!' )
667 { state = ST_BANG; continue; }
668 else
669 { state = ST_GROUND; putchar( '<' ); }
670 break;
671 case ST_BANG:
672 if ( ich == '-' )
673 { state = ST_MINUS1; continue; }
674 else
675 { state = ST_GROUND; (void) fputs ( "<!", stdout ); }
676 break;
677 case ST_MINUS1:
678 if ( ich == '-' )
679 { state = ST_MINUS2; continue; }
680 else
681 { state = ST_GROUND; (void) fputs ( "<!-", stdout ); }
682 break;
683 case ST_MINUS2:
684 if ( ich == '#' )
685 {
686 slurp( vfilename, filename, fp );
687 state = ST_GROUND;
688 continue;
689 }
690 else
691 { state = ST_GROUND; (void) fputs ( "<!--", stdout ); }
692 break;
693 }
694 putchar( (char) ich );
695 }
696 }
697
698
699 int
700 main( int argc, char** argv )
701 {
702 char* script_name;
703 char* path_info;
704 char* path_translated;
705 FILE* fp;
706
707 argv0 = argv[0];
708
709 /* Default formats. */
710 (void) strcpy( timefmt, "%a %b %e %T %Z %Y" );
711 sizefmt = SF_BYTES;
712
713 /* The MIME type has to be text/html. */
714 (void) fputs( "Content-type: text/html\n\n", stdout );
715
716 /* Get the name that we were run as. */
717 script_name = getenv( "SCRIPT_NAME" );
718 if ( script_name == (char*) 0 )
719 {
720 internal_error( "Couldn't get SCRIPT_NAME environment variable." );
721 exit( 1 );
722 }
723
724 /* Append the PATH_INFO, if any, to get the full URL. */
725 path_info = getenv( "PATH_INFO" );
726 if ( path_info == (char*) 0 )
727 path_info = "";
728 url = (char*) malloc( strlen( script_name ) + strlen( path_info ) + 1 );
729 if ( url == (char*) 0 )
730 {
731 internal_error( "Out of memory." );
732 exit( 1 );
733 }
734 (void) sprintf( url, "%s%s", script_name, path_info );
735
736 /* Get the name of the file to parse. */
737 path_translated = getenv( "PATH_TRANSLATED" );
738 if ( path_translated == (char*) 0 )
739 {
740 internal_error( "Couldn't get PATH_TRANSLATED environment variable." );
741 exit( 1 );
742 }
743
744 if ( ! check_filename( path_translated ) )
745 {
746 not_permitted( "initial", "PATH_TRANSLATED", path_translated );
747 exit( 1 );
748 }
749
750 /* Open it. */
751 fp = fopen( path_translated, "r" );
752 if ( fp == (FILE*) 0 )
753 {
754 not_found( path_translated );
755 exit( 1 );
756 }
757
758 /* Read and handle the file. */
759 read_file( path_info, path_translated, fp );
760
761 (void) fclose( fp );
762 exit( 0 );
763 }
Imprint / Impressum