Annotation of embedaddon/thttpd/cgi-src/ssi.c, revision 1.1.1.1
1.1 misho 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 sb;
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, &sb );
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: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>