Added .SkeletonDir for new users
[CGIscriptor.git] / CGIscriptor.pl
blobc3520c56ae0d0081dc50ef0d9aa3dab1143c5264
1 #! /usr/bin/perl
3 # (configure the first line to contain YOUR path to perl 5.000+)
5 # CGIscriptor.pl
6 # Version 2.3
7 # 15 January 2002
9 # YOU NEED:
11 # perl 5.0 or higher (see: "http://www.perl.org/")
13 # Notes:
15 if(grep(/\-\-help/i, @ARGV))
17 print << 'ENDOFPREHELPTEXT1';
18 # CGIscriptor.pl is a Perl program will run on any WWW server that
19 # runs Perl scripts, just add a line like the following to your
20 # httpd.conf file (Apache example):
22 # ScriptAlias /SHTML/ "/real-path/CGIscriptor.pl/"
24 # URL's that refer to http://www.your.address/SHTML/... will now be handled
25 # by CGIscriptor.pl, which can use a private directory tree (default is the
26 # DOCUMENT_ROOT directory tree, but it can be anywhere, see below).
27 # NOTE: if you cannot use a ScriptAlias, there is a way to use .htaccess
28 # instead. See below.
30 # This file contains all documentation as comments. These comments
31 # can be removed to speed up loading (e.g., `egrep -v '^#' CGIscriptor.pl` >
32 # leanScriptor.pl). A bare bones version of CGIscriptor.pl, lacking
33 # documentation, most comments, access control, example functions etc.
34 # (but still with the copyright notice and some minimal documentation)
35 # can be obtained by calling CGIscriptor.pl with the '-slim'
36 # command line argument, e.g.,
37 # >CGIscriptor.pl -slim >slimCGIscriptor.pl
39 # CGIscriptor.pl can be run from the command line as
40 # `CGIscriptor.pl <path> <query>`, inside a perl script with
41 # 'do CGIscriptor.pl' after setting $ENV{PATH_INFO} and $ENV{QUERY_STRING},
42 # or CGIscriptor.pl can be loaded with 'require "/real-path/CGIscriptor.pl"'.
43 # In the latter case, requests are processed by 'Handle_Request();'
44 # (again after setting $ENV{PATH_INFO} and $ENV{QUERY_STRING}).
46 # The --help command line switch will print the manual.
48 # Running demo's and more information can be found at
49 # http://www.fon.hum.uva.nl/rob/OSS/OSS.html
51 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site
52 # or CPAN that can use CGIscriptor.pl as the base of a µWWW server and
53 # demonstrates its use.
55 ENDOFPREHELPTEXT1
58 # Configuration, copyright notice, and user manual follow the next
59 # (Changes) section.
61 ############################################################################
63 # Changes (document ALL changes with date, name and email here):
64 # 11 Jun 2012 - Securing CGIvariable setting. Made
65 # 'if($ENV{QUERY_STRING} =~ /$name/)' into elsif in
66 # defineCGIvariable/List/Hash to give precedence to ENV{$name}
67 # This was a very old security bug. Added ProtectCGIvariable($name).
68 # 06 Jun 2012 - Added IP only session types after login.
69 # 31 May 2012 - Session ticket system added for handling login sessions.
70 # 29 May 2012 - CGIsafeFileName does not accept filenames starting with '.'
71 # 29 May 2012 - Added CGIscriptor::BrowseAllDirs to handle browsing directories
72 # correctly.
73 # 22 May 2012 - Added Access control with Session Tickets linked to
74 # IP Address and PATH_INFO.
75 # 21 May 2012 - Corrected the links generated by CGIscriptor::BrowseDirs
76 # Will link to current base URL when the HTTP server is '.' or '~'
77 # 29 Oct 2009 - Adapted David A. Wheeler's suggestion about filenames:
78 # CGIsafeFileName does not accept filenames starting with '-'
79 # (http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html)
80 # 08 Oct 2009 - Some corrections in the README.txt file, eg, new email address
81 # 28 Jan 2005 - Added a file selector to performTranslation.
82 # Changed %TranslationTable to @TranslationTable
83 # and patterns to lists.
84 # 27 Jan 2005 - Added a %TranslationTable with associated
85 # performTranslation(\$text) function to allow
86 # run changes in the web pages. Say, to translate
87 # legacy pages with <%=...%> delimiters to the new
88 # <SCRIPT TYPE=..></SCRIPT> format.
89 # 27 Jan 2005 - Small bug of extra '\n' in output removed from the
90 # Other Languages Code.
91 # 10 May 2004 - Belated upload of latest version (2.3) to CPAN
92 # 07 Oct 2003 - Corrected error '\s' -> '\\s' in rebol scripting
93 # language call
94 # 07 Oct 2003 - Corrected omitted INS tags in <DIV><INS> handling
95 # 20 May 2003 - Added a --help switch to print the manual.
96 # 06 Mar 2003 - Adapted the blurb at the end of the file.
97 # 03 Mar 2003 - Added a user definable dieHandler function to catch all
98 # "die" calls. Also "enhanced" the STDERR printout.
99 # 10 Feb 2003 - Split off the reading of the POST part of a query
100 # from Initialize_output. This was suggested by Gerd Franke
101 # to allow for the catching of the file_path using a
102 # POST based lookup. That is, he needed the POST part
103 # to change the file_path.
104 # 03 Feb 2003 - %{$name}; => %{$name} = (); in defineCGIvariableHash.
105 # 03 Feb 2003 - \1 better written as $1 in
106 # $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
107 # 29 Jan 2003 - This makes "CLASS="ssperl" CSS-compatible Gerd Franke
108 # added:
109 # $ServerScriptContentClass = "ssperl";
110 # changed in ProcessFile():
111 # unless(($CurrentContentType =~
112 # 28 Jan 2003 - Added 'INS' Tag! Gerd Franke
113 # 20 Dec 2002 - Removed useless $Directoryseparator variable.
114 # Update comments and documentation.
115 # 18 Dec 2002 - Corrected bug in Accept/Reject processing.
116 # Files didn't work.
117 # 24 Jul 2002 - Added .htaccess documentation (from Gerd Franke)
118 # Also added a note that RawFilePattern can be a
119 # complete file name.
120 # 19 Mar 2002 - Added SRC pseudo-files PREFIX and POSTFIX. These
121 # switch to prepending or to appending the content
122 # of the SRC attribute. Default is prefixing. You
123 # can add as many of these switches as you like.
124 # 13 Mar 2002 - Do not search for tag content if a tag closes with
125 # />, i.e., <DIV ... /> will be handled the XML/XHTML way.
126 # 25 Jan 2002 - Added 'curl' and 'snarf' to SRC attribute URL handling
127 # (replaces wget).
128 # 25 Jan 2002 - Found a bug in SAFEqx, now executes qx() in a scalar context
129 # (i.o. a list context). This is necessary for binary results.
130 # 24 Jan 2002 - Disambiguated -T $SRCfile to -T "$SRCfile" (and -e) and
131 # changed the order of if/elsif to allow removing these
132 # conditions in systems with broken -T functions.
133 # (I also removed a spurious ')' bracket)
134 # 17 Jan 2002 - Changed DIV tag SRC from <SOURCE> to sysread(SOURCE,...)
135 # to support binary files.
136 # 17 Jan 2002 - Removed WhiteSpace from $FileAllowedCharacters.
137 # 17 Jan 2002 - Allow "file://" prefix in SRC attribute. It is simply
138 # stipped from the path.
139 # 15 Jan 2002 - Version 2.2
140 # 15 Jan 2002 - Debugged and completed URL support (including
141 # CGIscriptor::read_url() function)
142 # 07 Jan 2002 - Added automatic (magic) URL support to the SRC attribute
143 # with the main::GET_URL function. Uses wget -O underlying.
144 # 04 Jan 2002 - Added initialization of $NewDirective in InsertForeignScript
145 # (i.e., my $NewDirective = "";) to clear old output
146 # (this was a realy anoying bug).
147 # 03 Jan 2002 - Added a <DIV CLASS='text/ssperl' ID='varname'></DIV>
148 # tags that assign the body text as-is (literally)
149 # to $varname. Allows standard HTML-tools to handle
150 # Cascading Style Sheet templates. This implements a
151 # design by Gerd Franke (franke@roo.de).
152 # 03 Jan 2002 - I finaly gave in and allowed SRC files to expand ~/.
153 # 12 Oct 2001 - Normalized spelling of "CGIsafFileName" in documentation.
154 # 09 Oct 2001 - Added $ENV{'CGI_BINARY_FILE'} to log files to
155 # detect unwanted indexing of TAR files by webcrawlers.
156 # 10 Sep 2001 - Added $YOUR_SCRIPTS directory to @INC for 'require'.
157 # 22 Aug 2001 - Added .txt (Content-type: text/plain) as a default
158 # processed file type. Was processed via BinaryMapFile.
159 # 31 May 2001 - Changed =~ inside CGIsafeEmailAddress that was buggy.
160 # 29 May 2001 - Updated $CGI_HOME to point to $ENV{DOCUMENT_ROOT} io
161 # the root of PATH_TRANSLATED. DOCUMENT_ROOT can now
162 # be manipulated to achieve a "Sub Root".
163 # NOTE: you can have $YOUR_HTML_FILES != DOCUMENT_ROOT
164 # 28 May 2001 - Changed CGIscriptor::BrowsDirs function for security
165 # and debugging (it now works).
166 # 21 May 2001 - defineCGIvariableHash will ADD values to existing
167 # hashes,instead of replacing existing hashes.
168 # 17 May 2001 - Interjected a '&' when pasting POST to GET data
169 # 24 Apr 2001 - Blocked direct requests for BinaryMapFile.
170 # 16 Aug 2000 - Added hash table extraction for CGI parameters with
171 # CGIparseValueHash (used with structured parameters).
172 # Use: CGI='%<CGI-partial-name>' (fill in your name in <>)
173 # Will collect all <CGI-partial-name><key>=value pairs in
174 # $<CGI-partial-name>{<key>} = value;
175 # 16 Aug 2000 - Adapted SAFEqx to protect @PARAMETER values.
176 # 09 Aug 2000 - Added support for non-filesystem input by way of
177 # the CGI_FILE_CONTENTS and CGI_DATA_ACCESS_CODE
178 # environment variables.
179 # 26 Jul 2000 - On the command-line, file-path '-' indicates STDIN.
180 # This allows CGIscriptor to be used in pipes.
181 # Default, $BLOCK_STDIN_HTTP_REQUEST=1 will block this
182 # in an HTTP request (i.e., in a web server).
183 # 26 Jul 2000 - Blocked 'Content-type: text/html' if the SERVER_PROTOCOL
184 # is not HTTP or another protocol. Changed the default
185 # source directory to DOCUMENT_ROOT (i.o. the incorrect
186 # SERVER_ROOT).
187 # 24 Jul 2000 - -slim Command-line argument added to remove all
188 # comments, security, etc.. Updated documentation.
189 # 05 Jul 2000 - Added IF and UNLESS attributes to make the
190 # execution of all <META> and <SCRIPT> code
191 # conditional.
192 # 05 Jul 2000 - Rewrote and isolated the code for extracting
193 # quoted items from CGI and SRC attributes.
194 # Now all attributes expect the same set of
195 # quotes: '', "", ``, (), {}, [] and the same
196 # preceded by a \, e.g., "\((aap)\)" will be
197 # extracted as "(aap)".
198 # 17 Jun 2000 - Construct @ARGV list directly in CGIexecute
199 # name-space (i.o. by evaluation) from
200 # CGI attributes to prevent interference with
201 # the processing for non perl scripts.
202 # Changed CGIparseValueList to prevent runaway
203 # loops.
204 # 16 Jun 2000 - Added a direct (interpolated) display mode
205 # (text/ssdisplay) and a user log mode
206 # (text/sslogfile).
207 # 06 Jun 2000 - Replace "print $Result" with a syswrite loop to
208 # allow large string output.
209 # 02 Jun 2000 - Corrected shrubCGIparameter($CGI_VALUE) to realy
210 # remove all control characters. Changed Interpreter
211 # initialization to shrub interpolated CGI parameters.
212 # Added 'text/ssmailto' interpreter script.
213 # 22 May 2000 - Changed some of the comments
214 # 09 May 2000 - Added list extraction for CGI parameters with
215 # CGIparseValueList (used with multiple selections).
216 # Use: CGI='@<CGI-parameter>' (fill in your name in <>)
217 # 09 May 2000 - Added a 'Not Present' condition to CGIparseValue.
218 # 27 Apr 2000 - Updated documentation to reflect changes.
219 # 27 Apr 2000 - SRC attribute "cleaned". Supported for external
220 # interpreters.
221 # 27 Apr 2000 - CGI attribute can be used in <SCRIPT> tag.
222 # 27 Apr 2000 - Gprolog, M4 support added.
223 # 26 Apr 2000 - Lisp (rep) support added.
224 # 20 Apr 2000 - Use of external interpreters now functional.
225 # 20 Apr 2000 - Removed bug from extracting Content types (RegExp)
226 # 10 Mar 2000 - Qualified unconditional removal of '#' that preclude
227 # the use of $#foo, i.e., I changed
228 # s/[^\\]\#[^\n\f\r]*([\n\f\r])/\1/g
229 # to
230 # s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/\1/g
231 # 03 Mar 2000 - Added a '$BlockPathAccess' variable to "hide"
232 # things like, e.g., CVS information in CVS subtrees
233 # 10 Feb 2000 - URLencode/URLdecode have been made case-insensitive
234 # 10 Feb 2000 - Added a BrowseDirs function (CGIscriptor package)
235 # 01 Feb 2000 - A BinaryMapFile in the ~/ directory has precedence
236 # over a "burried" BinaryMapFile.
237 # 04 Oct 1999 - Added two functions to check file names and email addresses
238 # (CGIscriptor::CGIsafeFileName and
239 # CGIscriptor::CGIsafeEmailAddress)
240 # 28 Sept 1999 - Corrected bug in sysread call for reading POST method
241 # to allow LONG posts.
242 # 28 Sept 1999 - Changed CGIparseValue to handle multipart/form-data.
243 # 29 July 1999 - Refer to BinaryMapFile from CGIscriptor directory, if
244 # this directory exists.
245 # 07 June 1999 - Limit file-pattern matching to LAST extension
246 # 04 June 1999 - Default text/html content type is printed only once.
247 # 18 May 1999 - Bug in replacement of ~/ and ./ removed.
248 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
249 # 15 May 1999 - Changed the name of the execute package to CGIexecute.
250 # Changed the processing of the Accept and Reject file.
251 # Added a full expression evaluation to Access Control.
252 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
253 # 27 Apr 1999 - Brought CGIscriptor under the GNU GPL. Made CGIscriptor
254 # Version 1.1 a module that can be called with 'require "CGIscriptor.pl"'.
255 # Requests are serviced by "Handle_Request()". CGIscriptor
256 # can still be called as a isolated perl script and a shell
257 # command.
258 # Changed the "factory default setting" so that it will run
259 # from the DOCUMENT_ROOT directory.
260 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
261 # 29 Mar 1999 - Remove second debugging STDERR switch. Moved most code
262 # to subroutines to change CGIscriptor into a module.
263 # Added mapping to process unsupported file types (e.g., binary
264 # pictures). See $BinaryMapFile.
265 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
266 # 24 Sept 1998 - Changed text of license (Rob van Son, R.J.J.H.vanSon@uva.nl)
267 # Removed a double setting of filepatterns and maximum query
268 # size. Changed email address. Removed some typos from the
269 # comments.
270 # 02 June 1998 - Bug fixed in URLdecode. Changing the foreach loop variable
271 # caused quiting CGIscriptor.(Rob van Son, R.J.J.H.vanSon@uva.nl)
272 # 02 June 1998 - $SS_PUB and $SS_SCRIPT inserted an extra /, removed.
273 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
276 # Known Bugs:
278 # 23 Mar 2000
279 # It is not possible to use operators or variables to construct variable names,
280 # e.g., $bar = \@{$foo}; won't work. However, eval('$bar = \@{'.$foo.'};');
281 # will indeed work. If someone could tell me why, I would be obliged.
284 ############################################################################
286 # OBLIGATORY USER CONFIGURATION
288 # Configure the directories where all user files can be found (this
289 # is the equivalent of the server root directory of a WWW-server).
290 # These directories can be located ANYWHERE. For security reasons, it is
291 # better to locate them outside the WWW-tree of your HTTP server, unless
292 # CGIscripter handles ALL requests.
294 # For convenience, the defaults are set to the root of the WWW server.
295 # However, this might not be safe!
297 # ~/ text files
298 # $YOUR_HTML_FILES = "/usr/pub/WWW/SHTML"; # or SS_PUB as environment var
299 # (patch to use the parent directory of CGIscriptor as document root, should be removed)
300 if($ENV{'SCRIPT_FILENAME'}) # && $ENV{'SCRIPT_FILENAME'} !~ /\Q$ENV{'DOCUMENT_ROOT'}\E/)
302 $ENV{'DOCUMENT_ROOT'} = $ENV{'SCRIPT_FILENAME'};
303 $ENV{'DOCUMENT_ROOT'} =~ s@/CGIscriptor.*$@@ig;
306 # Just enter your own directory path here
307 $YOUR_HTML_FILES = $ENV{'DOCUMENT_ROOT'}; # default is the DOCUMENT_ROOT
309 # ./ script files (recommended to be different from the previous)
310 # $YOUR_SCRIPTS = "/usr/pub/WWW/scripts"; # or SS_SCRIPT as environment var
311 $YOUR_SCRIPTS = $YOUR_HTML_FILES; # This might be a SECURITY RISK
313 # End of obligatory user configuration
314 # (note: there is more non-essential user configuration below)
316 ############################################################################
318 # OPTIONAL USER CONFIGURATION (all values are used CASE INSENSITIVE)
320 # Script content-types: TYPE="Content-type" (user defined mime-type)
321 $ServerScriptContentType = "text/ssperl"; # Server Side Perl scripts
322 # CSS require a simple class
323 $ServerScriptContentClass = $ServerScriptContentType =~ m!/! ?
324 $' : "ssperl"; # Server Side Perl CSS classes
326 $ShellScriptContentType = "text/osshell"; # OS shell scripts
327 # # (Server Side perl ``-execution)
329 # Accessible file patterns, block any request that doesn't match.
330 # Matches any file with the extension .(s)htm(l), .txt, or .xmr
331 # (\. is used in regexp)
332 # Note: die unless $PATH_INFO =~ m@($FilePattern)$@is;
333 $FilePattern = ".shtml|.htm|.html|.xml|.xmr|.txt|.js";
335 # The table with the content type MIME types
336 # (allows to differentiate MIME types, if needed)
337 %ContentTypeTable =
339 '.html' => 'text/html',
340 '.shtml' => 'text/html',
341 '.htm' => 'text/html',
342 '.xml' => 'text/xml',
343 '.txt' => 'text/plain',
344 '.js' => 'text/plain'
348 # File pattern post-processing
349 $FilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
351 # SHAsum command needed for Authorization and Login
352 # (note, these have to be accessible in the HTML pages, ie, the CGIexecute environment)
353 my $shasum = "shasum -a 256";
354 if(qx{uname} =~ /Darwin/)
356 $shasum = "shasum-5.12 -a 256" unless `which shasum`;
358 my $SHASUMCMD = $shasum.' |cut -f 1 -d" "';
359 $ENV{"SHASUMCMD"} = $SHASUMCMD;
360 my $RANDOMHASHCMD = 'dd bs=1 count=64 if=/dev/urandom 2>/dev/null | '.$shasum.' -b |cut -f 1 -d" "';
361 $ENV{"RANDOMHASHCMD"} = $RANDOMHASHCMD;
363 # Hash a string, return hex of hash
364 sub hash_string # ($string) -> hex_hash
366 my $string = shift || "";
367 # Catch nasty \'-quotes, embed them in '..'"'"'..'
368 $string =~ s/\'/\'\"\'\"\'/isg;
369 my $hash = `printf '%s' '$string'| $ENV{"SHASUMCMD"}`;
370 chomp($hash);
371 return $hash;
374 # Generate random hex hash
375 sub get_random_hex # () -> hex
377 # Create Random Hash Salt
378 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $RANDOMHASHCMD | $!\n";
379 my $RANDOMSALT= <URANDOM>;
380 close(URANDOM);
381 chomp($RANDOMSALT);
383 return $RANDOMSALT;
387 # File patterns of files which are handled by session tickets.
388 %TicketRequiredPatterns = (
389 '^/Private(/|$)' => "Private/.Sessions\tPrivate/.Passwords\t/Private/Login.html\t+36000"
391 # Used to set cookies, only session cookies supported
392 my %SETCOOKIELIST = ();
394 # Session Ticket Directory: Private/.Sessions
395 # Password Directory: Private/.Passwords
396 # Login page (url path): /Private/Login.html
397 # Expiration time (s): +3600
398 # +<seconds> = relative time <seconds> is absolute date-time
400 # Manage login
401 # Set up a valid ticket from a given text file
402 # Use from command line. DO NOT USE ONLINE
403 # Watch out for passwords that get stored in the history file
405 # perl CGIscriptor.pl --managelogin [options] [files]
406 # Options:
407 # salt={file or saltvalue}
408 # masterkey={file or plaintext}
409 # newmasterkey={file or plaintext}
410 # password={file or palintext}
412 # Followed by one or more file names.
413 # Options can be interspersed between filenames,
414 # e.g., password='plaintext'
415 # Note that passwords are only used once!
417 if($ARGV[0] =~ /^\-\-managelogin/i)
419 my @arguments = @ARGV;
420 shift(@arguments);
421 setup_ticket_file(@arguments);
422 # Should be run on the command line
423 exit;
428 # Raw files must contain their own Content-type (xmr <- x-multipart-replace).
429 # THIS IS A SUBSET OF THE FILES DEFINED IN $FilePattern
430 $RawFilePattern = ".xmr";
431 # (In principle, this could contain a full file specification, e.g.,
432 # ".xmr|relocated.html")
434 # Raw File pattern post-processing
435 $RawFilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
437 # Server protocols for which "Content-type: text/html\n\n" should be printed
438 # (you should not bother with these, except for HTTP, they are mostly imaginary)
439 $ContentTypeServerProtocols = 'HTTP|MAIL|MIME';
441 # Block access to all (sub-) paths and directories that match the
442 # following (URL) path (is used as:
443 # 'die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;' )
444 $BlockPathAccess = '/(CVS|\.git)/'; # Protect CVS and .git information
446 # All (blocked) other file-types can be mapped to a single "binary-file"
447 # processor (a kind of pseudo-file path). This can either be an error
448 # message (e.g., "illegal file") or contain a script that serves binary
449 # files.
450 # Note: the real file path wil be stored in $ENV{CGI_BINARY_FILE}.
451 $BinaryMapFile = "/BinaryMapFile.xmr";
452 # Allow for the addition of a CGIscriptor directory
453 # Note that a BinaryMapFile in the root "~/" directory has precedence
454 $BinaryMapFile = "/CGIscriptor".$BinaryMapFile
455 if ! -e "$YOUR_HTML_FILES".$BinaryMapFile
456 && -e "$YOUR_HTML_FILES/CGIscriptor".$BinaryMapFile;
459 # List of all characters that are allowed in file names and paths.
460 # All requests containing illegal characters are blocked. This
461 # blocks most tricks (e.g., adding "\000", "\n", or other control
462 # characters, also blocks URI's using %FF)
463 # THIS IS A SECURITY FEATURE
464 # (this is also used to parse filenames in SRC= features, note the
465 # '-quotes, they are essential)
466 $FileAllowedChars = '\w\.\~\/\:\*\?\-'; # Covers Unix and Mac, but NO spaces
468 # Maximum size of the Query (number of characters clients can send
469 # covers both GET & POST combined)
470 $MaximumQuerySize = 2**20 - 1; # = 2**14 - 1
473 # Embeded URL get function used in SRC attributes and CGIscriptor::read_url
474 # (returns a string with the PERL code to transfer the URL contents, e.g.,
475 # "SAFEqx(\'curl \"http://www.fon.hum.uva.nl\"\')")
476 # "SAFEqx(\'wget --quiet --output-document=- \"http://www.fon.hum.uva.nl\"\')")
477 # Be sure to handle <BASE HREF='URL'> and allow BOTH
478 # direct printing GET_URL($URL [, 0]) and extracting the content of
479 # the $URL for post-processing GET_URL($URL, 1).
480 # You get the WHOLE file, including HTML header.
481 # The shell command Use $URL where the URL should go
482 # ('wget', 'snarf' or 'curl', uncomment the one you would like to use)
483 my $GET_URL_shell_command = 'wget --quiet --output-document=- $URL';
484 #my $GET_URL_shell_command = 'snarf $URL -';
485 #my $GET_URL_shell_command = 'curl $URL';
487 sub GET_URL # ($URL, $ValueNotPrint) -> content_of_url
489 my $URL = shift || return;
490 my $ValueNotPrint = shift || 0;
492 # Check URL for illegal characters
493 return "print '<h1>Illegal URL<h1>'\"\n\";" if $URL =~ /[^$FileAllowedChars\%]/;
495 # Include URL in final command
496 my $CurrentCommand = $GET_URL_shell_command;
497 $CurrentCommand =~ s/\$URL/$URL/g;
499 # Print to STDOUT or return a value
500 my $BlockPrint = "print STDOUT ";
501 $BlockPrint = "" if $ValueNotPrint;
503 my $Commands = <<"GETURLCODE";
504 # Get URL
506 my \$Page = "";
508 # Simple, using shell command
509 \$Page = SAFEqx('$CurrentCommand');
511 # Add a BASE tage to the header
512 \$Page =~ s!\\</head!\\<base href='$URL'\\>\\</head!ig unless \$Page =~ m!\\<base!;
514 # Print the URL value, or return it as a value
515 $BlockPrint\$Page;
517 GETURLCODE
518 return $Commands;
521 # As files can get rather large (and binary), you might want to use
522 # some more intelligent reading procedure, e.g.,
523 # Direct Perl
524 # # open(URLHANDLE, '/usr/bin/wget --quiet --output-document=- "$URL"|') || die "wget: \$!";
525 # #open(URLHANDLE, '/usr/bin/snarf "$URL" -|') || die "snarf: \$!";
526 # open(URLHANDLE, '/usr/bin/curl "$URL"|') || die "curl: \$!";
527 # my \$text = "";
528 # while(sysread(URLHANDLE,\$text, 1024) > 0)
530 # \$Page .= \$text;
531 # };
532 # close(URLHANDLE) || die "\$!";
533 # However, this doesn't work with the CGIexecute->evaluate() function.
534 # You get an error: 'No child processes at (eval 16) line 15, <file0> line 8.'
536 # You can forget the next two variables, they are only needed when
537 # you don't want to use a regular file system (i.e., with open)
538 # but use some kind of database/RAM image for accessing (generating)
539 # the data.
541 # Name of the environment variable that contains the file contents
542 # when reading directly from Database/RAM. When this environment variable,
543 # $ENV{$CGI_FILE_CONTENTS}, is not false, no real file will be read.
544 $CGI_FILE_CONTENTS = 'CGI_FILE_CONTENTS';
545 # Uncomment the following if you want to force the use of the data access code
546 # $ENV{$CGI_FILE_CONTENTS} = '-'; # Force use of $ENV{$CGI_DATA_ACCESS_CODE}
548 # Name of the environment variable that contains the RAM access perl
549 # code needed to read additional "files", i.e.,
550 # $ENV{$CGI_FILE_CONTENTS} = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
551 # When $ENV{$CGI_FILE_CONTENTS} eq '-', this code is executed to generate the data.
552 $CGI_DATA_ACCESS_CODE = 'CGI_DATA_ACCESS_CODE';
554 # You can, of course, fill this yourself, e.g.,
555 # $ENV{$CGI_DATA_ACCESS_CODE} =
556 # 'open(INPUT, "<$_[0]"); while(<INPUT>){print;};close(INPUT);'
559 # DEBUGGING
561 # Suppress error messages, this can be changed for debugging or error-logging
562 #open(STDERR, "/dev/null"); # (comment out for use in debugging)
564 # SPECIAL: Remove Comments, security, etc. if the command line is
565 # '>CGIscriptor.pl -slim >slimCGIscriptor.pl'
566 $TrimDownCGIscriptor = 1 if $ARGV[0] =~ /^\-slim/i;
568 # If CGIscriptor is used from the command line, the command line
569 # arguments are interpreted as the file (1st) and the Query String (rest).
570 # Get the arguments
571 $ENV{'PATH_INFO'} = shift(@ARGV) unless exists($ENV{'PATH_INFO'}) || grep(/\-\-help/i, @ARGV);
572 $ENV{'QUERY_STRING'} = join("&", @ARGV) unless exists($ENV{'QUERY_STRING'});
575 # Handle bail-outs in a user definable way.
576 # Catch Die and replace it with your own function.
577 # Ends with a call to "die $_[0];"
579 sub dieHandler # ($ErrorCode, "Message", @_) -> DEAD
581 my $ErrorCode = shift;
582 my $ErrorMessage = shift;
584 # Place your own reporting functions here
586 # Now, kill everything (default)
587 print STDERR "$ErrorCode: $ErrorMessage\n";
588 die $ErrorMessage;
592 # End of optional user configuration
593 # (note: there is more non-essential user configuration below)
595 if(grep(/\-\-help/i, @ARGV))
597 print << 'ENDOFPREHELPTEXT2';
599 ###############################################################################
601 # Author and Copyright (c):
602 # Rob van Son, © 1995,1996,1997,1998,1999,2000,2001,2002-2012
603 # NKI-AVL Amsterdam
604 # r.v.son@nki.nl
605 # Institute of Phonetic Sciences & IFOTT/ACLS
606 # University of Amsterdam
607 # Email: R.J.J.H.vanSon@gmail.com
608 # Email: R.J.J.H.vanSon@uva.nl
609 # WWW : http://www.fon.hum.uva.nl/rob/
611 # License for use and disclaimers
613 # CGIscriptor merges plain ASCII HTML files transparantly
614 # with CGI variables, in-line PERL code, shell commands,
615 # and executable scripts in other scripting languages.
617 # This program is free software; you can redistribute it and/or
618 # modify it under the terms of the GNU General Public License
619 # as published by the Free Software Foundation; either version 2
620 # of the License, or (at your option) any later version.
622 # This program is distributed in the hope that it will be useful,
623 # but WITHOUT ANY WARRANTY; without even the implied warranty of
624 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
625 # GNU General Public License for more details.
627 # You should have received a copy of the GNU General Public License
628 # along with this program; if not, write to the Free Software
629 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
632 # Contributors:
633 # Rob van Son (R.J.J.H.vanSon@uva.nl)
634 # Gerd Franke franke@roo.de (designed the <DIV> behaviour)
636 #######################################################
637 ENDOFPREHELPTEXT2
639 #######################################################>>>>>>>>>>Start Remove
641 # You can skip the following code, it is an auto-splice
642 # procedure.
644 # Construct a slimmed down version of CGIscriptor
645 # (i.e., CGIscriptor.pl -slim > slimCGIscriptor.pl)
647 if($TrimDownCGIscriptor)
649 open(CGISCRIPTOR, "<CGIscriptor.pl")
650 || dieHandler(1, "<CGIscriptor.pl not slimmed down: $!\n");
651 my $SKIPtext = 0;
652 my $SKIPComments = 0;
654 while(<CGISCRIPTOR>)
656 my $SKIPline = 0;
658 ++$LineCount;
660 # Start of SKIP text
661 $SKIPtext = 1 if /[\>]{10}Start Remove/;
662 $SKIPComments = 1 if $SKIPtext == 1;
664 # Skip this line?
665 $SKIPline = 1 if $SKIPtext || ($SKIPComments && /^\s*\#/);
667 ++$PrintCount unless $SKIPline;
669 print STDOUT $_ unless $SKIPline;
671 # End of SKIP text ?
672 $SKIPtext = 0 if /[\<]{10}End Remove/;
674 # Ready!
675 print STDERR "\# Printed $PrintCount out of $LineCount lines\n";
676 exit;
679 #######################################################
681 if(grep(/\-\-help/i, @ARGV))
683 print << 'ENDOFHELPTEXT';
685 # HYPE
687 # CGIscriptor merges plain ASCII HTML files transparantly and safely
688 # with CGI variables, in-line PERL code, shell commands, and executable
689 # scripts in many languages (on-line and real-time). It combines the
690 # "ease of use" of HTML files with the versatillity of specialized
691 # scripts and PERL programs. It hides all the specifics and
692 # idiosyncrasies of correct output and CGI coding and naming. Scripts
693 # do not have to be aware of HTML, HTTP, or CGI conventions just as HTML
694 # files can be ignorant of scripts and the associated values. CGIscriptor
695 # complies with the W3C HTML 4.0 recommendations.
696 # In addition to its use as a WWW embeded CGI processor, it can
697 # be used as a command-line document preprocessor (text-filter).
699 # THIS IS HOW IT WORKS
701 # The aim of CGIscriptor is to execute "plain" scripts inside a text file
702 # using any required CGIparameters and environment variables. It
703 # is optimized to transparantly process HTML files inside a WWW server.
704 # The native language is Perl, but many other scripting languages
705 # can be used.
707 # CGIscriptor reads text files from the requested input file (i.e., from
708 # $YOUR_HTML_FILES$PATH_INFO) and writes them to <STDOUT> (i.e., the
709 # client requesting the service) preceded by the obligatory
710 # "Content-type: text/html\n\n" or "Content-type: text/plain\n\n" string
711 # (except for "raw" files which supply their own Content-type message
712 # and only if the SERVER_PROTOCOL supports HTTP, MAIL, or MIME).
714 # When CGIscriptor encounters an embedded script, indicated by an HTML4 tag
716 # <SCRIPT TYPE="text/ssperl" [CGI="$VAR='default value'"] [SRC="ScriptSource"]>
717 # PERL script
718 # </SCRIPT>
720 # or
722 # <SCRIPT TYPE="text/osshell" [CGI="$name='default value'"] [SRC="ScriptSource"]>
723 # OS Shell script
724 # </SCRIPT>
726 # construct (anything between []-brackets is optional, other MIME-types
727 # and scripting languages are supported), the embedded script is removed
728 # and both the contents of the source file (i.e., "do 'ScriptSource'")
729 # AND the script are evaluated as a PERL program (i.e., by eval()),
730 # shell script (i.e., by a "safe" version of `Command`, qx) or an external
731 # interpreter. The output of the eval() function takes the place of the
732 # original <SCRIPT></SCRIPT> construct in the output string. Any CGI
733 # parameters declared by the CGI attribute are available as simple perl
734 # variables, and can subsequently be made available as variables to other
735 # scripting languages (e.g., bash, python, or lisp).
737 # Example: printing "Hello World"
738 # <HTML><HEAD><TITLE>Hello World</TITLE>
739 # <BODY>
740 # <H1><SCRIPT TYPE="text/ssperl">"Hello World"</SCRIPT></H1>
741 # </BODY></HTML>
743 # Save this in a file, hello.html, in the directory you indicated with
744 # $YOUR_HTML_FILES and access http://your_server/SHTML/hello.html
745 # (or to whatever name you use as an alias for CGIscriptor.pl).
746 # This is realy ALL you need to do to get going.
748 # You can use any values that are delivered in CGI-compliant form (i.e.,
749 # the "?name=value" type URL additions) transparently as "$name" variables
750 # in your scripts IFF you have declared them in the CGI attribute of
751 # a META or SCRIPT tag before e.g.:
752 # <META CONTENT="text/ssperl; CGI='$name = `default value`'
753 # [SRC='ScriptSource']">
754 # or
755 # <SCRIPT TYPE="text/ssperl" CGI="$name = 'default value'"
756 # [SRC='ScriptSource']>
757 # After such a 'CGI' attribute, you can use $name as an ordinary PERL variable
758 # (the ScriptSource file is immediately evaluated with "do 'ScriptSource'").
759 # The CGIscriptor script allows you to write ordinary HTML files which will
760 # include dynamic CGI aware (run time) features, such as on-line answers
761 # to specific CGI requests, queries, or the results of calculations.
763 # For example, if you wanted to answer questions of clients, you could write
764 # a Perl program called "Answer.pl" with a function "AnswerQuestion()"
765 # that prints out the answer to requests given as arguments. You then write
766 # an HTML page "Respond.html" containing the following fragment:
768 # <center>
769 # The Answer to your question
770 # <META CONTENT="text/ssperl; CGI='$Question'">
771 # <h3><SCRIPT TYPE="text/ssperl">$Question</SCRIPT></h3>
772 # is
773 # <h3><SCRIPT TYPE="text/ssperl" SRC="./PATH/Answer.pl">
774 # AnswerQuestion($Question);
775 # </SCRIPT></h3>
776 # </center>
777 # <FORM ACTION=Respond.html METHOD=GET>
778 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
779 # <INPUT TYPE=SUBMIT VALUE="Ask">
780 # </FORM>
782 # The output could look like the following (in HTML-speak):
784 # <CENTER>
785 # The Answer to your question
786 # <h3>What is the capital of the Netherlands?</h3>
787 # is
788 # <h3>Amsterdam</h3>
789 # </CENTER>
790 # <FORM ACTION=Respond.html METHOD=GET>
791 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
792 # <INPUT TYPE=SUBMIT VALUE="Ask">
794 # Note that the function "Answer.pl" does know nothing about CGI or HTML,
795 # it just prints out answers to arguments. Likewise, the text has no
796 # provisions for scripts or CGI like constructs. Also, it is completely
797 # trivial to extend this "program" to use the "Answer" later in the page
798 # to call up other information or pictures/sounds. The final text never
799 # shows any cue as to what the original "source" looked like, i.e.,
800 # where you store your scripts and how they are called.
802 # There are some extra's. The argument of the files called in a SRC= tag
803 # can access the CGI variables declared in the preceding META tag from
804 # the @ARGV array. Executable files are called as:
805 # `file '$ARGV[0]' ... ` (e.g., `Answer.pl \'$Question\'`;)
806 # The files called from SRC can even be (CGIscriptor) html files which are
807 # processed in-line. Furthermore, the SRC= tag can contain a perl block
808 # that is evaluated. That is,
809 # <META CONTENT="text/ssperl; CGI='$Question' SRC='{$Question}'">
810 # will result in the evaluation of "print do {$Question};" and the VALUE
811 # of $Question will be printed. Note that these "SRC-blocks" can be
812 # preceded and followed by other file names, but only a single block is
813 # allowed in a SRC= tag.
815 # One of the major hassles of dynamic WWW pages is the fact that several
816 # mutually incompatible browsers and platforms must be supported. For example,
817 # the way sound is played automatically is different for Netscape and
818 # Internet Explorer, and for each browser it is different again on
819 # Unix, MacOS, and Windows. Realy dangerous is processing user-supplied
820 # (form-) values to construct email addresses, file names, or database
821 # queries. All Apache WWW-server exploits reported in the media are
822 # based on faulty CGI-scripts that didn't check their user-data properly.
824 # There is no panacee for these problems, but a lot of work and problems
825 # can be saved by allowing easy and transparent control over which
826 # <SCRIPT></SCRIPT> blocks are executed on what CGI-data. CGIscriptor
827 # supplies such a method in the form of a pair of attributes:
828 # IF='...condition..' and UNLESS='...condition...'. When added to a
829 # script tag, the whole block (including the SRC attribute) will be
830 # ignored if the condition is false (IF) or true (UNLESS).
831 # For example, the following block will NOT be evaluated if the value
832 # of the CGI variable FILENAME is NOT a valid filename:
834 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
835 # IF='CGIscriptor::CGIsafeFileName($FILENAME)'>
836 # .....
837 # </SCRIPT>
839 # (the function CGIsafeFileName(String) returns an empty string ("")
840 # if the String argument is not a valid filename).
841 # The UNLESS attribute is the mirror image of IF.
843 # A user manual follows the HTML 4 and security paragraphs below.
845 ##########################################################################
847 # HTML 4 compliance
849 # In general, CGIscriptor.pl complies with the HTML 4 recommendations of
850 # the W3C. This means that any software to manage Web sites will be able
851 # to handle CGIscriptor files, as will web agents.
853 # All script code should be placed between <SCRIPT></SCRIPT> tags, the
854 # script type is indicated with TYPE="mime-type", the LANGUAGE
855 # feature is ignored, and a SRC feature is implemented. All CGI specific
856 # features are delegated to the CGI attribute.
858 # However, the behavior deviates from the W3C recommendations at some
859 # points. Most notably:
860 # 0- The scripts are executed at the server side, invissible to the
861 # client (i.e., the browser)
862 # 1- The mime-types are personal and idiosyncratic, but can be adapted.
863 # 2- Code in the body of a <SCRIPT></SCRIPT> tag-pair is still evaluated
864 # when a SRC feature is present.
865 # 3- The SRC attribute reads a list of files.
866 # 4- The files in a SRC attribute are processed according to file type.
867 # 5- The SRC attribute evaluates inline Perl code.
868 # 6- Processed META, DIV, INS tags are removed from the output
869 # document.
870 # 7- All attributes of the processed META tags, except CONTENT, are ignored
871 # (i.e., deleted from the output).
872 # 8- META tags can be placed ANYWHERE in the document.
873 # 9- Through the SRC feature, META tags can have visible output in the
874 # document.
875 # 10- The CGI attribute that declares CGI parameters, can be used
876 # inside the <SCRIPT> tag.
877 # 11- Use of an extended quote set, i.e., '', "", ``, (), {}, []
878 # and their \-slashed combinations: \'\', \"\", \`\`, \(\),
879 # \{\}, \[\].
880 # 12- IF and UNLESS attributes to <SCRIPT>, <META>, <DIV>, <INS> tags.
881 # 13- <DIV> tags cannot be nested, DIV tags are not
882 # rendered with new-lines.
883 # 14- The XML style <TAG .... /> is recognized and handled correctly.
884 # (i.e., no content is processed)
886 # The reasons for these choices are:
887 # You can still write completely HTML4 compliant documents. CGIscriptor
888 # will not force you to write "deviant" code. However, it allows you to
889 # do so (which is, in fact, just as bad). The prime design principle
890 # was to allow users to include plain Perl code. The code itself should
891 # be "enhancement free". Therefore, extra features were needed to
892 # supply easy access to CGI and Web site components. For security
893 # reasons these have to be declared explicitly. The SRC feature
894 # transparently manages access to external files, especially the safe
895 # use of executable files.
896 # The CGI attribute handles the declarations of external (CGI) variables
897 # in the SCRIPT and META tag's.
898 # EVERYTHING THE CGI ATTRIBUTE AND THE META TAG DO CAN BE DONE INSIDE
899 # A <SCRIPT></SCRIPT> TAG CONSTRUCT.
901 # The reason for the IF, UNLESS, and SRC attributes (and their Perl code
902 # evaluation) were build into the META and SCRIPT tags is part laziness,
903 # part security. The SRC blocks allows more compact documents and easier
904 # debugging. The values of the CGI variables can be immediately screened
905 # for security by IF or UNLESS conditions, and even SRC attributes (e.g.,
906 # email addresses and file names), and a few commands can be called
907 # without having to add another Perl TAG pair. This is especially important
908 # for documents that require the use of other (more restricted) "scripting"
909 # languages and facilities that lag transparent control structures.
911 ##########################################################################
913 # SECURITY
915 # Your WWW site is a few keystrokes away from a few hundred million internet
916 # users. A fair percentage of these users knows more about your computer
917 # than you do. And some of these just might have bad intentions.
919 # To ensure uncompromized operation of your server and platform, several
920 # features are incorporated in CGIscriptor.pl to enhance security.
921 # First of all, you should check the source of this program. No security
922 # measures will help you when you download programs from anonymous sources.
923 # If you want to use THIS file, please make sure that it is uncompromized.
924 # The best way to do this is to contact the source and try to determine
925 # whether s/he is reliable (and accountable).
927 # BE AWARE THAT ANY PROGRAMMER CAN CHANGE THIS PROGRAM IN SUCH A WAY THAT
928 # IT WILL SET THE DOORS TO YOUR SYSTEM WIDE OPEN
930 # I would like to ask any user who finds bugs that could compromise
931 # security to report them to me (and any other bug too,
932 # Email: R.J.J.H.vanSon@uva.nl or ifa@hum.uva.nl).
934 # Security features
936 # 1 Invisibility
937 # The inner workings of the HTML source files are completely hidden
938 # from the client. Only the HTTP header and the ever changing content
939 # of the output distinguish it from the output of a plain, fixed HTML
940 # file. Names, structures, and arguments of the "embedded" scripts
941 # are invisible to the client. Error output is suppressed except
942 # during debugging (user configurable).
944 # 2 Separate directory trees
945 # Directories containing Inline text and script files can reside on
946 # separate trees, distinct from those of the HTTP server. This means
947 # that NEITHER the text files, NOR the script files can be read by
948 # clients other than through CGIscriptor.pl, UNLESS they are
949 # EXPLICITELY made available.
951 # 3 Requests are NEVER "evaluated"
952 # All client supplied values are used as literal values (''-quoted).
953 # Client supplied ''-quotes are ALWAYS removed. Therefore, as long as the
954 # embedded scripts do NOT themselves evaluate these values, clients CANNOT
955 # supply executable commands. Be sure to AVOID scripts like:
957 # <META CONTENT="text/ssperl; CGI='$UserValue'">
958 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 $UserValue`;</SCRIPT>
960 # These are a recipe for disaster. However, the following quoted
961 # form should be save (but is still not adviced):
963 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 \'$UserValue\'`;</SCRIPT>
965 # A special function, SAFEqx(), will automatically do exactly this,
966 # e.g., SAFEqx('ls -1 $UserValue') will execute `ls -1 \'$UserValue\'`
967 # with $UserValue interpolated. I recommend to use SAFEqx() instead
968 # of backticks whenever you can. The OS shell scripts inside
970 # <SCRIPT TYPE="text/osshell">ls -1 $UserValue</SCRIPT>
972 # are handeld by SAFEqx and automatically ''-quoted.
974 # 4 Logging of requests
975 # All requests can be logged separate from the Host server. The level of
976 # detail is user configurable: Including or excluding the actual queries.
977 # This allows for the inspection of (im-) proper use.
979 # 5 Access control: Clients
980 # The Remote addresses can be checked against a list of authorized
981 # (i.e., accepted) or non-authorized (i.e., rejected) clients. Both
982 # REMOTE_HOST and REMOTE_ADDR are tested so clients without a proper
983 # HOST name can be (in-) excluded by their IP-address. Client patterns
984 # containing all numbers and dots are considered IP-addresses, all others
985 # domain names. No wild-cards or regexp's are allowed, only partial
986 # addresses.
987 # Matching of names is done from the back to the front (domain first,
988 # i.e., $REMOTE_HOST =~ /\Q$pattern\E$/is), so including ".edu" will
989 # accept or reject all clients from the domain EDU. Matching of
990 # IP-addresses is done from the front to the back (domain first, i.e.,
991 # $REMOTE_ADDR =~ /^\Q$pattern\E/is), so including "128." will (in-)
992 # exclude all clients whose IP-address starts with 128.
993 # There are two special symbols: "-" matches HOSTs with no name and "*"
994 # matches ALL HOSTS/clients.
995 # For those needing more expressional power, lines starting with
996 # "-e" are evaluated by the perl eval() function. E.g.,
997 # '-e $REMOTE_HOST =~ /\.edu$/is;' will accept/reject clients from the
998 # domain '.edu'.
1000 # 6 Access control: Files
1001 # In principle, CGIscriptor could read ANY file in the directory
1002 # tree as discussed in 1. However, for security reasons this is
1003 # restricted to text files. It can be made more restricted by entering
1004 # a global file pattern (e.g., ".html"). This is done by default.
1005 # For each client requesting access, the file pattern(s) can be made
1006 # more restrictive than the global pattern by entering client specific
1007 # file patterns in the Access Control files (see 5).
1008 # For example: if the ACCEPT file contained the lines
1009 # * DEMO
1010 # .hum.uva.nl LET
1011 # 145.18.230.
1012 # Then all clients could request paths containing "DEMO" or "demo", e.g.
1013 # "/my/demo/file.html" ($PATH_INFO =~ /\Q$pattern\E/), Clients from
1014 # *.hum.uva.nl could also request paths containing "LET or "let", e.g.
1015 # "/my/let/file.html", and clients from the local cluster
1016 # 145.18.230.[0-9]+ could access ALL files.
1017 # Again, for those needing more expressional power, lines starting with
1018 # "-e" are evaluated. For instance:
1019 # '-e $REMOTE_HOST =~ /\.edu$/is && $PATH_INFO =~ m@/DEMO/@is;'
1020 # will accept/reject requests for files from the directory "/demo/" from
1021 # clients from the domain '.edu'.
1023 # 7 Access control: Server side session tickets
1024 # Specific paths can be controlled by Session Tickets which must be
1025 # present as a SESSIONTICKET=<value> CGI variable in the request. These paths
1026 # are defined in %TicketRequiredPatterns as pairs of:
1027 # ('regexp' => 'SessionPath\tPasswordPath\tLogin.html\tExpiration').
1028 # Session Tickets are stored in a separate directory (SessionPath, e.g.,
1029 # "Private/.Session") as files with the exact same name of the SESSIONTICKET
1030 # CGI. The following is an example:
1031 # Type: SESSION
1032 # IPaddress: 127.0.0.1
1033 # AllowedPaths: ^/Private/Name/
1034 # Expires: 3600
1035 # Username: test
1036 # ...
1037 # Other content can follow.
1039 # It is adviced that Session Tickets should be deleted
1040 # after some (idle) time. The IP address should be the IP number at login, and
1041 # the SESSIONTICKET will be rejected if it is presented from another IP address.
1042 # AllowedPaths and DeniedPaths are perl regexps. Be careful how they match. Make sure to delimit
1043 # the names to prevent access to overlapping names, eg, "^/Private/Rob" will also
1044 # match "^/Private/Robert", however, "^/Private/Rob/" will not. Expires is the
1045 # time the ticket will remain valid after creation (file ctime). Time can be given
1046 # in s[econds] (default), m[inutes], h[hours], or d[ays], eg, "24h" means 24 hours.
1047 # None of these need be present, but the Ticket must have a non-zero size.
1049 # Next to Session Tickets, there are two other type of ticket files:
1050 # - LOGIN tickets store information about a current login request
1051 # - PASSWORD ticket store account information to authorize login requests
1053 # 8 Query length limiting
1054 # The length of the Query string can be limited. If CONTENT_LENGTH is larger
1055 # than this limit, the request is rejected. The combined length of the
1056 # Query string and the POST input is checked before any processing is done.
1057 # This will prevent clients from overloading the scripts.
1058 # The actual, combined, Query Size is accessible as a variable through
1059 # $CGI_Content_Length.
1061 # 9 Illegal filenames, paths, and protected directories
1062 # One of the primary security concerns in handling CGI-scripts is the
1063 # use of "funny" characters in the requests that con scripts in executing
1064 # malicious commands. Examples are inserting ';', null bytes, or <newline>
1065 # characters in URL's and filenames, followed by executable commands. A
1066 # special variable $FileAllowedChars stores a string of all allowed
1067 # characters. Any request that translates to a filename with a character
1068 # OUTSIDE this set will be rejected.
1069 # In general, all (readable files) in the DocumentRoot tree are accessible.
1070 # This might not be what you want. For instance, your DocumentRoot directory
1071 # might be the working directory of a CVS project and contain sensitive
1072 # information (e.g., the password to get to the repository). You can block
1073 # access to these subdirectories by adding the corresponding patterns to
1074 # the $BlockPathAccess variable. For instance, $BlockPathAccess = '/CVS/'
1075 # will block any request that contains '/CVS/' or:
1076 # die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;
1078 #10 The execution of code blocks can be controlled in a transparent way
1079 # by adding IF or UNLESS conditions in the tags themselves. That is,
1080 # a simple check of the validity of filenames or email addresses can
1081 # be done before any code is executed.
1083 ###############################################################################
1085 # USER MANUAL (sort of)
1087 # CGIscriptor removes embedded scripts, indicated by an HTML 4 type
1088 # <SCRIPT TYPE='text/ssperl'> </SCRIPT> or <SCRIPT TYPE='text/osshell'>
1089 # </SCRIPT> constructs. CGIscriptor also recognizes XML-type
1090 # <SCRIPT TYPE='text/ssperl'/> constructs. These are usefull when
1091 # the necessary code is already available in the TAG itself (e.g.,
1092 # using external files). The contents of the directive are executed by
1093 # the PERL eval() and `` functions (in a separate name space). The
1094 # result of the eval() function replaces the <SCRIPT> </SCRIPT> construct
1095 # in the output file. You can use the values that are delivered in
1096 # CGI-compliant form (i.e., the "?name=value&.." type URL additions)
1097 # transparently as "$name" variables in your directives after they are
1098 # defined in a <META> or <SCRIPT> tag.
1099 # If you define the variable "$CGIscriptorResults" in a CGI attribute, all
1100 # subsequent <SCRIPT> and <META> results (including the defining
1101 # tag) will also be pushed onto a stack: @CGIscriptorResults. This list
1102 # behaves like any other, ordinary list and can be manipulated.
1104 # Both GET and POST requests are accepted. These two methods are treated
1105 # equal. Variables, i.e., those values that are determined when a file is
1106 # processed, are indicated in the CGI attribute by $<name> or $<name>=<default>
1107 # in which <name> is the name of the variable and <default> is the value
1108 # used when there is NO current CGI value for <name> (you can use
1109 # white-spaces in $<name>=<default> but really DO make sure that the
1110 # default value is followed by white space or is quoted). Names can contain
1111 # any alphanumeric characters and _ (i.e., names match /[\w]+/).
1112 # If the Content-type: is 'multipart/*', the input is treated as a
1113 # MIME multipart message and automatically delimited. CGI variables get
1114 # the "raw" (i.e., undecoded) body of the corresponding message part.
1116 # Variables can be CGI variables, i.e., those from the QUERY_STRING,
1117 # environment variables, e.g., REMOTE_USER, REMOTE_HOST, or REMOTE_ADDR,
1118 # or predefined values, e.g., CGI_Decoded_QS (The complete, decoded,
1119 # query string), CGI_Content_Length (the length of the decoded query
1120 # string), CGI_Year, CGI_Month, CGI_Time, and CGI_Hour (the current
1121 # date and time).
1123 # All these are available when defined in a CGI attribute. All environment
1124 # variables are accessible as $ENV{'name'}. So, to access the REMOTE_HOST
1125 # and the REMOTE_USER, use, e.g.:
1127 # <SCRIPT TYPE='text/ssperl'>
1128 # ($ENV{'REMOTE_HOST'}||"-")." $ENV{'REMOTE_USER'}"
1129 # </SCRIPT>
1131 # (This will print a "-" if REMOTE_HOST is not known)
1132 # Another way to do this is:
1134 # <META CONTENT="text/ssperl; CGI='$REMOTE_HOST = - $REMOTE_USER'">
1135 # <SCRIPT TYPE='text/ssperl'>"$REMOTE_HOST $REMOTE_USER"</SCRIPT>
1136 # or
1137 # <META CONTENT='text/ssperl; CGI="$REMOTE_HOST = - $REMOTE_USER"
1138 # SRC={"$REMOTE_HOST $REMOTE_USER\n"}'>
1140 # This is possible because ALL environment variables are available as
1141 # CGI variables. The environment variables take precedence over CGI
1142 # names in case of a "name clash". For instance:
1143 # <META CONTENT="text/ssperl; CGI='$HOME' SRC={$HOME}">
1144 # Will print the current HOME directory (environment) irrespective whether
1145 # there is a CGI variable from the query
1146 # (e.g., Where do you live? <INPUT TYPE="TEXT" NAME="HOME">)
1147 # THIS IS A SECURITY FEATURE. It prevents clients from changing
1148 # the values of defined environment variables (e.g., by supplying
1149 # a bogus $REMOTE_ADDR). Although $ENV{} is not changed by the META tags,
1150 # it would make the use of declared variables insecure. You can still
1151 # access CGI variables after a name clash with
1152 # CGIscriptor::CGIparseValue(<name>).
1154 # Some CGI variables are present several times in the query string
1155 # (e.g., from multiple selections). These should be defined as
1156 # @VARIABLENAME=default in the CGI attribute. The list @VARIABLENAME
1157 # will contain ALL VARIABLENAME values from the query, or a single
1158 # default value. If there is an ENVIRONMENT variable of the
1159 # same name, it will be used instead of the default AND the query
1160 # values. The corresponding function is
1161 # CGIscriptor::CGIparseValueList(<name>)
1163 # CGI variables collected in a @VARIABLENAME list are unordered.
1164 # When more structured variables are needed, a hash table can be used.
1165 # A variable defined as %VARIABLE=default will collect all
1166 # CGI-parameters whose name start with 'VARIABLE' in a hash table with
1167 # the remainder of the name as a key. For instance, %PERSON will
1168 # collect PERSONname='John Doe', PERSONbirthdate='01 Jan 00', and
1169 # PERSONspouse='Alice' into a hash table %PERSON such that $PERSON{'spouse'}
1170 # equals 'Alice'. Any default value or environment value will be stored
1171 # under the "" key. If there is an ENVIRONMENT variable of the same name,
1172 # it will be used instead of the default AND the query values. The
1173 # corresponding function is CGIscriptor::CGIparseValueHash(<name>)
1175 # This method of first declaring your environment and CGI variables
1176 # before being able to use them in the scripts might seem somewhat
1177 # clumsy, but it protects you from inadvertedly printing out the values of
1178 # system environment variables when their names coincide with those used
1179 # in the CGI forms. It also prevents "clients" from supplying CGI
1180 # parameter values for your private variables.
1181 # THIS IS A SECURITY FEATURE!
1184 # NON-HTML CONTENT TYPES
1186 # Normally, CGIscriptor prints the standard "Content-type: text/html\n\n"
1187 # message before anything is printed. This has been extended to include
1188 # plain text (.txt) files, for which the Content-type (MIME type)
1189 # 'text/plain' is printed. In all other respects, text files are treated
1190 # as HTML files (this can be switched off by removing '.txt' from the
1191 # $FilePattern variable) . When the content type should be something else,
1192 # e.g., with multipart files, use the $RawFilePattern (.xmr, see also next
1193 # item). CGIscriptor will not print a Content-type message for this file
1194 # type (which must supply its OWN Content-type message). Raw files must
1195 # still conform to the <SCRIPT></SCRIPT> and <META> tag specifications.
1198 # NON-HTML FILES
1200 # CGIscriptor is intended to process HTML and text files only. You can
1201 # create documents of any mime-type on-the-fly using "raw" text files,
1202 # e.g., with the .xmr extension. However, CGIscriptor will not process
1203 # binary files of any type, e.g., pictures or sounds. Given the sheer
1204 # number of formats, I do not have any intention to do so. However,
1205 # an escape route has been provided. You can construct a genuine raw
1206 # (.xmr) text file that contains the perl code to service any file type
1207 # you want. If the global $BinaryMapFile variable contains the path to
1208 # this file (e.g., /BinaryMapFile.xmr), this file will be called
1209 # whenever an unsupported (non-HTML) file type is requested. The path
1210 # to the requested binary file is stored in $ENV('CGI_BINARY_FILE')
1211 # and can be used like any other CGI-variable. Servicing binary files
1212 # then becomes supplying the correct Content-type (e.g., print
1213 # "Content-type: image/jpeg\n\n";) and reading the file and writing it
1214 # to STDOUT (e.g., using sysread() and syswrite()).
1217 # THE META TAG
1219 # All attributes of a META tag are ignored, except the
1220 # CONTENT='text/ssperl; CGI=" ... " [SRC=" ... "]' attribute. The string
1221 # inside the quotes following the CONTENT= indication (white-space is
1222 # ignored, "" '' `` (){}[]-quote pairs are allowed, plus their \ versions)
1223 # MUST start with any of the CGIscriptor mime-types (e.g.: text/ssperl or
1224 # text/osshell) and a comma or semicolon.
1225 # The quoted string following CGI= contains a white-space separated list
1226 # of declarations of the CGI (and Environment) values and default values
1227 # used when no CGI values are supplied by the query string.
1229 # If the default value is a longer string containing special characters,
1230 # possibly spanning several lines, the string must be enclosed in quotes.
1231 # You may use any pair of quotes or brackets from the list '', "", ``, (),
1232 # [], or {} to distinguish default values (or preceded by \, e.g., \(...\)
1233 # is different from (...)). The outermost pair will always be used and any
1234 # other quotes inside the string are considered to be part of the string
1235 # value, e.g.,
1237 # $Value = {['this'
1238 # "and" (this)]}
1239 # will result in $Value getting the default value: ['this'
1240 # "and" (this)]
1241 # (NOTE that the newline is part of the default value!).
1243 # Internally, for defining and initializing CGI (ENV) values, the META
1244 # and SCRIPT tags use the functions "defineCGIvariable($name, $default)"
1245 # (scalars) and "defineCGIvariableList($name, $default)" (lists).
1246 # These functions can be used inside scripts as
1247 # "CGIscriptor::defineCGIvariable($name, $default)" and
1248 # "CGIscriptor::defineCGIvariableList($name, $default)".
1249 # "CGIscriptor::defineCGIvariableHash($name, $default)".
1251 # The CGI attribute will be processed exactly identical when used inside
1252 # the <SCRIPT> tag. However, this use is not according to the
1253 # HTML 4.0 specifications of the W3C.
1256 # THE DIV/INS TAGS
1258 # There is a problem when constructing html files containing
1259 # server-side perl scripts with standard HTML tools. These
1260 # tools will refuse to process any text between <SCRIPT></SCRIPT>
1261 # tags. This is quite annoying when you want to use large
1262 # HTML templates where you will fill in values.
1264 # For this purpose, CGIscriptor will read the neutral
1265 # <DIV CLASS="ssperl" ID="varname"></DIV> or
1266 # <INS CLASS="ssperl" ID="varname"></INS>
1267 # tag (in Cascading Style Sheet manner) Note that
1268 # "varname" has NO '$' before it, it is a bare name.
1269 # Any text between these <DIV ...></DIV> or
1270 # <INS ...></INS>tags will be assigned to '$varname'
1271 # as is (e.g., as a literal).
1272 # No processing or interpolation will be performed.
1273 # There is also NO nesting possible. Do NOT nest a
1274 # </DIV> inside a <DIV></DIV>! Moreover, neither INS nor
1275 # DIV tags do ensure a block structure in the final
1276 # rendering (i.e., no empty lines).
1278 # Note that <DIV CLASS="ssperl" ID="varname"/>
1279 # is handled the XML way. No content is processed,
1280 # but varname is defined, and any SRC directives are
1281 # processed.
1283 # You can use $varname like any other variable name.
1284 # However, $varname is NOT a CGI variable and will be
1285 # completely internal to your script. There is NO
1286 # interaction between $varname and the outside world.
1288 # To interpolate a DIV derived text, you can use:
1289 # $varname =~ s/([\]])/\\\1/g; # Mark ']'-quotes
1290 # $varname = eval("qq[$varname]"); # Interpolate all values
1292 # The DIV tags will process IF, UNLESS, CGI and
1293 # SRC attributes. The SRC files will be pre-pended to the
1294 # body text of the tag. SRC blocks are NOT executed.
1296 # CONDITIONAL PROCESSING: THE 'IF' AND 'UNLESS' ATTRIBUTES
1298 # It is often necessary to include code-blocks that should be executed
1299 # conditionally, e.g., only for certain browsers or operating system.
1300 # Furthermore, quite often sanity and security checks are necessary
1301 # before user (form) data can be processed, e.g., with respect to
1302 # email addresses and filenames.
1304 # Checks added to the code are often difficult to find, interpret or
1305 # maintain and in general mess up the code flow. This kind of confussion
1306 # is dangerous.
1307 # Also, for many of the supported "foreign" scripting languages, adding
1308 # these checks is cumbersome or even impossible.
1310 # As a uniform method for asserting the correctness of "context", two
1311 # attributes are added to all supported tags: IF and UNLESS.
1312 # They both evaluate their value and block execution when the
1313 # result is <FALSE> (IF) or <TRUE> (UNLESS) in Perl, e.g.,
1314 # UNLESS='$NUMBER \> 100;' blocks execution if $NUMBER <= 100. Note that
1315 # the backslash in the '\>' is removed and only used to differentiate
1316 # this conditional '>' from the tag-closing '>'. For symmetry, the
1317 # backslash in '\<' is also removed. Inside these conditionals,
1318 # ~/ and ./ are expanded to their respective directory root paths.
1320 # For example, the following tag will be ignored when the filename is
1321 # invalid:
1323 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
1324 # IF='CGIscriptor::CGIsafeFileName($FILENAME);'>
1325 # ...
1326 # </SCRIPT>
1328 # The IF and UNLESS values must be quoted. The same quotes are supported
1329 # as with the other attributes. The SRC attribute is ignored when IF and
1330 # UNLESS block execution.
1332 # NOTE: 'IF' and 'UNLESS' always evaluate perl code.
1335 # THE MAGIC SOURCE ATTRIBUTE (SRC=)
1337 # The SRC attribute inside tags accepts a list of filenames and URL's
1338 # separated by "," comma's (or ";" semicolons).
1339 # ALL the variable values defined in the CGI attribute are available
1340 # in @ARGV as if the file or block was executed from the command line,
1341 # in the exact order in which they were declared in the preceding CGI
1342 # attribute.
1344 # First, a SRC={}-block will be evaluated as if the code inside the
1345 # block was part of a <SCRIPT></SCRIPT> construct, i.e.,
1346 # "print do { code };'';" or `code` (i.e., SAFEqx('code)).
1347 # Only a single block is evaluated. Note that this is processed less
1348 # efficiently than <SCRIPT> </SCRIPT> blocks. Type of evaluation
1349 # depends on the content-type: Perl for text/ssperl and OS shell for
1350 # text/osshell. For other mime types (scripting languages), anything in
1351 # the source block is put in front of the code block "inside" the tag.
1353 # Second, executable files (i.e., -x filename != 0) are evaluated as:
1354 # print `filename \'$ARGV[0]\' \'$ARGV[1]\' ...`
1355 # That is, you can actually call executables savely from the SRC tag.
1357 # Third, text files that match the file pattern, used by CGIscriptor to
1358 # check whether files should be processed ($FilePattern), are
1359 # processed in-line (i.e., recursively) by CGIscriptor as if the code
1360 # was inserted in the original source file. Recursions, i.e., calling
1361 # a file inside itself, are blocked. If you need them, you have to code
1362 # them explicitely using "main::ProcessFile($file_path)".
1364 # Fourth, Perl text files (i.e., -T filename != 0) are evaluated as:
1365 # "do FileName;'';".
1367 # Last, URL's (i.e., starting with 'HTTP://', 'FTP://', 'GOPHER://',
1368 # 'TELNET://', 'WHOIS://' etc.) are loaded
1369 # and printed. The loading and handling of <BASE> and document header
1370 # is done by a command generated by main::GET_URL($URL [, 0]). You can enter your
1371 # own code (default is curl, wget, or snarf and some post-processing to add a <BASE> tag).
1373 # There are two pseudo-file names: PREFIX and POSTFIX. These implement
1374 # a switch from prefixing the SRC code/files (PREFIX, default) before the
1375 # content of the tag to appending the code after the content of the tag
1376 # (POSTFIX). The switches are done in the order in which the PREFIX and
1377 # POSTFIX labels are encountered. You can mix PREFIX and POSTFIX labels
1378 # in any order with the SRC files. Note that the ORDER of file execution
1379 # is determined for prefixed and postfixed files seperately.
1381 # File paths can be preceded by the URL protocol prefix "file://". This
1382 # is simply STRIPPED from the name.
1384 # Example:
1385 # The request
1386 # "http://cgi-bin/Action_Forms.pl/Statistics/Sign_Test.html?positive=8&negative=22
1387 # will result in printing "${SS_PUB}/Statistics/Sign_Test.html"
1388 # With QUERY_STRING = "positive=8&negative=22"
1390 # on encountering the lines:
1391 # <META CONTENT="text/osshell; CGI='$positive=11 $negative=3'">
1392 # <b><SCRIPT LANGUAGE=PERL TYPE="text/ssperl" SRC="./Statistics/SignTest.pl">
1393 # </SCRIPT></b><p>"
1395 # This line will be processed as:
1396 # "<b>`${SS_SCRIPT}/Statistics/SignTest.pl '8' '22'`</b><p>"
1398 # In which "${SS_SCRIPT}/Statistics/SignTest.pl" is an executable script,
1399 # This line will end up printed as:
1400 # "<b>p <= 0.0161</b><p>"
1402 # Note that the META tag itself will never be printed, and is invisible to
1403 # the outside world.
1405 # The SRC files in a DIV or INS tag will be added (pre-pended) to the body
1406 # of the <DIV></DIV> tag. Blocks are NOT executed! If you do not
1407 # need any content, you can use the <DIV...../> format.
1410 # THE CGISCRIPTOR ROOT DIRECTORIES ~/ AND ./
1412 # Inside <SCRIPT></SCRIPT> tags, filepaths starting
1413 # with "~/" are replaced by "$YOUR_HTML_FILES/", this way files in the
1414 # public directories can be accessed without direct reference to the
1415 # actual paths. Filepaths starting with "./" are replaced by
1416 # "$YOUR_SCRIPTS/" and this should only be used for scripts.
1418 # Note: this replacement can seriously affect Perl scripts. Watch
1419 # out for constructs like $a =~ s/aap\./noot./g, use
1420 # $a =~ s@aap\.@noot.@g instead.
1422 # CGIscriptor.pl will assign the values of $SS_PUB and $SS_SCRIPT
1423 # (i.e., $YOUR_HTML_FILES and $YOUR_SCRIPTS) to the environment variables
1424 # $SS_PUB and $SS_SCRIPT. These can be accessed by the scripts that are
1425 # executed.
1426 # Values not preceded by $, ~/, or ./ are used as literals
1429 # OS SHELL SCRIPT EVALUATION (CONTENT-TYPE=TEXT/OSSHELL)
1431 # OS scripts are executed by a "safe" version of the `` operator (i.e.,
1432 # SAFEqx(), see also below) and any output is printed. CGIscriptor will
1433 # interpolate the script and replace all user-supplied CGI-variables by
1434 # their ''-quoted values (actually, all variables defined in CGI attributes
1435 # are quoted). Other Perl variables are interpolated in a simple fasion,
1436 # i.e., $scalar by their value, @list by join(' ', @list), and %hash by
1437 # their name=value pairs. Complex references, e.g., @$variable, are all
1438 # evaluated in a scalar context. Quotes should be used with care.
1439 # NOTE: the results of the shell script evaluation will appear in the
1440 # @CGIscriptorResults stack just as any other result.
1441 # All occurrences of $@% that should NOT be interpolated must be
1442 # preceeded by a "\". Interpolation can be switched off completely by
1443 # setting $CGIscriptor::NoShellScriptInterpolation = 1
1444 # (set to 0 or undef to switch interpolation on again)
1445 # i.e.,
1446 # <SCRIPT TYPE="text/ssperl">
1447 # $CGIscriptor::NoShellScriptInterpolation = 1;
1448 # </SCRIPT>
1451 # RUN TIME TRANSLATION OF INPUT FILES
1453 # Allows general and global conversions of files using Regular Expressions.
1454 # Very handy (but costly) to rewrite legacy pages to a new format.
1455 # Select files to use it on with
1456 # my $TranslationPaths = 'filepattern';
1457 # This is costly. For efficiency, define:
1458 # $TranslationPaths = ''; when not using translations.
1459 # Accepts general regular expressions: [$pattern, $replacement]
1461 # Define:
1462 # my $TranslationPaths = 'filepattern'; # Pattern matching PATH_INFO
1464 # push(@TranslationTable, ['pattern', 'replacement']);
1465 # e.g. (for Ruby Rails):
1466 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
1467 # push(@TranslationTable, ['%>', '</SCRIPT>']);
1469 # Runs:
1470 # my $currentRegExp;
1471 # foreach $currentRegExp (@TranslationTable)
1473 # my ($pattern, $replacement) = @$currentRegExp;
1474 # $$text =~ s!$pattern!$replacement!msg;
1475 # };
1478 # EVALUATION OF OTHER SCRIPTING LANGUAGES
1480 # Adding a MIME-type and an interpreter command to
1481 # %ScriptingLanguages automatically will catch any other
1482 # scripting language in the standard
1483 # <SCRIPT TYPE="[mime]"></SCRIPT> manner.
1484 # E.g., adding: $ScriptingLanguages{'text/sspython'} = 'python';
1485 # will actually execute the folowing code in an HTML page
1486 # (ignore 'REMOTE_HOST' for the moment):
1487 # <SCRIPT TYPE="text/sspython">
1488 # # A Python script
1489 # x = ["A","real","python","script","Hello","World","and", REMOTE_HOST]
1490 # print x[4:8] # Prints the list ["Hello","World","and", REMOTE_HOST]
1491 # </SCRIPT>
1493 # The script code is NOT interpolated by perl, EXCEPT for those
1494 # interpreters that cannot handle variables themselves.
1495 # Currently, several interpreters are pre-installed:
1497 # Perl test - "text/testperl" => 'perl',
1498 # Python - "text/sspython" => 'python',
1499 # Ruby - "text/ssruby" => 'ruby',
1500 # Tcl - "text/sstcl" => 'tcl',
1501 # Awk - "text/ssawk" => 'awk -f-',
1502 # Gnu Lisp - "text/sslisp" => 'rep | tail +5 '.
1503 # "| egrep -v '> |^rep. |^nil\\\$'",
1504 # XLispstat - "text/xlispstat" => 'xlispstat | tail +7 '.
1505 # "| egrep -v '> \\\$|^NIL'",
1506 # Gnu Prolog- "text/ssprolog" => 'gprolog',
1507 # M4 macro's- "text/ssm4" => 'm4',
1508 # Born shell- "text/sh" => 'sh',
1509 # Bash - "text/bash" => 'bash',
1510 # C-shell - "text/csh" => 'csh',
1511 # Korn shell- "text/ksh" => 'ksh',
1512 # Praat - "text/sspraat" => "praat - | sed 's/Praat > //g'",
1513 # R - "text/ssr" => "R --vanilla --slave | sed 's/^[\[0-9\]*] //g'",
1514 # REBOL - "text/ssrebol" =>
1515 # "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\s*\[> \]* //g'",
1516 # PostgreSQL- "text/postgresql" => 'psql 2>/dev/null',
1517 # (psql)
1519 # Note that the "value" of $ScriptingLanguages{mime} must be a command
1520 # that reads Standard Input and writes to standard output. Any extra
1521 # output of interactive interpreters (banners, echo's, prompts)
1522 # should be removed by piping the output through 'tail', 'grep',
1523 # 'sed', or even 'awk' or 'perl'.
1525 # For access to CGI variables there is a special hashtable:
1526 # %ScriptingCGIvariables.
1527 # CGI variables can be accessed in three ways.
1528 # 1. If the mime type is not present in %ScriptingCGIvariables,
1529 # nothing is done and the script itself should parse the relevant
1530 # environment variables.
1531 # 2. If the mime type IS present in %ScriptingCGIvariables, but it's
1532 # value is empty, e.g., $ScriptingCGIvariables{"text/sspraat"} = '';,
1533 # the script text is interpolated by perl. That is, all $var, @array,
1534 # %hash, and \-slashes are replaced by their respective values.
1535 # 3. In all other cases, the CGI and environment variables are added
1536 # in front of the script according to the format stored in
1537 # %ScriptingCGIvariables. That is, the following (pseudo-)code is
1538 # executed for each CGI- or Environment variable defined in the CGI-tag:
1539 # printf(INTERPRETER, $ScriptingCGIvariables{$mime}, $CGI_NAME, $CGI_VALUE);
1541 # For instance, "text/testperl" => '$%s = "%s";' defines variable
1542 # definitions for Perl, and "text/sspython" => '%s = "%s"' for Python
1543 # (note that these definitions are not save, the real ones contain '-quotes).
1545 # THIS WILL NOT WORK FOR @VARIABLES, the (empty) $VARIABLES will be used
1546 # instead.
1548 # The $CGI_VALUE parameters are "shrubed" of all control characters
1549 # and quotes (by &shrubCGIparameter($CGI_VALUE)) for the options 2 and 3.
1550 # Control characters are replaced by \0<octal ascii value> (the exception
1551 # is \015, the newline, which is replaced by \n) and quotes
1552 # and backslashes by their HTML character
1553 # value (' -> &#39; ` -> &#96; " -> &quot; \ -> &#92; & -> &amper;).
1554 # For example:
1555 # if a client would supply the string value (in standard perl, e.g.,
1556 # \n means <newline>)
1557 # "/dev/null';\nrm -rf *;\necho '"
1558 # it would be processed as
1559 # '/dev/null&#39;;\nrm -rf *;\necho &#39;'
1560 # (e.g., sh or bash would process the latter more according to your
1561 # intentions).
1562 # If your intepreter requires different protection measures, you will
1563 # have to supply these in %main::SHRUBcharacterTR (string => translation),
1564 # e.g., $SHRUBcharacterTR{"\'"} = "&#39;";
1566 # Currently, the following definitions are used:
1567 # %ScriptingCGIvariables = (
1568 # "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value' (for testing)
1569 # "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
1570 # "text/ssruby" => '@%s = "%s"', # Ruby @VAR = "value"
1571 # "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
1572 # "text/ssawk" => '%s = "%s";', # Awk VAR = "value";
1573 # "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
1574 # "text/xlispstat" => '(setq %s "%s")', # Xlispstat (setq VAR "value")
1575 # "text/ssprolog" => '', # Gnu prolog (interpolated)
1576 # "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
1577 # "text/sh" => "\%s='\%s';", # Born shell VAR='value';
1578 # "text/bash" => "\%s='\%s';", # Born again shell VAR='value';
1579 # "text/csh" => "\$\%s = '\%s';", # C shell $VAR = 'value';
1580 # "text/ksh" => "\$\%s = '\%s';", # Korn shell $VAR = 'value';
1581 # "text/sspraat" => '', # Praat (interpolation)
1582 # "text/ssr" => '%s <- "%s";', # R VAR <- "value";
1583 # "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
1584 # "text/postgresql" => '', # PostgreSQL (interpolation)
1585 # "" => ""
1586 # );
1588 # Four tables allow fine-tuning of interpreter with code that should be
1589 # added before and after each code block:
1591 # Code added before each script block
1592 # %ScriptingPrefix = (
1593 # "text/testperl" => "\# Prefix Code;", # Perl script testing
1594 # "text/ssm4" => 'divert(0)' # M4 macro's (open STDOUT)
1595 # );
1596 # Code added at the end of each script block
1597 # %ScriptingPostfix = (
1598 # "text/testperl" => "\# Postfix Code;", # Perl script testing
1599 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1600 # );
1601 # Initialization code, inserted directly after opening (NEVER interpolated)
1602 # %ScriptingInitialization = (
1603 # "text/testperl" => "\# Initialization Code;", # Perl script testing
1604 # "text/ssawk" => 'BEGIN {', # Server Side awk scripts
1605 # "text/sslisp" => '(prog1 nil ', # Lisp (rep)
1606 # "text/xlispstat" => '(prog1 nil ', # xlispstat
1607 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1608 # );
1609 # Cleanup code, inserted before closing (NEVER interpolated)
1610 # %ScriptingCleanup = (
1611 # "text/testperl" => "\# Cleanup Code;", # Perl script testing
1612 # "text/sspraat" => 'Quit',
1613 # "text/ssawk" => '};', # Server Side awk scripts
1614 # "text/sslisp" => '(princ "\n" standard-output)).' # Closing print to rep
1615 # "text/xlispstat" => '(print "" *standard-output*)).' # Closing print to xlispstat
1616 # "text/postgresql" => '\q',
1617 # );
1620 # The SRC attribute is NOT magical for these interpreters. In short,
1621 # all code inside a source file or {} block is written verbattim
1622 # to the interpreter. No (pre-)processing or executional magic is done.
1624 # A serious shortcomming of the described mechanism for handling other
1625 # (scripting) languages, with respect to standard perl scripts
1626 # (i.e., 'text/ssperl'), is that the code is only executed when
1627 # the pipe to the interpreter is closed. So the pipe has to be
1628 # closed at the end of each block. This means that the state of the
1629 # interpreter (e.g., all variable values) is lost after the closing of
1630 # the next </SCRIPT> tag. The standard 'text/ssperl' scripts retain
1631 # all values and definitions.
1633 # APPLICATION MIME TYPES
1635 # To ease some important auxilliary functions from within the
1636 # html pages I have added them as MIME types. This uses
1637 # the mechanism that is also used for the evaluation of
1638 # other scripting languages, with interpolation of CGI
1639 # parameters (and perl-variables). Actually, these are
1640 # defined exactly like any other "scripting language".
1642 # text/ssdisplay: display some (HTML) text with interpolated
1643 # variables (uses `cat`).
1644 # text/sslogfile: write (append) the interpolated block to the file
1645 # mentioned on the first, non-empty line
1646 # (the filename can be preceded by 'File: ',
1647 # note the space after the ':',
1648 # uses `awk .... >> <filename>`).
1649 # text/ssmailto: send email directly from within the script block.
1650 # The first line of the body must contain
1651 # To:Name@Valid.Email.Address
1652 # (note: NO space between 'To:' and the email adres)
1653 # For other options see the mailto man pages.
1654 # It works by directly sending the (interpolated)
1655 # content of the text block to a pipe into the
1656 # Linux program 'mailto'.
1658 # In these script blocks, all Perl variables will be
1659 # replaced by their values. All CGI variables are cleaned before
1660 # they are used. These CGI variables must be redefined with a
1661 # CGI attribute to restore their original values.
1662 # In general, this will be more secure than constructing
1663 # e.g., your own email command lines. For instance, Mailto will
1664 # not execute any odd (forged) email addres, but just stops
1665 # when the email address is invalid and awk will construct
1666 # any filename you give it (e.g. '<File;rm\\\040-f' would end up
1667 # as a "valid" UNIX filename). Note that it will also gladly
1668 # store this file anywhere (/../../../etc/passwd will work!).
1669 # Use the CGIscriptor::CGIsafeFileName() function to clean the
1670 # filename.
1672 # SHELL SCRIPT PIPING
1674 # If a shell script starts with the UNIX style "#! <shell command> \n"
1675 # line, the rest of the shell script is piped into the indicated command,
1676 # i.e.,
1677 # open(COMMAND, "| command");print COMMAND $RestOfScript;
1679 # In many ways this is equivalent to the MIME-type profiling for
1680 # evaluating other scripting languages as discussed above. The
1681 # difference breaks down to convenience. Shell script piping is a
1682 # "raw" implementation. It allows you to control all aspects of
1683 # execution. Using the MIME-type profiling is easier, but has a
1684 # lot of defaults built in that might get in the way. Another
1685 # difference is that shell script piping uses the SAFEqx() function,
1686 # and MIME-type profiling does not.
1688 # Execution of shell scripts is under the control of the Perl Script blocks
1689 # in the document. The MIME-type triggered execution of <SCRIPT></SCRIPT>
1690 # blocks can be simulated easily. You can switch to a different shell,
1691 # e.g. tcl, completely by executing the following Perl commands inside
1692 # your document:
1694 # <SCRIPT TYPE="text/ssperl">
1695 # $main::ShellScriptContentType = "text/ssTcl"; # Yes, you can do this
1696 # CGIscriptor::RedirectShellScript('/usr/bin/tcl'); # Pipe to Tcl
1697 # $CGIscriptor::NoShellScriptInterpolation = 1;
1698 # </SCRIPT>
1700 # After this script is executed, CGIscriptor will parse scripts of
1701 # TYPE="text/ssTcl" and pipe their contents into '|/usr/bin/tcl'
1702 # WITHOUT interpolation (i.e., NO substitution of Perl variables).
1703 # The crucial function is :
1704 # CGIscriptor::RedirectShellScript('/usr/bin/tcl')
1705 # After executing this function, all shell scripts AND all
1706 # calls to SAFEqx()) are piped into '|/usr/bin/tcl'. If the argument
1707 # of RedirectShellScript is empty, e.g., '', the original (default)
1708 # value is reset.
1710 # The standard output, STDOUT, of any pipe is send to the client.
1711 # Currently, you should be carefull with quotes in such a piped script.
1712 # The results of a pipe is NOT put on the @CGIscriptorResults stack.
1713 # As a result, you do not have access to the output of any piped (#!)
1714 # process! If you want such access, execute
1715 # <SCRIPT TYPE="text/osshell">echo "script"|command</SCRIPT>
1716 # or
1717 # <SCRIPT TYPE="text/ssperl">
1718 # $resultvar = SAFEqx('echo "script"|command');
1719 # </SCRIPT>.
1721 # Safety is never complete. Although SAFEqx() prevents some of the
1722 # most obvious forms of attacks and security slips, it cannot prevent
1723 # them all. Especially, complex combinations of quotes and intricate
1724 # variable references cannot be handled safely by SAFEqx. So be on
1725 # guard.
1728 # PERL CODE EVALUATION (CONTENT-TYPE=TEXT/SSPERL)
1730 # All PERL scripts are evaluated inside a PERL package. This package
1731 # has a separate name space. This isolated name space protects the
1732 # CGIscriptor.pl program against interference from user code. However,
1733 # some variables, e.g., $_, are global and cannot be protected. You are
1734 # advised NOT to use such global variable names. You CAN write
1735 # directives that directly access the variables in the main program.
1736 # You do so at your own risk (there is definitely enough rope available
1737 # to hang yourself). The behavior of CGIscriptor becomes undefined if
1738 # you change its private variables during run time. The PERL code
1739 # directives are used as in:
1740 # $Result = eval($directive); print $Result;'';
1741 # ($directive contains all text between <SCRIPT></SCRIPT>).
1742 # That is, the <directive> is treated as ''-quoted string and
1743 # the result is treated as a scalar. To prevent the VALUE of the code
1744 # block from appearing on the client's screen, end the directive with
1745 # ';""</SCRIPT>'. Evaluated directives return the last value, just as
1746 # eval(), blocks, and subroutines, but only as a scalar.
1748 # IMPORTANT: All PERL variables defined are persistent. Each <SCRIPT>
1749 # </SCRIPT> construct is evaluated as a {}-block with associated scope
1750 # (e.g., for "my $var;" declarations). This means that values assigned
1751 # to a PERL variable can be used throughout the document unless they
1752 # were declared with "my". The following will actually work as intended
1753 # (note that the ``-quotes in this example are NOT evaluated, but used
1754 # as simple quotes):
1756 # <META CONTENT="text/ssperl; CGI=`$String='abcdefg'`">
1757 # anything ...
1758 # <SCRIPT TYPE=text/ssperl>@List = split('', $String);</SCRIPT>
1759 # anything ...
1760 # <SCRIPT TYPE=text/ssperl>join(", ", @List[1..$#List]);</SCRIPT>
1762 # The first <SCRIPT TYPE=text/ssperl></SCRIPT> construct will return the
1763 # value scalar(@List), the second <SCRIPT TYPE=text/ssperl></SCRIPT>
1764 # construct will print the elements of $String separated by commas, leaving
1765 # out the first element, i.e., $List[0].
1767 # Another warning: './' and '~/' are ALWAYS replaced by the values of
1768 # $YOUR_SCRIPTS and $YOUR_HTML_FILES, respectively . This can interfere
1769 # with pattern matching, e.g., $a =~ s/aap\./noot\./g will result in the
1770 # evaluations of $a =~ s/aap\\${YOUR_SCRIPTS}noot\\${YOUR_SCRIPTS}g. Use
1771 # s@<regexp>.@<replacement>.@g instead.
1774 # SERVER SIDE SESSIONS AND ACCESS CONTROL (LOGIN)
1776 # An infrastructure for user acount authorization and file access control
1777 # is available. Each request is matched against a list of URL path patterns.
1778 # If the request matches, a Session Ticket is required to access the URL.
1779 # This Session Ticket should be present as a CGI parameter or Cookie, eg:
1781 # CGI: SESSIONTICKET=&lt;value&gt;
1782 # Cookie: CGIscriptorSESSION=&lt;value&gt;
1784 # The example implementation stores Session Tickets as files in a local
1785 # directory. To create Session Tickets, a Login request must be given
1786 # with a LOGIN=&lt;value&gt; CGI parameter, a user name and a (doubly hashed)
1787 # password. The user name and (singly hashed) password are stored in a
1788 # PASSWORD ticket with the same name as the user account (name cleaned up
1789 # for security).
1791 # The example session model implements 4 functions:
1792 # - Login
1793 # The password is hashed with the user name and server side salt, and then
1794 # hashed with a random salt. Client and Server both perform these actions
1795 # and the Server only grants access if restults are the same. The server
1796 # side only stores the password hashed with the user name and
1797 # server side salt. Neither the plain password, nor the hashed password is
1798 # ever exchanged. Only values hashed with the one-time salt are exchanged.
1799 # - Session
1800 # For every access to a restricted URL, the Session Ticket is checked before
1801 # access is granted. There are three session modes. The first uses a fixed
1802 # Session Ticket that is stored as a cookie value in the browser (actually,
1803 # as a sessionStorage value). The second uses only the IP address at login
1804 # to authenticate requests. The third
1805 # is a Challenge mode, where the client has to calculate the value of the
1806 # next one-time Session Ticket from a value derived from the password and
1807 # a random string.
1808 # - Password Change
1809 # A new password is hashed with the user name and server side salt, and
1810 # then encrypted (XORed)
1811 # with the old password hashed with the user name and salt. That value is
1812 # exchanged and XORed with the stored old hashed(salt+password+username).
1813 # Again, the stored password value is never exchanged unencrypted.
1814 # - New Account
1815 # The text of a new account (Type: PASSWORD) file is constructed from
1816 # the new username (CGI: NEWUSERNAME, converted to lowercase) and
1817 # hashed new password (CGI: NEWPASSWORD). The same process is used to encrypt
1818 # the new password as is used for the Password Change function.
1819 # Again, the stored password value is never exchanged unencrypted.
1820 # Some default setting are encoded. For display in the browser, the new password
1821 # is reencrypted (XORed) with a special key, the old password hash
1822 # hashed with a session specific random hex value sent initially with the
1823 # session login ticket ($RANDOMSALT).
1824 # For example for user "NewUser" and password "NewPassword" with filename
1825 # "newuser":
1827 # Type: PASSWORD
1828 # Username: newuser
1829 # Password: 19afeadfba8d5dcd252e157fafd3010859f8762b87682b6b6cdb3e565194fa91
1830 # IPaddress: 127\.0\.0\.1
1831 # AllowedPaths: ^/Private/[\w\-]+\.html?
1832 # AllowedPaths: ^/Private/newuser/
1833 # Salt: e93cf858a1d5626bf095ea5c25df990dfa969ff5a5dc908b22c9a5229b525f65
1834 # Session: SESSION
1835 # Date: Fri Jun 29 12:46:22 2012
1836 # Time: 1340973982
1837 # Signature: 676c35d3aa63540293ea5442f12872bfb0a22665b504f58f804582493b6ef04e
1839 # The password is created with the commands:
1841 # printf '%s' '970e68017413fb0ea84d7fe3c463077636dd6d53486910d4a53c693dd4109b1aNewPasswordnewuser'|shasum -a 256
1843 # However, the password account files are protected against unauthorized change.
1844 # To obtain a valid Password account, the following command should be given:
1846 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
1847 # masterkey='Sherlock investigates oleander curry in Bath' \
1848 # password='NewPassword' \
1849 # Private/.Passwords/newuser
1852 # Implementation
1854 # The session authentication mechanism is based on the exchange of ticket
1855 # identifiers. A ticket identifier is just a string of characters, a name
1856 # or a random 64 character hexadecimal string. Ticket identifiers should be
1857 # "safe" filenames (except user names). There are four types of tickets:
1858 # PASSWORD: User account descriptors, including a user name and password
1859 # LOGIN: Temporary anonymous tickets used during login
1860 # IPADDRESS: Authetication tokens that allow access based on the IP address of the request
1861 # SESSION: Reusable authetication tokens
1862 # CHALLENGE: One-time authetication tokens
1863 # All tickets can have an expiration date in the form of a time duration
1864 # from creation, in seconds, minutes, hours, or days (+duration[smhd]).
1865 # An absolute time can be given in seconds since the epoch of the server host.
1866 # Note that expiration times of CHALLENGE authetication tokens are calculated
1867 # from the last access time. Accounts can include a maximal lifetime
1868 # for session tickets (MaxLifetime).
1870 # A Login page should create a LOGIN ticket file locally and send a
1871 # server specific salt, a Random salt, and a LOGIN ticket
1872 # identifier. The server side compares the username and hashed password,
1873 # actually hashed(Random salt+hashed(serversalt+password)) from the client with
1874 # the values it calculates from the stored Random salt from the LOGIN
1875 # ticket and the hashed(serversalt+password) from the PASSWORD ticket. If
1876 # successful, a new SESSION ticket is generated as a hash sum of the LOGIN
1877 # ticket and the stored password. This SESSION ticket should also be
1878 # generated by the client and stored as sessionStorage and cookie values
1879 # as needed. The Username, IP address and Path are available as
1880 # $LoginUsername, $LoginIPaddress, and $LoginPath, respectively.
1882 # The CHALLENGE protocol stores the same value as the SESSION tickets.
1883 # However, this value is not exchanged, but kept secret in the JavaScript
1884 # sessionStorage object. Instead, every page returned from the
1885 # server will contain a one-time Challenge value ($CHALLENGETICKET) which
1886 # has to be hashed with the stored value to return the current ticket
1887 # id string.
1889 # In the current example implementation, all random values are created as
1890 # full, 256 bit SHA256 hash values (Hex strings) of 64 bytes read from
1891 # /dev/urandom.
1894 # Authorization
1896 # A limited level of authorization tuning is build into the login system.
1897 # Each account file (PASSWORD ticket file) can contain a number of
1898 # Capabilities lines. These control special priveliges. The
1899 # Capabilities can be checked inside the HTML pages as part of the
1900 # ticket information. Two privileges are handled internally:
1901 # CreateUser and VariableREMOTE_ADDR.
1902 # CreateUser allows the logged in user to create a new user account.
1903 # With VariableREMOTE_ADDR, the session of the logged in user is
1904 # not limited to the Remote IP address from which the inital log-in took
1905 # place. Sessions can hop from one apparant (proxy) IP address to another,
1906 # e.g., when using Tor. Any IPaddress patterns given in the PASSWORD
1907 # ticket file remain in effect during the session. For security reasons,
1908 # the VariableREMOTE_ADDR capability is only effective if the session
1909 # type is CHALLENGE.
1912 # Security considerations with Session tickets
1914 # For strong security, please use end-to-end encryption. This can be
1915 # achieved using a VPN (Virtual Private Network), SSH tunnel, or a HTTPS
1916 # capable server with OpenSSL. The session ticket system of CGIscriptor.pl
1917 # is intended to be used as a simple authentication mechanism WITHOUT
1918 # END-TO-END ENCRYPTION. The authenticating mechanism tries to use some
1919 # simple means to protect the authentication process from eavesdropping.
1920 # For this it uses a secure hash function, SHA256. For all practial purposes,
1921 # it is impossible to "decrypt" a SHA256 sum. But this login scheme is
1922 # only as secure as your browser. Which, in general, is not very secure.
1924 # One weakness of the implemented procedure is that the Client obtains
1925 # the code to encrypt the passwords from the server. It is the JavaScript
1926 # code in the HTML pages. An attacker who could place himself between Server
1927 # and Client, a man in the middle attack, could change the code to
1928 # reveal the plaintext password and other information. There is no real
1929 # protection against this attack without end-to-end encryption and
1930 # authentication. A simple, but rather cumbersome, way to check for such
1931 # attacks would be to store known good copys of the pages (downloaded
1932 # with a browser or automatically with curl or wget) and
1933 # then use other tools to download new pages at random intervals and compare
1934 # them to the old pages. A simple diff command between old and
1935 # new files should give only differences in half a dozen lines, where only
1936 # hexadecimal salt values will actually differ.
1938 # Humans tend to reuse passwords. A compromise of a site running
1939 # CGIscriptor.pl could therefore lead to a compromise of user accounts at
1940 # other sites. Therefore, plain text passwords are never stored, used, or
1941 # exchanged. Instead, a server site salt value is "encrypted" with
1942 # the plain password and user name. Actually, all are concatenated and hashed
1943 # with a one-way secure hash function (SHA256) into a single string.
1944 # Whenever the word "password" is used, this hash sum is meant. Note that
1945 # the salts are generated from /dev/urandom. You should check whether the
1946 # implementation of /dev/urandom on your platform is secure before
1947 # relying on it. This might be a problem when running CGIscriptor under
1948 # Cygwin on MS Windows.
1949 # Note: no attempt is made to slow down the password hash, so bad
1950 # passwords can be cracked by brute force
1952 # As the (hashed) passwords are all that is needed to identify at the site,
1953 # these should not be stored in this form. A site specific passphrase
1954 # can be entered as an environment variable ($ENV{'CGIMasterKey'}). This
1955 # phrase is hashed with the server site salt and the result is hashed with
1956 # the user name and then XORed with the password when it is stored. Also, to
1957 # detect changes to the account (PASSWORD) and session tickets, a
1958 # (HMAC) hash of some of the contents of the ticket with the server salt and
1959 # CGIMasterKey is stored in each ticket.
1961 # Creating a valid (hashed) password, encrypt it with the CGIMasterKey and
1962 # construct a signature of the ticket are non-trivial. This has to be redone
1963 # with every change of the ticket file or CGIMasterKey change. CGIscriptor
1964 # can do this from the command line with the command:
1966 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
1967 # masterkey='Sherlock investigates oleander curry in Bath' \
1968 # password='There is no password like more password' \
1969 # admin
1971 # CGIscriptor will exit after this command with the first option being
1972 # --managelogin. Options have the form:
1974 # salt=[file or string]
1975 # Server salt value to use io the value
1976 # stored in the ticket file. Will replace the stored value if a new
1977 # password is given. If you change the server salt, you have to
1978 # reset all the passwords. There is absolutely no procedure known
1979 # to recover plaintext passwords, except asking the account holders.
1980 # You are strongly adviced to make a backup before you apply such a change
1981 # masterkey=[file or string]
1982 # CGIMasterKey used to read and decrypt the ticket
1983 # newmasterkey=[file or string]
1984 # CGIMasterKey used to encrypt, sign,
1985 # and write the ticket. Defaults to the masterkey. If you change
1986 # the masterkey, you will have to reset all the accounts. You are strongly
1987 # adviced to make a backup before you apply such a change
1988 # password=[file or string]
1989 # New plaintext password
1991 # When the value of an option is a existing file path, the first line of
1992 # that file is used. Options are followed by one or more paths plus names
1993 # of existing ticket files. Each password option is only used for a single
1994 # ticket file. It is most definitely a bad idea to use a password that is
1995 # identical to an existing filepath, as the file will be read instead. Be
1996 # aware that the name of the file should be a cleaned up version of the
1997 # Username. This will not be checked.
1999 # For the authentication and a change of password, the (old) password
2000 # is used to "encrypt" a random one-time token or the new password,
2001 # respectively. For authentication, decryption is not needed, so a secure
2002 # hash function (SHA256) is used to create a one-way hash sum "encryption".
2003 # A new password must be decrypted. New passwords are encryped by XORing
2004 # them with the old password.
2006 # Strong Passwords: It is so easy
2007 # If you only could see what you are typing
2009 # Your password might be vulnerable to brute force guessing
2010 # (https://en.wikipedia.org/wiki/Brute_force_attack).
2011 # Protections against such attacks are costly in terms of code
2012 # complexity, bugs, and execution time. However, there is a very
2013 # simple and secure counter measure. See the XKCD comic
2014 # (http://xkcd.com/936/). The phrase, "There is no password like more
2015 # password" would be both much easier to remember, and still stronger
2016 # than "h4]D%@m:49", at least before this phrase was pasted as an
2017 # example on the Internet.
2019 # For the procedures used at this site, a basic computer setup can
2020 # check in the order of a billion passwords per second. You need a
2021 # password (or phrase) strength in the order of 56 bits to be a
2022 # little secure (one year on a single computer). Please be so kind
2023 # and add the name of your favorite flower, dish, fictional
2024 # character, or small town to your password. Say, Oleander, Curry,
2025 # Sherlock, or Bath (each adds ~12 bits) or even the phrase "Sherlock
2026 # investigates oleander curry in Bath" (adds > 56 bits, note that
2027 # oleander is poisonous, so do not try this curry at home). That
2028 # would be more effective than adding a thousand rounds of encryption.
2029 # Typing long passwords without seeing what you are typing is
2030 # problematic. So a button should be included to make password
2031 # visible.
2034 # Technical matters
2036 Client side JavaScript code definitions. Variable names starting with '$'
2037 # are CGIscriptor CGI variables. Some of the hashes could be strengthened
2038 # by switching to HMAC signatures. However, the security issues of
2039 # maintaining parallel functions for HMAC in both Perl and Javascript seem
2040 # to be more serious than the attack vectors against the hashes. But HMAC
2041 # is indeed used for the ticket signatures.
2043 # // On Login
2044 # HashPlaintextPassword() {
2045 # var plaintextpassword = document.getElementById('PASSWORD');
2046 # var serversalt = document.getElementById('SERVERSALT');
2047 # var username = document.getElementById('CGIUSERNAME');
2048 # return hex_sha256(serversalt.value+plaintextpassword.value+username.value.toLowerCase());
2050 # var randomsalt = $RANDOMSALT; // From CGIscriptor
2051 # var loginticket = $LOGINTICKET; // From CGIscriptor
2052 # // Hash plaintext password
2053 # var password = HashPlaintextPassword();
2054 # // Authorize login
2055 # var hashedpassword = hex_sha256(randomsalt+password);
2056 # // Sessionticket
2057 # var sessionticket = hex_sha256(loginticket+password);
2058 # sessionStorage.setItem("CGIscriptorPRIVATE", sessionticket);
2059 # // Secretkey for encrypting new passwords, acts like a one-time pad
2060 # // Is set anew with every login, ie, also whith password changes
2061 # // and for each create new user request
2062 # var secretkey = hex_sha256(randomsalt+loginticket+password);
2063 # sessionStorage.setItem("CGIscriptorSECRET", secretkey);
2065 # // For a SESSION type request
2066 # sessionticket = sessionStorage.getItem("CGIscriptorPRIVATE");
2067 # createCookie("CGIscriptorSESSION",sessionticket, 0, "");
2069 // For a CHALLENGE type request
2070 # var sessionset = "$CHALLENGETICKET"; // From CGIscriptor
2071 # var sessionkey = sessionStorage.getItem("CGIscriptorPRIVATE");
2072 # sessionticket = hex_sha256(sessionset+sessionkey);
2073 # createCookie("CGIscriptorCHALLENGE",sessionticket, 0, "");
2075 # // For transmitting a new password
2076 # HashPlaintextNewPassword() {
2077 # var plaintextpassword = document.getElementById('NEWPASSWORD');
2078 # var serversalt = document.getElementById('SERVERSALT');
2079 # var username = document.getElementById('NEWUSERNAME');
2080 # return hex_sha256(serversalt.value+plaintextpassword.value+username.value.toLowerCase());
2083 # var newpassword = document.getElementById('NEWPASSWORD');
2084 # var newpasswordrep = document.getElementById('NEWPASSWORDREP');
2085 # // Hash plaintext password
2086 # newpassword.value = HashPlaintextNewPassword();
2087 # var secretkey = sessionStorage.getItem("CGIscriptorSECRET");
2089 # var encrypted = XOR_hex_strings(secretkey, newpassword.value);
2090 # newpassword.value = encrypted;
2091 # newpasswordrep.value = encrypted;
2093 # // XOR of hexadecimal strings of equal length
2094 # function XOR_hex_strings(hex1, hex2) {
2095 # var resultHex = "";
2096 # var maxlength = Math.max(hex1.length, hex2.length);
2098 # for(var i=0; i &lt; maxlength; ++i) {
2099 # var h1 = hex1.charAt(i);
2100 # if(! h1) h1='0';
2101 # var h2 = hex2.charAt(i);
2102 # if(! h2) h2 ='0';
2103 # var d1 = parseInt(h1,16);
2104 # var d2 = parseInt(h2,16);
2105 # var resultD = d1^d2;
2106 # resultHex = resultHex+resultD.toString(16);
2107 # };
2108 # return resultHex;
2109 # };
2111 # Password encryption based on $ENV{'CGIMasterKey'}.
2112 # Server side Perl code:
2114 # # Password encryption
2115 # my $masterkey = $ENV{'CGIMasterKey'}
2116 # my $hash1 = hash_string($serversalt.$masterkey);
2117 # my $CryptKey = hash_string($hash1.$username);
2118 # $password = XOR_hex_strings($CryptKey,$password);
2120 # # Key for HMAC signing
2121 # my $hash1 = hash_string($serversalt.$masterkey);
2122 # my $HMACKey = hash_string($username.$hash1);
2126 # USER EXTENSIONS
2128 # A CGIscriptor package is attached to the bottom of this file. With
2129 # this package you can personalize your version of CGIscriptor by
2130 # including often used perl routines. These subroutines can be
2131 # accessed by prefixing their names with CGIscriptor::, e.g.,
2132 # <SCRIPT LANGUAGE=PERL TYPE=text/ssperl>
2133 # CGIscriptor::ListDocs("/Books/*") # List all documents in /Books
2134 # </SCRIPT>
2135 # It already contains some useful subroutines for Document Management.
2136 # As it is a separate package, it has its own namespace, isolated from
2137 # both the evaluator and the main program. To access variables from
2138 # the document <SCRIPT></SCRIPT> blocks, use $CGIexecute::<var>.
2140 # Currently, the following functions are implemented
2141 # (precede them with CGIscriptor::, see below for more information)
2142 # - SAFEqx ('String') -> result of qx/"String"/ # Safe application of ``-quotes
2143 # Is used by text/osshell Shell scripts. Protects all CGI
2144 # (client-supplied) values with single quotes before executing the
2145 # commands (one of the few functions that also works WITHOUT CGIscriptor::
2146 # in front)
2147 # - defineCGIvariable ($name[, $default) -> 0/1 (i.e., failure/success)
2148 # Is used by the META tag to define and initialize CGI and ENV
2149 # name/value pairs. Tries to obtain an initializing value from (in order):
2150 # $ENV{$name}
2151 # The Query string
2152 # The default value given (if any)
2153 # (one of the few functions that also works WITHOUT CGIscriptor::
2154 # in front)
2155 # - CGIsafeFileName (FileName) -> FileName or ""
2156 # Check a string against the Allowed File Characters (and ../ /..).
2157 # Returns an empty string for unsafe filenames.
2158 # - CGIsafeEmailAddress (Email) -> Email or ""
2159 # Check a string against correct email address pattern.
2160 # Returns an empty string for unsafe addresses.
2161 # - RedirectShellScript ('CommandString') -> FILEHANDLER or undef
2162 # Open a named PIPE for SAFEqx to receive ALL shell scripts
2163 # - URLdecode (URL encoded string) -> plain string # Decode URL encoded argument
2164 # - URLencode (plain string) -> URL encoded string # Encode argument as URL code
2165 # - CGIparseValue (ValueName [, URL_encoded_QueryString]) -> Decoded value
2166 # Extract the value of a CGI variable from the global or a private
2167 # URL-encoded query (multipart POST raw, NOT decoded)
2168 # - CGIparseValueList (ValueName [, URL_encoded_QueryString])
2169 # -> List of decoded values
2170 # As CGIparseValue, but now assembles ALL values of ValueName into a list.
2171 # - CGIparseHeader (ValueName [, URL_encoded_QueryString]) -> Header
2172 # Extract the header of a multipart CGI variable from the global or a private
2173 # URL-encoded query ("" when not a multipart variable or absent)
2174 # - CGIparseForm ([URL_encoded_QueryString]) -> Decoded Form
2175 # Decode the complete global URL-encoded query or a private
2176 # URL-encoded query
2177 # - read_url(URL) # Returns the page from URL (with added base tag, both FTP and HTTP)
2178 # Uses main::GET_URL(URL, 1) to get at the command to read the URL.
2179 # - BrowseDirs(RootDirectory [, Pattern, Startdir, CGIname]) # print browsable directories
2180 # - ListDocs(Pattern [,ListType]) # Prints a nested HTML directory listing of
2181 # all documents, e.g., ListDocs("/*", "dl");.
2182 # - HTMLdocTree(Pattern [,ListType]) # Prints a nested HTML listing of all
2183 # local links starting from a given document, e.g.,
2184 # HTMLdocTree("/Welcome.html", "dl");
2187 # THE RESULTS STACK: @CGISCRIPTORRESULTS
2189 # If the pseudo-variable "$CGIscriptorResults" has been defined in a
2190 # META tag, all subsequent SCRIPT and META results are pushed
2191 # on the @CGIscriptorResults stack. This list is just another
2192 # Perl variable and can be used and manipulated like any other list.
2193 # $CGIscriptorResults[-1] is always the last result.
2194 # This is only of limited use, e.g., to use the results of an OS shell
2195 # script inside a Perl script. Will NOT contain the results of Pipes
2196 # or code from MIME-profiling.
2199 # USEFULL CGI PREDEFINED VARIABLES (DO NOT ASSIGN TO THESE)
2201 # $CGI_HOME - The DocumentRoot directory
2202 # $CGI_Decoded_QS - The complete decoded Query String
2203 # $CGI_Content_Length - The ACTUAL length of the Query String
2204 # $CGI_Date - Current date and time
2205 # $CGI_Year $CGI_Month $CGI_Day $CGI_WeekDay - Current Date
2206 # $CGI_Time - Current Time
2207 # $CGI_Hour $CGI_Minutes $CGI_Seconds - Current Time, split
2208 # GMT Date/Time:
2209 # $CGI_GMTYear $CGI_GMTMonth $CGI_GMTDay $CGI_GMTWeekDay $CGI_GMTYearDay
2210 # $CGI_GMTHour $CGI_GMTMinutes $CGI_GMTSeconds $CGI_GMTisdst
2213 # USEFULL CGI ENVIRONMENT VARIABLES
2215 # Variables accessible (in APACHE) as $ENV{<name>}
2216 # (see: "http://hoohoo.ncsa.uiuc.edu/cgi/env.html"):
2218 # QUERY_STRING - The query part of URL, that is, everything that follows the
2219 # question mark.
2220 # PATH_INFO - Extra path information given after the script name
2221 # PATH_TRANSLATED - Extra pathinfo translated through the rule system.
2222 # (This doesn't always make sense.)
2223 # REMOTE_USER - If the server supports user authentication, and the script is
2224 # protected, this is the username they have authenticated as.
2225 # REMOTE_HOST - The hostname making the request. If the server does not have
2226 # this information, it should set REMOTE_ADDR and leave this unset
2227 # REMOTE_ADDR - The IP address of the remote host making the request.
2228 # REMOTE_IDENT - If the HTTP server supports RFC 931 identification, then this
2229 # variable will be set to the remote user name retrieved from
2230 # the server. Usage of this variable should be limited to logging
2231 # only.
2232 # AUTH_TYPE - If the server supports user authentication, and the script
2233 # is protected, this is the protocol-specific authentication
2234 # method used to validate the user.
2235 # CONTENT_TYPE - For queries which have attached information, such as HTTP
2236 # POST and PUT, this is the content type of the data.
2237 # CONTENT_LENGTH - The length of the said content as given by the client.
2238 # SERVER_SOFTWARE - The name and version of the information server software
2239 # answering the request (and running the gateway).
2240 # Format: name/version
2241 # SERVER_NAME - The server's hostname, DNS alias, or IP address as it
2242 # would appear in self-referencing URLs
2243 # GATEWAY_INTERFACE - The revision of the CGI specification to which this
2244 # server complies. Format: CGI/revision
2245 # SERVER_PROTOCOL - The name and revision of the information protocol this
2246 # request came in with. Format: protocol/revision
2247 # SERVER_PORT - The port number to which the request was sent.
2248 # REQUEST_METHOD - The method with which the request was made. For HTTP,
2249 # this is "GET", "HEAD", "POST", etc.
2250 # SCRIPT_NAME - A virtual path to the script being executed, used for
2251 # self-referencing URLs.
2252 # HTTP_ACCEPT - The MIME types which the client will accept, as given by
2253 # HTTP headers. Other protocols may need to get this
2254 # information from elsewhere. Each item in this list should
2255 # be separated by commas as per the HTTP spec.
2256 # Format: type/subtype, type/subtype
2257 # HTTP_USER_AGENT - The browser the client is using to send the request.
2258 # General format: software/version library/version.
2261 # INSTRUCTIONS FOR RUNNING CGIscriptor ON UNIX
2263 # CGIscriptor.pl will run on any WWW server that runs Perl scripts, just add
2264 # a line like the following to your srm.conf file (Apache example):
2266 # ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
2268 # URL's that refer to http://www.your.address/SHTML/... will now be handled
2269 # by CGIscriptor.pl, which can use a private directory tree (default is the
2270 # DOCUMENT_ROOT directory tree, but it can be anywhere, see manual).
2272 # If your hosting ISP won't let you add ScriptAlias lines you can use
2273 # the following "rewrite"-based "scriptalias" in .htaccess
2274 # (from Gerd Franke)
2276 # RewriteEngine On
2277 # RewriteBase /
2278 # RewriteCond %{REQUEST_FILENAME} .html$
2279 # RewriteCond %{SCRIPT_FILENAME} !cgiscriptor.pl$
2280 # RewriteCond %{REQUEST_FILENAME} -f
2281 # RewriteRule ^(.*)$ /cgi-bin/cgiscriptor.pl/$1?&%{QUERY_STRING}
2283 # Everthing with the extension ".html" and not including "cgiscriptor.pl"
2284 # in the url and where the file "path/filename.html" exists is redirected
2285 # to "/cgi.bin/cgiscriptor.pl/path/filename.html?query".
2286 # The user configuration should get the same path-level as the
2287 # .htaccess-file:
2289 # # Just enter your own directory path here
2290 # $YOUR_HTML_FILES = "$ENV{'DOCUMENT_ROOT'}";
2291 # # use DOCUMENT_ROOT only, if .htaccess lies in the root-directory.
2293 # If this .htaccess goes in a specific directory, the path to this
2294 # directory must be added to $ENV{'DOCUMENT_ROOT'}.
2296 # The CGIscriptor file contains all documentation as comments. These
2297 # comments can be removed to speed up loading (e.g., `egrep -v '^#'
2298 # CGIscriptor.pl` > leanScriptor.pl). A bare bones version of
2299 # CGIscriptor.pl, lacking documentation, most comments, access control,
2300 # example functions etc. (but still with the copyright notice and some
2301 # minimal documentation) can be obtained by calling CGIscriptor.pl on the
2302 # command line with the '-slim' command line argument, e.g.,
2304 # >CGIscriptor.pl -slim > slimCGIscriptor.pl
2306 # CGIscriptor.pl can be run from the command line with <path> and <query> as
2307 # arguments, as `CGIscriptor.pl <path> <query>`, inside a perl script
2308 # with 'do CGIscriptor.pl' after setting $ENV{PATH_INFO}
2309 # and $ENV{QUERY_STRING}, or CGIscriptor.pl can be loaded with 'require
2310 # "/real-path/CGIscriptor.pl"'. In the latter case, requests are processed
2311 # by 'Handle_Request();' (again after setting $ENV{PATH_INFO} and
2312 # $ENV{QUERY_STRING}).
2314 # Using the command line execution option, CGIscriptor.pl can be used as a
2315 # document (meta-)preprocessor. If the first argument is '-', STDIN will be read.
2316 # For example:
2318 # > cat MyDynamicDocument.html | CGIscriptor.pl - '[QueryString]' > MyStaticFile.html
2320 # This command line will produce a STATIC file with the DYNAMIC content of
2321 # MyDocument.html "interpolated".
2323 # This option would be very dangerous when available over the internet.
2324 # If someone could sneak a 'http://www.your.domain/-' URL past your
2325 # server, CGIscriptor could EXECUTE any POSTED contend.
2326 # Therefore, for security reasons, STDIN will NOT be read
2327 # if ANY of the HTTP server environment variables is set (e.g.,
2328 # SERVER_PORT, SERVER_PROTOCOL, SERVER_NAME, SERVER_SOFTWARE,
2329 # HTTP_USER_AGENT, REMOTE_ADDR).
2330 # This block on processing STDIN on HTTP requests can be lifted by setting
2331 # $BLOCK_STDIN_HTTP_REQUEST = 0;
2332 # In the security configuration. Butbe carefull when doing this.
2333 # It can be very dangerous.
2335 # Running demo's and more information can be found at
2336 # http://www.fon.hum.uva.nl/~rob/OSS/OSS.html
2338 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site or
2339 # CPAN that can use CGIscriptor.pl as the base of a µWWW server and
2340 # demonstrates its use.
2343 # PROCESSING NON-FILESYSTEM DATA
2345 # Normally, HTTP (WWW) requests map onto file that can be accessed
2346 # using the perl open() function. That is, the web server runs on top of
2347 # some directory structure. However, we can envission (and put to good
2348 # use) other systems that do not use a normal file system. The whole CGI
2349 # was developed to make dynamic document generation possible.
2351 # A special case is where we want to have it both: A normal web server
2352 # with normal "file data", but not a normal files system. For instance,
2353 # we want or normal Web Site to run directly from a RAM hash table or
2354 # other database, instead of from disk. But we do NOT want to code the
2355 # whole site structure in CGI.
2357 # CGIscriptor can do this. If the web server fills an environment variable
2358 # $ENV{'CGI_FILE_CONTENT'} with the content of the "file", then the content
2359 # of this variable is processed instead of opening a file. If this environment
2360 # variable has the value '-', the content of another environment variable,
2361 # $ENV{'CGI_DATA_ACCESS_CODE'} is executed as:
2362 # eval("\@_ = ($file_path); do {$ENV{'CGI_DATA_ACCESS_CODE'}};")
2363 # and the result is processed as if it was the content of the requested
2364 # file.
2365 # (actually, the names of the environment variables are user configurable,
2366 # they are stored in the local variables $CGI_FILE_CONTENT and
2367 # $CGI_DATA_ACCESS_CODE)
2369 # When using this mechanism, the SRC attribute mechanism will only partially work.
2370 # Only the "recursive" calls to CGIscriptor (the ProcessFile() function)
2371 # will work, the automagical execution of SRC files won't. (In this case,
2372 # the SRC attribute won't work either for other scripting languages)
2375 # NON-UNIX PLATFORMS
2377 # CGIscriptor.pl was mainly developed and tested on UNIX. However, as I
2378 # coded part of the time on an Apple Macintosh under MacPerl, I made sure
2379 # CGIscriptor did run under MacPerl (with command line options). But only
2380 # as an independend script, not as part of a HTTP server. I have used it
2381 # under Apache in Windows XP.
2383 ENDOFHELPTEXT
2384 exit;
2386 ###############################################################################
2388 # SECURITY CONFIGURATION
2390 # Special configurations related to SECURITY
2391 # (i.e., optional, see also environment variables below)
2393 # LOGGING
2394 # Log Clients and the requested paths (Redundant when loging Queries)
2396 $ClientLog = "./Client.log"; # (uncomment for use)
2398 # Format: Localtime | REMOTE_USER REMOTE_IDENT REMOTE_HOST REMOTE_ADDRESS \
2399 # PATH_INFO CONTENT_LENGTH (actually, the real query+post length)
2401 # Log Clients and the queries, the CGIQUERYDECODE is required if you want
2402 # to log queries. If you log Queries, the loging of Clients is redundant
2403 # (note that queries can be quite long, so this might not be a good idea)
2405 #$QueryLog = "./Query.log"; # (uncomment for use)
2407 # ACCESS CONTROL
2408 # the Access files should contain Hostnames or IP addresses,
2409 # i.e. REMOTE_HOST or REMOTE_ADDR, each on a separate line
2410 # optionally followed by one ore more file patterns, e.g., "edu /DEMO".
2411 # Matching is done "domain first". For example ".edu" matches all
2412 # clients whose "name" ends in ".edu" or ".EDU". The file pattern
2413 # "/DEMO" matches all paths that contain the strings "/DEMO" or "/demo"
2414 # (both matchings are done case-insensitive).
2415 # The name special symbol "-" matches ALL clients who do not supply a
2416 # REMOTE_HOST name, "*" matches all clients.
2417 # Lines starting with '-e' are evaluated. A non-zero return value indicates
2418 # a match. You can use $REMOTE_HOST, $REMOTE_ADDR, and $PATH_INFO. These
2419 # lines are evaluated in the program's own name-space. So DO NOT assign to
2420 # variables.
2422 # Accept the following users (remove comment # and adapt filename)
2423 $CGI_Accept = -s "$YOUR_SCRIPTS/ACCEPT.lis" ? "$YOUR_SCRIPTS/ACCEPT.lis" : ''; # (uncomment for use)
2425 # Reject requests from the following users (remove comment # and
2426 # adapt filename, this is only of limited use)
2427 $CGI_Reject = -s "$YOUR_SCRIPTS/REJECT.lis" ? "$YOUR_SCRIPTS/REJECT.lis" : ''; # (uncomment for use)
2429 # Empty lines or comment lines starting with '#' are ignored in both
2430 # $CGI_Accept and $CGI_Reject.
2432 # Block STDIN (i.e., '-') requests when servicing an HTTP request
2433 # Comment this out if you realy want to use STDIN in an on-line web server
2434 $BLOCK_STDIN_HTTP_REQUEST = 1;
2437 # End of security configuration
2439 ##################################################<<<<<<<<<<End Remove
2441 # PARSING CGI VALUES FROM THE QUERY STRING (USER CONFIGURABLE)
2443 # The CGI parse commands. These commands extract the values of the
2444 # CGI variables from the URL encoded Query String.
2445 # If you want to use your own CGI decoders, you can call them here
2446 # instead, using your own PATH and commenting/uncommenting the
2447 # appropriate lines
2449 # CGI parse command for individual values
2450 # (if $List > 0, returns a list value, if $List < 0, a hash table, this is optional)
2451 sub YOUR_CGIPARSE # ($Name [, $List]) -> Decoded value
2453 my $Name = shift;
2454 my $List = shift || 0;
2455 # Use one of the following by uncommenting
2456 if(!$List) # Simple value
2458 return CGIscriptor::CGIparseValue($Name) ;
2460 elsif($List < 0) # Hash tables
2462 return CGIscriptor::CGIparseValueHash($Name); # Defined in CGIscriptor below
2464 else # Lists
2466 return CGIscriptor::CGIparseValueList($Name); # Defined in CGIscriptor below
2469 # return `/PATH/cgiparse -value $Name`; # Shell commands
2470 # require "/PATH/cgiparse.pl"; return cgivalue($Name); # Library
2472 # Complete queries
2473 sub YOUR_CGIQUERYDECODE
2475 # Use one of the following by uncommenting
2476 return CGIscriptor::CGIparseForm(); # Defined in CGIscriptor below
2477 # return `/PATH/cgiparse -form`; # Shell commands
2478 # require "/PATH/cgiparse.pl"; return cgiform(); # Library
2481 # End of configuration
2483 #######################################################################
2485 # Translating input files.
2486 # Allows general and global conversions of files using Regular Expressions
2487 # Translations are applied in the order of definition.
2489 # Define:
2490 # my $TranslationPaths = 'pattern'; # Pattern matching PATH_INFO
2492 # push(@TranslationTable, ['pattern', 'replacement']);
2493 # e.g. (for Ruby Rails):
2494 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2495 # push(@TranslationTable, ['%>', '</SCRIPT>']);
2497 # Runs:
2498 # my $currentRegExp;
2499 # foreach $currentRegExp (keys(%TranslationTable))
2501 # my $currentRegExp;
2502 # foreach $currentRegExp (@TranslationTable)
2504 # my ($pattern, $replacement) = @$currentRegExp;
2505 # $$text =~ s!$pattern!$replacement!msg;
2506 # };
2507 # };
2509 # Configuration section
2511 #######################################################################
2513 # The file paths on which to apply the translation
2514 my $TranslationPaths = ''; # NO files
2515 #$TranslationPaths = '.'; # ANY file
2516 # $TranslationPaths = '\.html'; # HTML files
2518 my @TranslationTable = ();
2519 # Some legacy code
2520 push(@TranslationTable, ['\<\s*CGI\s+([^\>])*\>', '\<SCRIPT TYPE=\"text/ssperl\"\>$1\<\/SCRIPT>']);
2521 # Ruby Rails?
2522 push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2523 push(@TranslationTable, ['%>', '</SCRIPT>']);
2525 sub performTranslation # (\$text)
2527 my $text = shift || return;
2528 if(@TranslationTable && $TranslationPaths && $ENV{'PATH_INFO'} =~ m!$TranslationPaths!)
2530 my $currentRegExp;
2531 foreach $currentRegExp (@TranslationTable)
2533 my ($pattern, $replacement) = @$currentRegExp;
2534 $$text =~ s!$pattern!$replacement!msg;
2539 #######################################################################
2541 # Seamless access to other (Scripting) Languages
2542 # TYPE='text/ss<interpreter>'
2544 # Configuration section
2546 #######################################################################
2548 # OTHER SCRIPTING LANGUAGES AT THE SERVER SIDE (MIME => OScommand)
2549 # Yes, it realy is this simple! (unbelievable, isn't it)
2550 # NOTE: Some interpreters require some filtering to obtain "clean" output
2552 %ScriptingLanguages = (
2553 "text/testperl" => 'perl', # Perl for testing
2554 "text/sspython" => 'python', # Python
2555 "text/ssruby" => 'ruby', # Ruby
2556 "text/sstcl" => 'tcl', # TCL
2557 "text/ssawk" => 'awk -f-', # Awk
2558 "text/sslisp" => # lisp (rep, GNU)
2559 'rep | tail +4 '."| egrep -v '> |^rep. |^nil\\\$'",
2560 "text/xlispstat" => # xlispstat
2561 'xlispstat | tail +7 ' ."| egrep -v '> \\\$|^NIL'",
2562 "text/ssprolog" => # Prolog (GNU)
2563 "gprolog | tail +4 | sed 's/^| ?- //'",
2564 "text/ssm4" => 'm4', # M4 macro's
2565 "text/sh" => 'sh', # Born shell
2566 "text/bash" => 'bash', # Born again shell
2567 "text/csh" => 'csh', # C shell
2568 "text/ksh" => 'ksh', # Korn shell
2569 "text/sspraat" => # Praat (sound/speech analysis)
2570 "praat - | sed 's/Praat > //g'",
2571 "text/ssr" => # R
2572 "R --vanilla --slave | sed 's/^[\[0-9\]*] //'",
2573 "text/ssrebol" => # REBOL
2574 "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\\s*\[> \]* //'",
2575 "text/postgresql" => 'psql 2>/dev/null',
2577 # Not real scripting, but the use of other applications
2578 "text/ssmailto" => "awk 'NF||F{F=1;print \\\$0;}'|mailto >/dev/null", # Send mail from server
2579 "text/ssdisplay" => 'cat', # Display, (interpolation)
2580 "text/sslogfile" => # Log to file, (interpolation)
2581 "awk 'NF||L {if(!L){L=tolower(\\\$1)~/^file:\\\$/ ? \\\$2 : \\\$1;}else{print \\\$0 >> L;};}'",
2583 "" => ""
2586 # To be able to access the CGI variables in your script, they
2587 # should be passed to the scripting language in a readable form
2588 # Here you can enter how they should be printed (the first %s
2589 # is replaced by the NAME of the CGI variable as it apears in the
2590 # META tag, the second by its VALUE).
2591 # For Perl this would be:
2592 # "text/testperl" => '$%s = "%s";',
2593 # which would be executed as
2594 # printf('$%s = "%s";', $CGI_NAME, $CGI_VALUE);
2596 # If the hash table value doesn't exist, nothing is done
2597 # (you have to parse the Environment variables yourself).
2598 # If it DOES exist but is empty (e.g., "text/sspraat" => '',)
2599 # Perl string interpolation of variables (i.e., $var, @array,
2600 # %hash) is performed. This means that $@%\ must be protected
2601 # with a \.
2603 %ScriptingCGIvariables = (
2604 "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value'; (for testing)
2605 "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
2606 "text/ssruby" => '@%s = "%s"', # Ruby @VAR = 'value'
2607 "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
2608 "text/ssawk" => '%s = "%s";', # Awk VAR = 'value';
2609 "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
2610 "text/xlispstat" => '(setq %s "%s")', # xlispstat (setq VAR "value")
2611 "text/ssprolog" => '', # Gnu prolog (interpolated)
2612 "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
2613 "text/sh" => "\%s='\%s'", # Born shell VAR='value'
2614 "text/bash" => "\%s='\%s'", # Born again shell VAR='value'
2615 "text/csh" => "\$\%s='\%s';", # C shell $VAR = 'value';
2616 "text/ksh" => "\$\%s='\%s';", # Korn shell $VAR = 'value';
2618 "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
2619 "text/sspraat" => '', # Praat (interpolation)
2620 "text/ssr" => '%s <- "%s";', # R VAR <- "value";
2621 "text/postgresql" => '', # PostgreSQL (interpolation)
2623 # Not real scripting, but the use of other applications
2624 "text/ssmailto" => '', # MAILTO, (interpolation)
2625 "text/ssdisplay" => '', # Display, (interpolation)
2626 "text/sslogfile" => '', # Log to file, (interpolation)
2628 "" => ""
2631 # If you want something added in front or at the back of each script
2632 # block as send to the interpreter add it here.
2633 # mime => "string", e.g., "text/sspython" => "python commands"
2634 %ScriptingPrefix = (
2635 "text/testperl" => "\# Prefix Code;", # Perl script testing
2636 "text/ssm4" => 'divert(0)', # M4 macro's (open STDOUT)
2638 "" => ""
2640 # If you want something added at the end of each script block
2641 %ScriptingPostfix = (
2642 "text/testperl" => "\# Postfix Code;", # Perl script testing
2643 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2645 "" => ""
2647 # If you need initialization code, directly after opening
2648 %ScriptingInitialization = (
2649 "text/testperl" => "\# Initialization Code;", # Perl script testing
2650 "text/ssawk" => 'BEGIN {', # Server Side awk scripts (VAR = "value")
2651 "text/sslisp" => '(prog1 nil ', # Lisp (rep)
2652 "text/xlispstat" => '(prog1 nil ', # xlispstat
2653 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2655 "" => ""
2657 # If you need cleanup code before closing
2658 %ScriptingCleanup = (
2659 "text/testperl" => "\# Cleanup Code;", # Perl script testing
2660 "text/sspraat" => 'Quit',
2661 "text/ssawk" => '};', # Server Side awk scripts (VAR = "value")
2662 "text/sslisp" => '(princ "\n" standard-output)).', # Closing print to rep
2663 "text/xlispstat" => '(print ""))', # Closing print to xlispstat
2664 "text/postgresql" => '\q', # quit psql
2665 "text/ssdisplay" => "", # close cat
2667 "" => ""
2670 # End of configuration for foreign scripting languages
2672 ###############################################################################
2674 # Initialization Code
2677 sub Initialize_Request
2679 ###############################################################################
2681 # ENVIRONMENT VARIABLES
2683 # Use environment variables to configure CGIscriptor on a temporary basis.
2684 # If you define any of the configurable variables as environment variables,
2685 # these are used instead of the "hard coded" values above.
2687 $SS_PUB = $ENV{'SS_PUB'} || $YOUR_HTML_FILES;
2688 $SS_SCRIPT = $ENV{'SS_SCRIPT'} || $YOUR_SCRIPTS;
2691 # Substitution strings, these are used internally to handle the
2692 # directory separator strings, e.g., '~/' -> 'SS_PUB:' (Mac)
2693 $HOME_SUB = $SS_PUB;
2694 $SCRIPT_SUB = $SS_SCRIPT;
2697 # Make sure all script are reliably loaded
2698 push(@INC, $SS_SCRIPT);
2701 # Add the directory separator to the "home" directories.
2702 # (This is required for ~/ and ./ substitution)
2703 $HOME_SUB .= '/' if $HOME_SUB;
2704 $SCRIPT_SUB .= '/' if $SCRIPT_SUB;
2706 $CGI_HOME = $ENV{'DOCUMENT_ROOT'};
2707 $ENV{'PATH_TRANSLATED'} =~ /$ENV{'PATH_INFO'}/is;
2708 $CGI_HOME = $` unless $ENV{'DOCUMENT_ROOT'}; # Get the DOCUMENT_ROOT directory
2709 $default_values{'CGI_HOME'} = $CGI_HOME;
2710 $ENV{'HOME'} = $CGI_HOME;
2711 # Set SS_PUB and SS_SCRIPT as Environment variables (make them available
2712 # to the scripts)
2713 $ENV{'SS_PUB'} = $SS_PUB unless $ENV{'SS_PUB'};
2714 $ENV{'SS_SCRIPT'} = $SS_SCRIPT unless $ENV{'SS_SCRIPT'};
2716 $FilePattern = $ENV{'FilePattern'} || $FilePattern;
2717 $MaximumQuerySize = $ENV{'MaximumQuerySize'} || $MaximumQuerySize;
2718 $ClientLog = $ENV{'ClientLog'} || $ClientLog;
2719 $QueryLog = $ENV{'QueryLog'} || $QueryLog;
2720 $CGI_Accept = $ENV{'CGI_Accept'} || $CGI_Accept;
2721 $CGI_Reject = $ENV{'CGI_Reject'} || $CGI_Reject;
2723 # Parse file names
2724 $CGI_Accept =~ s@^\~/@$HOME_SUB@g if $CGI_Accept;
2725 $CGI_Reject =~ s@^\~/@$HOME_SUB@g if $CGI_Reject;
2726 $ClientLog =~ s@^\~/@$HOME_SUB@g if $ClientLog;
2727 $QueryLog =~ s@^\~/@$HOME_SUB@g if $QueryLog;
2729 $CGI_Accept =~ s@^\./@$SCRIPT_SUB@g if $CGI_Accept;
2730 $CGI_Reject =~ s@^\./@$SCRIPT_SUB@g if $CGI_Reject;
2731 $ClientLog =~ s@^\./@$SCRIPT_SUB@g if $ClientLog;
2732 $QueryLog =~ s@^\./@$SCRIPT_SUB@g if $QueryLog;
2734 @CGIscriptorResults = (); # A stack of results
2736 # end of Environment variables
2738 #############################################################################
2740 # Define and Store "standard" values
2742 # BEFORE doing ANYTHING check the size of Query String
2743 length($ENV{'QUERY_STRING'}) <= $MaximumQuerySize || dieHandler(2, "QUERY TOO LONG\n");
2745 # The Translated Query String and the Actual length of the (decoded)
2746 # Query String
2747 if($ENV{'QUERY_STRING'})
2749 # If this can contain '`"-quotes, be carefull to use it QUOTED
2750 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2751 $default_values{CGI_Content_Length} = length($default_values{CGI_Decoded_QS});
2754 # Get the current Date and time and store them as default variables
2756 # Get Local Time
2757 $LocalTime = localtime;
2759 # CGI_Year CGI_Month CGI_Day CGI_WeekDay CGI_Time
2760 # CGI_Hour CGI_Minutes CGI_Seconds
2762 $default_values{CGI_Date} = $LocalTime;
2763 ($default_values{CGI_WeekDay},
2764 $default_values{CGI_Month},
2765 $default_values{CGI_Day},
2766 $default_values{CGI_Time},
2767 $default_values{CGI_Year}) = split(' ', $LocalTime);
2768 ($default_values{CGI_Hour},
2769 $default_values{CGI_Minutes},
2770 $default_values{CGI_Seconds}) = split(':', $default_values{CGI_Time});
2772 # GMT:
2773 # CGI_GMTYear CGI_GMTMonth CGI_GMTDay CGI_GMTWeekDay CGI_GMTYearDay
2774 # CGI_GMTHour CGI_GMTMinutes CGI_GMTSeconds CGI_GMTisdst
2776 ($default_values{CGI_GMTSeconds},
2777 $default_values{CGI_GMTMinutes},
2778 $default_values{CGI_GMTHour},
2779 $default_values{CGI_GMTDay},
2780 $default_values{CGI_GMTMonth},
2781 $default_values{CGI_GMTYear},
2782 $default_values{CGI_GMTWeekDay},
2783 $default_values{CGI_GMTYearDay},
2784 $default_values{CGI_GMTisdst}) = gmtime;
2788 # End of Initialize Request
2790 ###################################################################
2792 # SECURITY: ACCESS CONTROL
2794 # Check the credentials of each client (use pattern matching, domain first).
2795 # This subroutine will kill-off (die) the current process whenever access
2796 # is denied.
2798 sub Access_Control
2800 # >>>>>>>>>>Start Remove
2802 # ACCEPTED CLIENTS
2804 # Only accept clients which are authorized, reject all unnamed clients
2805 # if REMOTE_HOST is given.
2806 # If file patterns are given, check whether the user is authorized for
2807 # THIS file.
2808 if($CGI_Accept)
2810 # Use local variables, REMOTE_HOST becomes '-' if undefined
2811 my $REMOTE_HOST = $ENV{REMOTE_HOST} || '-';
2812 my $REMOTE_ADDR = $ENV{REMOTE_ADDR};
2813 my $PATH_INFO = $ENV{'PATH_INFO'};
2815 open(CGI_Accept, "<$CGI_Accept") || dieHandler(3, "$CGI_Accept: $!\n");
2816 $NoAccess = 1;
2817 while(<CGI_Accept>)
2819 next unless /\S/; # Skip empty lines
2820 next if /^\s*\#/; # Skip comments
2822 # Full expressions
2823 if(/^\s*-e\s/is)
2825 my $Accept = $'; # Get the expression
2826 $NoAccess &&= eval($Accept); # evaluate the expresion
2828 else
2830 my ($Accept, @FilePatternList) = split;
2831 if($Accept eq '*' # Always match
2832 ||$REMOTE_HOST =~ /\Q$Accept\E$/is # REMOTE_HOST matches
2833 || (
2834 $Accept =~ /^[0-9\.]+$/
2835 && $REMOTE_ADDR =~ /^\Q$Accept\E/ # IP address matches
2839 if($FilePatternList[0])
2841 foreach $Pattern (@FilePatternList)
2843 # Check whether this patterns is accepted
2844 $NoAccess &&= ($PATH_INFO !~ m@\Q$Pattern\E@is);
2847 else
2849 $NoAccess = 0; # No file patterns -> Accepted
2853 # Blocked
2854 last unless $NoAccess;
2856 close(CGI_Accept);
2857 if($NoAccess){ dieHandler(4, "No Access: $PATH_INFO\n");};
2861 # REJECTED CLIENTS
2863 # Reject named clients, accept all unnamed clients
2864 if($CGI_Reject)
2866 # Use local variables, REMOTE_HOST becomes '-' if undefined
2867 my $REMOTE_HOST = $ENV{'REMOTE_HOST'} || '-';
2868 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
2869 my $PATH_INFO = $ENV{'PATH_INFO'};
2871 open(CGI_Reject, "<$CGI_Reject") || dieHandler(5, "$CGI_Reject: $!\n");
2872 $NoAccess = 0;
2873 while(<CGI_Reject>)
2875 next unless /\S/; # Skip empty lines
2876 next if /^\s*\#/; # Skip comments
2878 # Full expressions
2879 if(/^-e\s/is)
2881 my $Reject = $'; # Get the expression
2882 $NoAccess ||= eval($Reject); # evaluate the expresion
2884 else
2886 my ($Reject, @FilePatternList) = split;
2887 if($Reject eq '*' # Always match
2888 ||$REMOTE_HOST =~ /\Q$Reject\E$/is # REMOTE_HOST matches
2889 ||($Reject =~ /^[0-9\.]+$/
2890 && $REMOTE_ADDR =~ /^\Q$Reject\E/is # IP address matches
2894 if($FilePatternList[0])
2896 foreach $Pattern (@FilePatternList)
2898 $NoAccess ||= ($PATH_INFO =~ m@\Q$Pattern\E@is);
2901 else
2903 $NoAccess = 1; # No file patterns -> Rejected
2907 last if $NoAccess;
2909 close(CGI_Reject);
2910 if($NoAccess){ dieHandler(6, "Request rejected: $PATH_INFO\n");};
2913 ##########################################################<<<<<<<<<<End Remove
2916 # Get the filename
2918 # Does the filename contain any illegal characters (e.g., |, >, or <)
2919 dieHandler(7, "Illegal request: $ENV{'PATH_INFO'}\n") if $ENV{'PATH_INFO'} =~ /[^$FileAllowedChars]/;
2920 # Does the pathname contain an illegal (blocked) "directory"
2921 dieHandler(8, "Illegal request: $ENV{'PATH_INFO'}\n") if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@; # Access is blocked
2922 # Does the pathname contain a direct referencer to BinaryMapFile
2923 dieHandler(9, "Illegal request: $ENV{'PATH_INFO'}\n") if $BinaryMapFile && $ENV{'PATH_INFO'} =~ m@\Q$BinaryMapFile\E@; # Access is blocked
2925 # SECURITY: Is PATH_INFO allowed?
2926 if($FilePattern && $ENV{'PATH_INFO'} && $ENV{'PATH_INFO'} ne '-' &&
2927 ($ENV{'PATH_INFO'} !~ m@($FilePattern)$@is))
2929 # Unsupported file types can be processed by a special raw-file
2930 if($BinaryMapFile)
2932 $ENV{'CGI_BINARY_FILE'} = $ENV{'PATH_INFO'};
2933 $ENV{'PATH_INFO'} = $BinaryMapFile;
2935 else
2937 dieHandler(10, "Illegal file\n");
2943 # End of Security Access Control
2946 ############################################################################
2948 # Get the POST part of the query and add it to the QUERY_STRING.
2951 sub Get_POST_part_of_query
2954 # If POST, Read data from stdin to QUERY_STRING
2955 if($ENV{'REQUEST_METHOD'} =~ /POST/is)
2957 # SECURITY: Check size of Query String
2958 $ENV{'CONTENT_LENGTH'} <= $MaximumQuerySize || dieHandler(11, "Query too long: $ENV{'CONTENT_LENGTH'}\n"); # Query too long
2959 my $QueryRead = 0;
2960 my $SystemRead = $ENV{'CONTENT_LENGTH'};
2961 $ENV{'QUERY_STRING'} .= '&' if length($ENV{'QUERY_STRING'}) > 0;
2962 while($SystemRead > 0)
2964 $QueryRead = sysread(STDIN, $Post, $SystemRead); # Limit length
2965 $ENV{'QUERY_STRING'} .= $Post;
2966 $SystemRead -= $QueryRead;
2968 # Update decoded Query String
2969 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2970 $default_values{CGI_Content_Length} =
2971 length($default_values{CGI_Decoded_QS});
2975 # End of getting POST part of query
2978 ############################################################################
2980 # Start (HTML) output and logging
2981 # (if there are irregularities, it can kill the current process)
2984 sub Initialize_output
2986 # Construct the REAL file path (except for STDIN on the command line)
2987 my $file_path = $ENV{'PATH_INFO'} ne '-' ? $SS_PUB . $ENV{'PATH_INFO'} : '-';
2988 $file_path =~ s/\?.*$//; # Remove query
2989 # This is only necessary if your server does not catch ../ directives
2990 $file_path !~ m@\.\./@ || dieHandler(12, "Illegal ../ Construct\n"); # SECURITY: Do not allow ../ constructs
2992 # Block STDIN use (-) if CGIscriptor is servicing a HTTP request
2993 if($file_path eq '-')
2995 dieHandler(13, "STDIN request in On Line system\n") if $BLOCK_STDIN_HTTP_REQUEST
2996 && ($ENV{'SERVER_SOFTWARE'}
2997 || $ENV{'SERVER_NAME'}
2998 || $ENV{'GATEWAY_INTERFACE'}
2999 || $ENV{'SERVER_PROTOCOL'}
3000 || $ENV{'SERVER_PORT'}
3001 || $ENV{'REMOTE_ADDR'}
3002 || $ENV{'HTTP_USER_AGENT'});
3007 if($ClientLog)
3009 open(ClientLog, ">>$ClientLog");
3010 print ClientLog "$LocalTime | ",
3011 ($ENV{REMOTE_USER} || "-"), " ",
3012 ($ENV{REMOTE_IDENT} || "-"), " ",
3013 ($ENV{REMOTE_HOST} || "-"), " ",
3014 $ENV{REMOTE_ADDR}, " ",
3015 $ENV{PATH_INFO}, " ",
3016 $ENV{'CGI_BINARY_FILE'}, " ",
3017 ($default_values{CGI_Content_Length} || "-"),
3018 "\n";
3019 close(ClientLog);
3021 if($QueryLog)
3023 open(QueryLog, ">>$QueryLog");
3024 print QueryLog "$LocalTime\n",
3025 ($ENV{REMOTE_USER} || "-"), " ",
3026 ($ENV{REMOTE_IDENT} || "-"), " ",
3027 ($ENV{REMOTE_HOST} || "-"), " ",
3028 $ENV{REMOTE_ADDR}, ": ",
3029 $ENV{PATH_INFO}, " ",
3030 $ENV{'CGI_BINARY_FILE'}, "\n";
3032 # Write Query to Log file
3033 print QueryLog $default_values{CGI_Decoded_QS}, "\n\n";
3034 close(QueryLog);
3037 # Return the file path
3038 return $file_path;
3041 # End of Initialize output
3044 ############################################################################
3046 # Handle login access
3048 # Access is based on a valid session ticket.
3049 # Session tickets should be dependend on user name
3050 # and IP address. The patterns of URLs for which a
3051 # session ticket is needed and the login URL are stored in
3052 # %TicketRequiredPatterns as:
3053 # 'RegEx pattern' -> 'SessionPath\tPasswordPath\tLogin URL\tExpiration'
3056 sub Log_In_Access # () -> 0 = Access Allowed, Login page if access is not allowed
3058 # No patterns, no login
3059 goto Return unless %TicketRequiredPatterns;
3061 # Get and initialize values (watch out for stuff processed by BinaryMap files)
3062 my ($SessionPath, $PasswordsPath, $Login, $valid_duration) = ("", "", "", 0);
3063 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3064 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
3065 goto Return if $REMOTE_ADDR =~ /[^0-9\.]/;
3066 # Extract TICKETs, starting with returned cookies
3067 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3068 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3069 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3070 if($ENV{'COOKIE_JAR'})
3072 my $CurrentCookieJar = $ENV{'COOKIE_JAR'};
3073 $CurrentCookieJar =~ s/\w+\=\-\s*(\;\s*|$)//isg;
3074 if($CurrentCookieJar =~ /\s*CGIscriptorLOGIN\=\s*([^\;]+)/)
3076 ${"CGIexecute::LOGINTICKET"} = $1;
3078 if($CurrentCookieJar =~ /\s*CGIscriptorCHALLENGE\=\s*([^\;]+)/ && $1 ne '-')
3080 ${"CGIexecute::CHALLENGETICKET"} = $1;
3082 if($CurrentCookieJar =~ /\s*CGIscriptorSESSION\=\s*([^\;]+)/ && $1 ne '-')
3084 ${"CGIexecute::SESSIONTICKET"} = $1;
3087 # Get and check the tickets. Tickets are restricted to word-characters (alphanumeric+_+.)
3088 my $LOGINTICKET = ${"CGIexecute::LOGINTICKET"};
3089 goto Return if ($LOGINTICKET && $LOGINTICKET =~ /[^\w\.]/isg);
3090 my $SESSIONTICKET = ${"CGIexecute::SESSIONTICKET"};
3091 goto Return if ($SESSIONTICKET && $SESSIONTICKET =~ /[^\w\.]/isg);
3092 my $CHALLENGETICKET = ${"CGIexecute::CHALLENGETICKET"};
3093 goto Return if ($SESSIONTICKET && $SESSIONTICKET =~ /[^\w\.]/isg);
3094 # Look for a LOGOUT message
3095 my $LOGOUT = $ENV{QUERY_STRING} =~ /(^|\&)LOGOUT([\=\&]|$)/;
3096 # Username and password
3097 CGIexecute::defineCGIvariable('CGIUSERNAME', "");
3098 my $username = lc(${"CGIexecute::CGIUSERNAME"});
3099 goto Return if $username =~ m!^[^\w]!isg || $username =~ m![^\w \-]!isg;
3100 my $userfile = lc($username);
3101 $userfile =~ s/[^\w]/_/isg;
3102 CGIexecute::defineCGIvariable('PASSWORD', "");
3103 my $password = ${"CGIexecute::PASSWORD"};
3104 CGIexecute::defineCGIvariable('NEWUSERNAME', "");
3105 my $newuser = lc(${"CGIexecute::NEWUSERNAME"});
3106 CGIexecute::defineCGIvariable('NEWPASSWORD', "");
3107 my $newpassword = ${"CGIexecute::NEWPASSWORD"};
3109 foreach my $pattern (keys(%TicketRequiredPatterns))
3111 # Check BOTH the real PATH_INFO and the CGI_BINARY_FILE variable
3112 if($ENV{'PATH_INFO'} =~ m#$pattern# || $ENV{'CGI_BINARY_FILE'} =~ m#$pattern#)
3114 # Fall through a sieve of requirements
3115 ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3116 # If a LOGOUT is present, remove everything
3117 if($LOGOUT && !$LOGINTICKET)
3119 unlink "$SessionPath/$LOGINTICKET" if $LOGINTICKET && (-s "$SessionPath/$LOGINTICKET");
3120 $LOGINTICKET = "";
3121 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
3122 $SESSIONTICKET = "";
3123 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
3124 $CHALLENGETICKET = "";
3125 unlink "$SessionPath/$REMOTE_ADDR" if (-s "$SessionPath/$REMOTE_ADDR");
3126 $CHALLENGETICKET = "";
3127 goto Login;
3129 # Is there a change password request?
3130 if($newuser && $LOGINTICKET && $username)
3132 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3133 goto Login unless (-s "$PasswordsPath/$userfile");
3134 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3135 goto Login unless $ticket_valid;
3136 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".", 1);
3137 goto Login unless $ticket_valid;
3139 my ($sessiontype, $currentticket) = ("", "");
3140 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
3141 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
3142 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
3144 if($sessiontype)
3146 goto Login unless (-s "$SessionPath/$currentticket");
3147 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3148 goto Login unless $ticket_valid;
3150 # Authorize
3151 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath);
3152 goto Login unless $TMPTICKET;
3154 # Create a new user account
3155 CGIexecute::defineCGIvariable('NEWSESSION', "");
3156 my $newsession = ${"CGIexecute::NEWSESSION"};
3157 my $newaccount = create_newuser("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket",
3158 "$PasswordsPath/$userfile", $password, $newuser, $newpassword, $newsession);
3159 CGIexecute::defineCGIvariable('NEWACCOUNTTEXT', $newaccount);
3160 ${CGIexecute::NEWACCOUNTTEXT} = $newaccount;
3161 # NEWACCOUNTTEXT is NOT to be set by the query
3162 CGIexecute::ProtectCGIvariable('NEWACCOUNTTEXT');
3165 # Ready
3166 goto Return;
3168 # Is there a change password request?
3169 elsif($newpassword && $LOGINTICKET && $username)
3171 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3172 goto Login unless (-s "$PasswordsPath/$userfile");
3173 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3174 goto Login unless $ticket_valid;
3175 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".", 1);
3176 goto Login unless $ticket_valid;
3178 my ($sessiontype, $currentticket) = ("", "");
3179 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
3180 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
3181 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
3183 if($sessiontype)
3185 goto Login unless (-s "$SessionPath/$currentticket");
3186 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3187 goto Login unless $ticket_valid;
3189 # Authorize
3190 change_password("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket", "$PasswordsPath/$userfile", $password, $newpassword);
3191 # After a change of password, you have to login again for a CHALLENGE
3192 if($CHALLENGETICKET){$CHALLENGETICKET = "";};
3193 # Ready
3194 goto Return;
3196 # Is there a login ticket of this name?
3197 elsif($LOGINTICKET)
3199 my $tickets_removed = remove_expired_tickets($SessionPath);
3200 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3201 goto Login unless (-s "$PasswordsPath/$userfile");
3202 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3203 goto Login unless $ticket_valid;
3204 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".");
3205 goto Login unless $ticket_valid;
3207 # Remove any lingering tickets
3208 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
3209 $SESSIONTICKET = "";
3210 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
3211 $CHALLENGETICKET = "";
3214 # Authorize
3215 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath);
3216 if($TMPTICKET)
3218 my $authorization = read_ticket("$PasswordsPath/$userfile");
3219 goto Login unless $authorization;
3220 # Session type is read from the userfile
3221 if($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "CHALLENGE")
3223 # Create New Random CHALLENGETICKET
3224 $CHALLENGETICKET = $TMPTICKET;
3225 create_session_file("$SessionPath/$CHALLENGETICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3227 elsif($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "IPADDRESS")
3229 create_session_file("$SessionPath/$REMOTE_ADDR", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3231 else
3233 $SESSIONTICKET = $TMPTICKET;
3234 create_session_file("$SessionPath/$SESSIONTICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3235 $SETCOOKIELIST{"CGIscriptorSESSION"} = "-";
3238 # Login ticket file has been used, remove it
3239 unlink($loginfile);
3241 # Is there a session ticket of this name?
3242 # CHALLENGE
3243 if($CHALLENGETICKET)
3245 goto Login unless (-s "$SessionPath/$CHALLENGETICKET");
3246 my $ticket_valid = check_ticket_validity("CHALLENGE", "$SessionPath/$CHALLENGETICKET", $REMOTE_ADDR, $PATH_INFO);
3247 goto Login unless $ticket_valid;
3249 my $oldchallenge = read_ticket("$SessionPath/$CHALLENGETICKET");
3250 goto Login unless $oldchallenge;
3251 # Check whether the login still exists
3252 my $userfile = lc($oldchallenge->{"Username"}->[0]);
3253 $userfile =~ s/[^\w]/_/isg;
3254 goto Login unless (-s "$PasswordsPath/$userfile");
3256 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3257 goto Login unless $ticket_valid;
3259 my $NEWCHALLENGETICKET = "";
3260 $NEWCHALLENGETICKET = copy_challenge_file("$SessionPath/$CHALLENGETICKET", "$PasswordsPath/$userfile", $SessionPath);
3261 # Sessionticket is available to scripts, do NOT set the cookie
3262 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3263 goto Return;
3265 # IPADDRESS
3266 elsif(-s "$SessionPath/$REMOTE_ADDR")
3268 my $ticket_valid = check_ticket_validity("IPADDRESS", "$SessionPath/$REMOTE_ADDR", $REMOTE_ADDR, $PATH_INFO);
3269 goto Login unless $ticket_valid;
3270 # Check whether the login still exists
3271 my $currentsessionticket = read_ticket("$SessionPath/$REMOTE_ADDR");
3272 my $userfile = lc($currentsessionticket->{"Username"}->[0]);
3273 $userfile =~ s/[^\w]/_/isg;
3274 goto Login unless (-s "$PasswordsPath/$userfile");
3276 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3277 goto Login unless $ticket_valid;
3279 goto Return;
3281 # SESSION
3282 elsif($SESSIONTICKET)
3284 goto Login unless (-s "$SessionPath/$SESSIONTICKET");
3285 my $ticket_valid = check_ticket_validity("SESSION", "$SessionPath/$SESSIONTICKET", $REMOTE_ADDR, $PATH_INFO);
3286 goto Login unless $ticket_valid;
3288 # Check whether the login still exists
3289 my $currentsessionticket = read_ticket("$SessionPath/$SESSIONTICKET");
3290 my $userfile = lc($currentsessionticket->{"Username"}->[0]);
3291 $userfile =~ s/[^\w]/_/isg;
3292 goto Login unless (-s "$PasswordsPath/$userfile");
3294 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3295 goto Login unless $ticket_valid;
3297 # Sessionticket is available to scripts
3298 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3299 goto Return;
3302 goto Login;
3303 goto Return;
3306 Return:
3307 # The Masterkey should NOT be accessible by the parsed files
3308 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3309 return 0;
3311 Login:
3312 create_login_file($PasswordsPath, $SessionPath, $REMOTE_ADDR);
3313 # Note, cookies are set only ONCE
3314 $SETCOOKIELIST{"CGIscriptorLOGIN"} = "-";
3315 # The Masterkey should NOT be accessible by the parsed files
3316 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3317 return "$YOUR_HTML_FILES/$Login";
3320 sub authorize_login # ($loginfile, $authorizationfile, $password, $SessionPath) => SESSIONTICKET First two arguments are file paths
3322 my $loginfile = shift || "";
3323 my $authorizationfile = shift || "";
3324 my $password = shift || "";
3325 my $SessionPath = shift || "";
3327 # Get Login session ticket
3328 my $loginticket = read_ticket($loginfile);
3329 return 0 unless $loginticket;
3330 # Get User credentials for authorization
3331 my $authorization = read_ticket($authorizationfile);
3332 return 0 unless $authorization;
3334 # Get Randomsalt
3335 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3336 return "" unless $Randomsalt;
3338 my $storedpassword = $authorization->{'Password'}->[0];
3339 return "" unless $storedpassword;
3340 my $Hashedpassword = hash_string("$Randomsalt$storedpassword");
3341 return "" unless $password eq $Hashedpassword;
3343 # Extract Session Ticket
3344 my $loginsession = $loginticket->{'Session'}->[0];
3345 my $sessionticket = hash_string("$loginsession$storedpassword");
3346 chomp($sessionticket);
3347 $sessionticket = "" if -x "$SessionPath/$sessionticket";
3349 return $sessionticket;
3352 sub change_password # ($loginfile, $sessionfile, $authorizationfile, $password, $newpassword) First three arguments are file paths
3354 my $loginfile = shift || "";
3355 my $sessionfile = shift || "";
3356 my $authorizationfile = shift || "";
3357 my $password = shift || "";
3358 my $newpassword = shift || "";
3359 # Get Login session ticket
3360 my $loginticket = read_ticket($loginfile);
3361 return "" unless $loginticket;
3362 # Login ticket file has been used, remove it
3363 unlink($loginfile);
3364 # Get Randomsalt
3365 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3366 return "" unless $Randomsalt;
3367 my $LoginID = $loginticket->{'Session'}->[0];
3368 return "" unless $LoginID;
3370 # Get session ticket
3371 my $sessionticket = read_ticket($sessionfile);
3372 return "" unless $sessionticket;
3374 # Get User credentials for authorization
3375 my $authorization = read_ticket($authorizationfile);
3376 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3378 my $storedpassword = $authorization->{'Password'}->[0];
3379 my $Hashedpassword = hash_string("$Randomsalt$storedpassword");
3380 return "" unless $password eq $Hashedpassword;
3381 my $secretkey = hash_string("$Randomsalt$LoginID$storedpassword");
3383 # Decrypt the $newpassword
3384 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3385 return "" unless $decryptedPassword;
3386 # Authorization succeeded, change password
3387 $authorization->{'Password'}->[0] = $decryptedPassword;
3388 # Write out
3389 write_ticket($authorizationfile, $authorization, $authorization->{'Salt'}->[0]);
3391 return $newpassword;
3393 # First three arguments are file paths
3394 sub create_newuser # ($loginfile, $sessionfile, $authorizationfile, $password, $newuser, $newpassword, $newsession) -> account text
3396 my $loginfile = shift || "";
3397 my $sessionfile = shift || "";
3398 my $authorizationfile = shift || "";
3399 my $password = shift || "";
3400 my $newuser = shift || "";
3401 my $newpassword = shift || "";
3402 my $newsession = shift || "";
3404 # Get Login session ticket
3405 my $loginticket = read_ticket($loginfile);
3406 return "" unless $loginticket;
3407 # Login ticket file has been used, remove it
3408 unlink($loginfile);
3409 # Get Randomsalt
3410 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3411 return "" unless $Randomsalt;
3412 my $LoginID = $loginticket->{'Session'}->[0];
3413 return "" unless $LoginID;
3415 # Get session ticket
3416 my $sessionticket = read_ticket($sessionfile);
3417 return "" unless $sessionticket;
3418 # Get User credentials for authorization
3419 my $authorization = read_ticket($authorizationfile);
3420 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3421 my $sessionkey = $sessionticket->{'Key'}->[0];
3422 my $serversalt = $authorization->{'Salt'}->[0];
3423 return "" unless $serversalt;
3425 my $storedpassword = $authorization->{'Password'}->[0];
3426 my $Hashedpassword = hash_string("$Randomsalt$storedpassword");
3427 return "" unless $password eq $Hashedpassword;
3428 my $secretkey = hash_string("$Randomsalt$LoginID$storedpassword");
3430 # Decrypt the $newpassword
3431 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3432 return "" unless $decryptedPassword;
3434 # Authorization succeeded, create new account
3435 my $newaccount = {};
3436 $newaccount->{'Type'} = ['PASSWORD'];
3437 $newaccount->{'Username'} = [$newuser];
3438 $newaccount->{'Password'} = [$decryptedPassword];
3439 $newaccount->{'Salt'} = [$serversalt];
3440 $newaccount->{'Session'} = ['SESSION'];
3441 if($newsession eq 'IPADDRESS'){$newaccount->{'Session'} = ['IPADDRESS'];};
3442 if($newsession eq 'CHALLENGE'){$newaccount->{'Session'} = ['CHALLENGE'];};
3443 my $timesec = time();
3444 my $gmt_date = gmtime();
3445 $newaccount->{'Time'} = [$timesec];
3446 $newaccount->{'Date'} = [$gmt_date];
3448 # AllowedPaths
3449 my $NewAllowedPaths = "";
3450 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3451 my $currentRoot = "";
3452 $currentRoot = $1 if $PATH_INFO =~ m!^([\w\-\. /]+)!isg;
3453 $currentRoot =~ s![^/]+$!!isg;
3454 if($currentRoot)
3456 $currentRoot .= '/' unless $currentRoot =~ m!/$!;
3457 my $newpath = "^".${currentRoot}.'[\w\-]+\.html?';
3458 $NewAllowedPaths .= 'AllowedPaths: ^'.${currentRoot}.'[\w\-]+\.html?'."\n";
3459 $newaccount->{'AllowedPaths'} = [$newpath];
3461 else
3463 # Tricky PATH_INFO, deny all
3464 $NewAllowedPaths .= "DeniedPaths: ^/\n";
3465 $newaccount->{'DeniedPaths'} = ["DeniedPaths: ^/\n"];
3468 # Construct home directory path
3469 my $FullHomeDirectoryPath = "";
3470 my $currentHome = lc($newuser);
3471 if($currentHome && $currentHome !~ /^\s*\#/)
3473 $currentHome =~ s![^\w]!_!isg;
3474 my $newpath = "^${currentRoot}$currentHome/";
3475 push(@{$newaccount->{'AllowedPaths'}}, $newpath);
3476 # Create home directory
3477 $FullHomeDirectoryPath = $ENV{'HOME'}.${currentRoot}.$currentHome;
3480 # Allowed Paths
3481 CGIexecute::defineCGIvariable('ALLOWEDPATHS', "");
3482 my $allowedpaths = ${"CGIexecute::ALLOWEDPATHS"};
3483 if($allowedpaths && $allowedpaths !~ /^\s*\#/)
3485 $allowedpaths =~ s![^\^\w\./\;\+\*\?\[\]\$]!!isg;
3486 my @pathlist = split(/\;/, $allowedpaths);
3487 foreach my $entry (@pathlist)
3489 push(@{$newaccount->{'AllowedPaths'}}, "^".${currentRoot}.$entry);
3493 # Allowed IP addresses
3494 CGIexecute::defineCGIvariable('IPADDRESS', "");
3495 my $ipaddress = ${"CGIexecute::IPADDRESS"};
3496 if($ipaddress && $ipaddress !~ /^\s*\#/)
3498 $ipaddress =~ s![^\d\.\;]!!isg;
3499 my @iplist = split(/\;/, $ipaddress);
3500 foreach my $entry (@iplist)
3502 next unless $entry =~ /\d/;
3503 next if $entry =~ /^\s*\#/;
3504 $entry =~ s/\./\\./g;
3505 push(@{$newaccount->{'IPaddress'}}, $entry);
3509 # Sign the new ticket
3510 my $Signature = SignTicketWithMasterkey($newaccount, $newaccount->{'Salt'}->[0]);
3512 # Write
3513 my $datetime = gmtime();
3514 my $passwordline = "<span id='newaccount'>".($newaccount->{'Password'}->[0])."</span>";
3515 my $newuserfile = "";
3516 if(grep(/^CreateUser$/, @{$authorization->{'Capabilities'}}))
3518 my $newuserfilename = lc($newuser);
3519 $newuserfilename =~ s/[^\w]/_/isg;
3520 $newuserfile = $authorizationfile;
3521 $newuserfile =~ s![^/]*$!!isg;
3522 $newuserfile .= $newuserfilename;
3523 if(-s $newuserfile)
3525 $newuserfile = "";
3527 elsif($FullHomeDirectoryPath && !(-d $FullHomeDirectoryPath || -s $FullHomeDirectoryPath))
3529 if(-d "$ENV{'HOME'}${currentRoot}.SkeletonDir")
3531 `cp '$ENV{'HOME'}${currentRoot}.SkeletonDir' '$FullHomeDirectoryPath/'`;
3533 elsif(-d "$ENV{'HOME'}${currentRoot}SkeletonDir")
3535 `cp '$ENV{'HOME'}${currentRoot}SkeletonDir' '$FullHomeDirectoryPath/'`;
3537 elsif(-s "$ENV{'HOME'}${currentRoot}UserIndex.html")
3539 mkdir $FullHomeDirectoryPath;
3540 `cp '$ENV{'HOME'}${currentRoot}UserIndex.html' '$FullHomeDirectoryPath/index.html'`;
3542 elsif(-s "$ENV{'HOME'}${currentRoot}index.html")
3544 mkdir $FullHomeDirectoryPath;
3545 `cp '$ENV{'HOME'}${currentRoot}index.html' '$FullHomeDirectoryPath/index.html'`;
3551 my $newaccounttext = write_ticket($newuserfile, $newaccount, $serversalt);
3553 # Re-encrypt the new password for transmission
3554 if($newaccounttext =~ /^(Password\:\s+)(\S+)\s*$/)
3556 my $passwordvalue = $1;
3557 my $reencryptedpassword = XOR_hex_strings($secretkey, $passwordvalue);
3558 my $encryptedpasswordline = "<span id='newaccount'>$reencryptedpassword</span>";
3559 $newaccounttext =~ s/^(Password\:\s+)(\S+)\s*$/\1$encryptedpasswordline/gim;
3561 return $newaccounttext;
3564 # Copy a Challenge ticket file to a new name which is the hash of the new $CHALLENGETICKET and the password
3565 sub copy_challenge_file #($oldchallengefile, $authorizationfile, $sessionpath) -> $CHALLENGETICKET
3567 my $oldchallengefile = shift || "";
3568 my $authorizationfile = shift || "";
3569 my $sessionpath = shift || "";
3570 $sessionpath =~ s!/+$!!g;
3572 # Get Login session ticket
3573 my $oldchallenge = read_ticket($oldchallengefile);
3574 return "" unless $oldchallenge;
3576 # Get Authorization (user) session file
3577 my $authorization = read_ticket($authorizationfile);
3578 return "" unless $authorization;
3579 my $storedpassword = $authorization->{'Password'}->[0];
3580 return "" unless $storedpassword;
3581 my $challengekey = $oldchallenge->{'Key'}->[0];
3582 return "" unless $challengekey;
3584 # Create Random Hash Salt
3585 my $NEWCHALLENGETICKET = get_random_hex();;
3586 my $newchallengefile = hash_string("$NEWCHALLENGETICKET$challengekey");
3587 return "" unless $newchallengefile;
3589 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3590 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3591 ${"CGIexecute::CHALLENGETICKET"} = $NEWCHALLENGETICKET;
3593 # Write Session Ticket
3594 open(OLDCHALLENGE, "<$oldchallengefile") || die "<$oldchallengefile: $!\n";
3595 my @OldChallengeLines = <OLDCHALLENGE>;
3596 close(OLDCHALLENGE);
3597 # Old file should now be removed
3598 unlink($oldchallengefile);
3600 open(SESSION, ">$sessionpath/$newchallengefile") || die "$sessionpath/$newchallengefile: $!\n";
3601 foreach $line (@OldChallengeLines)
3603 print SESSION $line;
3605 close(SESSION);
3607 return $NEWCHALLENGETICKET;
3610 sub create_login_file #($PasswordDir, $SessionDir, $IPaddress)
3612 my $PasswordDir = shift || "";
3613 my $SessionDir = shift || "";
3614 my $IPaddress = shift || "";
3616 # Create Login Ticket
3617 my $LOGINTICKET= get_random_hex ();
3619 # Create Random Hash Salt
3620 my $RANDOMSALT= get_random_hex();
3622 # Create SALT file if it does not exist
3623 # Remove this, including test account for life system
3624 unless(-d "$SessionDir")
3626 `mkdir -p "$SessionDir"`;
3628 unless(-d "$PasswordDir")
3630 `mkdir -p "$PasswordDir"`;
3632 # Create SERVERSALT and default test account
3633 my $SERVERSALT = "";
3634 unless(-s "$PasswordDir/SALT")
3636 $SERVERSALT= get_random_hex();
3637 open(SALTFILE, ">$PasswordDir/SALT") || die ">$PasswordDir/SALT: $!\n";
3638 print SALTFILE "$SERVERSALT\n";
3639 close(SALTFILE);
3641 # Update test account (should be removed in live system)
3642 my @alltestusers = ("test", "testip", "testchallenge", "admin");
3643 foreach my $testuser (@alltestusers)
3645 if(-s "$PasswordDir/$testuser")
3647 my $plainpassword = $testuser eq 'admin' ? "There is no password like more password" : "testing";
3648 my $storedpassword = hash_string("${SERVERSALT}${plainpassword}${testuser}");
3649 # Encrypt the new password with the MasterKey
3650 my $authorization = read_ticket("$PasswordDir/$testuser") || return "";
3651 $authorization->{'Salt'} = [$SERVERSALT];
3652 set_password($authorization, $SERVERSALT, $plainpassword);
3653 write_ticket("$PasswordDir/$testuser", $authorization, $SERVERSALT);
3658 # Read in site Salt
3659 open(SALTFILE, "<$PasswordDir/SALT") || die "$PasswordDir/SALT: $!\n";
3660 $SERVERSALT=<SALTFILE>;
3661 close(SALTFILE);
3662 chomp($SERVERSALT);
3664 # Create login session ticket
3665 my $datetime = gmtime();
3666 my $timesec = time();
3667 my $loginticket = {};
3668 $loginticket->{Type} = ['LOGIN'];
3669 $loginticket->{IPaddress} = [$IPaddress];
3670 $loginticket->{Salt} = [$SERVERSALT];
3671 $loginticket->{Session} = [$LOGINTICKET];
3672 $loginticket->{Randomsalt} = [$RANDOMSALT];
3673 $loginticket->{Expires} = ['+600s'];
3674 $loginticket->{Date} = ["$datetime UTC"];
3675 $loginticket->{Time} = [$timesec];
3676 write_ticket("$SessionDir/$LOGINTICKET", $loginticket, $SERVERSALT);
3678 # Set global variables
3679 # $SERVERSALT
3680 $ENV{'SERVERSALT'} = $SERVERSALT;
3681 CGIexecute::defineCGIvariable('SERVERSALT', "");
3682 ${"CGIexecute::SERVERSALT"} = $SERVERSALT;
3684 # $SESSIONTICKET
3685 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3686 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3687 ${"CGIexecute::SESSIONTICKET"} = $SESSIONTICKET;
3689 # $RANDOMSALT
3690 $ENV{'RANDOMSALT'} = $RANDOMSALT;
3691 CGIexecute::defineCGIvariable('RANDOMSALT', "");
3692 ${"CGIexecute::RANDOMSALT"} = $RANDOMSALT;
3694 # $LOGINTICKET
3695 $ENV{'LOGINTICKET'} = $LOGINTICKET;
3696 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3697 ${"CGIexecute::LOGINTICKET"} = $LOGINTICKET;
3699 return $ENV{'LOGINTICKET'};
3702 sub create_session_file #($sessionfile, $loginfile, $authorizationfile, $path) -> Is $loginfile deleted? 0/1
3704 my $sessionfile = shift || "";
3705 my $loginfile = shift || "";
3706 my $authorizationfile = shift || "";
3707 my $path = shift || "";
3709 # Get Login session ticket
3710 my $loginticket = read_ticket($loginfile);
3711 return unlink($loginfile) unless $loginticket;
3713 # Get Authorization (user) session file
3714 my $authorization = read_ticket($authorizationfile);
3715 return unlink($loginfile) unless $authorization;
3717 # For a Session or a Challenge, we need a stored key
3718 my $sessionkey = "";
3719 my $secretkey = "";
3720 if($authorization->{'Session'} && $authorization->{'Session'}->[0] ne 'IPADDRESS')
3722 my $storedpassword = $authorization->{'Password'}->[0];
3723 my $loginticketid = $loginticket->{'Session'}->[0];
3724 my $randomsalt = $loginticket->{'Randomsalt'}->[0];
3725 $sessionkey = hash_string("$loginticketid$storedpassword");
3726 $secretkey = hash_string("$randomsalt$loginticketid$storedpassword");
3728 # Get Session id
3729 my $sessionid = "";
3730 if($sessionfile =~ m!([^/]+)$!)
3732 $sessionid = $1;
3735 # Convert Authorization content to Session content
3736 my $sessionContent = {};
3737 my $SessionType = $authorization->{'Session'}->[0] ? $authorization->{'Session'}->[0] : "SESSION";
3738 $sessionContent->{Type} = [$SessionType];
3739 $sessionContent->{Username} = [lc($authorization->{'Username'}->[0])];
3740 $sessionContent->{Session} = [$sessionid];
3741 $sessionContent->{Time} = [time];
3742 # Limit communication to the login IP address, except for Tor like situations with VariableREMOTE_ADDR
3743 $sessionContent->{IPaddress} = ['.'];
3744 if($sessionContent->{Type}->[0] eq 'CHALLENGE' && grep(/^VariableREMOTE_ADDR$/, @{$authorization->{'Capabilities'}}))
3746 $sessionContent->{IPaddress} = $authorization->{'IPaddress'} if $authorization->{'IPaddress'};
3748 else
3750 $sessionContent->{IPaddress} = $loginticket->{'IPaddress'};
3752 $sessionContent->{Salt} = $authorization->{'Salt'};
3753 $sessionContent->{Randomsalt} = $loginticket->{'Randomsalt'};
3754 $sessionContent->{AllowedPaths} = $authorization->{'AllowedPaths'};
3755 $sessionContent->{DeniedPaths} = $authorization->{'DeniedPaths'};
3756 $sessionContent->{Expires} = $authorization->{'MaxLifetime'};
3757 $sessionContent->{Capabilities} = $authorization->{'Capabilities'};
3758 foreach my $pattern (keys(%TicketRequiredPatterns))
3760 if($path =~ m#$pattern#)
3762 my ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3763 push(@{$sessionContent->{Expires}}, $validtime);
3766 $sessionContent->{Key} = [$sessionkey] if $sessionkey;
3767 $sessionContent->{Secretkey} = [$secretkey] if $secretkey;
3768 $sessionContent->{Date} = [gmtime()." UTC"];
3770 # Write Session Ticket
3771 write_ticket($sessionfile, $sessionContent, $authorization->{'Salt'}->[0]);
3773 # Login file should now be removed
3774 return unlink($loginfile);
3777 sub check_ticket_validity # ($type, $ticketfile, $address, $path [, $unsigned])
3779 my $type = shift || "SESSION";
3780 my $ticketfile = shift || "";
3781 my $address = shift || "";
3782 my $path = shift || "";
3783 my $unsigned = shift || 0;
3785 # Is there a session ticket of this name?
3786 return 0 unless -s "$ticketfile";
3788 # There is a session ticket, is it linked to this IP address?
3789 my $ticket = read_ticket($ticketfile);
3790 unless($ticket)
3792 print STDERR "Ticket expired or empty: $ticketfile\n";
3793 return;
3796 # Is this the right type of ticket
3797 unless($ticket && $ticket->{'Type'}->[0] eq $type)
3799 print STDERR "Wrong ticket type: $ticket->{'Type'}->[0] eq $type\n";
3800 return;
3803 # Does the IP address match?
3804 my $IPmatches = @{$ticket->{"IPaddress"}} ? 0 : 1;
3805 for $IPpattern (@{$ticket->{"IPaddress"}})
3807 ++$IPmatches if $address =~ m#^$IPpattern#ig;
3809 if($address && ! $IPmatches)
3811 print STDERR "Wrong REMOTE ADDR for $ticket->{'Username'}->[0]: $ticket->{'IPaddress'}->[0] vs $address\n";
3812 return 0;
3815 # Is the path denied
3816 my $Pathmatches = 0;
3817 foreach $Pathpattern (@{$ticket->{"DeniedPaths"}})
3819 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3821 return 0 if @{$ticket->{"DeniedPaths"}} && $Pathmatches;
3823 # Is the path allowed
3824 $Pathmatches = 0;
3825 foreach $Pathpattern (@{$ticket->{"AllowedPaths"}})
3827 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3829 return 0 unless !@{$ticket->{"AllowedPaths"}} || $Pathmatches;
3831 # Check signature if not told to use an unsigned ticket (dangerous)
3832 my $Signature = TicketSignature($ticket, $ticket->{'Salt'}->[0]);
3833 if((! $unsigned) && $Signature && $Signature ne $ticket->{'Signature'}->[0])
3835 print STDERR "Invalid signature for $ticket->{'Type'}: $ticket->{'Username'}\n$ticketfile\n";
3836 return 0;
3839 # Make login values available (will also protect against resetting by query)
3840 $ENV{"LOGINUSERNAME"} = lc($ticket->{'Username'}->[0]);
3841 $ENV{"LOGINIPADDRESS"} = $address;
3842 $ENV{"LOGINPATH"} = $path;
3843 $ENV{"SESSIONTYPE"} = $type unless $type eq "PASSWORD";
3845 # Set Capabilities, if present
3846 if($ticket->{'Username'}->[0] && @{$ticket->{'Capabilities'}})
3848 $ENV{'CAPABILITIES'} = $ticket->{'Username'}->[0];
3849 CGIexecute::defineCGIvariableList('CAPABILITIES', "");
3850 @{"CGIexecute::CAPABILITIES"} = @{$ticket->{'Capabilities'}};
3851 # Capabilities should not be changed anymore by CGI query!
3853 # Capabilities are NOT to be set by the query
3854 CGIexecute::ProtectCGIvariable('CAPABILITIES');
3856 return 1;
3860 sub remove_expired_tickets # ($path) -> number of tickets removed
3862 my $path = shift || "";
3863 return 0 unless $path;
3864 $path =~ s!/+$!!g;
3865 my $removed_tickets = 0;
3866 my @ticketlist = glob("$path/*");
3867 foreach my $ticketfile (@ticketlist)
3869 my $ticket = read_ticket($ticketfile);
3870 unless($ticket)
3872 unlink $ticketfile;
3873 ++$removed_tickets;
3876 return $removed_tickets;
3879 sub set_password # ($ticket, $salt, $plainpassword) -> $password
3881 my $ticket = shift || "";
3882 my $salt = shift || "";
3883 my $plainpassword = shift || "";
3885 my $user = lc($ticket->{'Username'}->[0]);
3886 return "" unless $user;
3887 $salt = $ticket->{'Salt'}->[0] unless $salt;
3889 my $storedpassword = hash_string("${salt}${plainpassword}${user}");
3890 $ticket->{'Password'} = [$storedpassword];
3891 $ticket->{'Salt'} = [$salt];
3893 return $ticket->{'Password'}->[0];
3896 sub write_ticket # ($ticketfile, $ticket, $salt [, $masterkey]) -> &%ticket
3898 my $ticketfile = shift || "";
3899 my $ticket = shift || "";
3900 my $salt = shift || "";
3901 my $masterkey = shift || $ENV{'CGIMasterKey'};
3903 # Encrypt password
3904 EncryptTicketWithMasterKey($ticket, $salt, $masterkey);
3906 # Sign the new ticket
3907 my $signature = SignTicketWithMasterkey($ticket, $salt, $masterkey);
3909 # Create ordered list with labels
3910 my @orderlist = ('Type', 'Username', 'Password', 'IPaddress', 'AllowedPaths', 'DeniedPaths',
3911 'Expires', 'Capabilities', 'Salt', 'Session', 'Randomsalt',
3912 'Date', 'Time', 'Signature', 'Key', 'Secretkey');
3913 my @labellist = keys(%{$ticket});
3914 foreach my $label (@orderlist)
3916 @labellist = grep(!/\b$label\b/, @labellist);
3919 # Create ticket in text
3920 my $TicketText = "";
3921 foreach my $label (@orderlist, @labellist)
3923 next unless exists($ticket->{$label}) && $ticket->{$label}->[0];
3924 foreach my $value (@{$ticket->{$label}})
3926 $TicketText .= "$label: $value\n";
3929 if($ticketfile)
3931 open(TICKET, ">$ticketfile") || die "$ticketfile: $!\n";
3932 print TICKET $TicketText;
3933 close(TICKET);
3936 return $TicketText;
3939 # Note, read_ticket will return 0 if the ticket has expired!
3940 sub read_ticket # ($ticketfile [, $salt, $masterkey]) -> &%ticket
3942 my $ticketfile = shift || "";
3943 my $serversalt = shift || "";
3944 my $masterkey = shift || $ENV{'CGIMasterKey'};
3946 my $ticket = {};
3947 if($ticketfile && -s $ticketfile)
3949 open(TICKETFILE, "<$ticketfile") || die "$ticketfile: $!\n";
3950 my @alllines = <TICKETFILE>;
3951 close(TICKETFILE);
3952 foreach my $currentline (@alllines)
3954 # Skip empty lines and comments
3955 next unless $currentline =~ /\S/;
3956 next if $currentline =~ /^\s*\#/;
3958 if($currentline =~ /^\s*(\S[^\:]+)\:\s+(.*)\s*$/)
3960 my $Label = $1;
3961 my $Value = $2;
3962 $ticket->{$Label} = () unless exists($ticket->{$Label});
3963 push(@{$ticket->{$Label}}, $Value);
3967 if($masterkey && exists($ticket->{'Password'}) && $ticket->{'Password'}->[0])
3969 # Use the ServerSalt stored in the ticket, if present
3970 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
3972 $serversalt = $ticket->{Salt}->[0];
3974 # Decrypt all passwords
3975 DecryptTicketWithMasterKey($ticket, $serversalt, $masterkey) ||
3976 die "Decryption failed: DecryptTicketWithMasterKey ($ticket, $serversalt)\n";
3979 # Check whether the ticket has expired
3980 if(exists($ticket->{Expires}))
3982 my $StartTime = 0;
3983 if(exists($ticket->{Time}) && $ticket->{Time}->[0] > 0)
3985 $StartTime = [(sort(@{$ticket->{Time}}))]->[0];
3987 else
3989 # Get SessionTicket file stats
3990 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)
3991 = stat($ticketfile);
3992 $StartTime = $ctime;
3994 foreach my $Value (@{$ticket->{'Expires'}})
3996 # Recalculate expire date from relative time
3997 if($Value =~ /^\+/)
3999 if($Value =~ /^\+(\d+)\s*d(ays)?\s*$/)
4001 $ExpireTime = 24*3600*$1;
4003 elsif($Value =~ /^\+(\d+)\s*m(inutes)?\s*$/)
4005 $ExpireTime = 60*$1;
4007 elsif($Value =~ /^\+(\d+)\s*h(ours)?\s*$/)
4009 $ExpireTime = 3600*$1;
4011 elsif($Value =~ /^\+(\d+)\s*s(econds)?\s*$/)
4013 $ExpireTime = $1;
4015 elsif($Value =~ /^\+(\d+)\s*$/)
4017 $ExpireTime = $1;
4020 my $absoluteTime = $Value =~ /^\+/ ? $StartTime + $ExpireTime : $Value;
4021 return 0 unless $absoluteTime > time;
4023 @{$ticket->{Expires}} = sort(@{$ticket->{Expires}});
4025 return $ticket;
4028 # Set up a valid ticket from a given text file
4029 # Use from command line. DO NOT USE ONLINE
4030 # Watch out for passwords that get stored in the history file
4032 # perl CGIscriptor.pl --managelogin [options] [files]
4033 # Options:
4034 # salt={file or saltvalue}
4035 # masterkey={file or plaintext}
4036 # newmasterkey={file or plaintext}
4037 # password={file or palintext}
4039 # Followed by one or more file names.
4040 # Options can be interspersed between filenames,
4041 # e.g., password='plaintext'
4042 # Note that passwords are only used once!
4044 sub setup_ticket_file # (@ARGV)
4046 # Stop when run on-line
4047 return if $ENV{'PATH_INFO'} || $ENV{'QUERY_STRING'};
4049 my %Settings = ();
4050 foreach my $input (@_)
4052 if($input =~ /^([\w]+)\=/)
4054 my $name = lc($1);
4055 my $value = $';
4056 chomp($value);
4058 if($value !~ m![^\w\.\~\/\:\-]! && $value !~ /^[\-\.]/ && -s "$value" && ! -d "$value")
4060 open(INPUTVALUE, "<$value") || die "$value: $!\n";
4061 $value = <INPUTVALUE>;
4062 chomp($value);
4065 $value =~ s/(^\'([^\']*)\'$)/\1/g;
4066 $value =~ s/(^\"([^\"]*)\"$)/\1/g;
4067 $Settings{$name} = $value;
4069 elsif($input !~ m![^\w\.\~\/\:\-]!i && $input !~ /^[\-\.]/i && -s $input)
4071 # We MUST have a salt
4072 $Settings{'salt'} = $ticket->{'Salt'}->[0] unless $Settings{'salt'};
4074 # Set the new masterkey to the old masterkey if there is no new masterkey
4075 $Settings{'newmasterkey'} = $Settings{'masterkey'} unless exists($Settings{'newmasterkey'});
4077 # Get the ticket
4078 my $ticket = read_ticket($input, $Settings{'salt'}, $Settings{'masterkey'});
4080 # Set a new password from plaintext
4081 $ticket->{'Salt'}->[0] = $Settings{'salt'} if $Settings{'salt'} && $Settings{'password'};
4082 set_password ($ticket, $Settings{'salt'}, $Settings{'password'}) if $Settings{'password'};
4083 # Write the ticket back to file
4084 write_ticket($input, $ticket, $Settings{'salt'}, $Settings{'newmasterkey'});
4086 # A password is only used once
4087 $Settings{'password'} = "";
4092 # Add a signature from $masterkey to a ticket in the label $signlabel
4093 sub SignTicketWithMasterkey # ($ticket, $serversalt [, $masterkey, $signlabel]) -> $Signature
4095 my $ticket = shift || return 0;
4096 my $serversalt = shift || "";
4097 my $masterkey = shift || $ENV{'CGIMasterKey'};
4098 my $signlabel = shift || 'Signature';
4100 my $Signature = TicketSignature($ticket, $serversalt, $masterkey);
4102 $ticket->{$signlabel} = [$Signature] if $Signature;
4104 return $Signature;
4107 # Determine ticket signature
4108 sub TicketSignature # ($ticket, $serversalt [, $masterkey]) -> $Signature
4110 my $ticket = shift || return 0;
4111 my $serversalt = shift || "";
4112 my $masterkey = shift || $ENV{'CGIMasterKey'};
4113 my $Signature = "";
4115 if($masterkey)
4117 # If the ServerSalt is not stored in the ticket, the SALT file has to be found
4118 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4120 $serversalt = $ticket->{Salt}->[0];
4122 # Sign
4123 if($serversalt)
4125 my $username = lc($ticket->{'Username'}->[0]);
4126 my $hash1 = hash_string(${serversalt}.$masterkey);
4127 my $CryptKey = hash_string($username.${'hash1'});
4128 my $SignText = "Type: ".$ticket->{'Type'}->[0]."\n";
4129 my @tmp = sort(@{$ticket->{'Username'}});
4130 $SignText .= "Username: @tmp\n";
4131 @tmp = sort(@{$ticket->{'IPaddress'}});
4132 $SignText .= "IPaddress: @tmp\n";
4133 @tmp = sort(@{$ticket->{'AllowedPaths'}});
4134 $SignText .= "AllowedPaths: @tmp\n";
4135 @tmp = sort(@{$ticket->{'DeniedPaths'}});
4136 $SignText .= "DeniedPaths: @tmp\n";
4137 @tmp = sort(@{$ticket->{'Session'}});
4138 $SignText .= "Session: @tmp\n";
4139 @tmp = sort(@{$ticket->{'Time'}});
4140 $SignText .= "Time: @tmp\n";
4141 @tmp = sort(@{$ticket->{'Expires'}});
4142 $SignText .= "Expires: @tmp\n";
4143 @tmp = sort(@{$ticket->{'Capabilities'}});
4144 $SignText .= "Capabilities: @tmp\n";
4145 $Signature = HMAC_hex($CryptKey, $SignText);
4148 return $Signature;
4151 # Decrypts a password list IN PLACE
4152 sub DecryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4154 my $ticket = shift || return 0;
4155 my $serversalt = shift || "";
4156 my $masterkey = shift || $ENV{'CGIMasterKey'};
4158 if($masterkey && exists($ticket->{Password}) && $ticket->{Password}->[0])
4160 # If the ServerSalt is not given, read it from the the ticket
4161 if(! $serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4163 $serversalt = $ticket->{Salt}->[0];
4165 # Decrypt password(s)
4166 if($serversalt)
4168 my $hash1 = hash_string(${serversalt}.$masterkey);
4169 my $username = lc($ticket->{'Username'}->[0]);
4170 my $CryptKey = hash_string(${'hash1'}.$username);
4171 foreach my $password (@{$ticket->{Password}})
4173 $password = XOR_hex_strings($CryptKey,$password);
4177 return $ticket->{'Password'};
4179 sub EncryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4181 DecryptTicketWithMasterKey(@_);
4184 # Implement HMAC signature hash.
4185 # Blocksize is length in HEX characters, NOT bytes
4186 sub HMAC_hex # ($key, $message [, $blocksizehex]) -> $hex
4188 my $key = shift || "";
4189 my $message = shift || "";
4190 my $blocksizehex = shift || length($key);
4191 $key = hash_string($key) if length($key) > $blocksizehex;
4193 my $innerkey = XOR_hex_byte ($key, "36");
4194 my $outerkey = XOR_hex_byte ($key, "5c");
4195 my $innerhash = hash_string($innerkey.$message);
4196 my $outerhash = hash_string($outerkey.$innerhash);
4198 return $outerhash;
4201 # XOR input with equally long string of repeated 2 hex character (byte)
4202 # string. Input must have even number of hex characters
4203 sub XOR_hex_byte # ($hex1, $hexbyte) -> $hex
4205 my $hex1 = shift || "";
4206 my $hexbyte = shift || "";
4207 my $bytelength = length($hexbyte);
4208 my $hex2 = $hex1;
4209 $hex2 =~ s/.{$bytelength}/$hexbyte/ig;
4210 return XOR_hex_strings($hex1, $hex2);
4213 sub XOR_hex_strings # ($hex1, $hex2) -> $hex
4215 my $hex1 = shift || "";
4216 my $hex2 = shift || "";
4217 my @hex1list = split('', $hex1);
4218 my @hex2list = split('', $hex2);
4219 my @hexresultlist = ();
4220 for(my $i; $i < scalar(@hex1list); ++$i)
4222 my $d1 = hex($hex1list[$i]);
4223 my $d2 = hex($hex2list[$i]);
4224 my $dresult = ($d1 ^ $d2);
4225 $hexresultlist[$i] = sprintf("%x", $dresult);
4227 $hexresult = join('', @hexresultlist);
4228 return $hexresult;
4231 # End of Handle login access
4234 ############################################################################
4236 # Handle foreign interpreters (i.e., scripting languages)
4238 # Insert perl code to execute scripts in foreign scripting languages.
4239 # Actually, the scripts inside the <SCRIPT></SCRIPT> blocks are piped
4240 # into an interpreter.
4241 # The code presented here is fairly confusing because it
4242 # actually writes perl code code to the output.
4244 # A table with the file handles
4245 %SCRIPTINGINPUT = ();
4247 # A function to clean up Client delivered CGI parameter values
4248 # (i.e., quote all odd characters)
4249 %SHRUBcharacterTR =
4251 "\'" => '&#39;',
4252 "\`" => '&#96;',
4253 "\"" => '&quot;',
4254 '&' => '&amper;',
4255 "\\" => '&#92;'
4258 sub shrubCGIparameter # ($String) -> Cleaned string
4260 my $String = shift || "";
4262 # Change all quotes [`'"] into HTML character entities
4263 my ($Char, $Transcript) = ('&', $SHRUBcharacterTR{'&'});
4265 # Protect &
4266 $String =~ s/\Q$Char\E/$Transcript/isg if $Transcript;
4268 while( ($Char, $Transcript) = each %SHRUBcharacterTR)
4270 next if $Char eq '&';
4271 $String =~ s/\Q$Char\E/$Transcript/isg;
4274 # Replace newlines
4275 $String =~ s/[\n]/\\n/g;
4276 # Replace control characters with their backslashed octal ordinal numbers
4277 $String =~ s/([^\S \t])/(sprintf("\\0%o", ord($1)))/eisg; #
4278 $String =~ s/([\x00-\x08\x0A-\x1F])/(sprintf("\\0%o", ord($1)))/eisg; #
4280 return $String;
4284 # The initial open statements: Open a pipe to the foreign script interpreter
4285 sub OpenForeignScript # ($ContentType) -> $DirectivePrefix
4287 my $ContentType = lc(shift) || return "";
4288 my $NewDirective = "";
4290 return $NewDirective if($SCRIPTINGINPUT{$ContentType});
4292 # Construct a unique file handle name
4293 $SCRIPTINGFILEHANDLE = uc($ContentType);
4294 $SCRIPTINGFILEHANDLE =~ s/\W/\_/isg;
4295 $SCRIPTINGINPUT{$ContentType} = $SCRIPTINGFILEHANDLE
4296 unless $SCRIPTINGINPUT{$ContentType};
4298 # Create the relevant script: Open the pipe to the interpreter
4299 $NewDirective .= <<"BLOCKCGISCRIPTOROPEN";
4300 # Open interpreter for '$ContentType'
4301 # Open pipe to interpreter (if it isn't open already)
4302 open($SCRIPTINGINPUT{$ContentType}, "|$ScriptingLanguages{$ContentType}") || main::dieHandler(14, "$ContentType: \$!\\n");
4303 BLOCKCGISCRIPTOROPEN
4305 # Insert Initialization code and CGI variables
4306 $NewDirective .= InitializeForeignScript($ContentType);
4308 # Ready
4309 return $NewDirective;
4313 # The final closing code to stop the interpreter
4314 sub CloseForeignScript # ($ContentType) -> $DirectivePrefix
4316 my $ContentType = lc(shift) || return "";
4317 my $NewDirective = "";
4319 # Do nothing unless the pipe realy IS open
4320 return "" unless $SCRIPTINGINPUT{$ContentType};
4322 # Initial comment
4323 $NewDirective .= "\# Close interpreter for '$ContentType'\n";
4326 # Write the Postfix code
4327 $NewDirective .= CleanupForeignScript($ContentType);
4329 # Create the relevant script: Close the pipe to the interpreter
4330 $NewDirective .= <<"BLOCKCGISCRIPTORCLOSE";
4331 close($SCRIPTINGINPUT{$ContentType}) || main::dieHandler(15, \"$ContentType: \$!\\n\");
4332 select(STDOUT); \$|=1;
4334 BLOCKCGISCRIPTORCLOSE
4336 # Remove the file handler of the foreign script
4337 delete($SCRIPTINGINPUT{$ContentType});
4339 return $NewDirective;
4343 # The initialization code for the foreign script interpreter
4344 sub InitializeForeignScript # ($ContentType) -> $DirectivePrefix
4346 my $ContentType = lc(shift) || return "";
4347 my $NewDirective = "";
4349 # Add initialization code
4350 if($ScriptingInitialization{$ContentType})
4352 $NewDirective .= <<"BLOCKCGISCRIPTORINIT";
4353 # Initialization Code for '$ContentType'
4354 # Select relevant output filehandle
4355 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4357 # The Initialization code (if any)
4358 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}INITIALIZATIONCODE';
4359 $ScriptingInitialization{$ContentType}
4360 ${ContentType}INITIALIZATIONCODE
4362 BLOCKCGISCRIPTORINIT
4365 # Add all CGI variables defined
4366 if(exists($ScriptingCGIvariables{$ContentType}))
4368 # Start writing variable definitions to the Interpreter
4369 if($ScriptingCGIvariables{$ContentType})
4371 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEF";
4372 # CGI variables (from the %default_values table)
4373 print $SCRIPTINGINPUT{$ContentType} << '${ContentType}CGIVARIABLES';
4374 BLOCKCGISCRIPTORVARDEF
4377 my ($N, $V);
4378 foreach $N (keys(%default_values))
4380 # Determine whether the parameter has been defined
4381 # (the eval is a workaround to get at the variable value)
4382 next unless eval("defined(\$CGIexecute::$N)");
4384 # Get the value from the EXECUTION environment
4385 $V = eval("\$CGIexecute::$N");
4386 # protect control characters (i.e., convert them to \0.. form)
4387 $V = shrubCGIparameter($V);
4389 # Protect interpolated variables
4390 eval("\$CGIexecute::$N = '$V';") unless $ScriptingCGIvariables{$ContentType};
4392 # Print the actual declaration for this scripting language
4393 if($ScriptingCGIvariables{$ContentType})
4395 $NewDirective .= sprintf($ScriptingCGIvariables{$ContentType}, $N, $V);
4396 $NewDirective .= "\n";
4400 # Stop writing variable definitions to the Interpreter
4401 if($ScriptingCGIvariables{$ContentType})
4403 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEFEND";
4404 ${ContentType}CGIVARIABLES
4405 BLOCKCGISCRIPTORVARDEFEND
4410 $NewDirective .= << "BLOCKCGISCRIPTOREND";
4412 # Select STDOUT filehandle
4413 select(STDOUT); \$|=1;
4415 BLOCKCGISCRIPTOREND
4417 return $NewDirective;
4421 # The cleanup code for the foreign script interpreter
4422 sub CleanupForeignScript # ($ContentType) -> $DirectivePrefix
4424 my $ContentType = lc(shift) || return "";
4425 my $NewDirective = "";
4427 # Return if not needed
4428 return $NewDirective unless $ScriptingCleanup{$ContentType};
4430 # Create the relevant script: Open the pipe to the interpreter
4431 $NewDirective .= <<"BLOCKCGISCRIPTORSTOP";
4432 # Cleanup Code for '$ContentType'
4433 # Select relevant output filehandle
4434 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4435 # Print Cleanup code to foreign script
4436 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}SCRIPTSTOP';
4437 $ScriptingCleanup{$ContentType}
4438 ${ContentType}SCRIPTSTOP
4440 # Select STDOUT filehandle
4441 select(STDOUT); \$|=1;
4442 BLOCKCGISCRIPTORSTOP
4444 return $NewDirective;
4448 # The prefix code for each <script></script> block
4449 sub PrefixForeignScript # ($ContentType) -> $DirectivePrefix
4451 my $ContentType = lc(shift) || return "";
4452 my $NewDirective = "";
4454 # Return if not needed
4455 return $NewDirective unless $ScriptingPrefix{$ContentType};
4457 my $Quote = "\'";
4458 # If the CGIvariables parameter is defined, but empty, interpolate
4459 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4460 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4461 !$ScriptingCGIvariables{$ContentType};
4463 # Add initialization code
4464 $NewDirective .= <<"BLOCKCGISCRIPTORPREFIX";
4465 # Prefix Code for '$ContentType'
4466 # Select relevant output filehandle
4467 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4469 # The block Prefix code (if any)
4470 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}PREFIXCODE$Quote;
4471 $ScriptingPrefix{$ContentType}
4472 ${ContentType}PREFIXCODE
4473 # Select STDOUT filehandle
4474 select(STDOUT); \$|=1;
4475 BLOCKCGISCRIPTORPREFIX
4477 return $NewDirective;
4481 # The postfix code for each <script></script> block
4482 sub PostfixForeignScript # ($ContentType) -> $DirectivePrefix
4484 my $ContentType = lc(shift) || return "";
4485 my $NewDirective = "";
4487 # Return if not needed
4488 return $NewDirective unless $ScriptingPostfix{$ContentType};
4490 my $Quote = "\'";
4491 # If the CGIvariables parameter is defined, but empty, interpolate
4492 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4493 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4494 !$ScriptingCGIvariables{$ContentType};
4496 # Create the relevant script: Open the pipe to the interpreter
4497 $NewDirective .= <<"BLOCKCGISCRIPTORPOSTFIX";
4498 # Postfix Code for '$ContentType'
4499 # Select filehandle to interpreter
4500 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4501 # Print postfix code to foreign script
4502 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SCRIPTPOSTFIX$Quote;
4503 $ScriptingPostfix{$ContentType}
4504 ${ContentType}SCRIPTPOSTFIX
4505 # Select STDOUT filehandle
4506 select(STDOUT); \$|=1;
4507 BLOCKCGISCRIPTORPOSTFIX
4509 return $NewDirective;
4512 sub InsertForeignScript # ($ContentType, $directive, @SRCfile) -> $NewDirective
4514 my $ContentType = lc(shift) || return "";
4515 my $directive = shift || return "";
4516 my @SRCfile = @_;
4517 my $NewDirective = "";
4519 my $Quote = "\'";
4520 # If the CGIvariables parameter is defined, but empty, interpolate
4521 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4522 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4523 !$ScriptingCGIvariables{$ContentType};
4525 # Create the relevant script
4526 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4527 # Insert Code for '$ContentType'
4528 # Select filehandle to interpreter
4529 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4530 BLOCKCGISCRIPTORINSERT
4532 # Use SRC feature files
4533 my $ThisSRCfile;
4534 while($ThisSRCfile = shift(@_))
4536 # Handle blocks
4537 if($ThisSRCfile =~ /^\s*\{\s*/)
4539 my $Block = $';
4540 $Block = $` if $Block =~ /\s*\}\s*$/;
4541 $NewDirective .= <<"BLOCKCGISCRIPTORSRCBLOCK";
4542 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SRCBLOCKCODE$Quote;
4543 $Block
4544 ${ContentType}SRCBLOCKCODE
4545 BLOCKCGISCRIPTORSRCBLOCK
4547 next;
4550 # Handle files
4551 $NewDirective .= <<"BLOCKCGISCRIPTORSRCFILES";
4552 # Read $ThisSRCfile
4553 open(SCRIPTINGSOURCE, "<$ThisSRCfile") || main::dieHandler(16, "$ThisSRCfILE: \$!");
4554 while(<SCRIPTINGSOURCE>)
4556 print $SCRIPTINGINPUT{$ContentType} \$_;
4558 close(SCRIPTINGSOURCE);
4560 BLOCKCGISCRIPTORSRCFILES
4564 # Add the directive
4565 if($directive)
4567 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4568 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}DIRECTIVECODE$Quote;
4569 $directive
4570 ${ContentType}DIRECTIVECODE
4571 BLOCKCGISCRIPTORINSERT
4575 $NewDirective .= <<"BLOCKCGISCRIPTORSELECT";
4576 # Select STDOUT filehandle
4577 select(STDOUT); \$|=1;
4578 BLOCKCGISCRIPTORSELECT
4580 # Ready
4581 return $NewDirective;
4584 sub CloseAllForeignScripts # Call CloseForeignScript on all open scripts
4586 my $ContentType;
4587 foreach $ContentType (keys(%SCRIPTINGINPUT))
4589 my $directive = CloseForeignScript($ContentType);
4590 print STDERR "\nDirective $CGI_Date: ", $directive;
4591 CGIexecute->evaluate($directive);
4596 # End of handling foreign (external) scripting languages.
4598 ############################################################################
4600 # A subroutine to handle "nested" quotes, it cuts off the leading
4601 # item or quoted substring
4602 # E.g.,
4603 # ' A_word and more words' -> @('A_word', ' and more words')
4604 # '"quoted string" The rest' -> @('quoted string', ' The rest')
4605 # (this is needed for parsing the <TAGS> and their attributes)
4606 my $SupportedQuotes = "\'\"\`\(\{\[";
4607 my %QuotePairs = ('('=>')','['=>']','{'=>'}'); # Brackets
4608 sub ExtractQuotedItem # ($String) -> @($QuotedString, $RestOfString)
4610 my @Result = ();
4611 my $String = shift || return @Result;
4613 if($String =~ /^\s*([\w\/\-\.]+)/is)
4615 push(@Result, $1, $');
4617 elsif($String =~ /^\s*(\\?)([\Q$SupportedQuotes\E])/is)
4619 my $BackSlash = $1 || "";
4620 my $OpenQuote = $2;
4621 my $CloseQuote = $OpenQuote;
4622 $CloseQuote = $QuotePairs{$OpenQuote} if $QuotePairs{$OpenQuote};
4624 if($BackSlash)
4626 $String =~ /^\s*\\\Q$OpenQuote\E/i;
4627 my $Onset = $';
4628 $Onset =~ /\\\Q$CloseQuote\E/i;
4629 my $Rest = $';
4630 my $Item = $`;
4631 push(@Result, $Item, $Rest);
4634 else
4636 $String =~ /^\s*\Q$OpenQuote\E([^\Q$CloseQuote\E]*)\Q$CloseQuote\E/i;
4637 push(@Result, $1, $');
4640 else
4642 push(@Result, "", $String);
4644 return @Result;
4647 # Now, start with the real work
4649 # Control the output of the Content-type: text/html\n\n message
4650 my $SupressContentType = 0;
4652 # Process a file
4653 sub ProcessFile # ($file_path)
4655 my $file_path = shift || return 0;
4658 # Generate a unique file handle (for recursions)
4659 my @SRClist = ();
4660 my $FileHandle = "file";
4661 my $n = 0;
4662 while(!eof($FileHandle.$n)) {++$n;};
4663 $FileHandle .= $n;
4665 # Start HTML output
4666 # Use the default Content-type if this is NOT a raw file
4667 unless(($RawFilePattern && $ENV{'PATH_INFO'} =~ m@($RawFilePattern)$@i)
4668 || $SupressContentType)
4670 $ENV{'PATH_INFO'} =~ m@($FilePattern)$@i;
4671 my $ContentType = $ContentTypeTable{$1};
4672 print "Content-type: $ContentType\n";
4673 if(%SETCOOKIELIST && keys(%SETCOOKIELIST))
4675 foreach my $name (keys(%SETCOOKIELIST))
4677 my $value = $SETCOOKIELIST{$name};
4678 print "Set-Cookie: $name=$value\n";
4680 # Cookies are set only ONCE
4681 %SETCOOKIELIST = ();
4683 print "\n";
4684 $SupressContentType = 1; # Content type has been printed
4688 # Get access to the actual data. This can be from RAM (by way of an
4689 # environment variable) or by opening a file.
4691 # Handle the use of RAM images (file-data is stored in the
4692 # $CGI_FILE_CONTENTS environment variable)
4693 # Note that this environment variable will be cleared, i.e., it is strictly for
4694 # single-use only!
4695 if($ENV{$CGI_FILE_CONTENTS})
4697 # File has been read already
4698 $_ = $ENV{$CGI_FILE_CONTENTS};
4699 # Sorry, you have to do the reading yourself (dynamic document creation?)
4700 # NOTE: you must read the whole document at once
4701 if($_ eq '-')
4703 $_ = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
4705 else # Clear environment variable
4707 $ENV{$CGI_FILE_CONTENTS} = '-';
4710 # Open Only PLAIN TEXT files (or STDIN) and NO executable files (i.e., scripts).
4711 # THIS IS A SECURITY FEATURE!
4712 elsif($file_path eq '-' || (-e "$file_path" && -r _ && -T _ && -f _ && ! (-x _ || -X _) ))
4714 open($FileHandle, $file_path) || dieHandler(17, "<h2>File not found</h2>\n");
4715 push(@OpenFiles, $file_path);
4716 $_ = <$FileHandle>; # Read first line
4718 else
4720 print "<h2>File not found</h2>\n";
4721 dieHandler(18, "$file_path\n");
4724 $| = 1; # Flush output buffers
4726 # Initialize variables
4727 my $METAarguments = ""; # The CGI arguments from the latest META tag
4728 my @METAvalues = (); # The ''-quoted CGI values from the latest META tag
4729 my $ClosedTag = 0; # <TAG> </TAG> versus <TAG/>
4732 # Send document to output
4733 # Process the requested document.
4734 # Do a loop BEFORE reading input again (this catches the RAM/Database
4735 # type of documents).
4736 do {
4739 # Handle translations if needed
4741 performTranslation(\$_) if $TranslationPaths;
4743 # Catch <SCRIPT LANGUAGE="PERL" TYPE="text/ssperl" > directives in $_
4744 # There can be more than 1 <SCRIPT> or META tags on a line
4745 while(/\<\s*(SCRIPT|META|DIV|INS)\s/is)
4747 my $directive = "";
4748 # Store rest of line
4749 my $Before = $`;
4750 my $ScriptTag = $&;
4751 my $After = $';
4752 my $TagType = uc($1);
4753 # The before part can be send to the output
4754 print $Before;
4756 # Read complete Tag from after and/or file
4757 until($After =~ /([^\\])\>/)
4759 $After .= <$FileHandle>;
4760 performTranslation(\$After) if $TranslationPaths;
4763 if($After =~ /([^\\])\>/)
4765 $ScriptTag .= $`.$&; # Keep the Script Tag intact
4766 $After = $';
4768 else
4770 dieHandler(19, "Closing > not found\n");
4773 # The tag could be closed by />, we handle this in the XML way
4774 # and don't process any content (we ignore whitespace)
4775 $ClosedTag = ($ScriptTag =~ m@[^\\]/\s*\>\s*$@) ? 1 : 0;
4778 # TYPE or CLASS?
4779 my $TypeName = ($TagType =~ /META/is) ? "CONTENT" : "TYPE";
4780 $TypeName = "CLASS" if $TagType eq 'DIV' || $TagType eq 'INS';
4782 # Parse <SCRIPT> or <META> directive
4783 # If NOT (TYPE|CONTENT)="text/ssperl" (i.e., $ServerScriptContentType),
4784 # send the line to the output and go to the next loop
4785 my $CurrentContentType = "";
4786 if($ScriptTag =~ /(^|\s)$TypeName\s*=\s*/is)
4788 my ($Type) = ExtractQuotedItem($');
4789 $Type =~ /^\s*([\w\/\-]+)\s*[\,\;]?/;
4790 $CurrentContentType = lc($1); # Note: mime-types are "case-less"
4791 # CSS classes are aliases of $ServerScriptContentType
4792 if($TypeName eq "CLASS" && $CurrentContentType eq $ServerScriptContentClass)
4794 $CurrentContentType = $ServerScriptContentType;
4799 # Not a known server-side content type, print and continue
4800 unless(($CurrentContentType =~
4801 /$ServerScriptContentType|$ShellScriptContentType/is) ||
4802 $ScriptingLanguages{$CurrentContentType})
4804 print $ScriptTag;
4805 $_ = $After;
4806 next;
4810 # A known server-side content type, evaluate
4812 # First, handle \> and \<
4813 $ScriptTag =~ s/\\\>/\>/isg;
4814 $ScriptTag =~ s/\\\</\</isg;
4816 # Extract the CGI, SRC, ID, IF and UNLESS attributes
4817 my %ScriptTagAttributes = ();
4818 while($ScriptTag =~ /(^|\s)(CGI|IF|UNLESS|SRC|ID)\s*=\s*/is)
4820 my $Attribute = $2;
4821 my $Rest = $';
4822 my $Value = "";
4823 ($Value, $ScriptTag) = ExtractQuotedItem($Rest);
4824 $ScriptTagAttributes{uc($Attribute)} = $Value;
4828 # The attribute used to define the CGI variables
4829 # Extract CGI-variables from
4830 # <META CONTENT="text/ssperl; CGI='' SRC=''">
4831 # <SCRIPT TYPE='text/ssperl' CGI='' SRC=''>
4832 # <DIV CLASS='ssperl' CGI='' SRC='' ID=""> tags
4833 # <INS CLASS='ssperl' CGI='' SRC='' ID=""> tags
4834 if($ScriptTagAttributes{'CGI'})
4836 @ARGV = (); # Reset ARGV
4837 $ARGC = 0;
4838 $METAarguments = ""; # Reset the META CGI arguments
4839 @METAvalues = ();
4840 my $Meta_CGI = $ScriptTagAttributes{'CGI'};
4842 # Process default values of variables ($<name> = 'default value')
4843 # Allowed quotes are '', "", ``, (), [], and {}
4844 while($Meta_CGI =~ /(^\s*|[^\\])([\$\@\%]?)([\w\-]+)\s*/is)
4846 my $varType = $2 || '$'; # Variable or list
4847 my $name = $3; # The Name
4848 my $default = "";
4849 $Meta_CGI = $';
4851 if($Meta_CGI =~ /^\s*\=\s*/is)
4853 # Locate (any) default value
4854 ($default, $Meta_CGI) = ExtractQuotedItem($'); # Cut the parameter from the CGI
4856 $RemainingTag = $Meta_CGI;
4859 # Define CGI (or ENV) variable, initalize it from the
4860 # Query string or the default value
4862 # Also construct the @ARGV and @_ arrays. This allows other (SRC=) Perl
4863 # scripts to access the CGI arguments defined in the META tag
4864 # (Not for CGI inside <SCRIPT> tags)
4865 if($varType eq '$')
4867 CGIexecute::defineCGIvariable($name, $default)
4868 || dieHandler(20, "INVALID CGI name/value pair ($name, $default)\n");
4869 push(@METAvalues, "'".${"CGIexecute::$name"}."'");
4870 # Add value to the @ARGV list
4871 push(@ARGV, ${"CGIexecute::$name"});
4872 ++$ARGC;
4874 elsif($varType eq '@')
4876 CGIexecute::defineCGIvariableList($name, $default)
4877 || dieHandler(21, "INVALID CGI name/value list pair ($name, $default)\n");
4878 push(@METAvalues, "'".join("'", @{"CGIexecute::$name"})."'");
4879 # Add value to the @ARGV list
4880 push(@ARGV, @{"CGIexecute::$name"});
4881 $ARGC = scalar(@CGIexecute::ARGV);
4883 elsif($varType eq '%')
4885 CGIexecute::defineCGIvariableHash($name, $default)
4886 || dieHandler(22, "INVALID CGI name/value hash pair ($name, $default)\n");
4887 my @PairList = map {"$_ => ".${"CGIexecute::$name"}{$_}} keys(%{"CGIexecute::$name"});
4888 push(@METAvalues, "'".join("'", @PairList)."'");
4889 # Add value to the @ARGV list
4890 push(@ARGV, %{"CGIexecute::$name"});
4891 $ARGC = scalar(@CGIexecute::ARGV);
4894 # Store the values for internal and later use
4895 $METAarguments .= "$varType".$name.","; # A string of CGI variable names
4897 push(@METAvalues, "\'".eval("\"$varType\{CGIexecute::$name\}\"")."\'"); # ALWAYS add '-quotes around values
4902 # The IF (conditional execution) Attribute
4903 # Evaluate the condition and stop unless it evaluates to true
4904 if($ScriptTagAttributes{'IF'})
4906 my $IFcondition = $ScriptTagAttributes{'IF'};
4908 # Convert SCRIPT calls, ./<script>
4909 $IFcondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
4911 # Convert FILE calls, ~/<file>
4912 $IFcondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
4914 # Block execution if necessary
4915 unless(CGIexecute->evaluate($IFcondition))
4917 %ScriptTagAttributes = ();
4918 $CurrentContentType = "";
4922 # The UNLESS (conditional execution) Attribute
4923 # Evaluate the condition and stop if it evaluates to true
4924 if($ScriptTagAttributes{'UNLESS'})
4926 my $UNLESScondition = $ScriptTagAttributes{'UNLESS'};
4928 # Convert SCRIPT calls, ./<script>
4929 $UNLESScondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
4931 # Convert FILE calls, ~/<file>
4932 $UNLESScondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
4934 # Block execution if necessary
4935 if(CGIexecute->evaluate($UNLESScondition))
4937 %ScriptTagAttributes = ();
4938 $CurrentContentType = "";
4942 # The SRC (Source File) Attribute
4943 # Extract any source script files and add them in
4944 # front of the directive
4945 # The SRC list should be emptied
4946 @SRClist = ();
4947 my $SRCtag = "";
4948 my $Prefix = 1;
4949 my $PrefixDirective = "";
4950 my $PostfixDirective = "";
4951 # There is a SRC attribute
4952 if($ScriptTagAttributes{'SRC'})
4954 $SRCtag = $ScriptTagAttributes{'SRC'};
4955 # Remove "file://" prefixes
4956 $SRCtag =~ s@([^\w\/\\]|^)file\://([^\s\/\@\=])@$1$2@gis;
4957 # Expand script filenames "./Script"
4958 $SRCtag =~ s@([^\w\/\\]|^)\./([^\s\/\@\=])@$1$SCRIPT_SUB/$2@gis;
4959 # Expand script filenames "~/Script"
4960 $SRCtag =~ s@([^\w\/\\]|^)\~/([^\s\/\@\=])@$1$HOME_SUB/$2@gis;
4963 # File source tags
4964 while($SRCtag =~ /\S/is)
4966 my $SRCdirective = "";
4968 # Pseudo file, just a switch to go from PREFIXING to POSTFIXING
4969 # SRC files
4970 if($SRCtag =~ /^[\s\;\,]*(POSTFIX|PREFIX)([^$FileAllowedChars]|$)/is)
4972 my $InsertionPlace = $1;
4973 $SRCtag = $2.$';
4975 $Prefix = $InsertionPlace =~ /POSTFIX/i ? 0 : 1;
4976 # Go to next round
4977 next;
4979 # {}-blocks are just evaluated by "do"
4980 elsif($SRCtag =~ /^[\s\;\,]*\{/is)
4982 my $SRCblock = $';
4983 if($SRCblock =~ /\}[\s\;\,]*([^\}]*)$/is)
4985 $SRCblock = $`;
4986 $SRCtag = $1.$';
4987 # SAFEqx shell script blocks
4988 if($CurrentContentType =~ /$ShellScriptContentType/is)
4990 # Handle ''-quotes inside the script
4991 $SRCblock =~ s/[\']/\\$&/gis;
4993 $SRCblock = "print do { SAFEqx(\'".$SRCblock."\'); };'';";
4994 $SRCdirective .= $SRCblock."\n";
4996 # do { SRCblocks }
4997 elsif($CurrentContentType =~ /$ServerScriptContentType/is)
4999 $SRCblock = "print do { $SRCblock };'';";
5000 $SRCdirective .= $SRCblock."\n";
5002 else # The interpreter should handle this
5004 push(@SRClist, "{ $SRCblock }");
5008 else
5009 { dieHandler(23, "Closing \} missing\n");};
5011 # Files are processed as Text or Executable files
5012 elsif($SRCtag =~ /[\s\;\,]*([$FileAllowedChars]+)[\;\,\s]*/is)
5014 my $SrcFile = $1;
5015 $SRCtag = $';
5017 # We are handling one of the external interpreters
5018 if($ScriptingLanguages{$CurrentContentType})
5020 push(@SRClist, $SrcFile);
5022 # We are at the start of a DIV tag, just load all SRC files and/or URL's
5023 elsif($TagType eq 'DIV' || $TagType eq 'INS') # All files are prepended in DIV's
5025 # $SrcFile is a URL pointing to an HTTP or FTP server
5026 if($SrcFile =~ m!^([a-z]+)\://!)
5028 my $URLoutput = CGIscriptor::read_url($SrcFile);
5029 $SRCdirective .= $URLoutput;
5031 # SRC file is an existing file
5032 elsif(-e "$SrcFile")
5034 open(DIVSOURCE, "<$SrcFile") || dieHandler(24, "<$SrcFile: $!\n");
5035 my $Content;
5036 while(sysread(DIVSOURCE, $Content, 1024) > 0)
5038 $SRCdirective .= $Content;
5040 close(DIVSOURCE);
5043 # Executable files are executed as
5044 # `$SrcFile 'ARGV[0]' 'ARGV[1]'`
5045 elsif(-x "$SrcFile")
5047 $SRCdirective .= "print \`$SrcFile @METAvalues\`;'';\n";
5049 # Handle 'standard' files, using ProcessFile
5050 elsif((-T "$SrcFile" || $ENV{$CGI_FILE_CONTENTS})
5051 && $SrcFile =~ m@($FilePattern)$@) # A recursion
5054 # Do not process still open files because it can lead
5055 # to endless recursions
5056 if(grep(/^$SrcFile$/, @OpenFiles))
5057 { dieHandler(25, "$SrcFile allready opened (endless recursion)\n")};
5058 # Prepare meta arguments
5059 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5060 # Process the file
5061 $SRCdirective .= "main::ProcessFile(\'$SrcFile\');'';\n";
5063 elsif($SrcFile =~ m!^([a-z]+)\://!) # URL's are loaded and printed
5065 $SRCdirective .= GET_URL($SrcFile);
5067 elsif(-T "$SrcFile") # Textfiles are "do"-ed (Perl execution)
5069 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5070 $SRCdirective .= "do \'$SrcFile\';'';\n";
5072 else # This one could not be resolved (should be handled by BinaryMapFile)
5074 $SRCdirective .= 'print "'.$SrcFile.' cannot be used"'."\n";
5079 # Postfix or Prefix
5080 if($Prefix)
5082 $PrefixDirective .= $SRCdirective;
5084 else
5086 $PostfixDirective .= $SRCdirective;
5089 # The prefix should be handled immediately
5090 $directive .= $PrefixDirective;
5091 $PrefixDirective = "";
5095 # Handle the content of the <SCRIPT></SCRIPT> tags
5096 # Do not process the content of <SCRIPT/>
5097 if($TagType =~ /SCRIPT/is && !$ClosedTag) # The <SCRIPT> TAG
5099 my $EndScriptTag = "";
5101 # Execute SHELL scripts with SAFEqx()
5102 if($CurrentContentType =~ /$ShellScriptContentType/is)
5104 $directive .= "SAFEqx(\'";
5107 # Extract Program
5108 while($After !~ /\<\s*\/SCRIPT[^\>]*\>/is && !eof($FileHandle))
5110 $After .= <$FileHandle>;
5111 performTranslation(\$After) if $TranslationPaths;
5114 if($After =~ /\<\s*\/SCRIPT[^\>]*\>/is)
5116 $directive .= $`;
5117 $EndScriptTag = $&;
5118 $After = $';
5120 else
5122 dieHandler(26, "Missing </SCRIPT> end tag in $ENV{'PATH_INFO'}\n");
5125 # Process only when content should be executed
5126 if($CurrentContentType)
5129 # Remove all comments from Perl scripts
5130 # (NOT from OS shell scripts)
5131 $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
5132 if $CurrentContentType =~ /$ServerScriptContentType/i;
5134 # Convert SCRIPT calls, ./<script>
5135 $directive =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5137 # Convert FILE calls, ~/<file>
5138 $directive =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5140 # Execute SHELL scripts with SAFEqx(), closing bracket
5141 if($CurrentContentType =~ /$ShellScriptContentType/i)
5143 # Handle ''-quotes inside the script
5144 $directive =~ /SAFEqx\(\'/;
5145 $directive = $`.$&;
5146 my $Executable = $';
5147 $Executable =~ s/[\']/\\$&/gs;
5149 $directive .= $Executable."\');"; # Closing bracket
5152 else
5154 $directive = "";
5157 # Handle the content of the <DIV></DIV> tags
5158 # Do not process the content of <DIV/>
5159 elsif(($TagType eq 'DIV' || $TagType eq 'INS') && !$ClosedTag) # The <DIV> TAGs
5161 my $EndScriptTag = "";
5163 # Extract Text
5164 while($After !~ /\<\s*\/$TagType[^\>]*\>/is && !eof($FileHandle))
5166 $After .= <$FileHandle>;
5167 performTranslation(\$After) if $TranslationPaths;
5170 if($After =~ /\<\s*\/$TagType[^\>]*\>/is)
5172 $directive .= $`;
5173 $EndScriptTag = $&;
5174 $After = $';
5176 else
5178 dieHandler(27, "Missing </$TagType> end tag in $ENV{'PATH_INFO'}\n");
5181 # Add the Postfixed directives (but only when it contains something printable)
5182 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5183 $PostfixDirective = "";
5186 # Process only when content should be handled
5187 if($CurrentContentType)
5190 # Get the name (ID), and clean it (i.e., remove anything that is NOT part of
5191 # a valid Perl name). Names should not contain $, but we can handle it.
5192 my $name = $ScriptTagAttributes{'ID'};
5193 $name =~ /^\s*[\$\@\%]?([\w\-]+)/;
5194 $name = $1;
5196 # Assign DIV contents to $NAME value OUTSIDE the CGI values!
5197 CGIexecute::defineCGIexecuteVariable($name, $directive);
5198 $directive = "";
5201 # Nothing to execute
5202 $directive = "";
5206 # Handle Foreign scripting languages
5207 if($ScriptingLanguages{$CurrentContentType})
5209 my $newDirective = "";
5210 $newDirective .= OpenForeignScript($CurrentContentType); # Only if not already done
5211 $newDirective .= PrefixForeignScript($CurrentContentType);
5212 $newDirective .= InsertForeignScript($CurrentContentType, $directive, @SRClist);
5213 $newDirective .= PostfixForeignScript($CurrentContentType);
5214 $newDirective .= CloseForeignScript($CurrentContentType); # This shouldn't be necessary
5216 $newDirective .= '"";';
5218 $directive = $newDirective;
5222 # Add the Postfixed directives (but only when it contains something printable)
5223 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5224 $PostfixDirective = "";
5227 # EXECUTE the script and print the results
5229 # Use this to debug the program
5230 # print STDERR "Directive $CGI_Date: \n", $directive, "\n\n";
5232 my $Result = CGIexecute->evaluate($directive) if $directive; # Evaluate as PERL code
5233 $Result =~ s/\n$//g; # Remove final newline
5235 # Print the Result of evaluating the directive
5236 # (this will handle LARGE, >64 kB output)
5237 my $BytesWritten = 1;
5238 while($Result && $BytesWritten)
5240 $BytesWritten = syswrite(STDOUT, $Result, 64);
5241 $Result = substr($Result, $BytesWritten);
5243 # print $Result; # Could be used instead of above code
5245 # Store result if wanted, i.e., if $CGIscriptorResults has been
5246 # defined in a <META> tag.
5247 push(@CGIexecute::CGIscriptorResults, $Result)
5248 if exists($default_values{'CGIscriptorResults'});
5250 # Process the rest of the input line (this could contain
5251 # another directive)
5252 $_ = $After;
5254 print $_;
5255 } while(<$FileHandle>); # Read and Test AFTER first loop!
5257 close ($FileHandle);
5258 dieHandler(28, "Error in recursion\n") unless pop(@OpenFiles) == $file_path;
5262 ###############################################################################
5264 # Call the whole package
5266 sub Handle_Request
5268 my $file_path = "";
5270 # Initialization Code
5271 Initialize_Request();
5273 # SECURITY: ACCESS CONTROL
5274 Access_Control();
5276 # Read the POST part of the query, if there is one
5277 Get_POST_part_of_query();
5279 # Start (HTML) output and logging
5280 $file_path = Initialize_output();
5282 # Check login access or divert to login procedure
5283 $Use_Login = Log_In_Access();
5284 $file_path = $Use_Login if $Use_Login;
5286 # Record which files are still open (to avoid endless recursions)
5287 my @OpenFiles = ();
5289 # Record whether the default HTML ContentType has already been printed
5290 # but only if the SERVER uses HTTP or some other protocol that might interpret
5291 # a content MIME type.
5293 $SupressContentType = !("$ENV{'SERVER_PROTOCOL'}" =~ /($ContentTypeServerProtocols)/i);
5295 # Process the specified file
5296 ProcessFile($file_path) if $file_path ne $SS_PUB;
5298 # Cleanup all open external (foreign) interpreters
5299 CloseAllForeignScripts();
5302 "" # SUCCESS
5305 # Make a single call to handle an (empty) request
5306 Handle_Request();
5309 # END OF PACKAGE MAIN
5312 ####################################################################################
5314 # The CGIEXECUTE PACKAGE
5316 ####################################################################################
5318 # Isolate the evaluation of directives as PERL code from the rest of the program.
5319 # Remember that each package has its own name space.
5320 # Note that only the FIRST argument of execute->evaluate is actually evaluated,
5321 # all other arguments are accessible inside the first argument as $_[0] to $_[$#_].
5323 package CGIexecute;
5325 sub evaluate
5327 my $self = shift;
5328 my $directive = shift;
5329 $directive = eval($directive);
5330 warn $@ if $@; # Write an error message to STDERR
5331 $directive; # Return value of directive
5335 # defineCGIexecuteVariable($name [, $value]) -> 0/1
5337 # Define and intialize variables inside CGIexecute
5338 # Does no sanity checking, for internal use only
5340 sub defineCGIexecuteVariable # ($name [, $value]) -> 0/1
5342 my $name = shift || return 0; # The Name
5343 my $value = shift || ""; # The value
5345 ${$name} = $value;
5347 return 1;
5350 # Protect certain CGI variables values when set internally
5351 # If not defined internally, there will be no variable set AT ALL
5352 my %CGIprotectedVariable = ();
5353 sub ProtectCGIvariable # ($name) -> 0/1
5355 my $name = shift || "";
5356 return 0 unless $name && $name =~ /\w/;
5358 ++$CGIprotectedVariable{$name};
5360 return $CGIprotectedVariable{$name};
5363 # defineCGIvariable($name [, $default]) -> 0/1
5365 # Define and intialize CGI variables
5366 # Tries (in order) $ENV{$name}, the Query string and the
5367 # default value.
5368 # Removes all '-quotes etc.
5370 sub defineCGIvariable # ($name [, $default]) -> 0/1
5372 my $name = shift || return 0; # The Name
5373 my $default = shift || ""; # The default value
5375 # Protect variables set internally
5376 return 1 if !$name || exists($CGIprotectedVariable{$name});
5378 # Remove \-quoted characters
5379 $default =~ s/\\(.)/$1/g;
5380 # Store default values
5381 $::default_values{$name} = $default if $default;
5383 # Process variables
5384 my $temp = undef;
5385 # If there is a user supplied value, it replaces the
5386 # default value.
5388 # Environment values have precedence
5389 if(exists($ENV{$name}))
5391 $temp = $ENV{$name};
5393 # Get name and its value from the query string
5394 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5396 $temp = ::YOUR_CGIPARSE($name);
5398 # Defined values must exist for security
5399 elsif(!exists($::default_values{$name}))
5401 $::default_values{$name} = undef;
5404 # SECURITY, do not allow '- and `-quotes in
5405 # client values.
5406 # Remove all existing '-quotes
5407 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5408 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5409 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5410 # If $temp is empty, use the default value (if it exists)
5411 unless($temp =~ /\S/ || length($temp) > 0) # I.e., $temp is empty
5413 $temp = $::default_values{$name};
5414 # Remove all existing '-quotes
5415 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5416 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5417 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5419 else # Store current CGI values and remove defaults
5421 $::default_values{$name} = $temp;
5423 # Define the CGI variable and its value (in the execute package)
5424 ${$name} = $temp;
5426 # return SUCCES
5427 return 1;
5430 sub defineCGIvariableList # ($name [, $default]) -> 0/1)
5432 my $name = shift || return 0; # The Name
5433 my $default = shift || ""; # The default value
5435 # Protect variables set internally
5436 return 1 if !$name || exists($CGIprotectedVariable{$name});
5438 # Defined values must exist for security
5439 if(!exists($::default_values{$name}))
5441 $::default_values{$name} = $default;
5444 my @temp = ();
5447 # For security:
5448 # Environment values have precedence
5449 if(exists($ENV{$name}))
5451 push(@temp, $ENV{$name});
5453 # Get name and its values from the query string
5454 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5456 push(@temp, ::YOUR_CGIPARSE($name, 1)); # Extract LIST
5458 else
5460 push(@temp, $::default_values{$name});
5464 # SECURITY, do not allow '- and `-quotes in
5465 # client values.
5466 # Remove all existing '-quotes
5467 @temp = map {s/([\r\f]+\n)/\n/g; $_} @temp; # Only \n is allowed
5468 @temp = map {s/[\']/&#8217;/igs; $_} @temp; # Remove all single quotes
5469 @temp = map {s/[\`]/&#8216;/igs; $_} @temp; # Remove all backtick quotes
5471 # Store current CGI values and remove defaults
5472 $::default_values{$name} = $temp[0];
5474 # Define the CGI variable and its value (in the execute package)
5475 @{$name} = @temp;
5477 # return SUCCES
5478 return 1;
5481 sub defineCGIvariableHash # ($name [, $default]) -> 0/1) Note: '$name{""} = $default';
5483 my $name = shift || return 0; # The Name
5484 my $default = shift || ""; # The default value
5486 # Protect variables set internally
5487 return 1 if !$name || exists($CGIprotectedVariable{$name});
5489 # Defined values must exist for security
5490 if(!exists($::default_values{$name}))
5492 $::default_values{$name} = $default;
5495 my %temp = ();
5498 # For security:
5499 # Environment values have precedence
5500 if(exists($ENV{$name}))
5502 $temp{""} = $ENV{$name};
5504 # Get name and its values from the query string
5505 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5507 %temp = ::YOUR_CGIPARSE($name, -1); # Extract HASH table
5509 elsif($::default_values{$name} ne "")
5511 $temp{""} = $::default_values{$name};
5515 # SECURITY, do not allow '- and `-quotes in
5516 # client values.
5517 # Remove all existing '-quotes
5518 my $Key;
5519 foreach $Key (keys(%temp))
5521 $temp{$Key} =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5522 $temp{$Key} =~ s/[\']/&#8217;/igs; # Remove all single quotes
5523 $temp{$Key} =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5526 # Store current CGI values and remove defaults
5527 $::default_values{$name} = $temp{""};
5529 # Define the CGI variable and its value (in the execute package)
5530 %{$name} = ();
5531 my $tempKey;
5532 foreach $tempKey (keys(%temp))
5534 ${$name}{$tempKey} = $temp{$tempKey};
5537 # return SUCCES
5538 return 1;
5542 # SAFEqx('CommandString')
5544 # A special function that is a safe alternative to backtick quotes (and qx//)
5545 # with client-supplied CGI values. All CGI variables are surrounded by
5546 # single ''-quotes (except between existing \'\'-quotes, don't try to be
5547 # too smart). All variables are then interpolated. Simple (@) lists are
5548 # expanded with join(' ', @List), and simple (%) hash tables expanded
5549 # as a list of "key=value" pairs. Complex variables, e.g., @$var, are
5550 # evaluated in a scalar context (e.g., as scalar(@$var)). All occurrences of
5551 # $@% that should NOT be interpolated must be preceeded by a "\".
5552 # If the first line of the String starts with "#! interpreter", the
5553 # remainder of the string is piped into interpreter (after interpolation), i.e.,
5554 # open(INTERPRETER, "|interpreter");print INTERPRETER remainder;
5555 # just like in UNIX. There are some problems with quotes. Be carefull in
5556 # using them. You do not have access to the output of any piped (#!)
5557 # process! If you want such access, execute
5558 # <SCRIPT TYPE="text/osshell">echo "script"|interpreter</SCRIPT> or
5559 # <SCRIPT TYPE="text/ssperl">$resultvar = SAFEqx('echo "script"|interpreter');
5560 # </SCRIPT>.
5562 # SAFEqx ONLY WORKS WHEN THE STRING ITSELF IS SURROUNDED BY SINGLE QUOTES
5563 # (SO THAT IT IS NOT INTERPOLATED BEFORE IT CAN BE PROTECTED)
5564 sub SAFEqx # ('String') -> result of executing qx/"String"/
5566 my $CommandString = shift;
5567 my $NewCommandString = "";
5569 # Only interpolate when required (check the On/Off switch)
5570 unless($CGIscriptor::NoShellScriptInterpolation)
5573 # Handle existing single quotes around CGI values
5574 while($CommandString =~ /\'[^\']+\'/s)
5576 my $CurrentQuotedString = $&;
5577 $NewCommandString .= $`;
5578 $CommandString = $'; # The remaining string
5579 # Interpolate CGI variables between quotes
5580 # (e.g., '$CGIscriptorResults[-1]')
5581 $CurrentQuotedString =~
5582 s/(^|[^\\])([\$\@])((\w*)([\{\[][\$\@\%]?[\:\w\-]+[\}\]])*)/if(exists($main::default_values{$4})){
5583 "$1".eval("$2$3")}else{"$&"}/egs;
5585 # Combine result with previous result
5586 $NewCommandString .= $CurrentQuotedString;
5588 $CommandString = $NewCommandString.$CommandString;
5590 # Select known CGI variables and surround them with single quotes,
5591 # then interpolate all variables
5592 $CommandString =~
5593 s/(^|[^\\])([\$\@\%]+)((\w*)([\{\[][\w\:\$\"\-]+[\}\]])*)/
5594 if($2 eq '$' && exists($main::default_values{$4}))
5595 {"$1\'".eval("\$$3")."\'";}
5596 elsif($2 eq '@'){$1.join(' ', @{"$3"});}
5597 elsif($2 eq '%'){my $t=$1;map {$t.=" $_=".${"$3"}{$_}}
5598 keys(%{"$3"});$t}
5599 else{$1.eval("${2}$3");
5600 }/egs;
5602 # Remove backslashed [$@%]
5603 $CommandString =~ s/\\([\$\@\%])/$1/gs;
5606 # Debugging
5607 # return $CommandString;
5609 # Handle UNIX style "#! shell command\n" constructs as
5610 # a pipe into the shell command. The output cannot be tapped.
5611 my $ReturnValue = "";
5612 if($CommandString =~ /^\s*\#\!([^\f\n\r]+)[\f\n\r]/is)
5614 my $ShellScripts = $';
5615 my $ShellCommand = $1;
5616 open(INTERPRETER, "|$ShellCommand") || dieHandler(29, "\'$ShellCommand\' PIPE not opened: &!\n");
5617 select(INTERPRETER);$| = 1;
5618 print INTERPRETER $ShellScripts;
5619 close(INTERPRETER);
5620 select(STDOUT);$| = 1;
5622 # Shell scripts which are redirected to an existing named pipe.
5623 # The output cannot be tapped.
5624 elsif($CGIscriptor::ShellScriptPIPE)
5626 CGIscriptor::printSAFEqxPIPE($CommandString);
5628 else # Plain ``-backtick execution
5630 # Execute the commands
5631 $ReturnValue = qx/$CommandString/;
5633 return $ReturnValue;
5636 ####################################################################################
5638 # The CGIscriptor PACKAGE
5640 ####################################################################################
5642 # Isolate the evaluation of CGIscriptor functions, i.e., those prefixed with
5643 # "CGIscriptor::"
5645 package CGIscriptor;
5648 # The Interpolation On/Off switch
5649 my $NoShellScriptInterpolation = undef;
5650 # The ShellScript redirection pipe
5651 my $ShellScriptPIPE = undef;
5653 # Open a named PIPE for SAFEqx to receive ALL shell scripts
5654 sub RedirectShellScript # ('CommandString')
5656 my $CommandString = shift || undef;
5658 if($CommandString)
5660 $ShellScriptPIPE = "ShellScriptNamedPipe";
5661 open($ShellScriptPIPE, "|$CommandString")
5662 || main::dieHandler(30, "\'|$CommandString\' PIPE open failed: $!\n");
5664 else
5666 close($ShellScriptPIPE);
5667 $ShellScriptPIPE = undef;
5669 return $ShellScriptPIPE;
5672 # Print to redirected shell script pipe
5673 sub printSAFEqxPIPE # ("String") -> print return value
5675 my $String = shift || undef;
5677 select($ShellScriptPIPE); $| = 1;
5678 my $returnvalue = print $ShellScriptPIPE ($String);
5679 select(STDOUT); $| = 1;
5681 return $returnvalue;
5684 # a pointer to CGIexecute::SAFEqx
5685 sub SAFEqx # ('String') -> result of qx/"String"/
5687 my $CommandString = shift;
5688 return CGIexecute::SAFEqx($CommandString);
5692 # a pointer to CGIexecute::defineCGIvariable
5693 sub defineCGIvariable # ($name[, $default]) ->0/1
5695 my $name = shift;
5696 my $default = shift;
5697 return CGIexecute::defineCGIvariable($name, $default);
5701 # a pointer to CGIexecute::defineCGIvariable
5702 sub defineCGIvariableList # ($name[, $default]) ->0/1
5704 my $name = shift;
5705 my $default = shift;
5706 return CGIexecute::defineCGIvariableList($name, $default);
5710 # a pointer to CGIexecute::defineCGIvariable
5711 sub defineCGIvariableHash # ($name[, $default]) ->0/1
5713 my $name = shift;
5714 my $default = shift;
5715 return CGIexecute::defineCGIvariableHash($name, $default);
5719 # Decode URL encoded arguments
5720 sub URLdecode # (URL encoded input) -> string
5722 my $output = "";
5723 my $char;
5724 my $Value;
5725 foreach $Value (@_)
5727 my $EncodedValue = $Value; # Do not change the loop variable
5728 # Convert all "+" to " "
5729 $EncodedValue =~ s/\+/ /g;
5730 # Convert all hexadecimal codes (%FF) to their byte values
5731 while($EncodedValue =~ /\%([0-9A-F]{2})/i)
5733 $output .= $`.chr(hex($1));
5734 $EncodedValue = $';
5736 $output .= $EncodedValue; # The remaining part of $Value
5738 $output;
5741 # Encode arguments as URL codes.
5742 sub URLencode # (input) -> URL encoded string
5744 my $output = "";
5745 my $char;
5746 my $Value;
5747 foreach $Value (@_)
5749 my @CharList = split('', $Value);
5750 foreach $char (@CharList)
5752 if($char =~ /\s/)
5753 { $output .= "+";}
5754 elsif($char =~ /\w\-/)
5755 { $output .= $char;}
5756 else
5758 $output .= uc(sprintf("%%%2.2x", ord($char)));
5762 $output;
5765 # Extract the value of a CGI variable from the URL-encoded $string
5766 # Also extracts the data blocks from a multipart request. Does NOT
5767 # decode the multipart blocks
5768 sub CGIparseValue # (ValueName [, URL_encoded_QueryString [, \$QueryReturnReference]]) -> Decoded value
5770 my $ValueName = shift;
5771 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5772 my $ReturnReference = shift || undef;
5773 my $output = "";
5775 if($QueryString =~ /(^|\&)$ValueName\=([^\&]*)(\&|$)/)
5777 $output = URLdecode($2);
5778 $$ReturnReference = $' if ref($ReturnReference);
5780 # Get multipart POST or PUT methods
5781 elsif($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
5783 my $MultipartType = $2;
5784 my $BoundaryString = $3;
5785 # Remove the boundary-string
5786 my $temp = $QueryString;
5787 $temp =~ /^\Q--$BoundaryString\E/m;
5788 $temp = $';
5790 # Identify the newline character(s), this is the first character in $temp
5791 my $NewLine = "\r\n"; # Actually, this IS the correct one
5792 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
5794 # Is this correct??? I have to check.
5795 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
5796 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
5797 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
5798 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
5801 # search through all data blocks
5802 while($temp =~ /^\Q--$BoundaryString\E/m)
5804 my $DataBlock = $`;
5805 $temp = $';
5806 # Get the empty line after the header
5807 $DataBlock =~ /$NewLine$NewLine/;
5808 $Header = $`;
5809 $output = $';
5810 my $Header = $`;
5811 $output = $';
5813 # Remove newlines from the header
5814 $Header =~ s/$NewLine/ /g;
5816 # Look whether this block is the one you are looking for
5817 # Require the quotes!
5818 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
5820 my $i;
5821 for($i=length($NewLine); $i; --$i)
5823 chop($output);
5825 # OK, get out
5826 last;
5828 # reinitialize the output
5829 $output = "";
5831 $$ReturnReference = $temp if ref($ReturnReference);
5833 elsif($QueryString !~ /(^|\&)$ValueName\=/) # The value simply isn't there
5835 return undef;
5836 $$ReturnReference = undef if ref($ReturnReference);
5838 else
5840 print "ERROR: $ValueName $main::ENV{'CONTENT_TYPE'}\n";
5842 return $output;
5846 # Get a list of values for the same ValueName. Uses CGIparseValue
5848 sub CGIparseValueList # (ValueName [, URL_encoded_QueryString]) -> List of decoded values
5850 my $ValueName = shift;
5851 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5852 my @output = ();
5853 my $RestQueryString;
5854 my $Value;
5855 while($QueryString &&
5856 (($Value = CGIparseValue($ValueName, $QueryString, \$RestQueryString))
5857 || defined($Value)))
5859 push(@output, $Value);
5860 $QueryString = $RestQueryString; # QueryString is consumed!
5862 # ready, return list with values
5863 return @output;
5866 sub CGIparseValueHash # (ValueName [, URL_encoded_QueryString]) -> Hash table of decoded values
5868 my $ValueName = shift;
5869 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5870 my $RestQueryString;
5871 my %output = ();
5872 while($QueryString && $QueryString =~ /(^|\&)$ValueName([\w]*)\=/)
5874 my $Key = $2;
5875 my $Value = CGIparseValue("$ValueName$Key", $QueryString, \$RestQueryString);
5876 $output{$Key} = $Value;
5877 $QueryString = $RestQueryString; # QueryString is consumed!
5879 # ready, return list with values
5880 return %output;
5883 sub CGIparseForm # ([URL_encoded_QueryString]) -> Decoded Form (NO multipart)
5885 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5886 my $output = "";
5888 $QueryString =~ s/\&/\n/g;
5889 $output = URLdecode($QueryString);
5891 $output;
5894 # Extract the header of a multipart CGI variable from the POST input
5895 sub CGIparseHeader # (ValueName [, URL_encoded_QueryString]) -> Decoded value
5897 my $ValueName = shift;
5898 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5899 my $output = "";
5901 if($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
5903 my $MultipartType = $2;
5904 my $BoundaryString = $3;
5905 # Remove the boundary-string
5906 my $temp = $QueryString;
5907 $temp =~ /^\Q--$BoundaryString\E/m;
5908 $temp = $';
5910 # Identify the newline character(s), this is the first character in $temp
5911 my $NewLine = "\r\n"; # Actually, this IS the correct one
5912 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
5914 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
5915 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
5916 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
5917 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
5920 # search through all data blocks
5921 while($temp =~ /^\Q--$BoundaryString\E/m)
5923 my $DataBlock = $`;
5924 $temp = $';
5925 # Get the empty line after the header
5926 $DataBlock =~ /$NewLine$NewLine/;
5927 $Header = $`;
5928 my $Header = $`;
5930 # Remove newlines from the header
5931 $Header =~ s/$NewLine/ /g;
5933 # Look whether this block is the one you are looking for
5934 # Require the quotes!
5935 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
5937 $output = $Header;
5938 last;
5940 # reinitialize the output
5941 $output = "";
5944 return $output;
5948 # Checking variables for security (e.g., file names and email addresses)
5949 # File names are tested against the $::FileAllowedChars and $::BlockPathAccess variables
5950 sub CGIsafeFileName # FileName -> FileName or ""
5952 my $FileName = shift || "";
5953 return "" if $FileName =~ m?[^$::FileAllowedChars]?;
5954 return "" if $FileName =~ m!(^|/|\:)[\-\.]!;
5955 return "" if $FileName =~ m@\.\.\Q$::DirectorySeparator\E@; # Higher directory not allowed
5956 return "" if $FileName =~ m@\Q$::DirectorySeparator\E\.\.@; # Higher directory not allowed
5957 return "" if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@; # Invisible (blocked) file
5959 return $FileName;
5962 sub CGIsafeEmailAddress # email -> email or ""
5964 my $Email = shift || "";
5965 return "" unless $Email =~ m/^[\w\.\-]+[\@][\w\.\-\:]+$/;
5966 return $Email;
5969 # Get a URL from the web. Needs main::GET_URL($URL) function
5970 # (i.e., curl, snarf, or wget)
5971 sub read_url # ($URL) -> page/file
5973 my $URL = shift || return "";
5975 # Get the commands to read the URL, do NOT add a print command
5976 my $URL_command = main::GET_URL($URL, 1);
5977 # execute the commands, i.e., actually read it
5978 my $URLcontent = CGIexecute->evaluate($URL_command);
5980 # Ready, return the content.
5981 return $URLcontent;
5984 ################################################>>>>>>>>>>Start Remove
5986 # BrowseAllDirs(Directory, indexfile)
5988 # usage:
5989 # <SCRIPT TYPE='text/ssperl'>
5990 # CGIscriptor::BrowseAllDirs('Sounds', 'index.html', '\.wav$')
5991 # </SCRIPT>
5993 # Allows to browse all directories. Stops at '/'. If the directory contains
5994 # an indexfile, eg, index.html, that file will be used instead. Files must match
5995 # the $Pattern, if it is given. Default is
5996 # CGIscriptor::BrowseAllDirs('/', 'index.html', '')
5998 sub BrowseAllDirs # (Directory, indexfile, $Pattern) -> Print HTML code
6000 my $Directory = shift || '/';
6001 my $indexfile = shift || 'index.html';
6002 my $Pattern = shift || '';
6003 $Directory =~ s!/$!!g;
6005 # If the index directory exists, use that one
6006 if(-s "$::CGI_HOME$Directory/$indexfile")
6008 return main::ProcessFile("$::CGI_HOME$Directory/$indexfile");
6011 # No indexfile, continue
6012 my @DirectoryList = glob("$::CGI_HOME$Directory");
6013 $CurrentDirectory = shift(@DirectoryList);
6014 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
6015 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
6016 print "<h1>";
6017 print "$CurrentDirectory" if $CurrentDirectory;
6018 print "</h1>\n";
6020 opendir(BROWSE, "$::CGI_HOME$Directory") || main::dieHandler(31, "$::CGI_HOME$Directory $!");
6021 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
6023 # Print directories
6024 my $file;
6025 print "<pre><ul TYPE='NONE'>\n";
6026 foreach $file (@AllFiles)
6028 next unless -d "$::CGI_HOME$Directory/$file";
6029 # Check whether this file should be visible
6030 next if $::BlockPathAccess &&
6031 "$Directory/$file/" =~ m@$::BlockPathAccess@;
6032 print "<dt><a href='$Directory/$file'>$file</a></dt>\n";
6034 print "</ul></pre>\n";
6036 # Print files
6037 print "<pre><ul TYPE='CIRCLE'>\n";
6038 my $TotalSize = 0;
6039 foreach $file (@AllFiles)
6041 next if $file =~ /^\./;
6042 next if -d "$::CGI_HOME$Directory/$file";
6043 next if -l "$::CGI_HOME$Directory/$file";
6044 # Check whether this file should be visible
6045 next if $::BlockPathAccess &&
6046 "$Directory/$file" =~ m@$::BlockPathAccess@;
6048 if(!$Pattern || $file =~ m@$Pattern@)
6050 my $Date = localtime($^T - (-M "$::CGI_HOME$Directory/$file")*3600*24);
6051 my $Size = -s "$::CGI_HOME$Directory/$file";
6052 $Size = sprintf("%6.0F kB", $Size/1024);
6053 my $Type = `file $::CGI_HOME$Directory/$file`;
6054 $Type =~ s@\s*$::CGI_HOME$Directory/$file\s*\:\s*@@ig;
6055 chomp($Type);
6057 print "<li>";
6058 print "<a href='$Directory/$file'>";
6059 printf("%-40s", "$file</a>");
6060 print "\t$Size\t$Date\t$Type";
6061 print "</li>\n";
6064 print "</ul></pre>";
6066 return 1;
6070 ################################################
6072 # BrowseDirs(RootDirectory [, Pattern, Start])
6074 # usage:
6075 # <SCRIPT TYPE='text/ssperl'>
6076 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', 'Speech', 'DIRECTORY')
6077 # </SCRIPT>
6079 # Allows to browse subdirectories. Start should be relative to the RootDirectory,
6080 # e.g., the full path of the directory 'Speech' is '~/Sounds/Speech'.
6081 # Only files which fit /$Pattern/ and directories are displayed.
6082 # Directories down or up the directory tree are supplied with a
6083 # GET request with the name of the CGI variable in the fourth argument (default
6084 # is 'BROWSEDIRS'). So the correct call for a subdirectory could be:
6085 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', $DIRECTORY, 'DIRECTORY')
6087 sub BrowseDirs # (RootDirectory [, Pattern, Start, CGIvariable, HTTPserver]) -> Print HTML code
6089 my $RootDirectory = shift; # || return 0;
6090 my $Pattern = shift || '\S';
6091 my $Start = shift || "";
6092 my $CGIvariable = shift || "BROWSEDIRS";
6093 my $HTTPserver = shift || '';
6095 $Start = CGIscriptor::URLdecode($Start); # Sometimes, too much has been encoded
6096 $Start =~ s@//+@/@g;
6097 $Start =~ s@[^/]+/\.\.@@ig;
6098 $Start =~ s@^\.\.@@ig;
6099 $Start =~ s@/\.$@@ig;
6100 $Start =~ s!/+$!!g;
6101 $Start .= "/" if $Start;
6103 my @Directory = glob("$::CGI_HOME/$RootDirectory/$Start");
6104 $CurrentDirectory = shift(@Directory);
6105 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
6106 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
6107 print "<h1>";
6108 print "$CurrentDirectory" if $CurrentDirectory;
6109 print "</h1>\n";
6110 opendir(BROWSE, "$::CGI_HOME/$RootDirectory/$Start") || main::dieHandler(31, "$::CGI_HOME/$RootDirectory/$Start $!");
6111 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
6113 # Print directories
6114 my $file;
6115 print "<pre><ul TYPE='NONE'>\n";
6116 foreach $file (@AllFiles)
6118 next unless -d "$::CGI_HOME/$RootDirectory/$Start$file";
6119 # Check whether this file should be visible
6120 next if $::BlockPathAccess &&
6121 "/$RootDirectory/$Start$file/" =~ m@$::BlockPathAccess@;
6123 my $NewURL = $Start ? "$Start$file" : $file;
6124 $NewURL = CGIscriptor::URLencode($NewURL);
6125 print "<dt><a href='";
6126 print "$ENV{SCRIPT_NAME}" if $ENV{SCRIPT_NAME} !~ m@[^\w+\-/]@;
6127 print "$ENV{PATH_INFO}?$CGIvariable=$NewURL'>$file</a></dt>\n";
6129 print "</ul></pre>\n";
6131 # Print files
6132 print "<pre><ul TYPE='CIRCLE'>\n";
6133 my $TotalSize = 0;
6134 foreach $file (@AllFiles)
6136 next if $file =~ /^\./;
6137 next if -d "$::CGI_HOME/$RootDirectory/$Start$file";
6138 next if -l "$::CGI_HOME/$RootDirectory/$Start$file";
6139 # Check whether this file should be visible
6140 next if $::BlockPathAccess &&
6141 "$::CGI_HOME/$RootDirectory/$Start$file" =~ m@$::BlockPathAccess@;
6143 if($file =~ m@$Pattern@)
6145 my $Date = localtime($^T - (-M "$::CGI_HOME/$RootDirectory/$Start$file")*3600*24);
6146 my $Size = -s "$::CGI_HOME/$RootDirectory/$Start$file";
6147 $Size = sprintf("%6.0F kB", $Size/1024);
6148 my $Type = `file $::CGI_HOME/$RootDirectory/$Start$file`;
6149 $Type =~ s@\s*$::CGI_HOME/$RootDirectory/$Start$file\s*\:\s*@@ig;
6150 chomp($Type);
6152 print "<li>";
6153 if($HTTPserver =~ /^\s*[\.\~]\s*$/)
6155 print "<a href='$RootDirectory/$Start$file'>";
6157 elsif($HTTPserver)
6159 print "<a href='$HTTPserver/$RootDirectory/$Start$file'>";
6161 printf("%-40s", "$file</a>") if $HTTPserver;
6162 printf("%-40s", "$file") unless $HTTPserver;
6163 print "\t$Size\t$Date\t$Type";
6164 print "</li>\n";
6167 print "</ul></pre>";
6169 return 1;
6173 # ListDocs(Pattern [,ListType])
6175 # usage:
6176 # <SCRIPT TYPE=text/ssperl>
6177 # CGIscriptor::ListDocs("/*", "dl");
6178 # </SCRIPT>
6180 # This subroutine is very usefull to manage collections of independent
6181 # documents. The resulting list will display the tree-like directory
6182 # structure. If this routine is too slow for online use, you can
6183 # store the result and use a link to that stored file.
6185 # List HTML and Text files with title and first header (HTML)
6186 # or filename and first meaningfull line (general text files).
6187 # The listing starts at the ServerRoot directory. Directories are
6188 # listed recursively.
6190 # You can change the list type (default is dl).
6191 # e.g.,
6192 # <dt><a href=<file.html>>title</a>
6193 # <dd>First Header
6194 # <dt><a href=<file.txt>>file.txt</a>
6195 # <dd>First meaningfull line of text
6197 sub ListDocs # ($Pattern [, prefix]) e.g., ("/Books/*", [, "dl"])
6199 my $Pattern = shift;
6200 $Pattern =~ /\*/;
6201 my $ListType = shift || "dl";
6202 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
6203 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
6204 my @FileList = glob("$::CGI_HOME$Pattern");
6205 my ($FileName, $Path, $Link);
6207 # Print List markers
6208 print "<$ListType>\n";
6210 # Glob all files
6211 File: foreach $FileName (@FileList)
6213 # Check whether this file should be visible
6214 next if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@;
6216 # Recursively list files in all directories
6217 if(-d $FileName)
6219 $FileName =~ m@([^/]*)$@;
6220 my $DirName = $1;
6221 print "<$Prefix>$DirName\n";
6222 $Pattern =~ m@([^/]*)$@;
6223 &ListDocs("$`$DirName/$1", $ListType);
6224 next;
6226 # Use textfiles
6227 elsif(-T "$FileName")
6229 open(TextFile, $FileName) || next;
6231 # Ignore all other file types
6232 else
6233 { next;};
6235 # Get file path for link
6236 $FileName =~ /$::CGI_HOME/;
6237 print "<$Prefix><a href=$URL_root$'>";
6238 # Initialize all variables
6239 my $Line = "";
6240 my $TitleFound = 0;
6241 my $Caption = "";
6242 my $Title = "";
6243 # Read file and step through
6244 while(<TextFile>)
6246 chop $_;
6247 $Line = $_;
6248 # HTML files
6249 if($FileName =~ /\.ht[a-zA-Z]*$/i)
6251 # Catch Title
6252 while(!$Title)
6254 if($Line =~ m@<title>([^<]*)</title>@i)
6256 $Title = $1;
6257 $Line = $';
6259 else
6261 $Line .= <TextFile> || goto Print;
6262 chop $Line;
6265 # Catch First Header
6266 while(!$Caption)
6268 if($Line =~ m@</h1>@i)
6270 $Caption = $`;
6271 $Line = $';
6272 $Caption =~ m@<h1>@i;
6273 $Caption = $';
6274 $Line = $`.$Caption.$Line;
6276 else
6278 $Line .= <TextFile> || goto Print;
6279 chop $Line;
6283 # Other text files
6284 else
6286 # Title equals file name
6287 $FileName =~ /([^\/]+)$/;
6288 $Title = $1;
6289 # Catch equals First Meaningfull line
6290 while(!$Caption)
6292 if($Line =~ /[A-Z]/ &&
6293 ($Line =~ /subject|title/i || $Line =~ /^[\w,\.\s\?\:]+$/)
6294 && $Line !~ /Newsgroup/ && $Line !~ /\:\s*$/)
6296 $Line =~ s/\<[^\>]+\>//g;
6297 $Caption = $Line;
6299 else
6301 $Line = <TextFile> || goto Print;
6305 Print: # Print title and subject
6306 print "$Title</a>\n";
6307 print "<dd>$Caption\n" if $ListType eq "dl";
6308 $TitleFound = 0;
6309 $Caption = "";
6310 close TextFile;
6311 next File;
6314 # Print Closing List Marker
6315 print "</$ListType>\n";
6316 ""; # Empty return value
6320 # HTMLdocTree(Pattern [,ListType])
6322 # usage:
6323 # <SCRIPT TYPE=text/ssperl>
6324 # CGIscriptor::HTMLdocTree("/Welcome.html", "dl");
6325 # </SCRIPT>
6327 # The following subroutine is very usefull for checking large document
6328 # trees. Starting from the root (s), it reads all files and prints out
6329 # a nested list of links to all attached files. Non-existing or misplaced
6330 # files are flagged. This is quite a file-i/o intensive routine
6331 # so you would not like it to be accessible to everyone. If you want to
6332 # use the result, save the whole resulting page to disk and use a link
6333 # to this file.
6335 # HTMLdocTree takes an HTML file or file pattern and constructs nested lists
6336 # with links to *local* files (i.e., only links to the local server are
6337 # followed). The list entries are the document titles.
6338 # If the list type is <dl>, the first <H1> header is used too.
6339 # For each file matching the pattern, a list is made recursively of all
6340 # HTML documents that are linked from it and are stored in the same directory
6341 # or a sub-directory. Warnings are given for missing files.
6342 # The listing starts for the ServerRoot directory.
6343 # You can change the default list type <dl> (<dl>, <ul>, <ol>).
6345 %LinkUsed = ();
6347 sub HTMLdocTree # ($Pattern [, listtype])
6348 # e.g., ("/Welcome.html", [, "ul"])
6350 my $Pattern = shift;
6351 my $ListType = shift || "dl";
6352 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
6353 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
6354 my ($Filename, $Path, $Link);
6355 my %LocalLinks = {};
6357 # Read files (glob them for expansion of wildcards)
6358 my @FileList = glob("$::CGI_HOME$Pattern");
6359 foreach $Path (@FileList)
6361 # Get URL_path
6362 $Path =~ /$::CGI_HOME/;
6363 my $URL_path = $';
6364 # Check whether this file should be visible
6365 next if $::BlockPathAccess && $URL_path =~ m@$::BlockPathAccess@;
6367 my $Title = $URL_path;
6368 my $Caption = "";
6369 # Current file should not be used again
6370 ++$LinkUsed{$URL_path};
6371 # Open HTML doc
6372 unless(open(TextFile, $Path))
6374 print "<$Prefix>$Title <blink>(not found)</blink><br>\n";
6375 next;
6377 while(<TextFile>)
6379 chop $_;
6380 $Line = $_;
6381 # Catch Title
6382 while($Line =~ m@<title>@i)
6384 if($Line =~ m@<title>([^<]*)</title>@i)
6386 $Title = $1;
6387 $Line = $';
6389 else
6391 $Line .= <TextFile>;
6392 chop $Line;
6395 # Catch First Header
6396 while(!$Caption && $Line =~ m@<h1>@i)
6398 if($Line =~ m@</h[1-9]>@i)
6400 $Caption = $`;
6401 $Line = $';
6402 $Caption =~ m@<h1>@i;
6403 $Caption = $';
6404 $Line = $`.$Caption.$Line;
6406 else
6408 $Line .= <TextFile>;
6409 chop $Line;
6412 # Catch and print Links
6413 while($Line =~ m@<a href\=([^>]*)>@i)
6415 $Link = $1;
6416 $Line = $';
6417 # Remove quotes
6418 $Link =~ s/\"//g;
6419 # Remove extras
6420 $Link =~ s/[\#\?].*$//g;
6421 # Remove Servername
6422 if($Link =~ m@(http://|^)@i)
6424 $Link = $';
6425 # Only build tree for current server
6426 next unless $Link =~ m@$::ENV{'SERVER_NAME'}|^/@;
6427 # Remove server name and port
6428 $Link =~ s@^[^\/]*@@g;
6430 # Store the current link
6431 next if $LinkUsed{$Link} || $Link eq $URL_path;
6432 ++$LinkUsed{$Link};
6433 ++$LocalLinks{$Link};
6437 close TextFile;
6438 print "<$Prefix>";
6439 print "<a href=http://";
6440 print "$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}$URL_path>";
6441 print "$Title</a>\n";
6442 print "<br>$Caption\n"
6443 if $Caption && $Caption ne $Title && $ListType =~ /dl/i;
6444 print "<$ListType>\n";
6445 foreach $Link (keys(%LocalLinks))
6447 &HTMLdocTree($Link, $ListType);
6449 print "</$ListType>\n";
6453 ###########################<<<<<<<<<<End Remove
6455 # Make require happy
6458 =head1 NAME
6460 CGIscriptor -
6462 =head1 DESCRIPTION
6464 A flexible HTML 4 compliant script/module for CGI-aware
6465 embeded Perl, shell-scripts, and other scripting languages,
6466 executed at the server side.
6468 =head1 README
6470 Executes embeded Perl code in HTML pages with easy
6471 access to CGI variables. Also processes embeded shell
6472 scripts and scripts in any other language with an
6473 interactive interpreter (e.g., in-line Python, Tcl,
6474 Ruby, Awk, Lisp, Xlispstat, Prolog, M4, R, REBOL, Praat,
6475 sh, bash, csh, ksh).
6477 CGIscriptor is very flexible and hides all the specifics
6478 and idiosyncrasies of correct output and CGI coding and naming.
6479 CGIscriptor complies with the W3C HTML 4.0 recommendations.
6481 This Perl program will run on any WWW server that runs
6482 Perl scripts, just add a line like the following to your
6483 srm.conf file (Apache example):
6485 ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
6487 URL's that refer to http://www.your.address/SHTML/... will
6488 now be handled by CGIscriptor.pl, which can use a private
6489 directory tree (default is the DOCUMENT_ROOT directory tree,
6490 but it can be anywhere).
6492 =head1 PREREQUISITES
6495 =head1 COREQUISITES
6498 =pod OSNAMES
6500 Linux, *BSD, *nix, MS WinXP
6502 =pod SCRIPT CATEGORIES
6504 Servers
6508 =cut