Corrected documentation
[CGIscriptor.git] / CGIservlet.pl
blob8e3e0c0b749e0d001276074f0450d0a7aace59a9
1 #! /usr/bin/perl
3 # Put the full path to perl on the first line, run the program with
4 # `perl CGIservlet`, or put a symbolic link to perl in
5 # the startup directory if you need a special version of perl.
7 if(grep(/\-\-help/i, @ARGV))
9 print << 'ENDOFHELPTEXT';
10 # CGIservlet:
11 # A HTTPd "connector" for running CGI scripts on unix systems as WWW
12 # accessible Web sites. The servlet starts a true HTTP daemon that channels
13 # HTTP requests to forked daughter processes. CGIservlet.pl is NOT a
14 # full fledged server. Moreover, this servlet is definitely NOT intended
15 # as a replacement of a real server (e.g., Apache). It's design goal was
16 # SIMPLICITY, and not mileage.
18 # Note that a HTTP server can be accessed on your local machine WITHOUT
19 # internet access (but WITH a DNS?):
20 # use "http://localhost[:port]/[path]" or "http://127.0.0.1[:port]/[path]"
21 # as the URL. It is also easy to restrict access to the servlet to localhost
22 # users (i.e., the computer running the servlet).
24 # Suggested uses:
25 # - A testbed for CGI-scripts and document-trees outside the primary server.
26 # When developing new scripts and services, you don't want to mess up your
27 # current Web-site. CGIservlet is an easy way to start a temporary (private)
28 # server. CGIservlet allows to test separate HTTP server components, e.g.,
29 # user authentication, in isolation.
31 # - A special purpose temporary server (WWW everywhere/anytime).
32 # We run identification and other experiments over the inter-/intra-net using
33 # CGI-scripts. This means a lot of development and changes and only little
34 # actual run-time. The people doing this do not want "scripting" access to our
35 # departmental server with all its restrictions and security. So we need a
36 # small, lightweigth, easy-to-configure server that can be run by each
37 # investigator on her own account (and risk).
39 # - Interactive WWW presentations.
40 # Not everyone is content with the features of "standard" office presentation
41 # software. HTML and its associated browsers are an alternative (especially
42 # under Linux). However, you need a server to realize the full interactive
43 # nature of the WWW. CGIservlet with the necessary scripts can be run from
44 # a floppie (a Web server in 100 kB). The CGIservlet can actually run a
45 # (small) web site from RAM, without disk access (if you DO NOT use the
46 # 2>pid.log redirection on startup).
47 # With the "localhost" or "127.0.0.1" id in your browser you can use the
48 # servlet standalone.
50 # When the servlet is started with the -r option, only requests from "localhost"
51 # or "127.0.0.1" are accepted (default) or from addresses indicated after the
52 # -r switch.
54 # Running demo's and more information can be found at
55 # http://www.fon.hum.uva.nl/rob/OSS/OSS.html
58 ############################################################################
60 # Changes (document ALL changes with date, name and email here):
62 # 05 Apr 2013 - Renamed COOKIE_JAR into HTTP_COOKIE
63 # 28 Mar 2013 - Version 1.4.
64 # 18 Jun 2012 - Added --env, %UserEnv user defined ENV variables. Freeze %ENV.
65 # 06 Jun 2012 - Added HTTP Cookie string to an Environment variable: COOKIE_JAR
66 # 29 May 2012 - Added .log -> / to @RegAliasTranslation, blocks attempts to read
67 # log files.
68 # 22 May 2012 - Blocked "hidden" files and directories starting with "." in
69 # default @RegAliasTranslation. Removed obsolete CVS reference.
70 # 21 May 2012 - Inserted the -m[emory] switch for loading and serving from RAM.
71 # The option was mentioned in the manual, but the cli switch was
72 # never added.
73 # 22 Jul 2003 - Plain output using binary print io. `cat ...`
74 # 22 Jul 2003 - Added 'use CGI::Carp qw(fatalsToBrowser);' line
75 # for debugging. Standard this is commented out
76 # for security reasons (suggested by Jochen_Hayek@ACM.org).
77 # 22 Jul 2003 - Added error checking to doarg (suggested by Jochen_Hayek@ACM.org)
78 # 22 Jul 2003 - Removed SERVER_PORT from HTTP_HOST (Bug found by Jochen_Hayek@ACM.org)
79 # 22 Jul 2003 - Updated documentation. Added CGIservlet directory
80 # to the search path of CGIservletSETUP.pl
81 # 20 May 2003 - Made sure recycled (double) pid's do not mess up the
82 # @brood list and added a --help switch.
83 # 20 May 2003 - Added a maximum running time for child processes
84 # with command line switch -xterm.
85 # 15 Jan 2002 - Version 1.3
86 # 19 Oct 2001 - Included browsing of directories and a new -s
87 # security switch. With security toggled of
88 # directories can be browsed and all mime-types
89 # are served, either as 'text/plain' or as
90 # 'application/octed-stream'.
91 # 18 May 2001 - Added some HTTP HTTP lines.
92 # 13 Jun 2000 - Included the possibility to add POST request
93 # to GET query-strings (and change the request
94 # method). The -l ($Maxlength) maximum length
95 # option now covers POST requests too.
96 # 8 Dec 1999 - Included hooks for compression when running from RAM.
97 # 2 Dec 1999 - Autoflush enabled.
98 # 2 Dec 1999 - Allow running a Web Site from RAM.
99 # 2 Dec 1999 - Changed the behavior of CGIservletSETUP. CGIservlet
100 # will eval ALL setup files, the one in the CGIscriptor
101 # subdirectory (if any) AND the one in the current
102 # directory. (also added a close(SETUP) command)
103 # 26 Nov 1999 - Added some minimal security for 'automatic', out of
104 # the box installation.
105 # 26 Nov 1999 - Made the text/osshell mime-type functional (i.e.,
106 # without any scripts, implement a dynamic web server)
107 # Linited to '.cgi' extension.
108 # 26 Nov 1999 - Added aliasing of URL paths, both one-to-one lookups
109 # and full regular expression, i.e., $Path =~ s/.../.../g
110 # replace commands
111 # 28 Sep 1999 - Made all client supplied HTTP parameter names lowercase
112 # to handle inconsistencies in case use.
113 # 29 Jul 1999 - Allowed for a SETUP configuration file 'CGIservletSETUP.pl'.
114 # Use $beginarg from the 'CGIscriptor/' directory if it exists.
115 # (R.J.J.H.vanSon@gmail.com)
118 ############################################################################
120 # Known bugs
122 # 23 Mar 2000 - An odd server side network error is reported by Netscape
123 # when a Post is initiated from a Javascript Submit of a
124 # <FORM>. This was found on Red Hat 6.1 Linux with perl 5.00503,
125 # 5.00503 and 5.6.0. But not on IRIX or Red Hat 5.0, 7.x.
127 ############################################################################
130 # Inner workings:
131 # Whenever an HTTP request is received, the specified CGI script is
132 # started inside a child process as if it was inside a real server (e.g.,
133 # Apache). The evironment variables are set more or less as in Apache.
134 # Note that CGIservlet only uses a SINGLE script for ALL requests.
135 # No attemps for security are made, it is the script's responsibility to
136 # check access rights and the validity of the request.
137 # When no scripts are given, CGIservlet runs as a bare bone WWW server
138 # configurable to execute scripts (the default setting is as a
139 # STATIC server).
141 # Author and copyright (c) :
142 # Rob van Son
143 # email:
144 # R.J.J.H.vanSon@gmail.com
145 # r.v.son@nki.nl
146 # NKI/AVL Amsterdam
148 # copying freely from the mhttpd server by Jerry LeVan (levan@eagle.eku.edu)
149 # Date: July 22, 2012
150 # Version:1.4
151 # Env: Perl 5.002 and later
154 ################################################################################
156 # LICENSE #
158 # This program is free software; you can redistribute it and/or #
159 # modify it under the terms of the GNU General Public License #
160 # as published by the Free Software Foundation; either version 2 #
161 # of the License, or (at your option) any later version. #
163 # This program is distributed in the hope that it will be useful, #
164 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
165 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
166 # GNU General Public License for more details. #
168 # You should have received a copy of the GNU General Public License #
169 # along with this program; if not, write to the Free Software #
170 # Foundation, Inc., 59 Temple Place - Suite 330, #
171 # Boston, MA 02111-1307, USA. #
173 ################################################################################
175 # Note: CGIservlet.pl was directly inspired by Jerry LeVan's
176 # (levan@eagle.eku.edu) simple mhttpd server which again was
177 # inspired by work of others. CGIservlet is used as a bare bones
178 # socket server for a single CGI script at a time.
180 # Use: CGIservlet.pl -<switch> <argument> 2>pid.log & (sh/bash)
181 # CGIservlet.pl -<switch> <argument> >&pid.log & (csh)
183 # The servlet prints out pid and port number on STDERR. It is
184 # adviced to store these in a separate file (this will become the
185 # error log).
186 # NOTE: When running CGIservlet from a Memmory Image (i.e. RAM),
187 # do NOT redirect the error output to a file, but use something
188 # like MAILTO or /dev/null!
190 # Stop: sh pid.log (kills the server process)
192 # The first line in the file that receives STDERR output is a command
193 # to stop CGIservlet.
195 # examples:
196 # CGIservlet.pl -p 2345 -d /cgi-bin/CGIscriptor.pl -t /WWW 2>pid.log &
197 # CGIservlet.pl -p 8080 -b 'require "CGIscriptor.pl";' -t $PWD -e \
198 # 'Handle_Request();' 2>pid.log &
200 # The following example settings implement a static WWW server using 'cat'
201 # (and prohibiting Queries):
202 # -p 8008
203 # -t `pwd`
204 # -b ''
205 # -e
206 # '$ENV{QUERY_STRING}="";$ENV{PATH_INFO}=~/\.([\w]+)$/; "Content-type: ".$mimeType{uc($1)}."\n\n";'
207 # -d 'cat -u -s'
208 # -w '/index.html'
209 # -c 32
210 # -l 512
212 # This is identical to the (static) behaviour of CGIservlet when
213 # -e '' -d '' -x '' is used.
214 # The CGIservlet command should be run from the intended server-root directory.
216 # Another setting will use a package 'CGIscriptor.pl' with a function
217 # 'HandleRequest()' to implement an interactive WWW server with inline
218 # Perl scripting:
219 # -p 8080
220 # -t `pwd`
221 # -b 'require "CGIscriptor.pl";'
222 # -e 'HandleRequest();'
223 # -d ''
224 # -w '/index.html'
225 # -c 32
226 # -l 32767
228 # Look below or in the CGIservletSETUP.pl file for the current default
229 # settings.
232 # ###############################################################################
234 # There are many switches to tailor the workings of CGIservlet.pl.
235 # Some are fairly esoteric and you should only look for them if you
236 # need something special urgently. When building a Web site,
237 # the specific options you need will "suggest" themselves (e.g., port
238 # number, script, or server-root directory). Most default settings
239 # should work fine.
241 # You can add your own configuration in a file called
242 # 'CGIservletSETUP.pl'. This file will be executed ("eval"-ed)
243 # after the default setup, but before the command line options take
244 # effect. CGIservlet looks for the SETUP file in the startup directory
245 # and in the CGIscriptor subdirectory.
246 # (Note that the $beginarg variable is evaluated AFTER the setup file).
248 # In any case, it is best to change the default settings instead of
249 # using the option switches. All defaults are put in a single block.
251 # switches and arguments:
252 # Realy important
253 # -p[ort] port number
254 # For example -p 2345
255 # Obviously the port CGIservlet listenes to. Suggested Default: -p 8008
257 # -a[lias] Alias1 RealURL1 ...
258 # For example -a '/Stimulus.aifc' '/catAIFC.xmr'
259 # Replaces the given Alias URL path by its real URL path. Accepts full
260 # regular expressions too (identified by NON-URL characters).
261 # That is, on each request it performs (in order):
262 # if($AliasTranslation{$Path})
263 # {
264 # $Path = $AliasTranslation{$Path};
266 # elsif(@RegAliasTranslation)
267 # {
268 # my $i;
269 # for($i=0; $i<scalar(@RegAliasTranslation); ++$i)
270 # {
271 # my $Alias = $RegAliasTranslation[$i];
272 # my $RealURL = $RegURLTranslation[$i];
273 # last if ($Path =~ s#$Alias#$RealURL#g);
274 # };
275 # };
276 # The effects can be quite drastic, so be
277 # carefull. Note also, that entering many Regular Expression
278 # aliases could slow down your servlet. Checking stops after
279 # the first match.
280 # Full regular expression alias translations are done in the
281 # order given! They are recognized as Aliases containing
282 # regexp's (i.e., non-URL) operator characters like '^' and
283 # '$'.
284 # Note: The command line is NOT a good place for entering
285 # Aliases, change the code below or add aliases to
286 # CGIservletSETUP.pl.
288 # --help
289 # Prints the manual
291 # Script related
292 # -b[egin] perl commands
293 # For example -b 'require "CGIscriptor.pl";' or
294 # 'require "/WWW/cgi-bin/XMLelement.pl";'
295 # Perl commands evaluated at server startup
297 # -d[o] perl script file
298 # For example -d '/WWW/cgi-bin/CGIscriptor.pl'
299 # The actual CGI-script started as a perl {do "scriptfile"} command.
300 # The PATH_INFO and the QUERY are pushed on @ARGV.
302 # -x shell command
303 # -qx shell command
304 # -exec shell command
305 # OS shell script or command, e.g., -x 'CGIscriptor.pl' or
306 # -x '/WWW/cgi-bin/my-script'
307 # The actual CGI-script started as `my-script \'$Path\' \'$QueryString\'`.
308 # -qx and -exec[ute] are aliases of -x. For security reasons, Paths or
309 # queries containing '-quotes are rejected.
311 # -e[val] perl commands
312 # For example -e 'Handle_Request();'
313 # The argument is evaluated as perl code. The actual CGI-script
314 # can be loaded once with -b 'require module.pm' and you only have to
315 # call the central function(s).
317 # WWW-tree related
318 # -t[extroot] path
319 # For example -t "$PWD" or -t "/WWW/documents"
320 # The root of the server hierachy. Defaults to the working directory
321 # at startup time (`pwd`)
323 # -w[elcome] filepath
324 # For example -w "/index.html" (default)
325 # The default welcome page used when no path is entered. Note that
326 # this path can point to anything (or nothing real at all).
328 # Security related
329 # The following arguments supply some rudimentary security. It is the
330 # responsibility of the script to ensure that the requests are indeed
331 # "legal".
333 # -c[hildren] maximum child processes
334 # For example -c 32
335 # The maximum number of subprocesses started. If there are more requests,
336 # the oldest requests are "killed". This should take care of "zombie"
337 # processes and server overloading. Note that new requests will be
338 # serviced irrespective of the success of killing it's older siblings.
340 # -xtime maximum running time of a child
341 # For example -xtime 36000
342 # The maximum time a child may run in seconds. After a new request has
343 # been servised, all children that have run for longer than this time
344 # will be killed. This stops runaway processes, often connected to
345 # web-crawlers.
347 # -l[ength] maximum length of HTTP request in bytes
348 # For example -l 32768
349 # This prevents overloading the server with enormous queries. Reading of
350 # requests simply stops when this limit is reached. This DOES affect
351 # POST requests. If the combined length of the COMPLETE HTTP request,
352 # including headers, exceeds this limit, the whole request is dropped.
354 # -r[estrict] [Remote-address [Remote-host]]
355 # For example -r 127.0.0.1 (default of -r)
356 # A space separated list of client IP addresses and/or domain names that
357 # should be serviced. Default, i.e., '-r' without any addresses or domain
358 # names, is the localhost IP address '127.0.0.1'.
359 # When using CGIservlet for local purposes only (e.g., development or a
360 # presentation), it would be unsafe to allow others to access the servlet.
361 # If -r is used (or the corresponding @RemoteAddr or @RemoteHost lists are
362 # filled in the code below), all requests from clients whose Remote-address
363 # or Remote-host do not match the indicated addresses will be rejected.
364 # Partial addresses and domain names are allowed. Matching is done according
365 # to Remote-addr =~ /^\Q$pattern\E/ (front to back) and
366 # Remote-host =~ /\Q$pattern\E$/ (back to front)
368 # --env name=value,name=value
369 # Watch double dash. Define $ENV{name}=value for every pair. These are
370 # internally stored in %UserEnv, eg, $UserEnv{name}=value; This is set anew
371 # in the Child with every request. That is, Changes in %ENV are not stored.
373 # --USEFAT
374 # Watch double dash. Define $ENV{USEFAT}=1. This is used to signal that
375 # runs from an MS FAT file system without file permissions.
377 # -s[ecure]
378 # No arguments.
379 # A toggle switch that blocks all access to files with undefined
380 # mime-types (or to serve ascii files as "text/plain"), and blocking directory
381 # browsing. Defaults to blocking what is not explicitely allowed.
383 # -m[emory]
384 # No arguments.
385 # Reads complete Web site into memory and runs from this image.
386 # Set $UseRAMimage = 1; to activate memory-only running.
387 # Under certain circumstance, this can improve security.
388 # Note, however, that running osshellscripts from this image
389 # makes any "security" related claims very shaky.
391 # Speedup
392 # -n[oname]
393 # No arguments.
394 # Retrieving the domain name of the Client (i.e., Remote-host) is a
395 # very slow process and normally useless. To skip it, enter this
396 # option. Note that you cannot use '-r Remote-host' anymore after
397 # you enter -n, only IP addresses will work.
399 # Configuration with the CGIservletSETUP.pl file
401 # You can add your own configuration in a file
402 # called 'CGIservletSETUP.pl'. This file will be executed ("eval"-ed)
403 # after the default setup, but before the command line options take
404 # effect. CGIservlet looks for the SETUP file in the startup directory
405 # and in the CGIservlet and CGIscriptor subdirectories.
406 # (Note that the $beginarg variable is evaluated even later).
408 # Changing POST to GET requests
410 # CGIservlet normally only handles requests with the GET method. Processing
411 # the input from POST requests is left to the reading application. POST
412 # requests add some extra complexity to processing requests. Sometimes,
413 # the reading application doesn't handle POST requests. CGIservlet
414 # already has to manage the HTTP request. Therefore, it can easily
415 # handle the POST request. If the variable $POSTtoGET is set to any
416 # non-false value, the content of whole POST request is added to the
417 # QUERY_STRING environment variable (preceeded by a '&' if necessary).
418 # The content-length is set to 0. If $POSTtoGET equals 'GET', the method
419 # will also be changed to 'GET'.
421 # remarks:
422 # All of the arguments of -d, -e, and -x are processed sequentially
423 # in this order. This might not be what you want so you should be
424 # carefull when using multiple executable arguments.
425 # If none of the executable arguments is DEFINED (i.e., they are entered
426 # as -d '' -e '' -x ''), each request is treated as a simple
427 # text-retrieval. THIS CAN BE A SECURITY RISK!
429 # The wiring of an interactive web-server, which also calls shell
430 # scripts with the extension '.cgi', is in place. You can
431 # "activate" it by changing the "my $ExecuteOSshell = 0;" line to
432 # "my $ExecuteOSshell = 1;".
433 # If you have trouble doing this, it might be a good idea
434 # to reconsider using a dynamic web server. Executing shell
435 # scripts inside a web server is a rather dangerous practise.
437 # CGIservlet can run its "standard" web server from memory.
438 # At startup, all files are read into a hash table. Upon
439 # request, the contents of the file are placed in the
440 # environment variable: CGI_FILE_CONTENTS.
441 # No further disk access is necessary. This means that:
442 # 1 CGIservlet can run a WWW site from a removable disk,
443 # e.g., a floppy
444 # 2 The web servlet can run without any read or write privilege.
445 # 3 The integrity of the Web-site contents can be secured at the
446 # level you want
448 # To compres the memory (RAM) immage, you should hook the
449 # compression function to
450 # $CompressRAMimage = sub { return shift;};
451 # and the decompression function to
452 # $DecompressRAMimage = sub { return shift;};
455 ENDOFHELPTEXT
456 exit;
458 ###################################################################################
460 require 5.002;
461 use strict; # Should realy be used!
462 use Socket;
463 use Carp; # could come in handy (can be missed, I think)
465 # For debugging: uncommenting the use-line below will send
466 # nicely formanted output to the client. However, it is
467 # generally not a good idea to enable clients to test your
468 # scripts and look for holes (SECURITY).
469 # use CGI::Carp qw(fatalsToBrowser);
471 $| = 1; # Autoflush (i'm not sure whether this is usefull)
473 my $version = "1.301";
474 my $program = "CGIservlet.pl";
476 ##################################################################
478 # print some information to STDERR, e.g., the process number #
480 ##################################################################
481 sub logmsg { print STDERR "kill -KILL $$;exit;\n", # Stop CGIservlet
482 "$0 $$: @_ at ", scalar localtime, "\n" }
484 ############################################################
486 # Parse arguments (you can define DEFAULT VALUES here) #
488 ############################################################
490 my $port = 8008; # The port number
492 # Add POST requests to the QUERY_STRING, change method to
493 # GET if the value is 'GET'
494 my $POSTtoGET = 0; # Add POST requests to the query string
496 # (Fast) direct translation of full URL paths
497 my %AliasTranslation = (); # Alias => RealURL pairs (ONLY paths)
498 # Regular expression alias translation, in order of application
499 # (this can be quite slow)
500 my @RegAliasTranslation = ('^(\..*|.*/\..*)$','\.htm$', '^.*\.log$'); # Full regular expression alias/url pairs: URL
501 my @RegURLTranslation = ('/','.html', '/'); # Full regular expression alias/url pairs: PATH
503 my $textroot = $ENV{'PWD'} || `pwd`; # current working directory
504 chomp($textroot); # Remove nasty newline, if present
505 my $doarg = ''; # do "filename",
507 my $beginarg = ''; # eval($Argument) at the start of the program
508 my $evalarg = ''; # eval($Argument) for each request
509 my $execarg = ''; # execute `command \'$textroot$Path\' \'$QueryString\'`
511 my $welcome = '/index.html'; # Default path
513 # Rudimentary security, overflow detection
514 my $MaxBrood = 32; # Maximum number of running children
515 my $MaxTime = 36000; # Maximum time a child may run in seconds
516 my $MaxLength = 2**15; # Maximum Request Length
517 my $UseFAT = 0; # Run on FAT systems (Windows) such as thumb drives (default: NO)
518 my $Secure = 1; # Block browsing directories and text files or not
519 my %UserEnv = ();
521 # If one of the following lists contains any client addresses or names, all others are
522 # blocked (be carefull, your site will be inaccessible if you misspell them).
523 my @RemoteHost = (); # Accepted Hosts, suggest: localhost
524 my @RemoteAddr = (); # Accepted IP addresses, suggest: @RemoteAddr=('127.0.0.1')
525 my $DefaultRemoteAddr = '127.0.0.1'; # default, use localhost IP address
526 my $NONAME = 0; # if 1, do NOT ask for REMOTE_HOST (faster)
528 # Initialization ready
529 my %FrozenEnv = %ENV; # Freeze %ENV
531 # Store the whole Web Site in a hash table and use this RAM memory image (if non-zero)
532 my $UseRAMimage = 0;
533 # Empty function handlers for data compression
534 # In general, these must be redefined in the $beginarg
535 my $CompressRAMimage = sub { return shift;};
536 my $DecompressRAMimage = sub { return shift;};
538 # Execute shell CGI scripts when no -d, -e, or -x are supplied
539 my $ExecuteOSshell = 0; # Do you REALY want this? It is dangerous
541 #################################################################
543 # Configure CGIservlet with a setup file (overides the #
544 # default settings, but not the command line options). #
545 # Note that, if it exists, the setup file in the CGIscriptor #
546 # subdirectory is processed EVEN if there is a SETUP file #
547 # in the current directory. #
549 #################################################################
550 # There exists a CGIservlet subdirectory and it contains
551 # a CGIservletSETUP.pl file
552 if((-e './CGIservlet/CGIservletSETUP.pl') &&
553 open(SETUP, '<./CGIservlet/CGIservletSETUP.pl'))
555 # Get the setup code
556 my $SetupCode = join("", <SETUP>);
557 # 'Eval' is used to ensure that the values are entered in the current
558 # package (contrary to what 'do' and 'require' do).
559 (eval $SetupCode) || die "$! $@\n";
560 close(SETUP);
562 # There exists a CGIscriptor subdirectory and it contains
563 # a CGIservletSETUP.pl file
564 if((-e './CGIscriptor/CGIservletSETUP.pl') &&
565 open(SETUP, '<./CGIscriptor/CGIservletSETUP.pl'))
567 # Get the setup code
568 my $SetupCode = join("", <SETUP>);
569 # 'Eval' is used to ensure that the values are entered in the current
570 # package (contrary to what 'do' and 'require' do).
571 (eval $SetupCode) || die "$! $@\n";
572 close(SETUP);
574 # There is a CGIservletSETUP.pl file in the current directory
575 if((-e './CGIservletSETUP.pl') &&
576 open(SETUP, '<./CGIservletSETUP.pl'))
578 # Get the setup code
579 my $SetupCode = join("", <SETUP>);
580 # 'Eval' is used to ensure that the values are entered in the current
581 # package (contrary to what 'do' and 'require' do).
582 (eval $SetupCode) || die "-e $SetupCode: $! $@\n";
583 close(SETUP);
586 ######################################
588 # process arguments and defaults #
590 ######################################
592 while ($_ = shift(@ARGV))
594 # With switches
595 if(/\-p/is) # Port
597 $port = shift(@ARGV);
599 elsif(/\-d/is) # Do
601 $doarg = shift(@ARGV);
603 elsif(/\-(x|qx|exec)/is) # Execute
605 $execarg = shift(@ARGV);
607 elsif(/\-b/is) # Begin
609 $beginarg = shift(@ARGV);
611 elsif(/^\-e/is) # Evaluate
613 $evalarg = shift(@ARGV);
615 elsif(/\-t/is) # Textroot
617 $textroot = shift(@ARGV);
619 elsif(/\-w/is) # Default welcome page
621 $welcome = shift(@ARGV);
623 elsif(/\-c/is) # Maximum Children
625 $MaxBrood = shift(@ARGV) || $MaxBrood;
627 elsif(/\-xtime/is) # Maximum running time
629 $MaxTime = shift(@ARGV) || $MaxTime;
631 elsif(/\-l/is) # Maximum Length
633 $MaxLength = shift(@ARGV) || $MaxLength;
635 elsif(/\-m/is) # Run from RAM
637 $UseRAMimage = 1;
639 elsif(/\-a/is) # Aliases
641 while(@ARGV && $ARGV[0] !~ /^\-/) # while not a parameter
643 my $Alias = shift(@ARGV);
644 my $RealURL = $ARGV[0] !~ /^\-/ ? shift(@ARGV) : "";
645 next unless $Alias && $RealURL;
646 # Store the alias
647 # Simple straight translations
648 unless($Alias =~ m/[\$\Q^*&@!\?(){}[];:\E]/)
650 $AliasTranslation{$Alias} = $RealURL;
652 else # Full regular expressions
654 push(@RegAliasTranslation, $Alias);
655 push(@RegURLTranslation, $RealURL);
660 elsif(/\-r/is) # Remote host or address
662 while(@ARGV && $ARGV[0] !~ /^\-/) # while not a parameter
664 my $Remote = shift(@ARGV);
665 if($Remote =~ /[\d\.]+/) # A host IP address
667 push(@RemoteAddr, $Remote);
669 else # A host domain name, less secure
671 push(@RemoteHost, $Remote);
675 # Use the default Remote Host (Client) IP address (e.g., localhost)
676 # if no addresses or domain names are entered.
677 push(@RemoteAddr, $DefaultRemoteAddr) unless @RemoteAddr || @RemoteHost;
679 elsif(/^\-\-env/is) # Environment variables
681 while(@ARGV && $ARGV[0] !~ /^\-/) # while not a parameter
683 my $envlist = shift(@ARGV);
684 foreach my $envstring (split(',', $envlist))
686 my ($name, $value) = split('=', $envstring);
687 next unless $name;
688 # Store the Environment variable
689 $UserEnv{$name} = $value;
693 elsif(/\-s/is) # Secure or not
695 $Secure = !$Secure; # Toggle blocking directory browsing and ASCII file access
697 elsif(/\-n/is) # Do NOT extract Remote host
699 $NONAME = 1;
701 elsif(/\-\-USEFAT/is) # Set USEFAT environment variable
703 $UseFAT = 1;
705 else # perform unreliable magick without switches
707 if(/^[0-9]+$/ && $_ > 1024) # A (large) number must be a port
709 $port = $_;
711 elsif(-T && /\.pl$/) # Text file with extension .pl is a Perl file
713 $doarg = $_;
715 elsif(-T && /\.pm$/) # Text file with extension .pm is a Perl module file
717 $beginarg = $_;
719 elsif(-x) # Executables can be executed
721 $execarg = $_;
723 elsif(-d) # A directory can only be the root
725 $textroot = $_;
727 elsif(-T && /^\// && /\.html$/) # An html file path is the default path
729 $welcome = $_;
731 elsif(-T) # A text file is something to do
733 $doarg = $_;
735 elsif(/[\s\{\`\[\@\%]/) # I give up, just try it
737 $evalarg = shift(@ARGV);
742 ################################################
744 # All argument values are known. #
745 # Initialize environment variables. #
746 # (should be accessible to eval($beginarg)) #
748 ################################################
750 # Initialize %ENV
751 $ENV{'SERVER_SOFTWARE'} = "$program $version";
752 $ENV{'GATEWAY_INTERFACE'} = "CGI/1.1";
753 $ENV{'SERVER_PORT'} = "$port";
754 $ENV{'CGI_HOME'} = $textroot;
755 $ENV{'SERVER_ROOT'} = $textroot; # Server Root Directory
756 $ENV{'DOCUMENT_ROOT'} = $textroot; # Server Root Directory
757 $ENV{'SCRIPT_NAME'} = $doarg.$execarg.$evalarg; # Combine executable arguments
758 $ENV{'USEFAT'} = $UseFAT; # Flag use of FAT filesystem
760 $FrozenEnv{'SERVER_SOFTWARE'} = $ENV{'SERVER_SOFTWARE'};
761 $FrozenEnv{'GATEWAY_INTERFACE'} = $ENV{'GATEWAY_INTERFACE'};
762 $FrozenEnv{'SERVER_PORT'} = $ENV{'SERVER_PORT'};
763 $FrozenEnv{'CGI_HOME'} = $ENV{'CGI_HOME'};
764 $FrozenEnv{'SERVER_ROOT'} = $ENV{'SERVER_ROOT'}; # Server Root Directory
765 $FrozenEnv{'DOCUMENT_ROOT'} = $ENV{'DOCUMENT_ROOT'}; # Server Root Directory
766 $FrozenEnv{'SCRIPT_NAME'} = $ENV{'SCRIPT_NAME'}; # Combine executable arguments
768 ################################################
770 # The initial argument should be evaluated #
772 ################################################
774 eval($beginarg) if $beginarg;
776 ################################################
778 # The initial argument has been evaluated #
780 ################################################
782 # Socket related code
783 my $proto = getprotobyname('tcp');
784 $port = $1 if $port =~ /(\d+)/; # untaint port number
786 socket(Server, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
787 setsockopt(Server, &SOL_SOCKET, &SO_REUSEADDR,
788 pack("l", 1)) || die "setsockopt: $!";
789 bind(Server, sockaddr_in($port, INADDR_ANY)) || die "bind: $!";
790 listen(Server,SOMAXCONN) || die "listen: $!";
793 # Report start of server
794 logmsg "server started on port $port";
796 # Set up SIG vector (every signal will kill the process that receives it)
797 $SIG{CHLD} = 'IGNORE';
798 $SIG{'KILL'} = "SigHandler";
799 $SIG{'TERM'} = "SigHandler";
800 $SIG{'QUIT'} = "SigHandler";
801 $SIG{'HUP'} = "SigHandler";
803 # Define text mime types served if no scripts are defined
804 # Note that the "text/osshell" mime-type is executed by CGIservlet ITSELF!
805 # You should remove it if you don't want that!
806 my %mimeType = (
807 'HTML'=> "text/html",
808 'TXT' => "text/plain",
809 'PL' => "text/plain", # This is incorrect, of course
810 'JPG' => "image/jpeg",
811 'JPEG' => "image/jpeg",
812 'GIF' => "image/gif",
813 'AU' => "audio/basic",
814 'AIF' => "audio/aiff",
815 'AIFC' => "audio/aiff",
816 'AIFF' => "audio/aiff",
817 'GZ' => "application/gzip",
818 'TGZ' => "application/tar",
819 #'CGI' => "text/osshell", # Executes SERVER side shell scripts, HIGHLY DANGEROUS
820 'WAV' => "audio/wav",
821 'OGG' => "audio/x-vorbis",
822 'PDF' => "application/pdf",
823 'PS' => "application/postscript"
826 ################################################
828 # Fill the RAM image of the web site #
830 ################################################
832 my %WWWramImage = ();
833 if($UseRAMimage)
835 my $TotalSize = 0;
836 my @WWWfilelist = `find $textroot ! -type l ! -type d -print`;
837 my $WWWfile;
838 foreach $WWWfile (@WWWfilelist)
840 chomp($WWWfile);
841 # Skip unsupported file types
842 $WWWfile =~ /\.(\w+)$/;
843 my $WWWfileExtension = uc($1);
844 next unless $mimeType{$WWWfileExtension};
845 # Store GnuZipped image of file
846 $WWWramImage{$WWWfile} = "";
847 open(FILEIN, "<$WWWfile") || die "$WWWfile could not be opened: $!\n";
848 my $Buffer;
849 while(sysread(FILEIN, $Buffer, 1024))
851 $WWWramImage{$WWWfile} .= $Buffer;
853 # Apply compression
854 my $CompressedPtr = &$CompressRAMimage(\${WWWramImage{$WWWfile}});
855 $WWWramImage{$WWWfile} = $$CompressedPtr;
856 $TotalSize += length($WWWramImage{$WWWfile});
859 # Report size of Web RAM image
860 print STDERR "Total number of $TotalSize bytes read in memory image\n";
863 ################################################
865 # The RAM image of the web site has been #
866 # filled. #
868 ################################################
870 # Map HTTP request parameters to Environment variables
871 # HTTP request => Environment variable
872 my %HTTPtype = (
873 'content-length' => 'CONTENT_LENGTH', # Necessary for POST
874 'user-agent' => 'HTTP_USER_AGENT',
875 'accept' => 'HTTP_ACCEPT',
876 'content-type' => 'CONTENT_TYPE',
877 'auth-type' => 'AUTH_TYPE',
878 'ident' => 'REMOTE_IDENT',
879 'referer' => 'HTTP_REFERER',
880 'user' => 'REMOTE_USER',
881 'address' => 'REMOTE_ADDR',
882 'connection' => 'HTTP_CONNECTION',
883 'accept-language' => 'HTTP_ACCEPT_LANGUAGE',
884 'accept-encoding' => 'HTTP_ACCEPT_ENCODING',
885 'accept-charset' => 'HTTP_ACCEPT_CHARSET',
886 'host' => 'HTTP_HOST',
887 'cookie' => 'HTTP_COOKIE'
890 ###############################################################################
892 # Now we start with the real work. When there is a request, get the required #
893 # values and fork a child to service it. #
895 ###############################################################################
897 my @brood = ();
898 my %StartTime = (); # Start time of the children
899 my $child;
901 # When someone knocks on the door
902 for (;;)
904 my $paddr;
906 if(!($paddr = accept(Client,Server)) ) # Knock knock
908 exit 1; # This went terrribly wrong
911 # Fork to child and parent
912 if(($child =fork()) == 0)
914 # this is the child
915 # The child does not need a PID list of the parent
916 @brood = ();
917 %StartTime = ();
919 # Read stuff
920 my ($port,$iaddr) = sockaddr_in($paddr);
921 my $address = inet_ntoa($iaddr); # The IP address of the Client
922 # The following is EXTREMELY slow and generally unnecessary.
923 # Use -n or set $NONAME = 1; if you don't need it.
924 my $name = $NONAME ? '' : gethostbyaddr($iaddr,AF_INET);
925 my @Input = ();
928 # Before doing anything else, check whether the client should be
929 # served at all.
930 # Is IP addr on the list?
931 if(@RemoteAddr && !grep(/^\Q$address\E/, @RemoteAddr))
933 print STDERR "Reject $address $name\n";
934 exit 1;
936 # Is name on the list?
937 if(@RemoteHost && !grep(/\Q$name\E$/, @RemoteHost))
939 print STDERR "Reject $name $address\n";
940 exit 1;
944 # Grab a line without using buffered input... Important for
945 # Post methods since they have to read the Client input stream.
947 my $string = "";
948 my $ch = "";
949 my $HTTPlength = 0;
950 alarm 120 ; # prevent deadly spin if other end goes away
951 while(sysread(Client, $ch, 1)>0)
953 $string .= $ch;
954 ++$HTTPlength;
955 last if $HTTPlength > $MaxLength; # Protect against overflow
957 next if $ch eq "\r"; # skip <cr>
958 if($ch eq "\n")
960 last unless $string =~ /\S/; # stop if empty line
961 push (@Input, split(' ', $string)); # Collect input in list
962 $string = "";
965 alarm 0; # clear alarm
967 # Reset %ENV
968 foreach my $varname (keys(%FrozenEnv))
970 $ENV{$varname} = $FrozenEnv{$varname};
973 # Extract input arguments
974 my $method = shift(@Input);
975 my $Request = shift(@Input);
976 my $protocol = shift(@Input);
977 my ($Path, $QueryString) = split('\?', $Request);
979 # Get rest of Input
980 my $HTTPparameter;
981 my %HTTPtable = ();
982 while($HTTPparameter = lc(shift(@Input)))
984 chop($HTTPparameter);
985 $HTTPtable{$HTTPparameter} = "";
986 while(@Input && $Input[0] !~ /\:$/)
988 $HTTPtable{$HTTPparameter} .= " " if $HTTPtable{$HTTPparameter};
989 $HTTPtable{$HTTPparameter} .= shift(@Input);
992 # Host can get the :SERVER_PORT appended. Set the correct SERVER_PORT
993 # and remove it from the host.
994 if($HTTPtable{'host'})
996 # Store current port number
997 if($HTTPtable{'host'} =~ /\:(\d+)\s*$/)
999 $ENV{'SERVER_PORT'} = $1;
1001 # Remove port number from host
1002 $HTTPtable{'host'} =~ s/\:(\d+)\s*$//g;
1005 # Translate the Aliases
1006 $Path = GetAlias($Path);
1008 # HTTP servers should always add the default path
1009 $Path = $welcome if !$Path || $Path eq '/'; # The common default path
1011 # Set fixed environment variables
1012 $ENV{'PATH_INFO'} = "$Path";
1013 $ENV{'QUERY_STRING'} = "$QueryString";
1014 $ENV{'PATH_TRANSLATED'} = "$textroot$Path";
1015 $ENV{'SERVER_PROTOCOL'} = "$protocol";
1016 $ENV{'REQUEST_METHOD'} = "$method";
1017 $ENV{'REMOTE_ADDR'} = "$address"; # The IP address of the Client
1018 $ENV{'REMOTE_HOST'} = "$name";
1020 # Load all request information in the %ENV.
1021 # MUST be done with a pre-defined list of parameter names (security).
1022 foreach $HTTPparameter (keys(%HTTPtype))
1024 my $Label = $HTTPtype{$HTTPparameter};
1025 # The following adds environment variables FROM THE REQUEST.
1026 # It is a VERY, VERY bad idea to just use the client supplied
1027 # parameter names!
1028 $ENV{$Label} = $HTTPtable{$HTTPparameter} unless exists($ENV{$Label});
1029 # (The last part prevents overwriting existing environment variables)
1032 # SECURITY: Check length of POST request. Stop if request is too long
1033 die if $HTTPlength + $ENV{'CONTENT_LENGTH'} > $MaxLength;
1035 # If POST requests are unwanted, they can be added tot the query string
1036 # NOTE: the method is set to GET if $POSTtoGET equals 'GET', otherwise,
1037 # the method stays POST and only the content length is set to 0
1038 if($POSTtoGET && $ENV{'REQUEST_METHOD'} =~ /^POST$/i)
1040 my $POSTlength = $ENV{'CONTENT_LENGTH'} || 0;
1041 my $ReadBytes = 1;
1043 # Add '&' if there is a query string already
1044 if($ENV{'QUERY_STRING'})
1046 # Before we add something to the string, check length again
1047 die if $HTTPlength + $ENV{'CONTENT_LENGTH'} + 1 > $MaxLength;
1048 # Now add the '&'
1049 $ENV{'QUERY_STRING'} .= '&';
1052 # Read Client
1053 while($POSTlength > 0 && $ReadBytes > 0)
1055 my $Read = "";
1056 $ReadBytes = sysread(Client, $Read, $POSTlength);
1057 $ENV{'QUERY_STRING'} .= $Read;
1058 $POSTlength -= $ReadBytes;
1061 # All has been read, the content length becomes 0
1062 $ENV{'CONTENT_LENGTH'} = 0;
1063 # Method can change
1064 $ENV{'REQUEST_METHOD'} = 'GET' if $POSTtoGET eq 'GET';
1067 # Reset User defience Env variables
1068 foreach my $varname (keys(%UserEnv))
1070 $ENV{$varname} = $UserEnv{$varname};
1072 # Clean out the User Environment variables in the child
1073 %UserEnv = ();
1076 # Connect STDOUT and STDIN to the client
1077 open(STDIN, "<&Client");
1078 open(STDOUT, ">&Client");
1079 print STDOUT "HTTP/1.1 200 OK\n"; # Supply HTTP protocol information
1080 print STDOUT "Date: ".gmtime()." GMT\n"; # Current date
1081 print STDOUT "Server: $program $version\n"; # This program
1082 print STDOUT "Connection: close\n"; # Don't allow persistent connections
1084 # Start processing of request (note that ALL scripts will be executed if
1085 # present, i.e., if -d, -x, and -e are entered, they are alle processed).
1087 # If in memory-only mode, store the requested file in an environment
1088 # variable: CGI_FILE_CONTENTS
1089 undef($ENV{'CGI_FILE_CONTENTS'}); # Make sure the ENV var doesn't exist
1090 if($UseRAMimage)
1092 my $DecompressedPtr = &$DecompressRAMimage(\${WWWramImage{"$textroot$Path"}});
1093 $ENV{'CGI_FILE_CONTENTS'} = $$DecompressedPtr;
1094 # Decompression does not seem to work
1097 # do perl script
1098 @ARGV = ("$textroot$Path", $QueryString);
1099 # This was suggested by Jochen_Hayek@ACM.org
1100 if($doarg)
1102 # The perl script should do the printing
1103 my ($return) = do "$doarg";
1105 warn "couldn't parse $doarg: $@" if $@;
1106 warn "couldn't $doarg: $!" unless defined $return;
1107 warn "couldn't run $doarg" unless $return;
1110 # evaluate perl command
1111 print STDOUT eval($evalarg) if $evalarg;
1113 # execute shell command
1114 if($execarg)
1116 my $shellscript = $execarg;
1118 # Attempts to use Paths or Queries containing '-quotes are rejected.
1119 # Executing these would compromise security.
1120 die "Quotes in path: $textroot$Path\n" if "$textroot$Path" =~ /\'/;
1121 $shellscript .= " '$textroot$Path'" if $Path;
1123 die "Quotes in query: $QueryString\n" if $QueryString =~ /\'/;
1124 $shellscript .= " '$QueryString'" if $QueryString;
1125 $shellscript = qx{$shellscript};
1126 print STDOUT $shellscript;
1129 # Output files if no scripts are given (actually, this should be
1130 # handled by a script). Unknown mimetypes are killed.
1131 # This is more or less a functional (dynamic) Web server in itself.
1132 unless($doarg || $execarg || $evalarg) # Request not already handled
1134 die ".. trick: $address $name $Path $QueryString\n"
1135 if $Path =~ m@\.\./@ ; # No tricks!
1137 # Handle mime-types and directory browsing
1138 $Path =~ /\.([\w]+)$/; # Get extension
1139 my $extension = uc($1);
1140 my $browse = ($Path =~ m@/\s*$@ || -d "$textroot$Path") ? 1 : 0;
1141 my $mime = $browse ? "" : $mimeType{$extension};
1143 # Serve up text and binary files unless they the $Secure option is given
1144 $mime = "text/plain" if !$mime && !$browse && (-T "$textroot$Path") && !$Secure;
1145 $mime = "application/octet-stream" if !$mime && !$browse && (-B "$textroot$Path") && !$Secure;
1147 # Remove final / in directory paths
1148 $Path =~ s@/\s*$@@g;
1150 # Block illegal mime-types
1151 die "Illegal mime type:$extension\n" unless $mime || $browse; # illegal mime's are killed
1153 # Print out the document
1154 if(($mime eq 'text/osshell') && $ExecuteOSshell) # Don't use this unless you know what you're doing
1156 # Note that CGI scripts must supply their own content type
1157 # Some rudimentary security tests
1158 # Kill child if the path contains any non-URL characters
1159 die "ATTACK: ADDR:$ENV{'REMOTE_ADDR'} HOST:$ENV{'REMOTE_HOST'} URL=$Path '$QueryString'\n"
1160 if $Path =~ m@[^\w\-\.\/]@; # Exclusive list of allowed characters
1161 # If you want to execute server side shell scripts, use the 'text/osshell'
1162 # mime-type (see above) but remember that there is NO SECURITY implemented
1163 # whatsoever.
1164 # IF YOU DIDN'T GET THE MESSAGE YET, YOU COULD NOW OPEN YOUR COMPUTER TO THE WHOLE
1165 # INTERNET TO PLAY WITH!
1166 # Plain Web site from DISK
1167 unless($UseRAMimage)
1169 print STDOUT `$textroot$Path`; # This is Russian Roulette
1171 else # Use a RAM image of the web site
1173 my $ShellInterpreter = '/usr/bin/sh';
1174 if($ENV{'CGI_FILE_CONTENTS'} =~ /^\#\!\s*([^\r\n]+)/isg)
1176 $ShellInterpreter = $1;
1178 # Execute shell script
1179 open(RAMOUT, "| $ShellInterpreter") || die "ERROR open RAMOUT $ShellInterpreter $textroot$Path $! $@\n";
1180 (print RAMOUT $ENV{'CGI_FILE_CONTENTS'}) || die "ERROR print RAMOUT $ShellInterpreter $textroot$Path $! $@\n";
1181 close(RAMOUT);
1184 elsif($mime)
1186 # Content-type and document
1187 print STDOUT "Content-type: $mime\n\n";
1188 # Plain Web site from DISK
1189 unless($UseRAMimage)
1191 my $String = "";
1192 my $number_of_bytes = 0;
1193 open(BINARY, "<$textroot$Path") || die "$textroot$Path: $!";
1195 # read and write block of 1024 bytes
1196 while($number_of_bytes = sysread(BINARY, $String, 1024))
1198 syswrite(STDOUT, $String, $number_of_bytes); # Actually print the file content
1200 close(BINARY);
1202 # Alternative output using the UNIX shell
1203 # print STDOUT `cat '$textroot$Path'`; # lazy, let the OS do the work
1205 else # Use a RAM image of the web site
1207 print STDOUT $ENV{'CGI_FILE_CONTENTS'};
1211 elsif($browse && !$Secure) # Block directory browsing in the Secure setup
1213 # Content-type and document
1214 print STDOUT "Content-type: text/html\n\n";
1215 opendir(BROWSE, "$textroot$Path") || die "<$textroot$Path: $!\n";
1217 print "<HTML>\n<HEAD>\n<TITLE>$Path</TITLE></HEAD>\n<BODY>\n<H1>$Path</H1>\n<pre>\n<dl>";
1219 my $DirEntry;
1220 foreach $DirEntry (sort {lc($a) cmp lc($b)} readdir(BROWSE))
1222 my $CurrentPath = $Path;
1223 # Handle '..'
1224 if($DirEntry eq '..')
1226 my $ParentDir = $CurrentPath;
1227 $ParentDir =~ s@/[^/]+$@@g;
1228 $ParentDir = '/' unless $ParentDir;
1229 print "<dt> <a href='$ParentDir'><h3>Parent directory</h3></a></dt>\n";
1231 next if $DirEntry !~ /[^\.\/\\\:]/;
1233 # Get aliases
1234 my $Alias = GetAlias("$CurrentPath/$DirEntry");
1235 if($Alias ne "$CurrentPath/$DirEntry")
1237 $Alias =~ m@/([^/]+)$@;
1238 $CurrentPath = $`;
1239 $DirEntry = $1;
1242 my $Date = localtime($^T - (-M "$textroot$CurrentPath/$DirEntry")*3600*24);
1243 my $Size = -s "$textroot$CurrentPath/$DirEntry";
1244 $Size = sprintf("%6.0F kB", $Size/1024);
1245 my $Type = `file $textroot$CurrentPath/$DirEntry`;
1246 $Type =~ s@\s*$textroot$CurrentPath/$DirEntry\s*\:\s*@@ig;
1247 chomp($Type);
1248 print "<dt> <a href='$CurrentPath/$DirEntry'>";
1249 printf("%-40s", $DirEntry."</a>");
1250 print "\t$Size\t$Date\t$Type</dt>\n";
1252 close(BROWSE);
1253 print "</dl></pre></BODY>\n</HTML>\n";
1258 close(STDOUT) || die "STDOUT: $!\n";
1259 close(STDIN) || die "STDIN: $!\n";
1260 close(Client) || die "Client: $!\n";
1262 exit 0; # Kill Child
1264 else
1267 # parent code...some systems will have to worry about waiting
1268 # before they can actually close the link to the Client
1269 my $current_time = time();
1271 # Determine which of the children are actually still alive
1272 # and kill those that have run for too long (probably not connected anymore)
1273 my @old_brood = @brood;
1274 @brood = (); # empty brood
1275 foreach (@old_brood)
1277 # Kill the child if it runs for longer than MaxTime
1278 if(($StartTime{$_} - $current_time) > $MaxTime)
1280 kill "KILL", $_;
1283 # Store children that are alive
1284 if(kill (0, $_)) # Alive?
1286 push(@brood, $_);
1288 else
1290 delete($StartTime{$_});
1294 # Weed out overflow of children (zombies etc.), keep pid for
1295 # removing the StartTime later on
1296 my $oldest;
1297 for($oldest=0; $oldest < scalar(@brood)-$MaxBrood; ++$oldest)
1299 kill "KILL", $brood[$oldest] if $brood[$oldest]; # Remove
1300 # Do NOT remove the killed children from @brood as the KILL
1301 # might not have worked and you can try again later
1304 # Child pid could be recycled, i.e., $child could be stored
1305 # in @brood already. Remove it
1306 @brood = grep($_ != $child, @brood);
1308 # Push new child on the list, if the fork succeeded
1309 if($child > 0)
1311 push (@brood, $child);
1312 $StartTime{$child} = $current_time;
1314 close Client; # This is it, ready!
1318 # Interupt handler for shutting down
1319 sub SigHandler
1321 my $sig = shift;
1322 exit 1;
1325 # Subroutine for Aliases
1326 # Uses Global variables: %AliasTranslation, @RegAliasTranslation, and @RegURLTranslation
1327 sub GetAlias # ($Path)->AliasURL
1329 my $Path = shift;
1331 # Translate the Aliases
1332 if($AliasTranslation{$Path})
1334 $Path = $AliasTranslation{$Path};
1336 elsif(@RegAliasTranslation)
1338 my $i;
1339 for($i=0; $i<scalar(@RegAliasTranslation); ++$i)
1341 my $Alias = $RegAliasTranslation[$i];
1342 my $RealURL = $RegURLTranslation[$i];
1343 last if ($Path =~ s#$Alias#$RealURL#g);
1346 return $Path;
1349 =head1 NAME
1351 CGIservlet - a HTTPd "connector" for running CGI scripts on unix systems as WWW
1352 accessible Web sites.
1354 =head1 DESCRIPTION
1356 The servlet starts a true HTTP daemon that channels
1357 HTTP requests to forked daughter processes. Can run
1358 a (small) WWW-site from memory.
1360 =head1 README
1362 Whenever an HTTP request is received, the specified CGI script is
1363 started inside a child process as if it was inside a real server (e.g.,
1364 Apache). The evironment variables are set more or less as in Apache.
1365 Note that CGIservlet only uses a SINGLE script for ALL requests.
1366 No attemps for security are made, it is the script's responsibility to
1367 check access rights and the validity of the request.
1368 Can store the files of Web site in memory and serve them
1369 on request.
1371 =head1 PREREQUISITES
1373 This script requires the C<strict>, Socket and Carp modules.
1375 =head1 COREQUISITES
1377 =pod OSNAMES
1379 Unix
1381 =pod SCRIPT CATEGORIES
1386 =cut