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
internal_error(char * reason)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
not_found(char * filename)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
not_found2(char * directive,char * tag,char * filename2)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
not_permitted(char * directive,char * tag,char * val)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
unknown_directive(char * filename,char * directive)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
unknown_tag(char * filename,char * directive,char * tag)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
unknown_value(char * filename,char * directive,char * tag,char * val)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
get_filename(char * vfilename,char * filename,char * directive,char * tag,char * val,char * fn,int fnsize)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
check_filename(char * filename)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
show_time(time_t t,int gmt)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
show_size(off_t size)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
do_config(char * vfilename,char * filename,FILE * fp,char * directive,char * tag,char * val)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
do_include(char * vfilename,char * filename,FILE * fp,char * directive,char * tag,char * val)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
do_echo(char * vfilename,char * filename,FILE * fp,char * directive,char * tag,char * val)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
do_fsize(char * vfilename,char * filename,FILE * fp,char * directive,char * tag,char * val)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
do_flastmod(char * vfilename,char * filename,FILE * fp,char * directive,char * tag,char * val)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
parse(char * vfilename,char * filename,FILE * fp,char * str)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
slurp(char * vfilename,char * filename,FILE * fp)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
read_file(char * vfilename,char * filename,FILE * fp)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
main(int argc,char ** argv)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     }
764