1 /* ssi - server-side-includes CGI program
3 ** Copyright © 1995 by Jef Poskanzer <jef@mail.acme.com>.
4 ** All rights reserved.
6 ** Redistribution and use in source and binary forms, with or without
7 ** modification, are permitted provided that the following conditions
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.
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
32 #include <sys/types.h>
46 static void read_file( char* vfilename, char* filename, FILE* fp );
52 static char timefmt[100];
56 static struct stat sb;
60 internal_error( char* reason )
62 char* title = "500 Internal Error";
65 <HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
67 Something unusual went wrong during a server-side-includes request:\n\
71 </BODY></HTML>\n", title, title, reason );
76 not_found( char* filename )
78 char* title = "404 Not Found";
81 <HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
83 The requested server-side-includes filename, %s,\n\
84 does not seem to exist.\n\
85 </BODY></HTML>\n", title, title, filename );
90 not_found2( char* directive, char* tag, char* filename2 )
92 char* title = "Not Found";
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 );
103 not_permitted( char* directive, char* tag, char* val )
105 char* title = "Not Permitted";
109 The filename requested in the %s %s=%s directive\n\
110 may not be fetched.\n\
111 <HR>\n", title, directive, tag, val );
116 unknown_directive( char* filename, char* directive )
118 char* title = "Unknown Directive";
122 The requested server-side-includes filename, %s,\n\
123 tried to use an unknown directive, %s.\n\
124 <HR>\n", title, filename, directive );
129 unknown_tag( char* filename, char* directive, char* tag )
131 char* title = "Unknown Tag";
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 );
142 unknown_value( char* filename, char* directive, char* tag, char* val )
144 char* title = "Unknown Value";
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 );
155 get_filename( char* vfilename, char* filename, char* directive, char* tag, char* val, char* fn, int fnsize )
160 /* Used for the various commands that accept a file name.
161 ** These commands accept two tags:
163 ** Gives a virtual path to a document on the server.
165 ** Gives a pathname relative to the current directory. ../ cannot
166 ** be used in this pathname, nor can absolute paths be used.
168 vl = strlen( vfilename );
169 fl = strlen( filename );
170 if ( strcmp( tag, "virtual" ) == 0 )
172 if ( strstr( val, "../" ) != (char*) 0 )
174 not_permitted( directive, tag, val );
177 /* Figure out root using difference between vfilename and filename. */
179 strcmp( vfilename, &filename[fl - vl] ) != 0 )
181 if ( fl - vl + strlen( val ) >= fnsize )
183 (void) strncpy( fn, filename, fl - vl );
184 (void) strcpy( &fn[fl - vl], val );
186 else if ( strcmp( tag, "file" ) == 0 )
188 if ( val[0] == '/' || strstr( val, "../" ) != (char*) 0 )
190 not_permitted( directive, tag, val );
193 if ( fl + 1 + strlen( val ) >= fnsize )
195 (void) strcpy( fn, filename );
196 cp = strrchr( fn, '/' );
197 if ( cp == (char*) 0 )
199 cp = &fn[strlen( fn )];
202 (void) strcpy( ++cp, val );
206 unknown_tag( filename, directive, tag );
214 check_filename( char* filename )
216 static int inited = 0;
217 static char* cgi_pattern;
227 /* Get the cgi pattern. */
228 cgi_pattern = getenv( "CGI_PATTERN" );
230 if ( cgi_pattern == (char*) 0 )
231 cgi_pattern = CGI_PATTERN;
232 #endif /* CGI_PATTERN */
236 /* ../ is not permitted. */
237 if ( strstr( filename, "../" ) != (char*) 0 )
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)] == '/' ) )
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.
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, "." );
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 );
271 #endif /* AUTH_FILE */
273 /* Ensure that we are not reading a CGI file. */
274 if ( cgi_pattern != (char*) 0 && match( cgi_pattern, filename ) )
282 show_time( time_t t, int gmt )
290 tmP = localtime( &t );
291 if ( strftime( tbuf, sizeof(tbuf), timefmt, tmP ) > 0 )
292 (void) fputs( tbuf, stdout );
297 show_size( off_t size )
302 (void) printf( "%ld", (long) size ); /* spec says should have commas */
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) );
312 (void) printf( "%ldG", (long) size / (1024L*1024L*1024L) );
319 do_config( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
321 /* The config directive controls various aspects of the file parsing.
322 ** There are two valid tags:
324 ** Gives the server a new format to use when providing dates. This
325 ** is a string compatible with the strftime library call.
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.
333 if ( strcmp( tag, "timefmt" ) == 0 )
335 (void) strncpy( timefmt, val, sizeof(timefmt) - 1 );
336 timefmt[sizeof(timefmt) - 1] = '\0';
338 else if ( strcmp( tag, "sizefmt" ) == 0 )
340 if ( strcmp( val, "bytes" ) == 0 )
342 else if ( strcmp( val, "abbrev" ) == 0 )
345 unknown_value( filename, directive, tag, val );
348 unknown_tag( filename, directive, tag );
353 do_include( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
355 char vfilename2[1000];
356 char filename2[1000];
359 /* Inserts the text of another document into the parsed document. */
362 vfilename, filename, directive, tag, val, filename2,
363 sizeof(filename2) ) < 0 )
366 if ( ! check_filename( filename2 ) )
368 not_permitted( directive, tag, filename2 );
372 fp2 = fopen( filename2, "r" );
373 if ( fp2 == (FILE*) 0 )
375 not_found2( directive, tag, filename2 );
379 if ( strcmp( tag, "virtual" ) == 0 )
381 if ( strlen( val ) < sizeof( vfilename2 ) )
382 (void) strcpy( vfilename2, val );
384 (void) strcpy( vfilename2, filename2 ); /* same size, has to fit */
388 if ( strlen( vfilename ) + 1 + strlen( val ) < sizeof(vfilename2) )
391 (void) strcpy( vfilename2, vfilename );
392 cp = strrchr( vfilename2, '/' );
393 if ( cp == (char*) 0 )
395 cp = &vfilename2[strlen( vfilename2 )];
398 (void) strcpy( ++cp, val );
401 (void) strcpy( vfilename2, filename2 ); /* same size, has to fit */
404 read_file( vfilename2, filename2, fp2 );
405 (void) fclose( fp2 );
410 do_echo( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
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.
420 if ( strcmp( tag, "var" ) != 0 )
421 unknown_tag( filename, directive, tag );
424 if ( strcmp( val, "DOCUMENT_NAME" ) == 0 )
426 /* The current filename. */
427 (void) fputs( filename, stdout );
429 else if ( strcmp( val, "DOCUMENT_URI" ) == 0 )
431 /* The virtual path to this file (such as /~robm/foo.shtml). */
432 (void) fputs( vfilename, stdout );
434 else if ( strcmp( val, "QUERY_STRING_UNESCAPED" ) == 0 )
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 );
441 else if ( strcmp( val, "DATE_LOCAL" ) == 0 )
443 /* The current date, local time zone. */
444 t = time( (time_t*) 0 );
447 else if ( strcmp( val, "DATE_GMT" ) == 0 )
449 /* Same as DATE_LOCAL but in Greenwich mean time. */
450 t = time( (time_t*) 0 );
453 else if ( strcmp( val, "LAST_MODIFIED" ) == 0 )
455 /* The last modification date of the current document. */
456 if ( fstat( fileno( fp ), &sb ) >= 0 )
457 show_time( sb.st_mtime, 0 );
461 /* Try an environment variable. */
463 if ( cp == (char*) 0 )
464 unknown_value( filename, directive, tag, val );
466 (void) fputs( cp, stdout );
473 do_fsize( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
475 char filename2[1000];
477 /* Prints the size of the specified file. */
480 vfilename, filename, directive, tag, val, filename2,
481 sizeof(filename2) ) < 0 )
483 if ( stat( filename2, &sb ) < 0 )
485 not_found2( directive, tag, filename2 );
488 show_size( sb.st_size );
493 do_flastmod( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
495 char filename2[1000];
497 /* Prints the last modification date of the specified file. */
500 vfilename, filename, directive, tag, val, filename2,
501 sizeof(filename2) ) < 0 )
503 if ( stat( filename2, &sb ) < 0 )
505 not_found2( directive, tag, filename2 );
508 show_time( sb.st_mtime, 0 );
513 parse( char* vfilename, char* filename, FILE* fp, char* str )
524 #define DI_FLASTMOD 4
529 directive += strspn( directive, " \t\n\r" );
535 cp = strpbrk( cp, " \t\n\r\"" );
536 if ( cp == (char*) 0 )
540 cp = strpbrk( cp + 1, "\"" );
546 cp += strspn( cp, " \t\n\r" );
549 if ( ntags < sizeof(tags)/sizeof(*tags) )
553 if ( strcmp( directive, "config" ) == 0 )
555 else if ( strcmp( directive, "include" ) == 0 )
557 else if ( strcmp( directive, "echo" ) == 0 )
559 else if ( strcmp( directive, "fsize" ) == 0 )
561 else if ( strcmp( directive, "flastmod" ) == 0 )
565 unknown_directive( filename, directive );
569 for ( i = 0; i < ntags; ++i )
573 val = strchr( tags[i], '=' );
574 if ( val == (char*) 0 )
578 if ( *val == '"' && val[strlen( val ) - 1] == '"' )
580 val[strlen( val ) - 1] = '\0';
586 do_config( vfilename, filename, fp, directive, tags[i], val );
589 do_include( vfilename, filename, fp, directive, tags[i], val );
592 do_echo( vfilename, filename, fp, directive, tags[i], val );
595 do_fsize( vfilename, filename, fp, directive, tags[i], val );
598 do_flastmod( vfilename, filename, fp, directive, tags[i], val );
606 slurp( char* vfilename, char* filename, FILE* fp )
613 /* Now slurp in the rest of the comment from the input file. */
616 while ( ( ich = getc( fp ) ) != EOF )
634 parse( vfilename, filename, fp, buf );
637 else if ( ich != '-' )
641 if ( i < sizeof(buf) - 1 )
642 buf[i++] = (char) ich;
648 read_file( char* vfilename, char* filename, FILE* fp )
653 /* Copy it to output, while running a state-machine to look for
657 while ( ( ich = getc( fp ) ) != EOF )
663 { state = ST_LESSTHAN; continue; }
667 { state = ST_BANG; continue; }
669 { state = ST_GROUND; putchar( '<' ); }
673 { state = ST_MINUS1; continue; }
675 { state = ST_GROUND; (void) fputs ( "<!", stdout ); }
679 { state = ST_MINUS2; continue; }
681 { state = ST_GROUND; (void) fputs ( "<!-", stdout ); }
686 slurp( vfilename, filename, fp );
691 { state = ST_GROUND; (void) fputs ( "<!--", stdout ); }
694 putchar( (char) ich );
700 main( int argc, char** argv )
704 char* path_translated;
709 /* Default formats. */
710 (void) strcpy( timefmt, "%a %b %e %T %Z %Y" );
713 /* The MIME type has to be text/html. */
714 (void) fputs( "Content-type: text/html\n\n", stdout );
716 /* Get the name that we were run as. */
717 script_name = getenv( "SCRIPT_NAME" );
718 if ( script_name == (char*) 0 )
720 internal_error( "Couldn't get SCRIPT_NAME environment variable." );
724 /* Append the PATH_INFO, if any, to get the full URL. */
725 path_info = getenv( "PATH_INFO" );
726 if ( path_info == (char*) 0 )
728 url = (char*) malloc( strlen( script_name ) + strlen( path_info ) + 1 );
729 if ( url == (char*) 0 )
731 internal_error( "Out of memory." );
734 (void) sprintf( url, "%s%s", script_name, path_info );
736 /* Get the name of the file to parse. */
737 path_translated = getenv( "PATH_TRANSLATED" );
738 if ( path_translated == (char*) 0 )
740 internal_error( "Couldn't get PATH_TRANSLATED environment variable." );
744 if ( ! check_filename( path_translated ) )
746 not_permitted( "initial", "PATH_TRANSLATED", path_translated );
751 fp = fopen( path_translated, "r" );
752 if ( fp == (FILE*) 0 )
754 not_found( path_translated );
758 /* Read and handle the file. */
759 read_file( path_info, path_translated, fp );