Changed password encoding
[CGIscriptor.git] / CGIscriptor.pl
blob243d531407089fe3d4e0bd03ce0abd6515300afe
1 #! /usr/bin/perl
3 # (configure the first line to contain YOUR path to perl 5.000+)
5 # CGIscriptor.pl
6 # Version 2.4
7 # 10 July 2012
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 # 10 Jul 2012 - Version 2.4
65 # 11 Jun 2012 - Securing CGIvariable setting. Made
66 # 'if($ENV{QUERY_STRING} =~ /$name/)' into elsif in
67 # defineCGIvariable/List/Hash to give precedence to ENV{$name}
68 # This was a very old security bug. Added ProtectCGIvariable($name).
69 # 06 Jun 2012 - Added IP only session types after login.
70 # 31 May 2012 - Session ticket system added for handling login sessions.
71 # 29 May 2012 - CGIsafeFileName does not accept filenames starting with '.'
72 # 29 May 2012 - Added CGIscriptor::BrowseAllDirs to handle browsing directories
73 # correctly.
74 # 22 May 2012 - Added Access control with Session Tickets linked to
75 # IP Address and PATH_INFO.
76 # 21 May 2012 - Corrected the links generated by CGIscriptor::BrowseDirs
77 # Will link to current base URL when the HTTP server is '.' or '~'
78 # 29 Oct 2009 - Adapted David A. Wheeler's suggestion about filenames:
79 # CGIsafeFileName does not accept filenames starting with '-'
80 # (http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html)
81 # 08 Oct 2009 - Some corrections in the README.txt file, eg, new email address
82 # 28 Jan 2005 - Added a file selector to performTranslation.
83 # Changed %TranslationTable to @TranslationTable
84 # and patterns to lists.
85 # 27 Jan 2005 - Added a %TranslationTable with associated
86 # performTranslation(\$text) function to allow
87 # run changes in the web pages. Say, to translate
88 # legacy pages with <%=...%> delimiters to the new
89 # <SCRIPT TYPE=..></SCRIPT> format.
90 # 27 Jan 2005 - Small bug of extra '\n' in output removed from the
91 # Other Languages Code.
92 # 10 May 2004 - Belated upload of latest version (2.3) to CPAN
93 # 07 Oct 2003 - Corrected error '\s' -> '\\s' in rebol scripting
94 # language call
95 # 07 Oct 2003 - Corrected omitted INS tags in <DIV><INS> handling
96 # 20 May 2003 - Added a --help switch to print the manual.
97 # 06 Mar 2003 - Adapted the blurb at the end of the file.
98 # 03 Mar 2003 - Added a user definable dieHandler function to catch all
99 # "die" calls. Also "enhanced" the STDERR printout.
100 # 10 Feb 2003 - Split off the reading of the POST part of a query
101 # from Initialize_output. This was suggested by Gerd Franke
102 # to allow for the catching of the file_path using a
103 # POST based lookup. That is, he needed the POST part
104 # to change the file_path.
105 # 03 Feb 2003 - %{$name}; => %{$name} = (); in defineCGIvariableHash.
106 # 03 Feb 2003 - \1 better written as $1 in
107 # $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
108 # 29 Jan 2003 - This makes "CLASS="ssperl" CSS-compatible Gerd Franke
109 # added:
110 # $ServerScriptContentClass = "ssperl";
111 # changed in ProcessFile():
112 # unless(($CurrentContentType =~
113 # 28 Jan 2003 - Added 'INS' Tag! Gerd Franke
114 # 20 Dec 2002 - Removed useless $Directoryseparator variable.
115 # Update comments and documentation.
116 # 18 Dec 2002 - Corrected bug in Accept/Reject processing.
117 # Files didn't work.
118 # 24 Jul 2002 - Added .htaccess documentation (from Gerd Franke)
119 # Also added a note that RawFilePattern can be a
120 # complete file name.
121 # 19 Mar 2002 - Added SRC pseudo-files PREFIX and POSTFIX. These
122 # switch to prepending or to appending the content
123 # of the SRC attribute. Default is prefixing. You
124 # can add as many of these switches as you like.
125 # 13 Mar 2002 - Do not search for tag content if a tag closes with
126 # />, i.e., <DIV ... /> will be handled the XML/XHTML way.
127 # 25 Jan 2002 - Added 'curl' and 'snarf' to SRC attribute URL handling
128 # (replaces wget).
129 # 25 Jan 2002 - Found a bug in SAFEqx, now executes qx() in a scalar context
130 # (i.o. a list context). This is necessary for binary results.
131 # 24 Jan 2002 - Disambiguated -T $SRCfile to -T "$SRCfile" (and -e) and
132 # changed the order of if/elsif to allow removing these
133 # conditions in systems with broken -T functions.
134 # (I also removed a spurious ')' bracket)
135 # 17 Jan 2002 - Changed DIV tag SRC from <SOURCE> to sysread(SOURCE,...)
136 # to support binary files.
137 # 17 Jan 2002 - Removed WhiteSpace from $FileAllowedCharacters.
138 # 17 Jan 2002 - Allow "file://" prefix in SRC attribute. It is simply
139 # stipped from the path.
140 # 15 Jan 2002 - Version 2.2
141 # 15 Jan 2002 - Debugged and completed URL support (including
142 # CGIscriptor::read_url() function)
143 # 07 Jan 2002 - Added automatic (magic) URL support to the SRC attribute
144 # with the main::GET_URL function. Uses wget -O underlying.
145 # 04 Jan 2002 - Added initialization of $NewDirective in InsertForeignScript
146 # (i.e., my $NewDirective = "";) to clear old output
147 # (this was a realy anoying bug).
148 # 03 Jan 2002 - Added a <DIV CLASS='text/ssperl' ID='varname'></DIV>
149 # tags that assign the body text as-is (literally)
150 # to $varname. Allows standard HTML-tools to handle
151 # Cascading Style Sheet templates. This implements a
152 # design by Gerd Franke (franke@roo.de).
153 # 03 Jan 2002 - I finaly gave in and allowed SRC files to expand ~/.
154 # 12 Oct 2001 - Normalized spelling of "CGIsafFileName" in documentation.
155 # 09 Oct 2001 - Added $ENV{'CGI_BINARY_FILE'} to log files to
156 # detect unwanted indexing of TAR files by webcrawlers.
157 # 10 Sep 2001 - Added $YOUR_SCRIPTS directory to @INC for 'require'.
158 # 22 Aug 2001 - Added .txt (Content-type: text/plain) as a default
159 # processed file type. Was processed via BinaryMapFile.
160 # 31 May 2001 - Changed =~ inside CGIsafeEmailAddress that was buggy.
161 # 29 May 2001 - Updated $CGI_HOME to point to $ENV{DOCUMENT_ROOT} io
162 # the root of PATH_TRANSLATED. DOCUMENT_ROOT can now
163 # be manipulated to achieve a "Sub Root".
164 # NOTE: you can have $YOUR_HTML_FILES != DOCUMENT_ROOT
165 # 28 May 2001 - Changed CGIscriptor::BrowsDirs function for security
166 # and debugging (it now works).
167 # 21 May 2001 - defineCGIvariableHash will ADD values to existing
168 # hashes,instead of replacing existing hashes.
169 # 17 May 2001 - Interjected a '&' when pasting POST to GET data
170 # 24 Apr 2001 - Blocked direct requests for BinaryMapFile.
171 # 16 Aug 2000 - Added hash table extraction for CGI parameters with
172 # CGIparseValueHash (used with structured parameters).
173 # Use: CGI='%<CGI-partial-name>' (fill in your name in <>)
174 # Will collect all <CGI-partial-name><key>=value pairs in
175 # $<CGI-partial-name>{<key>} = value;
176 # 16 Aug 2000 - Adapted SAFEqx to protect @PARAMETER values.
177 # 09 Aug 2000 - Added support for non-filesystem input by way of
178 # the CGI_FILE_CONTENTS and CGI_DATA_ACCESS_CODE
179 # environment variables.
180 # 26 Jul 2000 - On the command-line, file-path '-' indicates STDIN.
181 # This allows CGIscriptor to be used in pipes.
182 # Default, $BLOCK_STDIN_HTTP_REQUEST=1 will block this
183 # in an HTTP request (i.e., in a web server).
184 # 26 Jul 2000 - Blocked 'Content-type: text/html' if the SERVER_PROTOCOL
185 # is not HTTP or another protocol. Changed the default
186 # source directory to DOCUMENT_ROOT (i.o. the incorrect
187 # SERVER_ROOT).
188 # 24 Jul 2000 - -slim Command-line argument added to remove all
189 # comments, security, etc.. Updated documentation.
190 # 05 Jul 2000 - Added IF and UNLESS attributes to make the
191 # execution of all <META> and <SCRIPT> code
192 # conditional.
193 # 05 Jul 2000 - Rewrote and isolated the code for extracting
194 # quoted items from CGI and SRC attributes.
195 # Now all attributes expect the same set of
196 # quotes: '', "", ``, (), {}, [] and the same
197 # preceded by a \, e.g., "\((aap)\)" will be
198 # extracted as "(aap)".
199 # 17 Jun 2000 - Construct @ARGV list directly in CGIexecute
200 # name-space (i.o. by evaluation) from
201 # CGI attributes to prevent interference with
202 # the processing for non perl scripts.
203 # Changed CGIparseValueList to prevent runaway
204 # loops.
205 # 16 Jun 2000 - Added a direct (interpolated) display mode
206 # (text/ssdisplay) and a user log mode
207 # (text/sslogfile).
208 # 06 Jun 2000 - Replace "print $Result" with a syswrite loop to
209 # allow large string output.
210 # 02 Jun 2000 - Corrected shrubCGIparameter($CGI_VALUE) to realy
211 # remove all control characters. Changed Interpreter
212 # initialization to shrub interpolated CGI parameters.
213 # Added 'text/ssmailto' interpreter script.
214 # 22 May 2000 - Changed some of the comments
215 # 09 May 2000 - Added list extraction for CGI parameters with
216 # CGIparseValueList (used with multiple selections).
217 # Use: CGI='@<CGI-parameter>' (fill in your name in <>)
218 # 09 May 2000 - Added a 'Not Present' condition to CGIparseValue.
219 # 27 Apr 2000 - Updated documentation to reflect changes.
220 # 27 Apr 2000 - SRC attribute "cleaned". Supported for external
221 # interpreters.
222 # 27 Apr 2000 - CGI attribute can be used in <SCRIPT> tag.
223 # 27 Apr 2000 - Gprolog, M4 support added.
224 # 26 Apr 2000 - Lisp (rep) support added.
225 # 20 Apr 2000 - Use of external interpreters now functional.
226 # 20 Apr 2000 - Removed bug from extracting Content types (RegExp)
227 # 10 Mar 2000 - Qualified unconditional removal of '#' that preclude
228 # the use of $#foo, i.e., I changed
229 # s/[^\\]\#[^\n\f\r]*([\n\f\r])/\1/g
230 # to
231 # s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/\1/g
232 # 03 Mar 2000 - Added a '$BlockPathAccess' variable to "hide"
233 # things like, e.g., CVS information in CVS subtrees
234 # 10 Feb 2000 - URLencode/URLdecode have been made case-insensitive
235 # 10 Feb 2000 - Added a BrowseDirs function (CGIscriptor package)
236 # 01 Feb 2000 - A BinaryMapFile in the ~/ directory has precedence
237 # over a "burried" BinaryMapFile.
238 # 04 Oct 1999 - Added two functions to check file names and email addresses
239 # (CGIscriptor::CGIsafeFileName and
240 # CGIscriptor::CGIsafeEmailAddress)
241 # 28 Sept 1999 - Corrected bug in sysread call for reading POST method
242 # to allow LONG posts.
243 # 28 Sept 1999 - Changed CGIparseValue to handle multipart/form-data.
244 # 29 July 1999 - Refer to BinaryMapFile from CGIscriptor directory, if
245 # this directory exists.
246 # 07 June 1999 - Limit file-pattern matching to LAST extension
247 # 04 June 1999 - Default text/html content type is printed only once.
248 # 18 May 1999 - Bug in replacement of ~/ and ./ removed.
249 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
250 # 15 May 1999 - Changed the name of the execute package to CGIexecute.
251 # Changed the processing of the Accept and Reject file.
252 # Added a full expression evaluation to Access Control.
253 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
254 # 27 Apr 1999 - Brought CGIscriptor under the GNU GPL. Made CGIscriptor
255 # Version 1.1 a module that can be called with 'require "CGIscriptor.pl"'.
256 # Requests are serviced by "Handle_Request()". CGIscriptor
257 # can still be called as a isolated perl script and a shell
258 # command.
259 # Changed the "factory default setting" so that it will run
260 # from the DOCUMENT_ROOT directory.
261 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
262 # 29 Mar 1999 - Remove second debugging STDERR switch. Moved most code
263 # to subroutines to change CGIscriptor into a module.
264 # Added mapping to process unsupported file types (e.g., binary
265 # pictures). See $BinaryMapFile.
266 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
267 # 24 Sept 1998 - Changed text of license (Rob van Son, R.J.J.H.vanSon@uva.nl)
268 # Removed a double setting of filepatterns and maximum query
269 # size. Changed email address. Removed some typos from the
270 # comments.
271 # 02 June 1998 - Bug fixed in URLdecode. Changing the foreach loop variable
272 # caused quiting CGIscriptor.(Rob van Son, R.J.J.H.vanSon@uva.nl)
273 # 02 June 1998 - $SS_PUB and $SS_SCRIPT inserted an extra /, removed.
274 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
277 # Known Bugs:
279 # 23 Mar 2000
280 # It is not possible to use operators or variables to construct variable names,
281 # e.g., $bar = \@{$foo}; won't work. However, eval('$bar = \@{'.$foo.'};');
282 # will indeed work. If someone could tell me why, I would be obliged.
285 ############################################################################
287 # OBLIGATORY USER CONFIGURATION
289 # Configure the directories where all user files can be found (this
290 # is the equivalent of the server root directory of a WWW-server).
291 # These directories can be located ANYWHERE. For security reasons, it is
292 # better to locate them outside the WWW-tree of your HTTP server, unless
293 # CGIscripter handles ALL requests.
295 # For convenience, the defaults are set to the root of the WWW server.
296 # However, this might not be safe!
298 # ~/ text files
299 # $YOUR_HTML_FILES = "/usr/pub/WWW/SHTML"; # or SS_PUB as environment var
300 # (patch to use the parent directory of CGIscriptor as document root, should be removed)
301 if($ENV{'SCRIPT_FILENAME'}) # && $ENV{'SCRIPT_FILENAME'} !~ /\Q$ENV{'DOCUMENT_ROOT'}\E/)
303 $ENV{'DOCUMENT_ROOT'} = $ENV{'SCRIPT_FILENAME'};
304 $ENV{'DOCUMENT_ROOT'} =~ s@/CGIscriptor.*$@@ig;
307 # Just enter your own directory path here
308 $YOUR_HTML_FILES = $ENV{'DOCUMENT_ROOT'}; # default is the DOCUMENT_ROOT
310 # ./ script files (recommended to be different from the previous)
311 # $YOUR_SCRIPTS = "/usr/pub/WWW/scripts"; # or SS_SCRIPT as environment var
312 $YOUR_SCRIPTS = $YOUR_HTML_FILES; # This might be a SECURITY RISK
314 # End of obligatory user configuration
315 # (note: there is more non-essential user configuration below)
317 ############################################################################
319 # OPTIONAL USER CONFIGURATION (all values are used CASE INSENSITIVE)
321 # Script content-types: TYPE="Content-type" (user defined mime-type)
322 $ServerScriptContentType = "text/ssperl"; # Server Side Perl scripts
323 # CSS require a simple class
324 $ServerScriptContentClass = $ServerScriptContentType =~ m!/! ?
325 $' : "ssperl"; # Server Side Perl CSS classes
327 $ShellScriptContentType = "text/osshell"; # OS shell scripts
328 # # (Server Side perl ``-execution)
330 # Accessible file patterns, block any request that doesn't match.
331 # Matches any file with the extension .(s)htm(l), .txt, or .xmr
332 # (\. is used in regexp)
333 # Note: die unless $PATH_INFO =~ m@($FilePattern)$@is;
334 $FilePattern = ".shtml|.htm|.html|.xml|.xmr|.txt|.js|.css";
336 # The table with the content type MIME types
337 # (allows to differentiate MIME types, if needed)
338 %ContentTypeTable =
340 '.html' => 'text/html',
341 '.shtml' => 'text/html',
342 '.htm' => 'text/html',
343 '.xml' => 'text/xml',
344 '.txt' => 'text/plain',
345 '.js' => 'text/plain',
346 '.css' => 'text/plain'
350 # File pattern post-processing
351 $FilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
353 # SHAsum command needed for Authorization and Login
354 # (note, these have to be accessible in the HTML pages, ie, the CGIexecute environment)
355 my $shasum = "shasum -a 256";
356 if(qx{uname} =~ /Darwin/)
358 $shasum = "shasum-5.12 -a 256" unless `which shasum`;
360 my $SHASUMCMD = $shasum.' |cut -f 1 -d" "';
361 $ENV{"SHASUMCMD"} = $SHASUMCMD;
362 my $RANDOMHASHCMD = 'dd bs=1 count=64 if=/dev/urandom 2>/dev/null | '.$shasum.' -b |cut -f 1 -d" "';
363 $ENV{"RANDOMHASHCMD"} = $RANDOMHASHCMD;
365 # Hash a string, return hex of hash
366 sub hash_string # ($string) -> hex_hash
368 my $string = shift || "";
369 # Catch nasty \'-quotes, embed them in '..'"'"'..'
370 $string =~ s/\'/\'\"\'\"\'/isg;
371 my $hash = `printf '%s' '$string'| $ENV{"SHASUMCMD"}`;
372 chomp($hash);
373 return $hash;
376 # Generate random hex hash
377 sub get_random_hex # () -> hex
379 # Create Random Hash Salt
380 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $RANDOMHASHCMD | $!\n";
381 my $RANDOMSALT= <URANDOM>;
382 close(URANDOM);
383 chomp($RANDOMSALT);
385 return $RANDOMSALT;
389 # File patterns of files which are handled by session tickets.
390 %TicketRequiredPatterns = (
391 '^/Private(/|$)' => "Private/.Sessions\tPrivate/.Passwords\t/Private/Login.html\t+36000"
393 # Used to set cookies, only session cookies supported
394 my %SETCOOKIELIST = ();
396 # Session Ticket Directory: Private/.Sessions
397 # Password Directory: Private/.Passwords
398 # Login page (url path): /Private/Login.html
399 # Expiration time (s): +3600
400 # +<seconds> = relative time <seconds> is absolute date-time
402 # Manage login
403 # Set up a valid ticket from a given text file
404 # Use from command line. DO NOT USE ONLINE
405 # Watch out for passwords that get stored in the history file
407 # perl CGIscriptor.pl --managelogin [options] [files]
408 # Options:
409 # salt={file or saltvalue}
410 # masterkey={file or plaintext}
411 # newmasterkey={file or plaintext}
412 # password={file or palintext}
414 # Followed by one or more file names.
415 # Options can be interspersed between filenames,
416 # e.g., password='plaintext'
417 # Note that passwords are only used once!
419 if($ARGV[0] =~ /^\-\-managelogin/i)
421 my @arguments = @ARGV;
422 shift(@arguments);
423 setup_ticket_file(@arguments);
424 # Should be run on the command line
425 exit;
430 # Raw files must contain their own Content-type (xmr <- x-multipart-replace).
431 # THIS IS A SUBSET OF THE FILES DEFINED IN $FilePattern
432 $RawFilePattern = ".xmr";
433 # (In principle, this could contain a full file specification, e.g.,
434 # ".xmr|relocated.html")
436 # Raw File pattern post-processing
437 $RawFilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
439 # Server protocols for which "Content-type: text/html\n\n" should be printed
440 # (you should not bother with these, except for HTTP, they are mostly imaginary)
441 $ContentTypeServerProtocols = 'HTTP|MAIL|MIME';
443 # Block access to all (sub-) paths and directories that match the
444 # following (URL) path (is used as:
445 # 'die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;' )
446 $BlockPathAccess = '/(CVS|\.git)/'; # Protect CVS and .git information
448 # All (blocked) other file-types can be mapped to a single "binary-file"
449 # processor (a kind of pseudo-file path). This can either be an error
450 # message (e.g., "illegal file") or contain a script that serves binary
451 # files.
452 # Note: the real file path wil be stored in $ENV{CGI_BINARY_FILE}.
453 $BinaryMapFile = "/BinaryMapFile.xmr";
454 # Allow for the addition of a CGIscriptor directory
455 # Note that a BinaryMapFile in the root "~/" directory has precedence
456 $BinaryMapFile = "/CGIscriptor".$BinaryMapFile
457 if ! -e "$YOUR_HTML_FILES".$BinaryMapFile
458 && -e "$YOUR_HTML_FILES/CGIscriptor".$BinaryMapFile;
461 # List of all characters that are allowed in file names and paths.
462 # All requests containing illegal characters are blocked. This
463 # blocks most tricks (e.g., adding "\000", "\n", or other control
464 # characters, also blocks URI's using %FF)
465 # THIS IS A SECURITY FEATURE
466 # (this is also used to parse filenames in SRC= features, note the
467 # '-quotes, they are essential)
468 $FileAllowedChars = '\w\.\~\/\:\*\?\-'; # Covers Unix and Mac, but NO spaces
470 # Maximum size of the Query (number of characters clients can send
471 # covers both GET & POST combined)
472 $MaximumQuerySize = 2**20 - 1; # = 2**14 - 1
475 # Embeded URL get function used in SRC attributes and CGIscriptor::read_url
476 # (returns a string with the PERL code to transfer the URL contents, e.g.,
477 # "SAFEqx(\'curl \"http://www.fon.hum.uva.nl\"\')")
478 # "SAFEqx(\'wget --quiet --output-document=- \"http://www.fon.hum.uva.nl\"\')")
479 # Be sure to handle <BASE HREF='URL'> and allow BOTH
480 # direct printing GET_URL($URL [, 0]) and extracting the content of
481 # the $URL for post-processing GET_URL($URL, 1).
482 # You get the WHOLE file, including HTML header.
483 # The shell command Use $URL where the URL should go
484 # ('wget', 'snarf' or 'curl', uncomment the one you would like to use)
485 my $GET_URL_shell_command = 'wget --quiet --output-document=- $URL';
486 #my $GET_URL_shell_command = 'snarf $URL -';
487 #my $GET_URL_shell_command = 'curl $URL';
489 sub GET_URL # ($URL, $ValueNotPrint) -> content_of_url
491 my $URL = shift || return;
492 my $ValueNotPrint = shift || 0;
494 # Check URL for illegal characters
495 return "print '<h1>Illegal URL<h1>'\"\n\";" if $URL =~ /[^$FileAllowedChars\%]/;
497 # Include URL in final command
498 my $CurrentCommand = $GET_URL_shell_command;
499 $CurrentCommand =~ s/\$URL/$URL/g;
501 # Print to STDOUT or return a value
502 my $BlockPrint = "print STDOUT ";
503 $BlockPrint = "" if $ValueNotPrint;
505 my $Commands = <<"GETURLCODE";
506 # Get URL
508 my \$Page = "";
510 # Simple, using shell command
511 \$Page = SAFEqx('$CurrentCommand');
513 # Add a BASE tage to the header
514 \$Page =~ s!\\</head!\\<base href='$URL'\\>\\</head!ig unless \$Page =~ m!\\<base!;
516 # Print the URL value, or return it as a value
517 $BlockPrint\$Page;
519 GETURLCODE
520 return $Commands;
523 # As files can get rather large (and binary), you might want to use
524 # some more intelligent reading procedure, e.g.,
525 # Direct Perl
526 # # open(URLHANDLE, '/usr/bin/wget --quiet --output-document=- "$URL"|') || die "wget: \$!";
527 # #open(URLHANDLE, '/usr/bin/snarf "$URL" -|') || die "snarf: \$!";
528 # open(URLHANDLE, '/usr/bin/curl "$URL"|') || die "curl: \$!";
529 # my \$text = "";
530 # while(sysread(URLHANDLE,\$text, 1024) > 0)
532 # \$Page .= \$text;
533 # };
534 # close(URLHANDLE) || die "\$!";
535 # However, this doesn't work with the CGIexecute->evaluate() function.
536 # You get an error: 'No child processes at (eval 16) line 15, <file0> line 8.'
538 # You can forget the next two variables, they are only needed when
539 # you don't want to use a regular file system (i.e., with open)
540 # but use some kind of database/RAM image for accessing (generating)
541 # the data.
543 # Name of the environment variable that contains the file contents
544 # when reading directly from Database/RAM. When this environment variable,
545 # $ENV{$CGI_FILE_CONTENTS}, is not false, no real file will be read.
546 $CGI_FILE_CONTENTS = 'CGI_FILE_CONTENTS';
547 # Uncomment the following if you want to force the use of the data access code
548 # $ENV{$CGI_FILE_CONTENTS} = '-'; # Force use of $ENV{$CGI_DATA_ACCESS_CODE}
550 # Name of the environment variable that contains the RAM access perl
551 # code needed to read additional "files", i.e.,
552 # $ENV{$CGI_FILE_CONTENTS} = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
553 # When $ENV{$CGI_FILE_CONTENTS} eq '-', this code is executed to generate the data.
554 $CGI_DATA_ACCESS_CODE = 'CGI_DATA_ACCESS_CODE';
556 # You can, of course, fill this yourself, e.g.,
557 # $ENV{$CGI_DATA_ACCESS_CODE} =
558 # 'open(INPUT, "<$_[0]"); while(<INPUT>){print;};close(INPUT);'
561 # DEBUGGING
563 # Suppress error messages, this can be changed for debugging or error-logging
564 #open(STDERR, "/dev/null"); # (comment out for use in debugging)
566 # SPECIAL: Remove Comments, security, etc. if the command line is
567 # '>CGIscriptor.pl -slim >slimCGIscriptor.pl'
568 $TrimDownCGIscriptor = 1 if $ARGV[0] =~ /^\-slim/i;
570 # If CGIscriptor is used from the command line, the command line
571 # arguments are interpreted as the file (1st) and the Query String (rest).
572 # Get the arguments
573 $ENV{'PATH_INFO'} = shift(@ARGV) unless exists($ENV{'PATH_INFO'}) || grep(/\-\-help/i, @ARGV);
574 $ENV{'QUERY_STRING'} = join("&", @ARGV) unless exists($ENV{'QUERY_STRING'});
577 # Handle bail-outs in a user definable way.
578 # Catch Die and replace it with your own function.
579 # Ends with a call to "die $_[0];"
581 sub dieHandler # ($ErrorCode, "Message", @_) -> DEAD
583 my $ErrorCode = shift;
584 my $ErrorMessage = shift;
586 # Place your own reporting functions here
588 # Now, kill everything (default)
589 print STDERR "$ErrorCode: $ErrorMessage\n";
590 die $ErrorMessage;
594 # End of optional user configuration
595 # (note: there is more non-essential user configuration below)
597 if(grep(/\-\-help/i, @ARGV))
599 print << 'ENDOFPREHELPTEXT2';
601 ###############################################################################
603 # Author and Copyright (c):
604 # Rob van Son, © 1995,1996,1997,1998,1999,2000,2001,2002-2012
605 # NKI-AVL Amsterdam
606 # r.v.son@nki.nl
607 # Institute of Phonetic Sciences & IFOTT/ACLS
608 # University of Amsterdam
609 # Email: R.J.J.H.vanSon@gmail.com
610 # Email: R.J.J.H.vanSon@uva.nl
611 # WWW : http://www.fon.hum.uva.nl/rob/
613 # License for use and disclaimers
615 # CGIscriptor merges plain ASCII HTML files transparantly
616 # with CGI variables, in-line PERL code, shell commands,
617 # and executable scripts in other scripting languages.
619 # This program is free software; you can redistribute it and/or
620 # modify it under the terms of the GNU General Public License
621 # as published by the Free Software Foundation; either version 2
622 # of the License, or (at your option) any later version.
624 # This program is distributed in the hope that it will be useful,
625 # but WITHOUT ANY WARRANTY; without even the implied warranty of
626 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
627 # GNU General Public License for more details.
629 # You should have received a copy of the GNU General Public License
630 # along with this program; if not, write to the Free Software
631 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
634 # Contributors:
635 # Rob van Son (R.J.J.H.vanSon@uva.nl)
636 # Gerd Franke franke@roo.de (designed the <DIV> behaviour)
638 #######################################################
639 ENDOFPREHELPTEXT2
641 #######################################################>>>>>>>>>>Start Remove
643 # You can skip the following code, it is an auto-splice
644 # procedure.
646 # Construct a slimmed down version of CGIscriptor
647 # (i.e., CGIscriptor.pl -slim > slimCGIscriptor.pl)
649 if($TrimDownCGIscriptor)
651 open(CGISCRIPTOR, "<CGIscriptor.pl")
652 || dieHandler(1, "<CGIscriptor.pl not slimmed down: $!\n");
653 my $SKIPtext = 0;
654 my $SKIPComments = 0;
656 while(<CGISCRIPTOR>)
658 my $SKIPline = 0;
660 ++$LineCount;
662 # Start of SKIP text
663 $SKIPtext = 1 if /[\>]{10}Start Remove/;
664 $SKIPComments = 1 if $SKIPtext == 1;
666 # Skip this line?
667 $SKIPline = 1 if $SKIPtext || ($SKIPComments && /^\s*\#/);
669 ++$PrintCount unless $SKIPline;
671 print STDOUT $_ unless $SKIPline;
673 # End of SKIP text ?
674 $SKIPtext = 0 if /[\<]{10}End Remove/;
676 # Ready!
677 print STDERR "\# Printed $PrintCount out of $LineCount lines\n";
678 exit;
681 #######################################################
683 if(grep(/\-\-help/i, @ARGV))
685 print << 'ENDOFHELPTEXT';
687 # HYPE
689 # CGIscriptor merges plain ASCII HTML files transparantly and safely
690 # with CGI variables, in-line PERL code, shell commands, and executable
691 # scripts in many languages (on-line and real-time). It combines the
692 # "ease of use" of HTML files with the versatillity of specialized
693 # scripts and PERL programs. It hides all the specifics and
694 # idiosyncrasies of correct output and CGI coding and naming. Scripts
695 # do not have to be aware of HTML, HTTP, or CGI conventions just as HTML
696 # files can be ignorant of scripts and the associated values. CGIscriptor
697 # complies with the W3C HTML 4.0 recommendations.
698 # In addition to its use as a WWW embeded CGI processor, it can
699 # be used as a command-line document preprocessor (text-filter).
701 # THIS IS HOW IT WORKS
703 # The aim of CGIscriptor is to execute "plain" scripts inside a text file
704 # using any required CGIparameters and environment variables. It
705 # is optimized to transparantly process HTML files inside a WWW server.
706 # The native language is Perl, but many other scripting languages
707 # can be used.
709 # CGIscriptor reads text files from the requested input file (i.e., from
710 # $YOUR_HTML_FILES$PATH_INFO) and writes them to <STDOUT> (i.e., the
711 # client requesting the service) preceded by the obligatory
712 # "Content-type: text/html\n\n" or "Content-type: text/plain\n\n" string
713 # (except for "raw" files which supply their own Content-type message
714 # and only if the SERVER_PROTOCOL supports HTTP, MAIL, or MIME).
716 # When CGIscriptor encounters an embedded script, indicated by an HTML4 tag
718 # <SCRIPT TYPE="text/ssperl" [CGI="$VAR='default value'"] [SRC="ScriptSource"]>
719 # PERL script
720 # </SCRIPT>
722 # or
724 # <SCRIPT TYPE="text/osshell" [CGI="$name='default value'"] [SRC="ScriptSource"]>
725 # OS Shell script
726 # </SCRIPT>
728 # construct (anything between []-brackets is optional, other MIME-types
729 # and scripting languages are supported), the embedded script is removed
730 # and both the contents of the source file (i.e., "do 'ScriptSource'")
731 # AND the script are evaluated as a PERL program (i.e., by eval()),
732 # shell script (i.e., by a "safe" version of `Command`, qx) or an external
733 # interpreter. The output of the eval() function takes the place of the
734 # original <SCRIPT></SCRIPT> construct in the output string. Any CGI
735 # parameters declared by the CGI attribute are available as simple perl
736 # variables, and can subsequently be made available as variables to other
737 # scripting languages (e.g., bash, python, or lisp).
739 # Example: printing "Hello World"
740 # <HTML><HEAD><TITLE>Hello World</TITLE>
741 # <BODY>
742 # <H1><SCRIPT TYPE="text/ssperl">"Hello World"</SCRIPT></H1>
743 # </BODY></HTML>
745 # Save this in a file, hello.html, in the directory you indicated with
746 # $YOUR_HTML_FILES and access http://your_server/SHTML/hello.html
747 # (or to whatever name you use as an alias for CGIscriptor.pl).
748 # This is realy ALL you need to do to get going.
750 # You can use any values that are delivered in CGI-compliant form (i.e.,
751 # the "?name=value" type URL additions) transparently as "$name" variables
752 # in your scripts IFF you have declared them in the CGI attribute of
753 # a META or SCRIPT tag before e.g.:
754 # <META CONTENT="text/ssperl; CGI='$name = `default value`'
755 # [SRC='ScriptSource']">
756 # or
757 # <SCRIPT TYPE="text/ssperl" CGI="$name = 'default value'"
758 # [SRC='ScriptSource']>
759 # After such a 'CGI' attribute, you can use $name as an ordinary PERL variable
760 # (the ScriptSource file is immediately evaluated with "do 'ScriptSource'").
761 # The CGIscriptor script allows you to write ordinary HTML files which will
762 # include dynamic CGI aware (run time) features, such as on-line answers
763 # to specific CGI requests, queries, or the results of calculations.
765 # For example, if you wanted to answer questions of clients, you could write
766 # a Perl program called "Answer.pl" with a function "AnswerQuestion()"
767 # that prints out the answer to requests given as arguments. You then write
768 # an HTML page "Respond.html" containing the following fragment:
770 # <center>
771 # The Answer to your question
772 # <META CONTENT="text/ssperl; CGI='$Question'">
773 # <h3><SCRIPT TYPE="text/ssperl">$Question</SCRIPT></h3>
774 # is
775 # <h3><SCRIPT TYPE="text/ssperl" SRC="./PATH/Answer.pl">
776 # AnswerQuestion($Question);
777 # </SCRIPT></h3>
778 # </center>
779 # <FORM ACTION=Respond.html METHOD=GET>
780 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
781 # <INPUT TYPE=SUBMIT VALUE="Ask">
782 # </FORM>
784 # The output could look like the following (in HTML-speak):
786 # <CENTER>
787 # The Answer to your question
788 # <h3>What is the capital of the Netherlands?</h3>
789 # is
790 # <h3>Amsterdam</h3>
791 # </CENTER>
792 # <FORM ACTION=Respond.html METHOD=GET>
793 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
794 # <INPUT TYPE=SUBMIT VALUE="Ask">
796 # Note that the function "Answer.pl" does know nothing about CGI or HTML,
797 # it just prints out answers to arguments. Likewise, the text has no
798 # provisions for scripts or CGI like constructs. Also, it is completely
799 # trivial to extend this "program" to use the "Answer" later in the page
800 # to call up other information or pictures/sounds. The final text never
801 # shows any cue as to what the original "source" looked like, i.e.,
802 # where you store your scripts and how they are called.
804 # There are some extra's. The argument of the files called in a SRC= tag
805 # can access the CGI variables declared in the preceding META tag from
806 # the @ARGV array. Executable files are called as:
807 # `file '$ARGV[0]' ... ` (e.g., `Answer.pl \'$Question\'`;)
808 # The files called from SRC can even be (CGIscriptor) html files which are
809 # processed in-line. Furthermore, the SRC= tag can contain a perl block
810 # that is evaluated. That is,
811 # <META CONTENT="text/ssperl; CGI='$Question' SRC='{$Question}'">
812 # will result in the evaluation of "print do {$Question};" and the VALUE
813 # of $Question will be printed. Note that these "SRC-blocks" can be
814 # preceded and followed by other file names, but only a single block is
815 # allowed in a SRC= tag.
817 # One of the major hassles of dynamic WWW pages is the fact that several
818 # mutually incompatible browsers and platforms must be supported. For example,
819 # the way sound is played automatically is different for Netscape and
820 # Internet Explorer, and for each browser it is different again on
821 # Unix, MacOS, and Windows. Realy dangerous is processing user-supplied
822 # (form-) values to construct email addresses, file names, or database
823 # queries. All Apache WWW-server exploits reported in the media are
824 # based on faulty CGI-scripts that didn't check their user-data properly.
826 # There is no panacee for these problems, but a lot of work and problems
827 # can be saved by allowing easy and transparent control over which
828 # <SCRIPT></SCRIPT> blocks are executed on what CGI-data. CGIscriptor
829 # supplies such a method in the form of a pair of attributes:
830 # IF='...condition..' and UNLESS='...condition...'. When added to a
831 # script tag, the whole block (including the SRC attribute) will be
832 # ignored if the condition is false (IF) or true (UNLESS).
833 # For example, the following block will NOT be evaluated if the value
834 # of the CGI variable FILENAME is NOT a valid filename:
836 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
837 # IF='CGIscriptor::CGIsafeFileName($FILENAME)'>
838 # .....
839 # </SCRIPT>
841 # (the function CGIsafeFileName(String) returns an empty string ("")
842 # if the String argument is not a valid filename).
843 # The UNLESS attribute is the mirror image of IF.
845 # A user manual follows the HTML 4 and security paragraphs below.
847 ##########################################################################
849 # HTML 4 compliance
851 # In general, CGIscriptor.pl complies with the HTML 4 recommendations of
852 # the W3C. This means that any software to manage Web sites will be able
853 # to handle CGIscriptor files, as will web agents.
855 # All script code should be placed between <SCRIPT></SCRIPT> tags, the
856 # script type is indicated with TYPE="mime-type", the LANGUAGE
857 # feature is ignored, and a SRC feature is implemented. All CGI specific
858 # features are delegated to the CGI attribute.
860 # However, the behavior deviates from the W3C recommendations at some
861 # points. Most notably:
862 # 0- The scripts are executed at the server side, invissible to the
863 # client (i.e., the browser)
864 # 1- The mime-types are personal and idiosyncratic, but can be adapted.
865 # 2- Code in the body of a <SCRIPT></SCRIPT> tag-pair is still evaluated
866 # when a SRC feature is present.
867 # 3- The SRC attribute reads a list of files.
868 # 4- The files in a SRC attribute are processed according to file type.
869 # 5- The SRC attribute evaluates inline Perl code.
870 # 6- Processed META, DIV, INS tags are removed from the output
871 # document.
872 # 7- All attributes of the processed META tags, except CONTENT, are ignored
873 # (i.e., deleted from the output).
874 # 8- META tags can be placed ANYWHERE in the document.
875 # 9- Through the SRC feature, META tags can have visible output in the
876 # document.
877 # 10- The CGI attribute that declares CGI parameters, can be used
878 # inside the <SCRIPT> tag.
879 # 11- Use of an extended quote set, i.e., '', "", ``, (), {}, []
880 # and their \-slashed combinations: \'\', \"\", \`\`, \(\),
881 # \{\}, \[\].
882 # 12- IF and UNLESS attributes to <SCRIPT>, <META>, <DIV>, <INS> tags.
883 # 13- <DIV> tags cannot be nested, DIV tags are not
884 # rendered with new-lines.
885 # 14- The XML style <TAG .... /> is recognized and handled correctly.
886 # (i.e., no content is processed)
888 # The reasons for these choices are:
889 # You can still write completely HTML4 compliant documents. CGIscriptor
890 # will not force you to write "deviant" code. However, it allows you to
891 # do so (which is, in fact, just as bad). The prime design principle
892 # was to allow users to include plain Perl code. The code itself should
893 # be "enhancement free". Therefore, extra features were needed to
894 # supply easy access to CGI and Web site components. For security
895 # reasons these have to be declared explicitly. The SRC feature
896 # transparently manages access to external files, especially the safe
897 # use of executable files.
898 # The CGI attribute handles the declarations of external (CGI) variables
899 # in the SCRIPT and META tag's.
900 # EVERYTHING THE CGI ATTRIBUTE AND THE META TAG DO CAN BE DONE INSIDE
901 # A <SCRIPT></SCRIPT> TAG CONSTRUCT.
903 # The reason for the IF, UNLESS, and SRC attributes (and their Perl code
904 # evaluation) were build into the META and SCRIPT tags is part laziness,
905 # part security. The SRC blocks allows more compact documents and easier
906 # debugging. The values of the CGI variables can be immediately screened
907 # for security by IF or UNLESS conditions, and even SRC attributes (e.g.,
908 # email addresses and file names), and a few commands can be called
909 # without having to add another Perl TAG pair. This is especially important
910 # for documents that require the use of other (more restricted) "scripting"
911 # languages and facilities that lag transparent control structures.
913 ##########################################################################
915 # SECURITY
917 # Your WWW site is a few keystrokes away from a few hundred million internet
918 # users. A fair percentage of these users knows more about your computer
919 # than you do. And some of these just might have bad intentions.
921 # To ensure uncompromized operation of your server and platform, several
922 # features are incorporated in CGIscriptor.pl to enhance security.
923 # First of all, you should check the source of this program. No security
924 # measures will help you when you download programs from anonymous sources.
925 # If you want to use THIS file, please make sure that it is uncompromized.
926 # The best way to do this is to contact the source and try to determine
927 # whether s/he is reliable (and accountable).
929 # BE AWARE THAT ANY PROGRAMMER CAN CHANGE THIS PROGRAM IN SUCH A WAY THAT
930 # IT WILL SET THE DOORS TO YOUR SYSTEM WIDE OPEN
932 # I would like to ask any user who finds bugs that could compromise
933 # security to report them to me (and any other bug too,
934 # Email: R.J.J.H.vanSon@uva.nl or ifa@hum.uva.nl).
936 # Security features
938 # 1 Invisibility
939 # The inner workings of the HTML source files are completely hidden
940 # from the client. Only the HTTP header and the ever changing content
941 # of the output distinguish it from the output of a plain, fixed HTML
942 # file. Names, structures, and arguments of the "embedded" scripts
943 # are invisible to the client. Error output is suppressed except
944 # during debugging (user configurable).
946 # 2 Separate directory trees
947 # Directories containing Inline text and script files can reside on
948 # separate trees, distinct from those of the HTTP server. This means
949 # that NEITHER the text files, NOR the script files can be read by
950 # clients other than through CGIscriptor.pl, UNLESS they are
951 # EXPLICITELY made available.
953 # 3 Requests are NEVER "evaluated"
954 # All client supplied values are used as literal values (''-quoted).
955 # Client supplied ''-quotes are ALWAYS removed. Therefore, as long as the
956 # embedded scripts do NOT themselves evaluate these values, clients CANNOT
957 # supply executable commands. Be sure to AVOID scripts like:
959 # <META CONTENT="text/ssperl; CGI='$UserValue'">
960 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 $UserValue`;</SCRIPT>
962 # These are a recipe for disaster. However, the following quoted
963 # form should be save (but is still not adviced):
965 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 \'$UserValue\'`;</SCRIPT>
967 # A special function, SAFEqx(), will automatically do exactly this,
968 # e.g., SAFEqx('ls -1 $UserValue') will execute `ls -1 \'$UserValue\'`
969 # with $UserValue interpolated. I recommend to use SAFEqx() instead
970 # of backticks whenever you can. The OS shell scripts inside
972 # <SCRIPT TYPE="text/osshell">ls -1 $UserValue</SCRIPT>
974 # are handeld by SAFEqx and automatically ''-quoted.
976 # 4 Logging of requests
977 # All requests can be logged separate from the Host server. The level of
978 # detail is user configurable: Including or excluding the actual queries.
979 # This allows for the inspection of (im-) proper use.
981 # 5 Access control: Clients
982 # The Remote addresses can be checked against a list of authorized
983 # (i.e., accepted) or non-authorized (i.e., rejected) clients. Both
984 # REMOTE_HOST and REMOTE_ADDR are tested so clients without a proper
985 # HOST name can be (in-) excluded by their IP-address. Client patterns
986 # containing all numbers and dots are considered IP-addresses, all others
987 # domain names. No wild-cards or regexp's are allowed, only partial
988 # addresses.
989 # Matching of names is done from the back to the front (domain first,
990 # i.e., $REMOTE_HOST =~ /\Q$pattern\E$/is), so including ".edu" will
991 # accept or reject all clients from the domain EDU. Matching of
992 # IP-addresses is done from the front to the back (domain first, i.e.,
993 # $REMOTE_ADDR =~ /^\Q$pattern\E/is), so including "128." will (in-)
994 # exclude all clients whose IP-address starts with 128.
995 # There are two special symbols: "-" matches HOSTs with no name and "*"
996 # matches ALL HOSTS/clients.
997 # For those needing more expressional power, lines starting with
998 # "-e" are evaluated by the perl eval() function. E.g.,
999 # '-e $REMOTE_HOST =~ /\.edu$/is;' will accept/reject clients from the
1000 # domain '.edu'.
1002 # 6 Access control: Files
1003 # In principle, CGIscriptor could read ANY file in the directory
1004 # tree as discussed in 1. However, for security reasons this is
1005 # restricted to text files. It can be made more restricted by entering
1006 # a global file pattern (e.g., ".html"). This is done by default.
1007 # For each client requesting access, the file pattern(s) can be made
1008 # more restrictive than the global pattern by entering client specific
1009 # file patterns in the Access Control files (see 5).
1010 # For example: if the ACCEPT file contained the lines
1011 # * DEMO
1012 # .hum.uva.nl LET
1013 # 145.18.230.
1014 # Then all clients could request paths containing "DEMO" or "demo", e.g.
1015 # "/my/demo/file.html" ($PATH_INFO =~ /\Q$pattern\E/), Clients from
1016 # *.hum.uva.nl could also request paths containing "LET or "let", e.g.
1017 # "/my/let/file.html", and clients from the local cluster
1018 # 145.18.230.[0-9]+ could access ALL files.
1019 # Again, for those needing more expressional power, lines starting with
1020 # "-e" are evaluated. For instance:
1021 # '-e $REMOTE_HOST =~ /\.edu$/is && $PATH_INFO =~ m@/DEMO/@is;'
1022 # will accept/reject requests for files from the directory "/demo/" from
1023 # clients from the domain '.edu'.
1025 # 7 Access control: Server side session tickets
1026 # Specific paths can be controlled by Session Tickets which must be
1027 # present as a SESSIONTICKET=<value> CGI variable in the request. These paths
1028 # are defined in %TicketRequiredPatterns as pairs of:
1029 # ('regexp' => 'SessionPath\tPasswordPath\tLogin.html\tExpiration').
1030 # Session Tickets are stored in a separate directory (SessionPath, e.g.,
1031 # "Private/.Session") as files with the exact same name of the SESSIONTICKET
1032 # CGI. The following is an example:
1033 # Type: SESSION
1034 # IPaddress: 127.0.0.1
1035 # AllowedPaths: ^/Private/Name/
1036 # Expires: 3600
1037 # Username: test
1038 # ...
1039 # Other content can follow.
1041 # It is adviced that Session Tickets should be deleted
1042 # after some (idle) time. The IP address should be the IP number at login, and
1043 # the SESSIONTICKET will be rejected if it is presented from another IP address.
1044 # AllowedPaths and DeniedPaths are perl regexps. Be careful how they match. Make sure to delimit
1045 # the names to prevent access to overlapping names, eg, "^/Private/Rob" will also
1046 # match "^/Private/Robert", however, "^/Private/Rob/" will not. Expires is the
1047 # time the ticket will remain valid after creation (file ctime). Time can be given
1048 # in s[econds] (default), m[inutes], h[hours], or d[ays], eg, "24h" means 24 hours.
1049 # None of these need be present, but the Ticket must have a non-zero size.
1051 # Next to Session Tickets, there are two other type of ticket files:
1052 # - LOGIN tickets store information about a current login request
1053 # - PASSWORD ticket store account information to authorize login requests
1055 # 8 Query length limiting
1056 # The length of the Query string can be limited. If CONTENT_LENGTH is larger
1057 # than this limit, the request is rejected. The combined length of the
1058 # Query string and the POST input is checked before any processing is done.
1059 # This will prevent clients from overloading the scripts.
1060 # The actual, combined, Query Size is accessible as a variable through
1061 # $CGI_Content_Length.
1063 # 9 Illegal filenames, paths, and protected directories
1064 # One of the primary security concerns in handling CGI-scripts is the
1065 # use of "funny" characters in the requests that con scripts in executing
1066 # malicious commands. Examples are inserting ';', null bytes, or <newline>
1067 # characters in URL's and filenames, followed by executable commands. A
1068 # special variable $FileAllowedChars stores a string of all allowed
1069 # characters. Any request that translates to a filename with a character
1070 # OUTSIDE this set will be rejected.
1071 # In general, all (readable files) in the DocumentRoot tree are accessible.
1072 # This might not be what you want. For instance, your DocumentRoot directory
1073 # might be the working directory of a CVS project and contain sensitive
1074 # information (e.g., the password to get to the repository). You can block
1075 # access to these subdirectories by adding the corresponding patterns to
1076 # the $BlockPathAccess variable. For instance, $BlockPathAccess = '/CVS/'
1077 # will block any request that contains '/CVS/' or:
1078 # die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;
1080 #10 The execution of code blocks can be controlled in a transparent way
1081 # by adding IF or UNLESS conditions in the tags themselves. That is,
1082 # a simple check of the validity of filenames or email addresses can
1083 # be done before any code is executed.
1085 ###############################################################################
1087 # USER MANUAL (sort of)
1089 # CGIscriptor removes embedded scripts, indicated by an HTML 4 type
1090 # <SCRIPT TYPE='text/ssperl'> </SCRIPT> or <SCRIPT TYPE='text/osshell'>
1091 # </SCRIPT> constructs. CGIscriptor also recognizes XML-type
1092 # <SCRIPT TYPE='text/ssperl'/> constructs. These are usefull when
1093 # the necessary code is already available in the TAG itself (e.g.,
1094 # using external files). The contents of the directive are executed by
1095 # the PERL eval() and `` functions (in a separate name space). The
1096 # result of the eval() function replaces the <SCRIPT> </SCRIPT> construct
1097 # in the output file. You can use the values that are delivered in
1098 # CGI-compliant form (i.e., the "?name=value&.." type URL additions)
1099 # transparently as "$name" variables in your directives after they are
1100 # defined in a <META> or <SCRIPT> tag.
1101 # If you define the variable "$CGIscriptorResults" in a CGI attribute, all
1102 # subsequent <SCRIPT> and <META> results (including the defining
1103 # tag) will also be pushed onto a stack: @CGIscriptorResults. This list
1104 # behaves like any other, ordinary list and can be manipulated.
1106 # Both GET and POST requests are accepted. These two methods are treated
1107 # equal. Variables, i.e., those values that are determined when a file is
1108 # processed, are indicated in the CGI attribute by $<name> or $<name>=<default>
1109 # in which <name> is the name of the variable and <default> is the value
1110 # used when there is NO current CGI value for <name> (you can use
1111 # white-spaces in $<name>=<default> but really DO make sure that the
1112 # default value is followed by white space or is quoted). Names can contain
1113 # any alphanumeric characters and _ (i.e., names match /[\w]+/).
1114 # If the Content-type: is 'multipart/*', the input is treated as a
1115 # MIME multipart message and automatically delimited. CGI variables get
1116 # the "raw" (i.e., undecoded) body of the corresponding message part.
1118 # Variables can be CGI variables, i.e., those from the QUERY_STRING,
1119 # environment variables, e.g., REMOTE_USER, REMOTE_HOST, or REMOTE_ADDR,
1120 # or predefined values, e.g., CGI_Decoded_QS (The complete, decoded,
1121 # query string), CGI_Content_Length (the length of the decoded query
1122 # string), CGI_Year, CGI_Month, CGI_Time, and CGI_Hour (the current
1123 # date and time).
1125 # All these are available when defined in a CGI attribute. All environment
1126 # variables are accessible as $ENV{'name'}. So, to access the REMOTE_HOST
1127 # and the REMOTE_USER, use, e.g.:
1129 # <SCRIPT TYPE='text/ssperl'>
1130 # ($ENV{'REMOTE_HOST'}||"-")." $ENV{'REMOTE_USER'}"
1131 # </SCRIPT>
1133 # (This will print a "-" if REMOTE_HOST is not known)
1134 # Another way to do this is:
1136 # <META CONTENT="text/ssperl; CGI='$REMOTE_HOST = - $REMOTE_USER'">
1137 # <SCRIPT TYPE='text/ssperl'>"$REMOTE_HOST $REMOTE_USER"</SCRIPT>
1138 # or
1139 # <META CONTENT='text/ssperl; CGI="$REMOTE_HOST = - $REMOTE_USER"
1140 # SRC={"$REMOTE_HOST $REMOTE_USER\n"}'>
1142 # This is possible because ALL environment variables are available as
1143 # CGI variables. The environment variables take precedence over CGI
1144 # names in case of a "name clash". For instance:
1145 # <META CONTENT="text/ssperl; CGI='$HOME' SRC={$HOME}">
1146 # Will print the current HOME directory (environment) irrespective whether
1147 # there is a CGI variable from the query
1148 # (e.g., Where do you live? <INPUT TYPE="TEXT" NAME="HOME">)
1149 # THIS IS A SECURITY FEATURE. It prevents clients from changing
1150 # the values of defined environment variables (e.g., by supplying
1151 # a bogus $REMOTE_ADDR). Although $ENV{} is not changed by the META tags,
1152 # it would make the use of declared variables insecure. You can still
1153 # access CGI variables after a name clash with
1154 # CGIscriptor::CGIparseValue(<name>).
1156 # Some CGI variables are present several times in the query string
1157 # (e.g., from multiple selections). These should be defined as
1158 # @VARIABLENAME=default in the CGI attribute. The list @VARIABLENAME
1159 # will contain ALL VARIABLENAME values from the query, or a single
1160 # default value. If there is an ENVIRONMENT variable of the
1161 # same name, it will be used instead of the default AND the query
1162 # values. The corresponding function is
1163 # CGIscriptor::CGIparseValueList(<name>)
1165 # CGI variables collected in a @VARIABLENAME list are unordered.
1166 # When more structured variables are needed, a hash table can be used.
1167 # A variable defined as %VARIABLE=default will collect all
1168 # CGI-parameters whose name start with 'VARIABLE' in a hash table with
1169 # the remainder of the name as a key. For instance, %PERSON will
1170 # collect PERSONname='John Doe', PERSONbirthdate='01 Jan 00', and
1171 # PERSONspouse='Alice' into a hash table %PERSON such that $PERSON{'spouse'}
1172 # equals 'Alice'. Any default value or environment value will be stored
1173 # under the "" key. If there is an ENVIRONMENT variable of the same name,
1174 # it will be used instead of the default AND the query values. The
1175 # corresponding function is CGIscriptor::CGIparseValueHash(<name>)
1177 # This method of first declaring your environment and CGI variables
1178 # before being able to use them in the scripts might seem somewhat
1179 # clumsy, but it protects you from inadvertedly printing out the values of
1180 # system environment variables when their names coincide with those used
1181 # in the CGI forms. It also prevents "clients" from supplying CGI
1182 # parameter values for your private variables.
1183 # THIS IS A SECURITY FEATURE!
1186 # NON-HTML CONTENT TYPES
1188 # Normally, CGIscriptor prints the standard "Content-type: text/html\n\n"
1189 # message before anything is printed. This has been extended to include
1190 # plain text (.txt) files, for which the Content-type (MIME type)
1191 # 'text/plain' is printed. In all other respects, text files are treated
1192 # as HTML files (this can be switched off by removing '.txt' from the
1193 # $FilePattern variable) . When the content type should be something else,
1194 # e.g., with multipart files, use the $RawFilePattern (.xmr, see also next
1195 # item). CGIscriptor will not print a Content-type message for this file
1196 # type (which must supply its OWN Content-type message). Raw files must
1197 # still conform to the <SCRIPT></SCRIPT> and <META> tag specifications.
1200 # NON-HTML FILES
1202 # CGIscriptor is intended to process HTML and text files only. You can
1203 # create documents of any mime-type on-the-fly using "raw" text files,
1204 # e.g., with the .xmr extension. However, CGIscriptor will not process
1205 # binary files of any type, e.g., pictures or sounds. Given the sheer
1206 # number of formats, I do not have any intention to do so. However,
1207 # an escape route has been provided. You can construct a genuine raw
1208 # (.xmr) text file that contains the perl code to service any file type
1209 # you want. If the global $BinaryMapFile variable contains the path to
1210 # this file (e.g., /BinaryMapFile.xmr), this file will be called
1211 # whenever an unsupported (non-HTML) file type is requested. The path
1212 # to the requested binary file is stored in $ENV('CGI_BINARY_FILE')
1213 # and can be used like any other CGI-variable. Servicing binary files
1214 # then becomes supplying the correct Content-type (e.g., print
1215 # "Content-type: image/jpeg\n\n";) and reading the file and writing it
1216 # to STDOUT (e.g., using sysread() and syswrite()).
1219 # THE META TAG
1221 # All attributes of a META tag are ignored, except the
1222 # CONTENT='text/ssperl; CGI=" ... " [SRC=" ... "]' attribute. The string
1223 # inside the quotes following the CONTENT= indication (white-space is
1224 # ignored, "" '' `` (){}[]-quote pairs are allowed, plus their \ versions)
1225 # MUST start with any of the CGIscriptor mime-types (e.g.: text/ssperl or
1226 # text/osshell) and a comma or semicolon.
1227 # The quoted string following CGI= contains a white-space separated list
1228 # of declarations of the CGI (and Environment) values and default values
1229 # used when no CGI values are supplied by the query string.
1231 # If the default value is a longer string containing special characters,
1232 # possibly spanning several lines, the string must be enclosed in quotes.
1233 # You may use any pair of quotes or brackets from the list '', "", ``, (),
1234 # [], or {} to distinguish default values (or preceded by \, e.g., \(...\)
1235 # is different from (...)). The outermost pair will always be used and any
1236 # other quotes inside the string are considered to be part of the string
1237 # value, e.g.,
1239 # $Value = {['this'
1240 # "and" (this)]}
1241 # will result in $Value getting the default value: ['this'
1242 # "and" (this)]
1243 # (NOTE that the newline is part of the default value!).
1245 # Internally, for defining and initializing CGI (ENV) values, the META
1246 # and SCRIPT tags use the functions "defineCGIvariable($name, $default)"
1247 # (scalars) and "defineCGIvariableList($name, $default)" (lists).
1248 # These functions can be used inside scripts as
1249 # "CGIscriptor::defineCGIvariable($name, $default)" and
1250 # "CGIscriptor::defineCGIvariableList($name, $default)".
1251 # "CGIscriptor::defineCGIvariableHash($name, $default)".
1253 # The CGI attribute will be processed exactly identical when used inside
1254 # the <SCRIPT> tag. However, this use is not according to the
1255 # HTML 4.0 specifications of the W3C.
1258 # THE DIV/INS TAGS
1260 # There is a problem when constructing html files containing
1261 # server-side perl scripts with standard HTML tools. These
1262 # tools will refuse to process any text between <SCRIPT></SCRIPT>
1263 # tags. This is quite annoying when you want to use large
1264 # HTML templates where you will fill in values.
1266 # For this purpose, CGIscriptor will read the neutral
1267 # <DIV CLASS="ssperl" ID="varname"></DIV> or
1268 # <INS CLASS="ssperl" ID="varname"></INS>
1269 # tag (in Cascading Style Sheet manner) Note that
1270 # "varname" has NO '$' before it, it is a bare name.
1271 # Any text between these <DIV ...></DIV> or
1272 # <INS ...></INS>tags will be assigned to '$varname'
1273 # as is (e.g., as a literal).
1274 # No processing or interpolation will be performed.
1275 # There is also NO nesting possible. Do NOT nest a
1276 # </DIV> inside a <DIV></DIV>! Moreover, neither INS nor
1277 # DIV tags do ensure a block structure in the final
1278 # rendering (i.e., no empty lines).
1280 # Note that <DIV CLASS="ssperl" ID="varname"/>
1281 # is handled the XML way. No content is processed,
1282 # but varname is defined, and any SRC directives are
1283 # processed.
1285 # You can use $varname like any other variable name.
1286 # However, $varname is NOT a CGI variable and will be
1287 # completely internal to your script. There is NO
1288 # interaction between $varname and the outside world.
1290 # To interpolate a DIV derived text, you can use:
1291 # $varname =~ s/([\]])/\\\1/g; # Mark ']'-quotes
1292 # $varname = eval("qq[$varname]"); # Interpolate all values
1294 # The DIV tags will process IF, UNLESS, CGI and
1295 # SRC attributes. The SRC files will be pre-pended to the
1296 # body text of the tag. SRC blocks are NOT executed.
1298 # CONDITIONAL PROCESSING: THE 'IF' AND 'UNLESS' ATTRIBUTES
1300 # It is often necessary to include code-blocks that should be executed
1301 # conditionally, e.g., only for certain browsers or operating system.
1302 # Furthermore, quite often sanity and security checks are necessary
1303 # before user (form) data can be processed, e.g., with respect to
1304 # email addresses and filenames.
1306 # Checks added to the code are often difficult to find, interpret or
1307 # maintain and in general mess up the code flow. This kind of confussion
1308 # is dangerous.
1309 # Also, for many of the supported "foreign" scripting languages, adding
1310 # these checks is cumbersome or even impossible.
1312 # As a uniform method for asserting the correctness of "context", two
1313 # attributes are added to all supported tags: IF and UNLESS.
1314 # They both evaluate their value and block execution when the
1315 # result is <FALSE> (IF) or <TRUE> (UNLESS) in Perl, e.g.,
1316 # UNLESS='$NUMBER \> 100;' blocks execution if $NUMBER <= 100. Note that
1317 # the backslash in the '\>' is removed and only used to differentiate
1318 # this conditional '>' from the tag-closing '>'. For symmetry, the
1319 # backslash in '\<' is also removed. Inside these conditionals,
1320 # ~/ and ./ are expanded to their respective directory root paths.
1322 # For example, the following tag will be ignored when the filename is
1323 # invalid:
1325 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
1326 # IF='CGIscriptor::CGIsafeFileName($FILENAME);'>
1327 # ...
1328 # </SCRIPT>
1330 # The IF and UNLESS values must be quoted. The same quotes are supported
1331 # as with the other attributes. The SRC attribute is ignored when IF and
1332 # UNLESS block execution.
1334 # NOTE: 'IF' and 'UNLESS' always evaluate perl code.
1337 # THE MAGIC SOURCE ATTRIBUTE (SRC=)
1339 # The SRC attribute inside tags accepts a list of filenames and URL's
1340 # separated by "," comma's (or ";" semicolons).
1341 # ALL the variable values defined in the CGI attribute are available
1342 # in @ARGV as if the file or block was executed from the command line,
1343 # in the exact order in which they were declared in the preceding CGI
1344 # attribute.
1346 # First, a SRC={}-block will be evaluated as if the code inside the
1347 # block was part of a <SCRIPT></SCRIPT> construct, i.e.,
1348 # "print do { code };'';" or `code` (i.e., SAFEqx('code)).
1349 # Only a single block is evaluated. Note that this is processed less
1350 # efficiently than <SCRIPT> </SCRIPT> blocks. Type of evaluation
1351 # depends on the content-type: Perl for text/ssperl and OS shell for
1352 # text/osshell. For other mime types (scripting languages), anything in
1353 # the source block is put in front of the code block "inside" the tag.
1355 # Second, executable files (i.e., -x filename != 0) are evaluated as:
1356 # print `filename \'$ARGV[0]\' \'$ARGV[1]\' ...`
1357 # That is, you can actually call executables savely from the SRC tag.
1359 # Third, text files that match the file pattern, used by CGIscriptor to
1360 # check whether files should be processed ($FilePattern), are
1361 # processed in-line (i.e., recursively) by CGIscriptor as if the code
1362 # was inserted in the original source file. Recursions, i.e., calling
1363 # a file inside itself, are blocked. If you need them, you have to code
1364 # them explicitely using "main::ProcessFile($file_path)".
1366 # Fourth, Perl text files (i.e., -T filename != 0) are evaluated as:
1367 # "do FileName;'';".
1369 # Last, URL's (i.e., starting with 'HTTP://', 'FTP://', 'GOPHER://',
1370 # 'TELNET://', 'WHOIS://' etc.) are loaded
1371 # and printed. The loading and handling of <BASE> and document header
1372 # is done by a command generated by main::GET_URL($URL [, 0]). You can enter your
1373 # own code (default is curl, wget, or snarf and some post-processing to add a <BASE> tag).
1375 # There are two pseudo-file names: PREFIX and POSTFIX. These implement
1376 # a switch from prefixing the SRC code/files (PREFIX, default) before the
1377 # content of the tag to appending the code after the content of the tag
1378 # (POSTFIX). The switches are done in the order in which the PREFIX and
1379 # POSTFIX labels are encountered. You can mix PREFIX and POSTFIX labels
1380 # in any order with the SRC files. Note that the ORDER of file execution
1381 # is determined for prefixed and postfixed files seperately.
1383 # File paths can be preceded by the URL protocol prefix "file://". This
1384 # is simply STRIPPED from the name.
1386 # Example:
1387 # The request
1388 # "http://cgi-bin/Action_Forms.pl/Statistics/Sign_Test.html?positive=8&negative=22
1389 # will result in printing "${SS_PUB}/Statistics/Sign_Test.html"
1390 # With QUERY_STRING = "positive=8&negative=22"
1392 # on encountering the lines:
1393 # <META CONTENT="text/osshell; CGI='$positive=11 $negative=3'">
1394 # <b><SCRIPT LANGUAGE=PERL TYPE="text/ssperl" SRC="./Statistics/SignTest.pl">
1395 # </SCRIPT></b><p>"
1397 # This line will be processed as:
1398 # "<b>`${SS_SCRIPT}/Statistics/SignTest.pl '8' '22'`</b><p>"
1400 # In which "${SS_SCRIPT}/Statistics/SignTest.pl" is an executable script,
1401 # This line will end up printed as:
1402 # "<b>p <= 0.0161</b><p>"
1404 # Note that the META tag itself will never be printed, and is invisible to
1405 # the outside world.
1407 # The SRC files in a DIV or INS tag will be added (pre-pended) to the body
1408 # of the <DIV></DIV> tag. Blocks are NOT executed! If you do not
1409 # need any content, you can use the <DIV...../> format.
1412 # THE CGISCRIPTOR ROOT DIRECTORIES ~/ AND ./
1414 # Inside <SCRIPT></SCRIPT> tags, filepaths starting
1415 # with "~/" are replaced by "$YOUR_HTML_FILES/", this way files in the
1416 # public directories can be accessed without direct reference to the
1417 # actual paths. Filepaths starting with "./" are replaced by
1418 # "$YOUR_SCRIPTS/" and this should only be used for scripts.
1420 # Note: this replacement can seriously affect Perl scripts. Watch
1421 # out for constructs like $a =~ s/aap\./noot./g, use
1422 # $a =~ s@aap\.@noot.@g instead.
1424 # CGIscriptor.pl will assign the values of $SS_PUB and $SS_SCRIPT
1425 # (i.e., $YOUR_HTML_FILES and $YOUR_SCRIPTS) to the environment variables
1426 # $SS_PUB and $SS_SCRIPT. These can be accessed by the scripts that are
1427 # executed.
1428 # Values not preceded by $, ~/, or ./ are used as literals
1431 # OS SHELL SCRIPT EVALUATION (CONTENT-TYPE=TEXT/OSSHELL)
1433 # OS scripts are executed by a "safe" version of the `` operator (i.e.,
1434 # SAFEqx(), see also below) and any output is printed. CGIscriptor will
1435 # interpolate the script and replace all user-supplied CGI-variables by
1436 # their ''-quoted values (actually, all variables defined in CGI attributes
1437 # are quoted). Other Perl variables are interpolated in a simple fasion,
1438 # i.e., $scalar by their value, @list by join(' ', @list), and %hash by
1439 # their name=value pairs. Complex references, e.g., @$variable, are all
1440 # evaluated in a scalar context. Quotes should be used with care.
1441 # NOTE: the results of the shell script evaluation will appear in the
1442 # @CGIscriptorResults stack just as any other result.
1443 # All occurrences of $@% that should NOT be interpolated must be
1444 # preceeded by a "\". Interpolation can be switched off completely by
1445 # setting $CGIscriptor::NoShellScriptInterpolation = 1
1446 # (set to 0 or undef to switch interpolation on again)
1447 # i.e.,
1448 # <SCRIPT TYPE="text/ssperl">
1449 # $CGIscriptor::NoShellScriptInterpolation = 1;
1450 # </SCRIPT>
1453 # RUN TIME TRANSLATION OF INPUT FILES
1455 # Allows general and global conversions of files using Regular Expressions.
1456 # Very handy (but costly) to rewrite legacy pages to a new format.
1457 # Select files to use it on with
1458 # my $TranslationPaths = 'filepattern';
1459 # This is costly. For efficiency, define:
1460 # $TranslationPaths = ''; when not using translations.
1461 # Accepts general regular expressions: [$pattern, $replacement]
1463 # Define:
1464 # my $TranslationPaths = 'filepattern'; # Pattern matching PATH_INFO
1466 # push(@TranslationTable, ['pattern', 'replacement']);
1467 # e.g. (for Ruby Rails):
1468 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
1469 # push(@TranslationTable, ['%>', '</SCRIPT>']);
1471 # Runs:
1472 # my $currentRegExp;
1473 # foreach $currentRegExp (@TranslationTable)
1475 # my ($pattern, $replacement) = @$currentRegExp;
1476 # $$text =~ s!$pattern!$replacement!msg;
1477 # };
1480 # EVALUATION OF OTHER SCRIPTING LANGUAGES
1482 # Adding a MIME-type and an interpreter command to
1483 # %ScriptingLanguages automatically will catch any other
1484 # scripting language in the standard
1485 # <SCRIPT TYPE="[mime]"></SCRIPT> manner.
1486 # E.g., adding: $ScriptingLanguages{'text/sspython'} = 'python';
1487 # will actually execute the folowing code in an HTML page
1488 # (ignore 'REMOTE_HOST' for the moment):
1489 # <SCRIPT TYPE="text/sspython">
1490 # # A Python script
1491 # x = ["A","real","python","script","Hello","World","and", REMOTE_HOST]
1492 # print x[4:8] # Prints the list ["Hello","World","and", REMOTE_HOST]
1493 # </SCRIPT>
1495 # The script code is NOT interpolated by perl, EXCEPT for those
1496 # interpreters that cannot handle variables themselves.
1497 # Currently, several interpreters are pre-installed:
1499 # Perl test - "text/testperl" => 'perl',
1500 # Python - "text/sspython" => 'python',
1501 # Ruby - "text/ssruby" => 'ruby',
1502 # Tcl - "text/sstcl" => 'tcl',
1503 # Awk - "text/ssawk" => 'awk -f-',
1504 # Gnu Lisp - "text/sslisp" => 'rep | tail +5 '.
1505 # "| egrep -v '> |^rep. |^nil\\\$'",
1506 # XLispstat - "text/xlispstat" => 'xlispstat | tail +7 '.
1507 # "| egrep -v '> \\\$|^NIL'",
1508 # Gnu Prolog- "text/ssprolog" => 'gprolog',
1509 # M4 macro's- "text/ssm4" => 'm4',
1510 # Born shell- "text/sh" => 'sh',
1511 # Bash - "text/bash" => 'bash',
1512 # C-shell - "text/csh" => 'csh',
1513 # Korn shell- "text/ksh" => 'ksh',
1514 # Praat - "text/sspraat" => "praat - | sed 's/Praat > //g'",
1515 # R - "text/ssr" => "R --vanilla --slave | sed 's/^[\[0-9\]*] //g'",
1516 # REBOL - "text/ssrebol" =>
1517 # "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\s*\[> \]* //g'",
1518 # PostgreSQL- "text/postgresql" => 'psql 2>/dev/null',
1519 # (psql)
1521 # Note that the "value" of $ScriptingLanguages{mime} must be a command
1522 # that reads Standard Input and writes to standard output. Any extra
1523 # output of interactive interpreters (banners, echo's, prompts)
1524 # should be removed by piping the output through 'tail', 'grep',
1525 # 'sed', or even 'awk' or 'perl'.
1527 # For access to CGI variables there is a special hashtable:
1528 # %ScriptingCGIvariables.
1529 # CGI variables can be accessed in three ways.
1530 # 1. If the mime type is not present in %ScriptingCGIvariables,
1531 # nothing is done and the script itself should parse the relevant
1532 # environment variables.
1533 # 2. If the mime type IS present in %ScriptingCGIvariables, but it's
1534 # value is empty, e.g., $ScriptingCGIvariables{"text/sspraat"} = '';,
1535 # the script text is interpolated by perl. That is, all $var, @array,
1536 # %hash, and \-slashes are replaced by their respective values.
1537 # 3. In all other cases, the CGI and environment variables are added
1538 # in front of the script according to the format stored in
1539 # %ScriptingCGIvariables. That is, the following (pseudo-)code is
1540 # executed for each CGI- or Environment variable defined in the CGI-tag:
1541 # printf(INTERPRETER, $ScriptingCGIvariables{$mime}, $CGI_NAME, $CGI_VALUE);
1543 # For instance, "text/testperl" => '$%s = "%s";' defines variable
1544 # definitions for Perl, and "text/sspython" => '%s = "%s"' for Python
1545 # (note that these definitions are not save, the real ones contain '-quotes).
1547 # THIS WILL NOT WORK FOR @VARIABLES, the (empty) $VARIABLES will be used
1548 # instead.
1550 # The $CGI_VALUE parameters are "shrubed" of all control characters
1551 # and quotes (by &shrubCGIparameter($CGI_VALUE)) for the options 2 and 3.
1552 # Control characters are replaced by \0<octal ascii value> (the exception
1553 # is \015, the newline, which is replaced by \n) and quotes
1554 # and backslashes by their HTML character
1555 # value (' -> &#39; ` -> &#96; " -> &quot; \ -> &#92; & -> &amper;).
1556 # For example:
1557 # if a client would supply the string value (in standard perl, e.g.,
1558 # \n means <newline>)
1559 # "/dev/null';\nrm -rf *;\necho '"
1560 # it would be processed as
1561 # '/dev/null&#39;;\nrm -rf *;\necho &#39;'
1562 # (e.g., sh or bash would process the latter more according to your
1563 # intentions).
1564 # If your intepreter requires different protection measures, you will
1565 # have to supply these in %main::SHRUBcharacterTR (string => translation),
1566 # e.g., $SHRUBcharacterTR{"\'"} = "&#39;";
1568 # Currently, the following definitions are used:
1569 # %ScriptingCGIvariables = (
1570 # "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value' (for testing)
1571 # "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
1572 # "text/ssruby" => '@%s = "%s"', # Ruby @VAR = "value"
1573 # "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
1574 # "text/ssawk" => '%s = "%s";', # Awk VAR = "value";
1575 # "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
1576 # "text/xlispstat" => '(setq %s "%s")', # Xlispstat (setq VAR "value")
1577 # "text/ssprolog" => '', # Gnu prolog (interpolated)
1578 # "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
1579 # "text/sh" => "\%s='\%s';", # Born shell VAR='value';
1580 # "text/bash" => "\%s='\%s';", # Born again shell VAR='value';
1581 # "text/csh" => "\$\%s = '\%s';", # C shell $VAR = 'value';
1582 # "text/ksh" => "\$\%s = '\%s';", # Korn shell $VAR = 'value';
1583 # "text/sspraat" => '', # Praat (interpolation)
1584 # "text/ssr" => '%s <- "%s";', # R VAR <- "value";
1585 # "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
1586 # "text/postgresql" => '', # PostgreSQL (interpolation)
1587 # "" => ""
1588 # );
1590 # Four tables allow fine-tuning of interpreter with code that should be
1591 # added before and after each code block:
1593 # Code added before each script block
1594 # %ScriptingPrefix = (
1595 # "text/testperl" => "\# Prefix Code;", # Perl script testing
1596 # "text/ssm4" => 'divert(0)' # M4 macro's (open STDOUT)
1597 # );
1598 # Code added at the end of each script block
1599 # %ScriptingPostfix = (
1600 # "text/testperl" => "\# Postfix Code;", # Perl script testing
1601 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1602 # );
1603 # Initialization code, inserted directly after opening (NEVER interpolated)
1604 # %ScriptingInitialization = (
1605 # "text/testperl" => "\# Initialization Code;", # Perl script testing
1606 # "text/ssawk" => 'BEGIN {', # Server Side awk scripts
1607 # "text/sslisp" => '(prog1 nil ', # Lisp (rep)
1608 # "text/xlispstat" => '(prog1 nil ', # xlispstat
1609 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1610 # );
1611 # Cleanup code, inserted before closing (NEVER interpolated)
1612 # %ScriptingCleanup = (
1613 # "text/testperl" => "\# Cleanup Code;", # Perl script testing
1614 # "text/sspraat" => 'Quit',
1615 # "text/ssawk" => '};', # Server Side awk scripts
1616 # "text/sslisp" => '(princ "\n" standard-output)).' # Closing print to rep
1617 # "text/xlispstat" => '(print "" *standard-output*)).' # Closing print to xlispstat
1618 # "text/postgresql" => '\q',
1619 # );
1622 # The SRC attribute is NOT magical for these interpreters. In short,
1623 # all code inside a source file or {} block is written verbattim
1624 # to the interpreter. No (pre-)processing or executional magic is done.
1626 # A serious shortcomming of the described mechanism for handling other
1627 # (scripting) languages, with respect to standard perl scripts
1628 # (i.e., 'text/ssperl'), is that the code is only executed when
1629 # the pipe to the interpreter is closed. So the pipe has to be
1630 # closed at the end of each block. This means that the state of the
1631 # interpreter (e.g., all variable values) is lost after the closing of
1632 # the next </SCRIPT> tag. The standard 'text/ssperl' scripts retain
1633 # all values and definitions.
1635 # APPLICATION MIME TYPES
1637 # To ease some important auxilliary functions from within the
1638 # html pages I have added them as MIME types. This uses
1639 # the mechanism that is also used for the evaluation of
1640 # other scripting languages, with interpolation of CGI
1641 # parameters (and perl-variables). Actually, these are
1642 # defined exactly like any other "scripting language".
1644 # text/ssdisplay: display some (HTML) text with interpolated
1645 # variables (uses `cat`).
1646 # text/sslogfile: write (append) the interpolated block to the file
1647 # mentioned on the first, non-empty line
1648 # (the filename can be preceded by 'File: ',
1649 # note the space after the ':',
1650 # uses `awk .... >> <filename>`).
1651 # text/ssmailto: send email directly from within the script block.
1652 # The first line of the body must contain
1653 # To:Name@Valid.Email.Address
1654 # (note: NO space between 'To:' and the email adres)
1655 # For other options see the mailto man pages.
1656 # It works by directly sending the (interpolated)
1657 # content of the text block to a pipe into the
1658 # Linux program 'mailto'.
1660 # In these script blocks, all Perl variables will be
1661 # replaced by their values. All CGI variables are cleaned before
1662 # they are used. These CGI variables must be redefined with a
1663 # CGI attribute to restore their original values.
1664 # In general, this will be more secure than constructing
1665 # e.g., your own email command lines. For instance, Mailto will
1666 # not execute any odd (forged) email addres, but just stops
1667 # when the email address is invalid and awk will construct
1668 # any filename you give it (e.g. '<File;rm\\\040-f' would end up
1669 # as a "valid" UNIX filename). Note that it will also gladly
1670 # store this file anywhere (/../../../etc/passwd will work!).
1671 # Use the CGIscriptor::CGIsafeFileName() function to clean the
1672 # filename.
1674 # SHELL SCRIPT PIPING
1676 # If a shell script starts with the UNIX style "#! <shell command> \n"
1677 # line, the rest of the shell script is piped into the indicated command,
1678 # i.e.,
1679 # open(COMMAND, "| command");print COMMAND $RestOfScript;
1681 # In many ways this is equivalent to the MIME-type profiling for
1682 # evaluating other scripting languages as discussed above. The
1683 # difference breaks down to convenience. Shell script piping is a
1684 # "raw" implementation. It allows you to control all aspects of
1685 # execution. Using the MIME-type profiling is easier, but has a
1686 # lot of defaults built in that might get in the way. Another
1687 # difference is that shell script piping uses the SAFEqx() function,
1688 # and MIME-type profiling does not.
1690 # Execution of shell scripts is under the control of the Perl Script blocks
1691 # in the document. The MIME-type triggered execution of <SCRIPT></SCRIPT>
1692 # blocks can be simulated easily. You can switch to a different shell,
1693 # e.g. tcl, completely by executing the following Perl commands inside
1694 # your document:
1696 # <SCRIPT TYPE="text/ssperl">
1697 # $main::ShellScriptContentType = "text/ssTcl"; # Yes, you can do this
1698 # CGIscriptor::RedirectShellScript('/usr/bin/tcl'); # Pipe to Tcl
1699 # $CGIscriptor::NoShellScriptInterpolation = 1;
1700 # </SCRIPT>
1702 # After this script is executed, CGIscriptor will parse scripts of
1703 # TYPE="text/ssTcl" and pipe their contents into '|/usr/bin/tcl'
1704 # WITHOUT interpolation (i.e., NO substitution of Perl variables).
1705 # The crucial function is :
1706 # CGIscriptor::RedirectShellScript('/usr/bin/tcl')
1707 # After executing this function, all shell scripts AND all
1708 # calls to SAFEqx()) are piped into '|/usr/bin/tcl'. If the argument
1709 # of RedirectShellScript is empty, e.g., '', the original (default)
1710 # value is reset.
1712 # The standard output, STDOUT, of any pipe is send to the client.
1713 # Currently, you should be carefull with quotes in such a piped script.
1714 # The results of a pipe is NOT put on the @CGIscriptorResults stack.
1715 # As a result, you do not have access to the output of any piped (#!)
1716 # process! If you want such access, execute
1717 # <SCRIPT TYPE="text/osshell">echo "script"|command</SCRIPT>
1718 # or
1719 # <SCRIPT TYPE="text/ssperl">
1720 # $resultvar = SAFEqx('echo "script"|command');
1721 # </SCRIPT>.
1723 # Safety is never complete. Although SAFEqx() prevents some of the
1724 # most obvious forms of attacks and security slips, it cannot prevent
1725 # them all. Especially, complex combinations of quotes and intricate
1726 # variable references cannot be handled safely by SAFEqx. So be on
1727 # guard.
1730 # PERL CODE EVALUATION (CONTENT-TYPE=TEXT/SSPERL)
1732 # All PERL scripts are evaluated inside a PERL package. This package
1733 # has a separate name space. This isolated name space protects the
1734 # CGIscriptor.pl program against interference from user code. However,
1735 # some variables, e.g., $_, are global and cannot be protected. You are
1736 # advised NOT to use such global variable names. You CAN write
1737 # directives that directly access the variables in the main program.
1738 # You do so at your own risk (there is definitely enough rope available
1739 # to hang yourself). The behavior of CGIscriptor becomes undefined if
1740 # you change its private variables during run time. The PERL code
1741 # directives are used as in:
1742 # $Result = eval($directive); print $Result;'';
1743 # ($directive contains all text between <SCRIPT></SCRIPT>).
1744 # That is, the <directive> is treated as ''-quoted string and
1745 # the result is treated as a scalar. To prevent the VALUE of the code
1746 # block from appearing on the client's screen, end the directive with
1747 # ';""</SCRIPT>'. Evaluated directives return the last value, just as
1748 # eval(), blocks, and subroutines, but only as a scalar.
1750 # IMPORTANT: All PERL variables defined are persistent. Each <SCRIPT>
1751 # </SCRIPT> construct is evaluated as a {}-block with associated scope
1752 # (e.g., for "my $var;" declarations). This means that values assigned
1753 # to a PERL variable can be used throughout the document unless they
1754 # were declared with "my". The following will actually work as intended
1755 # (note that the ``-quotes in this example are NOT evaluated, but used
1756 # as simple quotes):
1758 # <META CONTENT="text/ssperl; CGI=`$String='abcdefg'`">
1759 # anything ...
1760 # <SCRIPT TYPE=text/ssperl>@List = split('', $String);</SCRIPT>
1761 # anything ...
1762 # <SCRIPT TYPE=text/ssperl>join(", ", @List[1..$#List]);</SCRIPT>
1764 # The first <SCRIPT TYPE=text/ssperl></SCRIPT> construct will return the
1765 # value scalar(@List), the second <SCRIPT TYPE=text/ssperl></SCRIPT>
1766 # construct will print the elements of $String separated by commas, leaving
1767 # out the first element, i.e., $List[0].
1769 # Another warning: './' and '~/' are ALWAYS replaced by the values of
1770 # $YOUR_SCRIPTS and $YOUR_HTML_FILES, respectively . This can interfere
1771 # with pattern matching, e.g., $a =~ s/aap\./noot\./g will result in the
1772 # evaluations of $a =~ s/aap\\${YOUR_SCRIPTS}noot\\${YOUR_SCRIPTS}g. Use
1773 # s@<regexp>.@<replacement>.@g instead.
1776 # SERVER SIDE SESSIONS AND ACCESS CONTROL (LOGIN)
1778 # An infrastructure for user acount authorization and file access control
1779 # is available. Each request is matched against a list of URL path patterns.
1780 # If the request matches, a Session Ticket is required to access the URL.
1781 # This Session Ticket should be present as a CGI parameter or Cookie, eg:
1783 # CGI: SESSIONTICKET=&lt;value&gt;
1784 # Cookie: CGIscriptorSESSION=&lt;value&gt;
1786 # The example implementation stores Session Tickets as files in a local
1787 # directory. To create Session Tickets, a Login request must be given
1788 # with a LOGIN=&lt;value&gt; CGI parameter, a user name and a (doubly hashed)
1789 # password. The user name and (singly hashed) password are stored in a
1790 # PASSWORD ticket with the same name as the user account (name cleaned up
1791 # for security).
1793 # The example session model implements 4 functions:
1794 # - Login
1795 # The password is hashed with the user name and server side salt, and then
1796 # hashed with a random salt. Client and Server both perform these actions
1797 # and the Server only grants access if restults are the same. The server
1798 # side only stores the password hashed with the user name and
1799 # server side salt. Neither the plain password, nor the hashed password is
1800 # ever exchanged. Only values hashed with the one-time salt are exchanged.
1801 # - Session
1802 # For every access to a restricted URL, the Session Ticket is checked before
1803 # access is granted. There are three session modes. The first uses a fixed
1804 # Session Ticket that is stored as a cookie value in the browser (actually,
1805 # as a sessionStorage value). The second uses only the IP address at login
1806 # to authenticate requests. The third
1807 # is a Challenge mode, where the client has to calculate the value of the
1808 # next one-time Session Ticket from a value derived from the password and
1809 # a random string.
1810 # - Password Change
1811 # A new password is hashed with the user name and server side salt, and
1812 # then encrypted (XORed)
1813 # with the old password hashed with the user name and salt. That value is
1814 # exchanged and XORed with the stored old hashed(password+username+salt).
1815 # Again, the stored password value is never exchanged unencrypted.
1816 # - New Account
1817 # The text of a new account (Type: PASSWORD) file is constructed from
1818 # the new username (CGI: NEWUSERNAME, converted to lowercase) and
1819 # hashed new password (CGI: NEWPASSWORD). The same process is used to encrypt
1820 # the new password as is used for the Password Change function.
1821 # Again, the stored password value is never exchanged unencrypted.
1822 # Some default setting are encoded. For display in the browser, the new password
1823 # is reencrypted (XORed) with a special key, the old password hash
1824 # hashed with a session specific random hex value sent initially with the
1825 # session login ticket ($RANDOMSALT).
1826 # For example for user "NewUser" and password "NewPassword" with filename
1827 # "newuser":
1829 # Type: PASSWORD
1830 # Username: newuser
1831 # Password: 19afeadfba8d5dcd252e157fafd3010859f8762b87682b6b6cdb3e565194fa91
1832 # IPaddress: 127\.0\.0\.1
1833 # AllowedPaths: ^/Private/[\w\-]+\.html?
1834 # AllowedPaths: ^/Private/newuser/
1835 # Salt: e93cf858a1d5626bf095ea5c25df990dfa969ff5a5dc908b22c9a5229b525f65
1836 # Session: SESSION
1837 # Date: Fri Jun 29 12:46:22 2012
1838 # Time: 1340973982
1839 # Signature: 676c35d3aa63540293ea5442f12872bfb0a22665b504f58f804582493b6ef04e
1841 # The password is created with the commands:
1843 # printf '%s' 'NewPasswordnewuser970e68017413fb0ea84d7fe3c463077636dd6d53486910d4a53c693dd4109b1a'|shasum -a 256
1845 # However, the password account files are protected against unauthorized change.
1846 # To obtain a valid Password account, the following command should be given:
1848 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
1849 # masterkey='Sherlock investigates oleander curry in Bath' \
1850 # password='NewPassword' \
1851 # Private/.Passwords/newuser
1854 # Implementation
1856 # The session authentication mechanism is based on the exchange of ticket
1857 # identifiers. A ticket identifier is just a string of characters, a name
1858 # or a random 64 character hexadecimal string. Ticket identifiers should be
1859 # "safe" filenames (except user names). There are four types of tickets:
1860 # PASSWORD: User account descriptors, including a user name and password
1861 # LOGIN: Temporary anonymous tickets used during login
1862 # IPADDRESS: Authetication tokens that allow access based on the IP address of the request
1863 # SESSION: Reusable authetication tokens
1864 # CHALLENGE: One-time authetication tokens
1865 # All tickets can have an expiration date in the form of a time duration
1866 # from creation, in seconds, minutes, hours, or days (+duration[smhd]).
1867 # An absolute time can be given in seconds since the epoch of the server host.
1868 # Note that expiration times of CHALLENGE authetication tokens are calculated
1869 # from the last access time. Accounts can include a maximal lifetime
1870 # for session tickets (MaxLifetime).
1872 # A Login page should create a LOGIN ticket file locally and send a
1873 # server specific salt, a Random salt, and a LOGIN ticket
1874 # identifier. The server side compares the username and hashed password,
1875 # actually hashed(hashed(password+serversalt)+Random salt) from the client with
1876 # the values it calculates from the stored Random salt from the LOGIN
1877 # ticket and the hashed(password+serversalt) from the PASSWORD ticket. If
1878 # successful, a new SESSION ticket is generated as a hash sum of the stored
1879 # password and the LOGIN ticket. This SESSION ticket should also be
1880 # generated by the client and stored as sessionStorage and cookie values
1881 # as needed. The Username, IP address and Path are available as
1882 # $LoginUsername, $LoginIPaddress, and $LoginPath, respectively.
1884 # The CHALLENGE protocol stores the same value as the SESSION tickets.
1885 # However, this value is not exchanged, but kept secret in the JavaScript
1886 # sessionStorage object. Instead, every page returned from the
1887 # server will contain a one-time Challenge value ($CHALLENGETICKET) which
1888 # has to be hashed with the stored value to return the current ticket
1889 # id string.
1891 # In the current example implementation, all random values are created as
1892 # full, 256 bit SHA256 hash values (Hex strings) of 64 bytes read from
1893 # /dev/urandom.
1896 # Authorization
1898 # A limited level of authorization tuning is build into the login system.
1899 # Each account file (PASSWORD ticket file) can contain a number of
1900 # Capabilities lines. These control special priveliges. The
1901 # Capabilities can be checked inside the HTML pages as part of the
1902 # ticket information. Two privileges are handled internally:
1903 # CreateUser and VariableREMOTE_ADDR.
1904 # CreateUser allows the logged in user to create a new user account.
1905 # With VariableREMOTE_ADDR, the session of the logged in user is
1906 # not limited to the Remote IP address from which the inital log-in took
1907 # place. Sessions can hop from one apparant (proxy) IP address to another,
1908 # e.g., when using Tor. Any IPaddress patterns given in the PASSWORD
1909 # ticket file remain in effect during the session. For security reasons,
1910 # the VariableREMOTE_ADDR capability is only effective if the session
1911 # type is CHALLENGE.
1914 # Security considerations with Session tickets
1916 # For strong security, please use end-to-end encryption. This can be
1917 # achieved using a VPN (Virtual Private Network), SSH tunnel, or a HTTPS
1918 # capable server with OpenSSL. The session ticket system of CGIscriptor.pl
1919 # is intended to be used as a simple authentication mechanism WITHOUT
1920 # END-TO-END ENCRYPTION. The authenticating mechanism tries to use some
1921 # simple means to protect the authentication process from eavesdropping.
1922 # For this it uses a secure hash function, SHA256. For all practial purposes,
1923 # it is impossible to "decrypt" a SHA256 sum. But this login scheme is
1924 # only as secure as your browser. Which, in general, is not very secure.
1926 # One weakness of the implemented procedure is that the Client obtains
1927 # the code to encrypt the passwords from the server. It is the JavaScript
1928 # code in the HTML pages. An attacker who could place himself between Server
1929 # and Client, a man in the middle attack, could change the code to
1930 # reveal the plaintext password and other information. There is no real
1931 # protection against this attack without end-to-end encryption and
1932 # authentication. A simple, but rather cumbersome, way to check for such
1933 # attacks would be to store known good copys of the pages (downloaded
1934 # with a browser or automatically with curl or wget) and
1935 # then use other tools to download new pages at random intervals and compare
1936 # them to the old pages. A simple diff command between old and
1937 # new files should give only differences in half a dozen lines, where only
1938 # hexadecimal salt values will actually differ.
1940 # Humans tend to reuse passwords. A compromise of a site running
1941 # CGIscriptor.pl could therefore lead to a compromise of user accounts at
1942 # other sites. Therefore, plain text passwords are never stored, used, or
1943 # exchanged. Instead, the plain password and user name are "encrypted" with
1944 # a server site salt value. Actually, all are concatenated and hashed
1945 # with a one-way secure hash function (SHA256) into a single string.
1946 # Whenever the word "password" is used, this hash sum is meant. Note that
1947 # the salts are generated from /dev/urandom. You should check whether the
1948 # implementation of /dev/urandom on your platform is secure before
1949 # relying on it. This might be a problem when running CGIscriptor under
1950 # Cygwin on MS Windows.
1951 # Note: no attempt is made to slow down the password hash, so bad
1952 # passwords can be cracked by brute force
1954 # As the (hashed) passwords are all that is needed to identify at the site,
1955 # these should not be stored in this form. A site specific passphrase
1956 # can be entered as an environment variable ($ENV{'CGIMasterKey'}). This
1957 # phrase is hashed with the server site salt and the result is hashed with
1958 # the user name and then XORed with the password when it is stored. Also, to
1959 # detect changes to the account (PASSWORD) and session tickets, a
1960 # (HMAC) hash of some of the contents of the ticket with the server salt and
1961 # CGIMasterKey is stored in each ticket.
1963 # Creating a valid (hashed) password, encrypt it with the CGIMasterKey and
1964 # construct a signature of the ticket are non-trivial. This has to be redone
1965 # with every change of the ticket file or CGIMasterKey change. CGIscriptor
1966 # can do this from the command line with the command:
1968 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
1969 # masterkey='Sherlock investigates oleander curry in Bath' \
1970 # password='There is no password like more password' \
1971 # admin
1973 # CGIscriptor will exit after this command with the first option being
1974 # --managelogin. Options have the form:
1976 # salt=[file or string]
1977 # Server salt value to use io the value
1978 # stored in the ticket file. Will replace the stored value if a new
1979 # password is given. If you change the server salt, you have to
1980 # reset all the passwords. There is absolutely no procedure known
1981 # to recover plaintext passwords, except asking the account holders.
1982 # You are strongly adviced to make a backup before you apply such a change
1983 # masterkey=[file or string]
1984 # CGIMasterKey used to read and decrypt the ticket
1985 # newmasterkey=[file or string]
1986 # CGIMasterKey used to encrypt, sign,
1987 # and write the ticket. Defaults to the masterkey. If you change
1988 # the masterkey, you will have to reset all the accounts. You are strongly
1989 # adviced to make a backup before you apply such a change
1990 # password=[file or string]
1991 # New plaintext password
1993 # When the value of an option is a existing file path, the first line of
1994 # that file is used. Options are followed by one or more paths plus names
1995 # of existing ticket files. Each password option is only used for a single
1996 # ticket file. It is most definitely a bad idea to use a password that is
1997 # identical to an existing filepath, as the file will be read instead. Be
1998 # aware that the name of the file should be a cleaned up version of the
1999 # Username. This will not be checked.
2001 # For the authentication and a change of password, the (old) password
2002 # is used to "encrypt" a random one-time token or the new password,
2003 # respectively. For authentication, decryption is not needed, so a secure
2004 # hash function (SHA256) is used to create a one-way hash sum "encryption".
2005 # A new password must be decrypted. New passwords are encryped by XORing
2006 # them with the old password.
2008 # Strong Passwords: It is so easy
2009 # If you only could see what you are typing
2011 # Your password might be vulnerable to brute force guessing
2012 # (https://en.wikipedia.org/wiki/Brute_force_attack).
2013 # Protections against such attacks are costly in terms of code
2014 # complexity, bugs, and execution time. However, there is a very
2015 # simple and secure counter measure. See the XKCD comic
2016 # (http://xkcd.com/936/). The phrase, "There is no password like more
2017 # password" would be both much easier to remember, and still stronger
2018 # than "h4]D%@m:49", at least before this phrase was pasted as an
2019 # example on the Internet.
2021 # For the procedures used at this site, a basic computer setup can
2022 # check in the order of a billion passwords per second. You need a
2023 # password (or phrase) strength in the order of 56 bits to be a
2024 # little secure (one year on a single computer). Please be so kind
2025 # and add the name of your favorite flower, dish, fictional
2026 # character, or small town to your password. Say, Oleander, Curry,
2027 # Sherlock, or Bath, UK (each adds ~12 bits) or even the phrase "Sherlock
2028 # investigates oleander curry in Bath" (adds > 56 bits, note that
2029 # oleander is poisonous, so do not try this curry at home). That
2030 # would be more effective than adding a thousand rounds of encryption.
2031 # Typing long passwords without seeing what you are typing is
2032 # problematic. So a button should be included to make password
2033 # visible.
2036 # Technical matters
2038 # Client side JavaScript code definitions. Variable names starting with '$'
2039 # are CGIscriptor CGI variables. Some of the hashes could be strengthened
2040 # by switching to HMAC signatures. However, the security issues of
2041 # maintaining parallel functions for HMAC in both Perl and Javascript seem
2042 # to be more serious than the attack vectors against the hashes. But HMAC
2043 # is indeed used for the ticket signatures.
2045 # // On Login
2046 # HashPlaintextPassword() {
2047 # var plaintextpassword = document.getElementById('PASSWORD');
2048 # var serversalt = document.getElementById('SERVERSALT');
2049 # var username = document.getElementById('CGIUSERNAME');
2050 # return hex_sha256(plaintextpassword.value+username.value.toLowerCase()+serversalt.value);
2052 # var randomsalt = $RANDOMSALT; // From CGIscriptor
2053 # var loginticket = $LOGINTICKET; // From CGIscriptor
2054 # // Hash plaintext password
2055 # var password = HashPlaintextPassword();
2056 # // Authorize login
2057 # var hashedpassword = hex_sha256(randomsalt+password);
2058 # // Sessionticket
2059 # var sessionticket = hex_sha256(loginticket+password);
2060 # sessionStorage.setItem("CGIscriptorPRIVATE", sessionticket);
2061 # // Secretkey for encrypting new passwords, acts like a one-time pad
2062 # // Is set anew with every login, ie, also whith password changes
2063 # // and for each create new user request
2064 # var secretkey = hex_sha256(password+loginticket+randomsalt);
2065 # sessionStorage.setItem("CGIscriptorSECRET", secretkey);
2067 # // For a SESSION type request
2068 # sessionticket = sessionStorage.getItem("CGIscriptorPRIVATE");
2069 # createCookie("CGIscriptorSESSION",sessionticket, 0, "");
2071 // For a CHALLENGE type request
2072 # var sessionset = "$CHALLENGETICKET"; // From CGIscriptor
2073 # var sessionkey = sessionStorage.getItem("CGIscriptorPRIVATE");
2074 # sessionticket = hex_sha256(sessionset+sessionkey);
2075 # createCookie("CGIscriptorCHALLENGE",sessionticket, 0, "");
2077 # // For transmitting a new password
2078 # HashPlaintextNewPassword() {
2079 # var plaintextpassword = document.getElementById('NEWPASSWORD');
2080 # var serversalt = document.getElementById('SERVERSALT');
2081 # var username = document.getElementById('NEWUSERNAME');
2082 # return hex_sha256(plaintextpassword.value+username.value.toLowerCase()+serversalt.value);
2085 # var newpassword = document.getElementById('NEWPASSWORD');
2086 # var newpasswordrep = document.getElementById('NEWPASSWORDREP');
2087 # // Hash plaintext password
2088 # newpassword.value = HashPlaintextNewPassword();
2089 # var secretkey = sessionStorage.getItem("CGIscriptorSECRET");
2091 # var encrypted = XOR_hex_strings(secretkey, newpassword.value);
2092 # newpassword.value = encrypted;
2093 # newpasswordrep.value = encrypted;
2095 # // XOR of hexadecimal strings of equal length
2096 # function XOR_hex_strings(hex1, hex2) {
2097 # var resultHex = "";
2098 # var maxlength = Math.max(hex1.length, hex2.length);
2100 # for(var i=0; i &lt; maxlength; ++i) {
2101 # var h1 = hex1.charAt(i);
2102 # if(! h1) h1='0';
2103 # var h2 = hex2.charAt(i);
2104 # if(! h2) h2 ='0';
2105 # var d1 = parseInt(h1,16);
2106 # var d2 = parseInt(h2,16);
2107 # var resultD = d1^d2;
2108 # resultHex = resultHex+resultD.toString(16);
2109 # };
2110 # return resultHex;
2111 # };
2113 # Password encryption based on $ENV{'CGIMasterKey'}.
2114 # Server side Perl code:
2116 # # Password encryption
2117 # my $masterkey = $ENV{'CGIMasterKey'}
2118 # my $hash1 = hash_string($masterkey.$serversalt);
2119 # my $CryptKey = hash_string($username.$hash1);
2120 # $password = XOR_hex_strings($CryptKey,$password);
2122 # # Key for HMAC signing
2123 # my $hash1 = hash_string($masterkey.$serversalt);
2124 # my $HMACKey = hash_string($username.$hash1);
2128 # USER EXTENSIONS
2130 # A CGIscriptor package is attached to the bottom of this file. With
2131 # this package you can personalize your version of CGIscriptor by
2132 # including often used perl routines. These subroutines can be
2133 # accessed by prefixing their names with CGIscriptor::, e.g.,
2134 # <SCRIPT LANGUAGE=PERL TYPE=text/ssperl>
2135 # CGIscriptor::ListDocs("/Books/*") # List all documents in /Books
2136 # </SCRIPT>
2137 # It already contains some useful subroutines for Document Management.
2138 # As it is a separate package, it has its own namespace, isolated from
2139 # both the evaluator and the main program. To access variables from
2140 # the document <SCRIPT></SCRIPT> blocks, use $CGIexecute::<var>.
2142 # Currently, the following functions are implemented
2143 # (precede them with CGIscriptor::, see below for more information)
2144 # - SAFEqx ('String') -> result of qx/"String"/ # Safe application of ``-quotes
2145 # Is used by text/osshell Shell scripts. Protects all CGI
2146 # (client-supplied) values with single quotes before executing the
2147 # commands (one of the few functions that also works WITHOUT CGIscriptor::
2148 # in front)
2149 # - defineCGIvariable ($name[, $default) -> 0/1 (i.e., failure/success)
2150 # Is used by the META tag to define and initialize CGI and ENV
2151 # name/value pairs. Tries to obtain an initializing value from (in order):
2152 # $ENV{$name}
2153 # The Query string
2154 # The default value given (if any)
2155 # (one of the few functions that also works WITHOUT CGIscriptor::
2156 # in front)
2157 # - CGIsafeFileName (FileName) -> FileName or ""
2158 # Check a string against the Allowed File Characters (and ../ /..).
2159 # Returns an empty string for unsafe filenames.
2160 # - CGIsafeEmailAddress (Email) -> Email or ""
2161 # Check a string against correct email address pattern.
2162 # Returns an empty string for unsafe addresses.
2163 # - RedirectShellScript ('CommandString') -> FILEHANDLER or undef
2164 # Open a named PIPE for SAFEqx to receive ALL shell scripts
2165 # - URLdecode (URL encoded string) -> plain string # Decode URL encoded argument
2166 # - URLencode (plain string) -> URL encoded string # Encode argument as URL code
2167 # - CGIparseValue (ValueName [, URL_encoded_QueryString]) -> Decoded value
2168 # Extract the value of a CGI variable from the global or a private
2169 # URL-encoded query (multipart POST raw, NOT decoded)
2170 # - CGIparseValueList (ValueName [, URL_encoded_QueryString])
2171 # -> List of decoded values
2172 # As CGIparseValue, but now assembles ALL values of ValueName into a list.
2173 # - CGIparseHeader (ValueName [, URL_encoded_QueryString]) -> Header
2174 # Extract the header of a multipart CGI variable from the global or a private
2175 # URL-encoded query ("" when not a multipart variable or absent)
2176 # - CGIparseForm ([URL_encoded_QueryString]) -> Decoded Form
2177 # Decode the complete global URL-encoded query or a private
2178 # URL-encoded query
2179 # - read_url(URL) # Returns the page from URL (with added base tag, both FTP and HTTP)
2180 # Uses main::GET_URL(URL, 1) to get at the command to read the URL.
2181 # - BrowseDirs(RootDirectory [, Pattern, Startdir, CGIname]) # print browsable directories
2182 # - ListDocs(Pattern [,ListType]) # Prints a nested HTML directory listing of
2183 # all documents, e.g., ListDocs("/*", "dl");.
2184 # - HTMLdocTree(Pattern [,ListType]) # Prints a nested HTML listing of all
2185 # local links starting from a given document, e.g.,
2186 # HTMLdocTree("/Welcome.html", "dl");
2189 # THE RESULTS STACK: @CGISCRIPTORRESULTS
2191 # If the pseudo-variable "$CGIscriptorResults" has been defined in a
2192 # META tag, all subsequent SCRIPT and META results are pushed
2193 # on the @CGIscriptorResults stack. This list is just another
2194 # Perl variable and can be used and manipulated like any other list.
2195 # $CGIscriptorResults[-1] is always the last result.
2196 # This is only of limited use, e.g., to use the results of an OS shell
2197 # script inside a Perl script. Will NOT contain the results of Pipes
2198 # or code from MIME-profiling.
2201 # USEFULL CGI PREDEFINED VARIABLES (DO NOT ASSIGN TO THESE)
2203 # $CGI_HOME - The DocumentRoot directory
2204 # $CGI_Decoded_QS - The complete decoded Query String
2205 # $CGI_Content_Length - The ACTUAL length of the Query String
2206 # $CGI_Date - Current date and time
2207 # $CGI_Year $CGI_Month $CGI_Day $CGI_WeekDay - Current Date
2208 # $CGI_Time - Current Time
2209 # $CGI_Hour $CGI_Minutes $CGI_Seconds - Current Time, split
2210 # GMT Date/Time:
2211 # $CGI_GMTYear $CGI_GMTMonth $CGI_GMTDay $CGI_GMTWeekDay $CGI_GMTYearDay
2212 # $CGI_GMTHour $CGI_GMTMinutes $CGI_GMTSeconds $CGI_GMTisdst
2215 # USEFULL CGI ENVIRONMENT VARIABLES
2217 # Variables accessible (in APACHE) as $ENV{<name>}
2218 # (see: "http://hoohoo.ncsa.uiuc.edu/cgi/env.html"):
2220 # QUERY_STRING - The query part of URL, that is, everything that follows the
2221 # question mark.
2222 # PATH_INFO - Extra path information given after the script name
2223 # PATH_TRANSLATED - Extra pathinfo translated through the rule system.
2224 # (This doesn't always make sense.)
2225 # REMOTE_USER - If the server supports user authentication, and the script is
2226 # protected, this is the username they have authenticated as.
2227 # REMOTE_HOST - The hostname making the request. If the server does not have
2228 # this information, it should set REMOTE_ADDR and leave this unset
2229 # REMOTE_ADDR - The IP address of the remote host making the request.
2230 # REMOTE_IDENT - If the HTTP server supports RFC 931 identification, then this
2231 # variable will be set to the remote user name retrieved from
2232 # the server. Usage of this variable should be limited to logging
2233 # only.
2234 # AUTH_TYPE - If the server supports user authentication, and the script
2235 # is protected, this is the protocol-specific authentication
2236 # method used to validate the user.
2237 # CONTENT_TYPE - For queries which have attached information, such as HTTP
2238 # POST and PUT, this is the content type of the data.
2239 # CONTENT_LENGTH - The length of the said content as given by the client.
2240 # SERVER_SOFTWARE - The name and version of the information server software
2241 # answering the request (and running the gateway).
2242 # Format: name/version
2243 # SERVER_NAME - The server's hostname, DNS alias, or IP address as it
2244 # would appear in self-referencing URLs
2245 # GATEWAY_INTERFACE - The revision of the CGI specification to which this
2246 # server complies. Format: CGI/revision
2247 # SERVER_PROTOCOL - The name and revision of the information protocol this
2248 # request came in with. Format: protocol/revision
2249 # SERVER_PORT - The port number to which the request was sent.
2250 # REQUEST_METHOD - The method with which the request was made. For HTTP,
2251 # this is "GET", "HEAD", "POST", etc.
2252 # SCRIPT_NAME - A virtual path to the script being executed, used for
2253 # self-referencing URLs.
2254 # HTTP_ACCEPT - The MIME types which the client will accept, as given by
2255 # HTTP headers. Other protocols may need to get this
2256 # information from elsewhere. Each item in this list should
2257 # be separated by commas as per the HTTP spec.
2258 # Format: type/subtype, type/subtype
2259 # HTTP_USER_AGENT - The browser the client is using to send the request.
2260 # General format: software/version library/version.
2263 # INSTRUCTIONS FOR RUNNING CGIscriptor ON UNIX
2265 # CGIscriptor.pl will run on any WWW server that runs Perl scripts, just add
2266 # a line like the following to your srm.conf file (Apache example):
2268 # ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
2270 # URL's that refer to http://www.your.address/SHTML/... will now be handled
2271 # by CGIscriptor.pl, which can use a private directory tree (default is the
2272 # DOCUMENT_ROOT directory tree, but it can be anywhere, see manual).
2274 # If your hosting ISP won't let you add ScriptAlias lines you can use
2275 # the following "rewrite"-based "scriptalias" in .htaccess
2276 # (from Gerd Franke)
2278 # RewriteEngine On
2279 # RewriteBase /
2280 # RewriteCond %{REQUEST_FILENAME} .html$
2281 # RewriteCond %{SCRIPT_FILENAME} !cgiscriptor.pl$
2282 # RewriteCond %{REQUEST_FILENAME} -f
2283 # RewriteRule ^(.*)$ /cgi-bin/cgiscriptor.pl/$1?&%{QUERY_STRING}
2285 # Everthing with the extension ".html" and not including "cgiscriptor.pl"
2286 # in the url and where the file "path/filename.html" exists is redirected
2287 # to "/cgi.bin/cgiscriptor.pl/path/filename.html?query".
2288 # The user configuration should get the same path-level as the
2289 # .htaccess-file:
2291 # # Just enter your own directory path here
2292 # $YOUR_HTML_FILES = "$ENV{'DOCUMENT_ROOT'}";
2293 # # use DOCUMENT_ROOT only, if .htaccess lies in the root-directory.
2295 # If this .htaccess goes in a specific directory, the path to this
2296 # directory must be added to $ENV{'DOCUMENT_ROOT'}.
2298 # The CGIscriptor file contains all documentation as comments. These
2299 # comments can be removed to speed up loading (e.g., `egrep -v '^#'
2300 # CGIscriptor.pl` > leanScriptor.pl). A bare bones version of
2301 # CGIscriptor.pl, lacking documentation, most comments, access control,
2302 # example functions etc. (but still with the copyright notice and some
2303 # minimal documentation) can be obtained by calling CGIscriptor.pl on the
2304 # command line with the '-slim' command line argument, e.g.,
2306 # >CGIscriptor.pl -slim > slimCGIscriptor.pl
2308 # CGIscriptor.pl can be run from the command line with <path> and <query> as
2309 # arguments, as `CGIscriptor.pl <path> <query>`, inside a perl script
2310 # with 'do CGIscriptor.pl' after setting $ENV{PATH_INFO}
2311 # and $ENV{QUERY_STRING}, or CGIscriptor.pl can be loaded with 'require
2312 # "/real-path/CGIscriptor.pl"'. In the latter case, requests are processed
2313 # by 'Handle_Request();' (again after setting $ENV{PATH_INFO} and
2314 # $ENV{QUERY_STRING}).
2316 # Using the command line execution option, CGIscriptor.pl can be used as a
2317 # document (meta-)preprocessor. If the first argument is '-', STDIN will be read.
2318 # For example:
2320 # > cat MyDynamicDocument.html | CGIscriptor.pl - '[QueryString]' > MyStaticFile.html
2322 # This command line will produce a STATIC file with the DYNAMIC content of
2323 # MyDocument.html "interpolated".
2325 # This option would be very dangerous when available over the internet.
2326 # If someone could sneak a 'http://www.your.domain/-' URL past your
2327 # server, CGIscriptor could EXECUTE any POSTED contend.
2328 # Therefore, for security reasons, STDIN will NOT be read
2329 # if ANY of the HTTP server environment variables is set (e.g.,
2330 # SERVER_PORT, SERVER_PROTOCOL, SERVER_NAME, SERVER_SOFTWARE,
2331 # HTTP_USER_AGENT, REMOTE_ADDR).
2332 # This block on processing STDIN on HTTP requests can be lifted by setting
2333 # $BLOCK_STDIN_HTTP_REQUEST = 0;
2334 # In the security configuration. Butbe carefull when doing this.
2335 # It can be very dangerous.
2337 # Running demo's and more information can be found at
2338 # http://www.fon.hum.uva.nl/~rob/OSS/OSS.html
2340 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site or
2341 # CPAN that can use CGIscriptor.pl as the base of a µWWW server and
2342 # demonstrates its use.
2345 # PROCESSING NON-FILESYSTEM DATA
2347 # Normally, HTTP (WWW) requests map onto file that can be accessed
2348 # using the perl open() function. That is, the web server runs on top of
2349 # some directory structure. However, we can envission (and put to good
2350 # use) other systems that do not use a normal file system. The whole CGI
2351 # was developed to make dynamic document generation possible.
2353 # A special case is where we want to have it both: A normal web server
2354 # with normal "file data", but not a normal files system. For instance,
2355 # we want or normal Web Site to run directly from a RAM hash table or
2356 # other database, instead of from disk. But we do NOT want to code the
2357 # whole site structure in CGI.
2359 # CGIscriptor can do this. If the web server fills an environment variable
2360 # $ENV{'CGI_FILE_CONTENT'} with the content of the "file", then the content
2361 # of this variable is processed instead of opening a file. If this environment
2362 # variable has the value '-', the content of another environment variable,
2363 # $ENV{'CGI_DATA_ACCESS_CODE'} is executed as:
2364 # eval("\@_ = ($file_path); do {$ENV{'CGI_DATA_ACCESS_CODE'}};")
2365 # and the result is processed as if it was the content of the requested
2366 # file.
2367 # (actually, the names of the environment variables are user configurable,
2368 # they are stored in the local variables $CGI_FILE_CONTENT and
2369 # $CGI_DATA_ACCESS_CODE)
2371 # When using this mechanism, the SRC attribute mechanism will only partially work.
2372 # Only the "recursive" calls to CGIscriptor (the ProcessFile() function)
2373 # will work, the automagical execution of SRC files won't. (In this case,
2374 # the SRC attribute won't work either for other scripting languages)
2377 # NON-UNIX PLATFORMS
2379 # CGIscriptor.pl was mainly developed and tested on UNIX. However, as I
2380 # coded part of the time on an Apple Macintosh under MacPerl, I made sure
2381 # CGIscriptor did run under MacPerl (with command line options). But only
2382 # as an independend script, not as part of a HTTP server. I have used it
2383 # under Apache in Windows XP.
2385 ENDOFHELPTEXT
2386 exit;
2388 ###############################################################################
2390 # SECURITY CONFIGURATION
2392 # Special configurations related to SECURITY
2393 # (i.e., optional, see also environment variables below)
2395 # LOGGING
2396 # Log Clients and the requested paths (Redundant when loging Queries)
2398 $ClientLog = "./Client.log"; # (uncomment for use)
2400 # Format: Localtime | REMOTE_USER REMOTE_IDENT REMOTE_HOST REMOTE_ADDRESS \
2401 # PATH_INFO CONTENT_LENGTH (actually, the real query+post length)
2403 # Log Clients and the queries, the CGIQUERYDECODE is required if you want
2404 # to log queries. If you log Queries, the loging of Clients is redundant
2405 # (note that queries can be quite long, so this might not be a good idea)
2407 #$QueryLog = "./Query.log"; # (uncomment for use)
2409 # ACCESS CONTROL
2410 # the Access files should contain Hostnames or IP addresses,
2411 # i.e. REMOTE_HOST or REMOTE_ADDR, each on a separate line
2412 # optionally followed by one ore more file patterns, e.g., "edu /DEMO".
2413 # Matching is done "domain first". For example ".edu" matches all
2414 # clients whose "name" ends in ".edu" or ".EDU". The file pattern
2415 # "/DEMO" matches all paths that contain the strings "/DEMO" or "/demo"
2416 # (both matchings are done case-insensitive).
2417 # The name special symbol "-" matches ALL clients who do not supply a
2418 # REMOTE_HOST name, "*" matches all clients.
2419 # Lines starting with '-e' are evaluated. A non-zero return value indicates
2420 # a match. You can use $REMOTE_HOST, $REMOTE_ADDR, and $PATH_INFO. These
2421 # lines are evaluated in the program's own name-space. So DO NOT assign to
2422 # variables.
2424 # Accept the following users (remove comment # and adapt filename)
2425 $CGI_Accept = -s "$YOUR_SCRIPTS/ACCEPT.lis" ? "$YOUR_SCRIPTS/ACCEPT.lis" : ''; # (uncomment for use)
2427 # Reject requests from the following users (remove comment # and
2428 # adapt filename, this is only of limited use)
2429 $CGI_Reject = -s "$YOUR_SCRIPTS/REJECT.lis" ? "$YOUR_SCRIPTS/REJECT.lis" : ''; # (uncomment for use)
2431 # Empty lines or comment lines starting with '#' are ignored in both
2432 # $CGI_Accept and $CGI_Reject.
2434 # Block STDIN (i.e., '-') requests when servicing an HTTP request
2435 # Comment this out if you realy want to use STDIN in an on-line web server
2436 $BLOCK_STDIN_HTTP_REQUEST = 1;
2439 # End of security configuration
2441 ##################################################<<<<<<<<<<End Remove
2443 # PARSING CGI VALUES FROM THE QUERY STRING (USER CONFIGURABLE)
2445 # The CGI parse commands. These commands extract the values of the
2446 # CGI variables from the URL encoded Query String.
2447 # If you want to use your own CGI decoders, you can call them here
2448 # instead, using your own PATH and commenting/uncommenting the
2449 # appropriate lines
2451 # CGI parse command for individual values
2452 # (if $List > 0, returns a list value, if $List < 0, a hash table, this is optional)
2453 sub YOUR_CGIPARSE # ($Name [, $List]) -> Decoded value
2455 my $Name = shift;
2456 my $List = shift || 0;
2457 # Use one of the following by uncommenting
2458 if(!$List) # Simple value
2460 return CGIscriptor::CGIparseValue($Name) ;
2462 elsif($List < 0) # Hash tables
2464 return CGIscriptor::CGIparseValueHash($Name); # Defined in CGIscriptor below
2466 else # Lists
2468 return CGIscriptor::CGIparseValueList($Name); # Defined in CGIscriptor below
2471 # return `/PATH/cgiparse -value $Name`; # Shell commands
2472 # require "/PATH/cgiparse.pl"; return cgivalue($Name); # Library
2474 # Complete queries
2475 sub YOUR_CGIQUERYDECODE
2477 # Use one of the following by uncommenting
2478 return CGIscriptor::CGIparseForm(); # Defined in CGIscriptor below
2479 # return `/PATH/cgiparse -form`; # Shell commands
2480 # require "/PATH/cgiparse.pl"; return cgiform(); # Library
2483 # End of configuration
2485 #######################################################################
2487 # Translating input files.
2488 # Allows general and global conversions of files using Regular Expressions
2489 # Translations are applied in the order of definition.
2491 # Define:
2492 # my $TranslationPaths = 'pattern'; # Pattern matching PATH_INFO
2494 # push(@TranslationTable, ['pattern', 'replacement']);
2495 # e.g. (for Ruby Rails):
2496 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2497 # push(@TranslationTable, ['%>', '</SCRIPT>']);
2499 # Runs:
2500 # my $currentRegExp;
2501 # foreach $currentRegExp (keys(%TranslationTable))
2503 # my $currentRegExp;
2504 # foreach $currentRegExp (@TranslationTable)
2506 # my ($pattern, $replacement) = @$currentRegExp;
2507 # $$text =~ s!$pattern!$replacement!msg;
2508 # };
2509 # };
2511 # Configuration section
2513 #######################################################################
2515 # The file paths on which to apply the translation
2516 my $TranslationPaths = ''; # NO files
2517 #$TranslationPaths = '.'; # ANY file
2518 # $TranslationPaths = '\.html'; # HTML files
2520 my @TranslationTable = ();
2521 # Some legacy code
2522 push(@TranslationTable, ['\<\s*CGI\s+([^\>])*\>', '\<SCRIPT TYPE=\"text/ssperl\"\>$1\<\/SCRIPT>']);
2523 # Ruby Rails?
2524 push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2525 push(@TranslationTable, ['%>', '</SCRIPT>']);
2527 sub performTranslation # (\$text)
2529 my $text = shift || return;
2530 if(@TranslationTable && $TranslationPaths && $ENV{'PATH_INFO'} =~ m!$TranslationPaths!)
2532 my $currentRegExp;
2533 foreach $currentRegExp (@TranslationTable)
2535 my ($pattern, $replacement) = @$currentRegExp;
2536 $$text =~ s!$pattern!$replacement!msg;
2541 #######################################################################
2543 # Seamless access to other (Scripting) Languages
2544 # TYPE='text/ss<interpreter>'
2546 # Configuration section
2548 #######################################################################
2550 # OTHER SCRIPTING LANGUAGES AT THE SERVER SIDE (MIME => OScommand)
2551 # Yes, it realy is this simple! (unbelievable, isn't it)
2552 # NOTE: Some interpreters require some filtering to obtain "clean" output
2554 %ScriptingLanguages = (
2555 "text/testperl" => 'perl', # Perl for testing
2556 "text/sspython" => 'python', # Python
2557 "text/ssruby" => 'ruby', # Ruby
2558 "text/sstcl" => 'tcl', # TCL
2559 "text/ssawk" => 'awk -f-', # Awk
2560 "text/sslisp" => # lisp (rep, GNU)
2561 'rep | tail +4 '."| egrep -v '> |^rep. |^nil\\\$'",
2562 "text/xlispstat" => # xlispstat
2563 'xlispstat | tail +7 ' ."| egrep -v '> \\\$|^NIL'",
2564 "text/ssprolog" => # Prolog (GNU)
2565 "gprolog | tail +4 | sed 's/^| ?- //'",
2566 "text/ssm4" => 'm4', # M4 macro's
2567 "text/sh" => 'sh', # Born shell
2568 "text/bash" => 'bash', # Born again shell
2569 "text/csh" => 'csh', # C shell
2570 "text/ksh" => 'ksh', # Korn shell
2571 "text/sspraat" => # Praat (sound/speech analysis)
2572 "praat - | sed 's/Praat > //g'",
2573 "text/ssr" => # R
2574 "R --vanilla --slave | sed 's/^[\[0-9\]*] //'",
2575 "text/ssrebol" => # REBOL
2576 "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\\s*\[> \]* //'",
2577 "text/postgresql" => 'psql 2>/dev/null',
2579 # Not real scripting, but the use of other applications
2580 "text/ssmailto" => "awk 'NF||F{F=1;print \\\$0;}'|mailto >/dev/null", # Send mail from server
2581 "text/ssdisplay" => 'cat', # Display, (interpolation)
2582 "text/sslogfile" => # Log to file, (interpolation)
2583 "awk 'NF||L {if(!L){L=tolower(\\\$1)~/^file:\\\$/ ? \\\$2 : \\\$1;}else{print \\\$0 >> L;};}'",
2585 "" => ""
2588 # To be able to access the CGI variables in your script, they
2589 # should be passed to the scripting language in a readable form
2590 # Here you can enter how they should be printed (the first %s
2591 # is replaced by the NAME of the CGI variable as it apears in the
2592 # META tag, the second by its VALUE).
2593 # For Perl this would be:
2594 # "text/testperl" => '$%s = "%s";',
2595 # which would be executed as
2596 # printf('$%s = "%s";', $CGI_NAME, $CGI_VALUE);
2598 # If the hash table value doesn't exist, nothing is done
2599 # (you have to parse the Environment variables yourself).
2600 # If it DOES exist but is empty (e.g., "text/sspraat" => '',)
2601 # Perl string interpolation of variables (i.e., $var, @array,
2602 # %hash) is performed. This means that $@%\ must be protected
2603 # with a \.
2605 %ScriptingCGIvariables = (
2606 "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value'; (for testing)
2607 "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
2608 "text/ssruby" => '@%s = "%s"', # Ruby @VAR = 'value'
2609 "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
2610 "text/ssawk" => '%s = "%s";', # Awk VAR = 'value';
2611 "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
2612 "text/xlispstat" => '(setq %s "%s")', # xlispstat (setq VAR "value")
2613 "text/ssprolog" => '', # Gnu prolog (interpolated)
2614 "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
2615 "text/sh" => "\%s='\%s'", # Born shell VAR='value'
2616 "text/bash" => "\%s='\%s'", # Born again shell VAR='value'
2617 "text/csh" => "\$\%s='\%s';", # C shell $VAR = 'value';
2618 "text/ksh" => "\$\%s='\%s';", # Korn shell $VAR = 'value';
2620 "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
2621 "text/sspraat" => '', # Praat (interpolation)
2622 "text/ssr" => '%s <- "%s";', # R VAR <- "value";
2623 "text/postgresql" => '', # PostgreSQL (interpolation)
2625 # Not real scripting, but the use of other applications
2626 "text/ssmailto" => '', # MAILTO, (interpolation)
2627 "text/ssdisplay" => '', # Display, (interpolation)
2628 "text/sslogfile" => '', # Log to file, (interpolation)
2630 "" => ""
2633 # If you want something added in front or at the back of each script
2634 # block as send to the interpreter add it here.
2635 # mime => "string", e.g., "text/sspython" => "python commands"
2636 %ScriptingPrefix = (
2637 "text/testperl" => "\# Prefix Code;", # Perl script testing
2638 "text/ssm4" => 'divert(0)', # M4 macro's (open STDOUT)
2640 "" => ""
2642 # If you want something added at the end of each script block
2643 %ScriptingPostfix = (
2644 "text/testperl" => "\# Postfix Code;", # Perl script testing
2645 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2647 "" => ""
2649 # If you need initialization code, directly after opening
2650 %ScriptingInitialization = (
2651 "text/testperl" => "\# Initialization Code;", # Perl script testing
2652 "text/ssawk" => 'BEGIN {', # Server Side awk scripts (VAR = "value")
2653 "text/sslisp" => '(prog1 nil ', # Lisp (rep)
2654 "text/xlispstat" => '(prog1 nil ', # xlispstat
2655 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2657 "" => ""
2659 # If you need cleanup code before closing
2660 %ScriptingCleanup = (
2661 "text/testperl" => "\# Cleanup Code;", # Perl script testing
2662 "text/sspraat" => 'Quit',
2663 "text/ssawk" => '};', # Server Side awk scripts (VAR = "value")
2664 "text/sslisp" => '(princ "\n" standard-output)).', # Closing print to rep
2665 "text/xlispstat" => '(print ""))', # Closing print to xlispstat
2666 "text/postgresql" => '\q', # quit psql
2667 "text/ssdisplay" => "", # close cat
2669 "" => ""
2672 # End of configuration for foreign scripting languages
2674 ###############################################################################
2676 # Initialization Code
2679 sub Initialize_Request
2681 ###############################################################################
2683 # ENVIRONMENT VARIABLES
2685 # Use environment variables to configure CGIscriptor on a temporary basis.
2686 # If you define any of the configurable variables as environment variables,
2687 # these are used instead of the "hard coded" values above.
2689 $SS_PUB = $ENV{'SS_PUB'} || $YOUR_HTML_FILES;
2690 $SS_SCRIPT = $ENV{'SS_SCRIPT'} || $YOUR_SCRIPTS;
2693 # Substitution strings, these are used internally to handle the
2694 # directory separator strings, e.g., '~/' -> 'SS_PUB:' (Mac)
2695 $HOME_SUB = $SS_PUB;
2696 $SCRIPT_SUB = $SS_SCRIPT;
2699 # Make sure all script are reliably loaded
2700 push(@INC, $SS_SCRIPT);
2703 # Add the directory separator to the "home" directories.
2704 # (This is required for ~/ and ./ substitution)
2705 $HOME_SUB .= '/' if $HOME_SUB;
2706 $SCRIPT_SUB .= '/' if $SCRIPT_SUB;
2708 $CGI_HOME = $ENV{'DOCUMENT_ROOT'};
2709 $ENV{'PATH_TRANSLATED'} =~ /$ENV{'PATH_INFO'}/is;
2710 $CGI_HOME = $` unless $ENV{'DOCUMENT_ROOT'}; # Get the DOCUMENT_ROOT directory
2711 $default_values{'CGI_HOME'} = $CGI_HOME;
2712 $ENV{'HOME'} = $CGI_HOME;
2713 # Set SS_PUB and SS_SCRIPT as Environment variables (make them available
2714 # to the scripts)
2715 $ENV{'SS_PUB'} = $SS_PUB unless $ENV{'SS_PUB'};
2716 $ENV{'SS_SCRIPT'} = $SS_SCRIPT unless $ENV{'SS_SCRIPT'};
2718 $FilePattern = $ENV{'FilePattern'} || $FilePattern;
2719 $MaximumQuerySize = $ENV{'MaximumQuerySize'} || $MaximumQuerySize;
2720 $ClientLog = $ENV{'ClientLog'} || $ClientLog;
2721 $QueryLog = $ENV{'QueryLog'} || $QueryLog;
2722 $CGI_Accept = $ENV{'CGI_Accept'} || $CGI_Accept;
2723 $CGI_Reject = $ENV{'CGI_Reject'} || $CGI_Reject;
2725 # Parse file names
2726 $CGI_Accept =~ s@^\~/@$HOME_SUB@g if $CGI_Accept;
2727 $CGI_Reject =~ s@^\~/@$HOME_SUB@g if $CGI_Reject;
2728 $ClientLog =~ s@^\~/@$HOME_SUB@g if $ClientLog;
2729 $QueryLog =~ s@^\~/@$HOME_SUB@g if $QueryLog;
2731 $CGI_Accept =~ s@^\./@$SCRIPT_SUB@g if $CGI_Accept;
2732 $CGI_Reject =~ s@^\./@$SCRIPT_SUB@g if $CGI_Reject;
2733 $ClientLog =~ s@^\./@$SCRIPT_SUB@g if $ClientLog;
2734 $QueryLog =~ s@^\./@$SCRIPT_SUB@g if $QueryLog;
2736 @CGIscriptorResults = (); # A stack of results
2738 # end of Environment variables
2740 #############################################################################
2742 # Define and Store "standard" values
2744 # BEFORE doing ANYTHING check the size of Query String
2745 length($ENV{'QUERY_STRING'}) <= $MaximumQuerySize || dieHandler(2, "QUERY TOO LONG\n");
2747 # The Translated Query String and the Actual length of the (decoded)
2748 # Query String
2749 if($ENV{'QUERY_STRING'})
2751 # If this can contain '`"-quotes, be carefull to use it QUOTED
2752 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2753 $default_values{CGI_Content_Length} = length($default_values{CGI_Decoded_QS});
2756 # Get the current Date and time and store them as default variables
2758 # Get Local Time
2759 $LocalTime = localtime;
2761 # CGI_Year CGI_Month CGI_Day CGI_WeekDay CGI_Time
2762 # CGI_Hour CGI_Minutes CGI_Seconds
2764 $default_values{CGI_Date} = $LocalTime;
2765 ($default_values{CGI_WeekDay},
2766 $default_values{CGI_Month},
2767 $default_values{CGI_Day},
2768 $default_values{CGI_Time},
2769 $default_values{CGI_Year}) = split(' ', $LocalTime);
2770 ($default_values{CGI_Hour},
2771 $default_values{CGI_Minutes},
2772 $default_values{CGI_Seconds}) = split(':', $default_values{CGI_Time});
2774 # GMT:
2775 # CGI_GMTYear CGI_GMTMonth CGI_GMTDay CGI_GMTWeekDay CGI_GMTYearDay
2776 # CGI_GMTHour CGI_GMTMinutes CGI_GMTSeconds CGI_GMTisdst
2778 ($default_values{CGI_GMTSeconds},
2779 $default_values{CGI_GMTMinutes},
2780 $default_values{CGI_GMTHour},
2781 $default_values{CGI_GMTDay},
2782 $default_values{CGI_GMTMonth},
2783 $default_values{CGI_GMTYear},
2784 $default_values{CGI_GMTWeekDay},
2785 $default_values{CGI_GMTYearDay},
2786 $default_values{CGI_GMTisdst}) = gmtime;
2790 # End of Initialize Request
2792 ###################################################################
2794 # SECURITY: ACCESS CONTROL
2796 # Check the credentials of each client (use pattern matching, domain first).
2797 # This subroutine will kill-off (die) the current process whenever access
2798 # is denied.
2800 sub Access_Control
2802 # >>>>>>>>>>Start Remove
2804 # ACCEPTED CLIENTS
2806 # Only accept clients which are authorized, reject all unnamed clients
2807 # if REMOTE_HOST is given.
2808 # If file patterns are given, check whether the user is authorized for
2809 # THIS file.
2810 if($CGI_Accept)
2812 # Use local variables, REMOTE_HOST becomes '-' if undefined
2813 my $REMOTE_HOST = $ENV{REMOTE_HOST} || '-';
2814 my $REMOTE_ADDR = $ENV{REMOTE_ADDR};
2815 my $PATH_INFO = $ENV{'PATH_INFO'};
2817 open(CGI_Accept, "<$CGI_Accept") || dieHandler(3, "$CGI_Accept: $!\n");
2818 $NoAccess = 1;
2819 while(<CGI_Accept>)
2821 next unless /\S/; # Skip empty lines
2822 next if /^\s*\#/; # Skip comments
2824 # Full expressions
2825 if(/^\s*-e\s/is)
2827 my $Accept = $'; # Get the expression
2828 $NoAccess &&= eval($Accept); # evaluate the expresion
2830 else
2832 my ($Accept, @FilePatternList) = split;
2833 if($Accept eq '*' # Always match
2834 ||$REMOTE_HOST =~ /\Q$Accept\E$/is # REMOTE_HOST matches
2835 || (
2836 $Accept =~ /^[0-9\.]+$/
2837 && $REMOTE_ADDR =~ /^\Q$Accept\E/ # IP address matches
2841 if($FilePatternList[0])
2843 foreach $Pattern (@FilePatternList)
2845 # Check whether this patterns is accepted
2846 $NoAccess &&= ($PATH_INFO !~ m@\Q$Pattern\E@is);
2849 else
2851 $NoAccess = 0; # No file patterns -> Accepted
2855 # Blocked
2856 last unless $NoAccess;
2858 close(CGI_Accept);
2859 if($NoAccess){ dieHandler(4, "No Access: $PATH_INFO\n");};
2863 # REJECTED CLIENTS
2865 # Reject named clients, accept all unnamed clients
2866 if($CGI_Reject)
2868 # Use local variables, REMOTE_HOST becomes '-' if undefined
2869 my $REMOTE_HOST = $ENV{'REMOTE_HOST'} || '-';
2870 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
2871 my $PATH_INFO = $ENV{'PATH_INFO'};
2873 open(CGI_Reject, "<$CGI_Reject") || dieHandler(5, "$CGI_Reject: $!\n");
2874 $NoAccess = 0;
2875 while(<CGI_Reject>)
2877 next unless /\S/; # Skip empty lines
2878 next if /^\s*\#/; # Skip comments
2880 # Full expressions
2881 if(/^-e\s/is)
2883 my $Reject = $'; # Get the expression
2884 $NoAccess ||= eval($Reject); # evaluate the expresion
2886 else
2888 my ($Reject, @FilePatternList) = split;
2889 if($Reject eq '*' # Always match
2890 ||$REMOTE_HOST =~ /\Q$Reject\E$/is # REMOTE_HOST matches
2891 ||($Reject =~ /^[0-9\.]+$/
2892 && $REMOTE_ADDR =~ /^\Q$Reject\E/is # IP address matches
2896 if($FilePatternList[0])
2898 foreach $Pattern (@FilePatternList)
2900 $NoAccess ||= ($PATH_INFO =~ m@\Q$Pattern\E@is);
2903 else
2905 $NoAccess = 1; # No file patterns -> Rejected
2909 last if $NoAccess;
2911 close(CGI_Reject);
2912 if($NoAccess){ dieHandler(6, "Request rejected: $PATH_INFO\n");};
2915 ##########################################################<<<<<<<<<<End Remove
2918 # Get the filename
2920 # Does the filename contain any illegal characters (e.g., |, >, or <)
2921 dieHandler(7, "Illegal request: $ENV{'PATH_INFO'}\n") if $ENV{'PATH_INFO'} =~ /[^$FileAllowedChars]/;
2922 # Does the pathname contain an illegal (blocked) "directory"
2923 dieHandler(8, "Illegal request: $ENV{'PATH_INFO'}\n") if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@; # Access is blocked
2924 # Does the pathname contain a direct referencer to BinaryMapFile
2925 dieHandler(9, "Illegal request: $ENV{'PATH_INFO'}\n") if $BinaryMapFile && $ENV{'PATH_INFO'} =~ m@\Q$BinaryMapFile\E@; # Access is blocked
2927 # SECURITY: Is PATH_INFO allowed?
2928 if($FilePattern && $ENV{'PATH_INFO'} && $ENV{'PATH_INFO'} ne '-' &&
2929 ($ENV{'PATH_INFO'} !~ m@($FilePattern)$@is))
2931 # Unsupported file types can be processed by a special raw-file
2932 if($BinaryMapFile)
2934 $ENV{'CGI_BINARY_FILE'} = $ENV{'PATH_INFO'};
2935 $ENV{'PATH_INFO'} = $BinaryMapFile;
2937 else
2939 dieHandler(10, "Illegal file\n");
2945 # End of Security Access Control
2948 ############################################################################
2950 # Get the POST part of the query and add it to the QUERY_STRING.
2953 sub Get_POST_part_of_query
2956 # If POST, Read data from stdin to QUERY_STRING
2957 if($ENV{'REQUEST_METHOD'} =~ /POST/is)
2959 # SECURITY: Check size of Query String
2960 $ENV{'CONTENT_LENGTH'} <= $MaximumQuerySize || dieHandler(11, "Query too long: $ENV{'CONTENT_LENGTH'}\n"); # Query too long
2961 my $QueryRead = 0;
2962 my $SystemRead = $ENV{'CONTENT_LENGTH'};
2963 $ENV{'QUERY_STRING'} .= '&' if length($ENV{'QUERY_STRING'}) > 0;
2964 while($SystemRead > 0)
2966 $QueryRead = sysread(STDIN, $Post, $SystemRead); # Limit length
2967 $ENV{'QUERY_STRING'} .= $Post;
2968 $SystemRead -= $QueryRead;
2970 # Update decoded Query String
2971 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2972 $default_values{CGI_Content_Length} =
2973 length($default_values{CGI_Decoded_QS});
2977 # End of getting POST part of query
2980 ############################################################################
2982 # Start (HTML) output and logging
2983 # (if there are irregularities, it can kill the current process)
2986 sub Initialize_output
2988 # Construct the REAL file path (except for STDIN on the command line)
2989 my $file_path = $ENV{'PATH_INFO'} ne '-' ? $SS_PUB . $ENV{'PATH_INFO'} : '-';
2990 $file_path =~ s/\?.*$//; # Remove query
2991 # This is only necessary if your server does not catch ../ directives
2992 $file_path !~ m@\.\./@ || dieHandler(12, "Illegal ../ Construct\n"); # SECURITY: Do not allow ../ constructs
2994 # Block STDIN use (-) if CGIscriptor is servicing a HTTP request
2995 if($file_path eq '-')
2997 dieHandler(13, "STDIN request in On Line system\n") if $BLOCK_STDIN_HTTP_REQUEST
2998 && ($ENV{'SERVER_SOFTWARE'}
2999 || $ENV{'SERVER_NAME'}
3000 || $ENV{'GATEWAY_INTERFACE'}
3001 || $ENV{'SERVER_PROTOCOL'}
3002 || $ENV{'SERVER_PORT'}
3003 || $ENV{'REMOTE_ADDR'}
3004 || $ENV{'HTTP_USER_AGENT'});
3009 if($ClientLog)
3011 open(ClientLog, ">>$ClientLog");
3012 print ClientLog "$LocalTime | ",
3013 ($ENV{REMOTE_USER} || "-"), " ",
3014 ($ENV{REMOTE_IDENT} || "-"), " ",
3015 ($ENV{REMOTE_HOST} || "-"), " ",
3016 $ENV{REMOTE_ADDR}, " ",
3017 $ENV{PATH_INFO}, " ",
3018 $ENV{'CGI_BINARY_FILE'}, " ",
3019 ($default_values{CGI_Content_Length} || "-"),
3020 "\n";
3021 close(ClientLog);
3023 if($QueryLog)
3025 open(QueryLog, ">>$QueryLog");
3026 print QueryLog "$LocalTime\n",
3027 ($ENV{REMOTE_USER} || "-"), " ",
3028 ($ENV{REMOTE_IDENT} || "-"), " ",
3029 ($ENV{REMOTE_HOST} || "-"), " ",
3030 $ENV{REMOTE_ADDR}, ": ",
3031 $ENV{PATH_INFO}, " ",
3032 $ENV{'CGI_BINARY_FILE'}, "\n";
3034 # Write Query to Log file
3035 print QueryLog $default_values{CGI_Decoded_QS}, "\n\n";
3036 close(QueryLog);
3039 # Return the file path
3040 return $file_path;
3043 # End of Initialize output
3046 ############################################################################
3048 # Handle login access
3050 # Access is based on a valid session ticket.
3051 # Session tickets should be dependend on user name
3052 # and IP address. The patterns of URLs for which a
3053 # session ticket is needed and the login URL are stored in
3054 # %TicketRequiredPatterns as:
3055 # 'RegEx pattern' -> 'SessionPath\tPasswordPath\tLogin URL\tExpiration'
3058 sub Log_In_Access # () -> 0 = Access Allowed, Login page if access is not allowed
3060 # No patterns, no login
3061 goto Return unless %TicketRequiredPatterns;
3063 # Get and initialize values (watch out for stuff processed by BinaryMap files)
3064 my ($SessionPath, $PasswordsPath, $Login, $valid_duration) = ("", "", "", 0);
3065 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3066 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
3067 goto Return if $REMOTE_ADDR =~ /[^0-9\.]/;
3068 # Extract TICKETs, starting with returned cookies
3069 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3070 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3071 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3072 if($ENV{'COOKIE_JAR'})
3074 my $CurrentCookieJar = $ENV{'COOKIE_JAR'};
3075 $CurrentCookieJar =~ s/\w+\=\-\s*(\;\s*|$)//isg;
3076 if($CurrentCookieJar =~ /\s*CGIscriptorLOGIN\=\s*([^\;]+)/)
3078 ${"CGIexecute::LOGINTICKET"} = $1;
3080 if($CurrentCookieJar =~ /\s*CGIscriptorCHALLENGE\=\s*([^\;]+)/ && $1 ne '-')
3082 ${"CGIexecute::CHALLENGETICKET"} = $1;
3084 if($CurrentCookieJar =~ /\s*CGIscriptorSESSION\=\s*([^\;]+)/ && $1 ne '-')
3086 ${"CGIexecute::SESSIONTICKET"} = $1;
3089 # Get and check the tickets. Tickets are restricted to word-characters (alphanumeric+_+.)
3090 my $LOGINTICKET = ${"CGIexecute::LOGINTICKET"};
3091 goto Return if ($LOGINTICKET && $LOGINTICKET =~ /[^\w\.]/isg);
3092 my $SESSIONTICKET = ${"CGIexecute::SESSIONTICKET"};
3093 goto Return if ($SESSIONTICKET && $SESSIONTICKET =~ /[^\w\.]/isg);
3094 my $CHALLENGETICKET = ${"CGIexecute::CHALLENGETICKET"};
3095 goto Return if ($CHALLENGETICKET && $CHALLENGETICKET =~ /[^\w\.]/isg);
3096 # Look for a LOGOUT message
3097 my $LOGOUT = $ENV{QUERY_STRING} =~ /(^|\&)LOGOUT([\=\&]|$)/;
3098 # Username and password
3099 CGIexecute::defineCGIvariable('CGIUSERNAME', "");
3100 my $username = lc(${"CGIexecute::CGIUSERNAME"});
3101 goto Return if $username =~ m!^[^\w]!isg || $username =~ m![^\w \-]!isg;
3102 my $userfile = lc($username);
3103 $userfile =~ s/[^\w]/_/isg;
3104 CGIexecute::defineCGIvariable('PASSWORD', "");
3105 my $password = ${"CGIexecute::PASSWORD"};
3106 CGIexecute::defineCGIvariable('NEWUSERNAME', "");
3107 my $newuser = lc(${"CGIexecute::NEWUSERNAME"});
3108 CGIexecute::defineCGIvariable('NEWPASSWORD', "");
3109 my $newpassword = ${"CGIexecute::NEWPASSWORD"};
3111 foreach my $pattern (keys(%TicketRequiredPatterns))
3113 # Check BOTH the real PATH_INFO and the CGI_BINARY_FILE variable
3114 if($ENV{'PATH_INFO'} =~ m#$pattern# || $ENV{'CGI_BINARY_FILE'} =~ m#$pattern#)
3116 # Fall through a sieve of requirements
3117 ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3118 # If a LOGOUT is present, remove everything
3119 if($LOGOUT && !$LOGINTICKET)
3121 unlink "$SessionPath/$LOGINTICKET" if $LOGINTICKET && (-s "$SessionPath/$LOGINTICKET");
3122 $LOGINTICKET = "";
3123 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
3124 $SESSIONTICKET = "";
3125 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
3126 $CHALLENGETICKET = "";
3127 unlink "$SessionPath/$REMOTE_ADDR" if (-s "$SessionPath/$REMOTE_ADDR");
3128 $CHALLENGETICKET = "";
3129 goto Login;
3131 # Is there a change password request?
3132 if($newuser && $LOGINTICKET && $username)
3134 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3135 goto Login unless (-s "$PasswordsPath/$userfile");
3136 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3137 goto Login unless $ticket_valid;
3138 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".", 1);
3139 goto Login unless $ticket_valid;
3141 my ($sessiontype, $currentticket) = ("", "");
3142 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
3143 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
3144 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
3146 if($sessiontype)
3148 goto Login unless (-s "$SessionPath/$currentticket");
3149 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3150 goto Login unless $ticket_valid;
3152 # Authorize
3153 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath);
3154 goto Login unless $TMPTICKET;
3156 # Create a new user account
3157 CGIexecute::defineCGIvariable('NEWSESSION', "");
3158 my $newsession = ${"CGIexecute::NEWSESSION"};
3159 my $newaccount = create_newuser("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket",
3160 "$PasswordsPath/$userfile", $password, $newuser, $newpassword, $newsession);
3161 CGIexecute::defineCGIvariable('NEWACCOUNTTEXT', $newaccount);
3162 ${CGIexecute::NEWACCOUNTTEXT} = $newaccount;
3163 # NEWACCOUNTTEXT is NOT to be set by the query
3164 CGIexecute::ProtectCGIvariable('NEWACCOUNTTEXT');
3167 # Ready
3168 goto Return;
3170 # Is there a change password request?
3171 elsif($newpassword && $LOGINTICKET && $username)
3173 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3174 goto Login unless (-s "$PasswordsPath/$userfile");
3175 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3176 goto Login unless $ticket_valid;
3177 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".", 1);
3178 goto Login unless $ticket_valid;
3180 my ($sessiontype, $currentticket) = ("", "");
3181 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
3182 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
3183 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
3185 if($sessiontype)
3187 goto Login unless (-s "$SessionPath/$currentticket");
3188 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3189 goto Login unless $ticket_valid;
3191 # Authorize
3192 change_password("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket", "$PasswordsPath/$userfile", $password, $newpassword);
3193 # After a change of password, you have to login again for a CHALLENGE
3194 if($CHALLENGETICKET){$CHALLENGETICKET = "";};
3195 # Ready
3196 goto Return;
3198 # Is there a login ticket of this name?
3199 elsif($LOGINTICKET)
3201 my $tickets_removed = remove_expired_tickets($SessionPath);
3202 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3203 goto Login unless (-s "$PasswordsPath/$userfile");
3204 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3205 goto Login unless $ticket_valid;
3206 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".");
3207 goto Login unless $ticket_valid;
3209 # Remove any lingering tickets
3210 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
3211 $SESSIONTICKET = "";
3212 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
3213 $CHALLENGETICKET = "";
3216 # Authorize
3217 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath);
3218 if($TMPTICKET)
3220 my $authorization = read_ticket("$PasswordsPath/$userfile");
3221 goto Login unless $authorization;
3222 # Session type is read from the userfile
3223 if($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "CHALLENGE")
3225 # Create New Random CHALLENGETICKET
3226 $CHALLENGETICKET = $TMPTICKET;
3227 create_session_file("$SessionPath/$CHALLENGETICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3229 elsif($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "IPADDRESS")
3231 create_session_file("$SessionPath/$REMOTE_ADDR", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3233 else
3235 $SESSIONTICKET = $TMPTICKET;
3236 create_session_file("$SessionPath/$SESSIONTICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3237 $SETCOOKIELIST{"CGIscriptorSESSION"} = "-";
3240 # Login ticket file has been used, remove it
3241 unlink($loginfile);
3243 # Is there a session ticket of this name?
3244 # CHALLENGE
3245 if($CHALLENGETICKET)
3247 # Do not log into a CHALLENGE account if the SESSION cookie is present
3248 goto Login if $SESSIONTICKET =~ /\S/;
3249 goto Login unless (-s "$SessionPath/$CHALLENGETICKET");
3250 my $ticket_valid = check_ticket_validity("CHALLENGE", "$SessionPath/$CHALLENGETICKET", $REMOTE_ADDR, $PATH_INFO);
3251 goto Login unless $ticket_valid;
3253 my $oldchallenge = read_ticket("$SessionPath/$CHALLENGETICKET");
3254 goto Login unless $oldchallenge;
3255 # Check whether the login still exists
3256 my $userfile = lc($oldchallenge->{"Username"}->[0]);
3257 $userfile =~ s/[^\w]/_/isg;
3258 goto Login unless (-s "$PasswordsPath/$userfile");
3260 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3261 goto Login unless $ticket_valid;
3263 my $NEWCHALLENGETICKET = "";
3264 $NEWCHALLENGETICKET = copy_challenge_file("$SessionPath/$CHALLENGETICKET", "$PasswordsPath/$userfile", $SessionPath);
3265 # Sessionticket is available to scripts, do NOT set the cookie
3266 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3267 goto Return;
3269 # IPADDRESS
3270 elsif(-s "$SessionPath/$REMOTE_ADDR")
3272 my $ticket_valid = check_ticket_validity("IPADDRESS", "$SessionPath/$REMOTE_ADDR", $REMOTE_ADDR, $PATH_INFO);
3273 goto Login unless $ticket_valid;
3274 # Check whether the login still exists
3275 my $currentsessionticket = read_ticket("$SessionPath/$REMOTE_ADDR");
3276 my $userfile = lc($currentsessionticket->{"Username"}->[0]);
3277 $userfile =~ s/[^\w]/_/isg;
3278 goto Login unless (-s "$PasswordsPath/$userfile");
3280 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3281 goto Login unless $ticket_valid;
3283 goto Return;
3285 # SESSION
3286 elsif($SESSIONTICKET)
3288 goto Login unless (-s "$SessionPath/$SESSIONTICKET");
3289 my $ticket_valid = check_ticket_validity("SESSION", "$SessionPath/$SESSIONTICKET", $REMOTE_ADDR, $PATH_INFO);
3290 goto Login unless $ticket_valid;
3292 # Check whether the login still exists
3293 my $currentsessionticket = read_ticket("$SessionPath/$SESSIONTICKET");
3294 my $userfile = lc($currentsessionticket->{"Username"}->[0]);
3295 $userfile =~ s/[^\w]/_/isg;
3296 goto Login unless (-s "$PasswordsPath/$userfile");
3298 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3299 goto Login unless $ticket_valid;
3301 # Sessionticket is available to scripts
3302 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3303 goto Return;
3306 goto Login;
3307 goto Return;
3310 Return:
3311 # The Masterkey should NOT be accessible by the parsed files
3312 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3313 return 0;
3315 Login:
3316 create_login_file($PasswordsPath, $SessionPath, $REMOTE_ADDR);
3317 # Note, cookies are set only ONCE
3318 $SETCOOKIELIST{"CGIscriptorLOGIN"} = "-";
3319 # The Masterkey should NOT be accessible by the parsed files
3320 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3321 return "$YOUR_HTML_FILES/$Login";
3324 sub authorize_login # ($loginfile, $authorizationfile, $password, $SessionPath) => SESSIONTICKET First two arguments are file paths
3326 my $loginfile = shift || "";
3327 my $authorizationfile = shift || "";
3328 my $password = shift || "";
3329 my $SessionPath = shift || "";
3331 # Get Login session ticket
3332 my $loginticket = read_ticket($loginfile);
3333 return 0 unless $loginticket;
3334 # Get User credentials for authorization
3335 my $authorization = read_ticket($authorizationfile);
3336 return 0 unless $authorization;
3338 # Get Randomsalt
3339 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3340 return "" unless $Randomsalt;
3342 my $storedpassword = $authorization->{'Password'}->[0];
3343 return "" unless $storedpassword;
3344 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3345 return "" unless $password eq $Hashedpassword;
3347 # Extract Session Ticket
3348 my $loginsession = $loginticket->{'Session'}->[0];
3349 my $sessionticket = hash_string($storedpassword.$loginsession);
3350 chomp($sessionticket);
3351 $sessionticket = "" if -x "$SessionPath/$sessionticket";
3353 return $sessionticket;
3356 sub change_password # ($loginfile, $sessionfile, $authorizationfile, $password, $newpassword) First three arguments are file paths
3358 my $loginfile = shift || "";
3359 my $sessionfile = shift || "";
3360 my $authorizationfile = shift || "";
3361 my $password = shift || "";
3362 my $newpassword = shift || "";
3363 # Get Login session ticket
3364 my $loginticket = read_ticket($loginfile);
3365 return "" unless $loginticket;
3366 # Login ticket file has been used, remove it
3367 unlink($loginfile);
3368 # Get Randomsalt
3369 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3370 return "" unless $Randomsalt;
3371 my $LoginID = $loginticket->{'Session'}->[0];
3372 return "" unless $LoginID;
3374 # Get session ticket
3375 my $sessionticket = read_ticket($sessionfile);
3376 return "" unless $sessionticket;
3378 # Get User credentials for authorization
3379 my $authorization = read_ticket($authorizationfile);
3380 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3382 my $storedpassword = $authorization->{'Password'}->[0];
3383 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3384 return "" unless $password eq $Hashedpassword;
3385 my $secretkey = hash_string($storedpassword.$LoginID.$Randomsalt);
3387 # Decrypt the $newpassword
3388 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3389 return "" unless $decryptedPassword;
3390 # Authorization succeeded, change password
3391 $authorization->{'Password'}->[0] = $decryptedPassword;
3392 # Write out
3393 write_ticket($authorizationfile, $authorization, $authorization->{'Salt'}->[0]);
3395 return $newpassword;
3397 # First three arguments are file paths
3398 sub create_newuser # ($loginfile, $sessionfile, $authorizationfile, $password, $newuser, $newpassword, $newsession) -> account text
3400 my $loginfile = shift || "";
3401 my $sessionfile = shift || "";
3402 my $authorizationfile = shift || "";
3403 my $password = shift || "";
3404 my $newuser = shift || "";
3405 my $newpassword = shift || "";
3406 my $newsession = shift || "";
3408 # Get Login session ticket
3409 my $loginticket = read_ticket($loginfile);
3410 return "" unless $loginticket;
3411 # Login ticket file has been used, remove it
3412 unlink($loginfile);
3413 # Get Randomsalt
3414 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3415 return "" unless $Randomsalt;
3416 my $LoginID = $loginticket->{'Session'}->[0];
3417 return "" unless $LoginID;
3419 # Get session ticket
3420 my $sessionticket = read_ticket($sessionfile);
3421 return "" unless $sessionticket;
3422 # Get User credentials for authorization
3423 my $authorization = read_ticket($authorizationfile);
3424 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3425 my $sessionkey = $sessionticket->{'Key'}->[0];
3426 my $serversalt = $authorization->{'Salt'}->[0];
3427 return "" unless $serversalt;
3429 my $storedpassword = $authorization->{'Password'}->[0];
3430 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3431 return "" unless $password eq $Hashedpassword;
3432 my $secretkey = hash_string($storedpassword.$LoginID.$Randomsalt);
3434 # Decrypt the $newpassword
3435 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3436 return "" unless $decryptedPassword;
3438 # Authorization succeeded, create new account
3439 my $newaccount = {};
3440 $newaccount->{'Type'} = ['PASSWORD'];
3441 $newaccount->{'Username'} = [$newuser];
3442 $newaccount->{'Password'} = [$decryptedPassword];
3443 $newaccount->{'Salt'} = [$serversalt];
3444 $newaccount->{'Session'} = ['SESSION'];
3445 if($newsession eq 'IPADDRESS'){$newaccount->{'Session'} = ['IPADDRESS'];};
3446 if($newsession eq 'CHALLENGE'){$newaccount->{'Session'} = ['CHALLENGE'];};
3447 my $timesec = time();
3448 my $gmt_date = gmtime();
3449 $newaccount->{'Time'} = [$timesec];
3450 $newaccount->{'Date'} = [$gmt_date];
3452 # AllowedPaths
3453 my $NewAllowedPaths = "";
3454 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3455 my $currentRoot = "";
3456 $currentRoot = $1 if $PATH_INFO =~ m!^([\w\-\. /]+)!isg;
3457 $currentRoot =~ s![^/]+$!!isg;
3458 if($currentRoot)
3460 $currentRoot .= '/' unless $currentRoot =~ m!/$!;
3461 my $newpath = "^".${currentRoot}.'[\w\-]+\.html?';
3462 $NewAllowedPaths .= 'AllowedPaths: ^'.${currentRoot}.'[\w\-]+\.html?'."\n";
3463 $newaccount->{'AllowedPaths'} = [$newpath];
3465 else
3467 # Tricky PATH_INFO, deny all
3468 $NewAllowedPaths .= "DeniedPaths: ^/\n";
3469 $newaccount->{'DeniedPaths'} = ["DeniedPaths: ^/\n"];
3472 # Construct home directory path
3473 my $FullHomeDirectoryPath = "";
3474 my $currentHome = lc($newuser);
3475 if($currentHome && $currentHome !~ /^\s*\#/)
3477 $currentHome =~ s![^\w]!_!isg;
3478 my $newpath = "^${currentRoot}$currentHome/";
3479 push(@{$newaccount->{'AllowedPaths'}}, $newpath);
3480 # Create home directory
3481 $FullHomeDirectoryPath = $ENV{'HOME'}.${currentRoot}.$currentHome;
3484 # Allowed Paths
3485 CGIexecute::defineCGIvariable('ALLOWEDPATHS', "");
3486 my $allowedpaths = ${"CGIexecute::ALLOWEDPATHS"};
3487 if($allowedpaths && $allowedpaths !~ /^\s*\#/)
3489 $allowedpaths =~ s![^\^\w\./\;\+\*\?\[\]\$]!!isg;
3490 my @pathlist = split(/\;/, $allowedpaths);
3491 foreach my $entry (@pathlist)
3493 push(@{$newaccount->{'AllowedPaths'}}, "^".${currentRoot}.$entry);
3497 # Allowed IP addresses
3498 CGIexecute::defineCGIvariable('IPADDRESS', "");
3499 my $ipaddress = ${"CGIexecute::IPADDRESS"};
3500 if($ipaddress && $ipaddress !~ /^\s*\#/)
3502 $ipaddress =~ s![^\d\.\;]!!isg;
3503 my @iplist = split(/\;/, $ipaddress);
3504 foreach my $entry (@iplist)
3506 next unless $entry =~ /\d/;
3507 next if $entry =~ /^\s*\#/;
3508 $entry =~ s/\./\\./g;
3509 push(@{$newaccount->{'IPaddress'}}, $entry);
3513 # Sign the new ticket
3514 my $Signature = SignTicketWithMasterkey($newaccount, $newaccount->{'Salt'}->[0]);
3516 # Write
3517 my $datetime = gmtime();
3518 my $passwordline = "<span id='newaccount'>".($newaccount->{'Password'}->[0])."</span>";
3519 my $newuserfile = "";
3520 if(grep(/^CreateUser$/, @{$authorization->{'Capabilities'}}))
3522 my $newuserfilename = lc($newuser);
3523 $newuserfilename =~ s/[^\w]/_/isg;
3524 $newuserfile = $authorizationfile;
3525 $newuserfile =~ s![^/]*$!!isg;
3526 $newuserfile .= $newuserfilename;
3527 if(-s $newuserfile)
3529 $newuserfile = "";
3531 elsif($FullHomeDirectoryPath && !(-d $FullHomeDirectoryPath || -s $FullHomeDirectoryPath))
3533 if(-d "$ENV{'HOME'}${currentRoot}.SkeletonDir")
3535 `cp -r '$ENV{'HOME'}${currentRoot}.SkeletonDir' '$FullHomeDirectoryPath'`;
3537 elsif(-d "$ENV{'HOME'}${currentRoot}SkeletonDir")
3539 `cp -r '$ENV{'HOME'}${currentRoot}SkeletonDir' '$FullHomeDirectoryPath'`;
3541 elsif(-s "$ENV{'HOME'}${currentRoot}UserIndex.html")
3543 mkdir $FullHomeDirectoryPath;
3544 `cp '$ENV{'HOME'}${currentRoot}UserIndex.html' '$FullHomeDirectoryPath/index.html'`;
3546 elsif(-s "$ENV{'HOME'}${currentRoot}index.html")
3548 mkdir $FullHomeDirectoryPath;
3549 `cp '$ENV{'HOME'}${currentRoot}index.html' '$FullHomeDirectoryPath/index.html'`;
3555 my $newaccounttext = write_ticket($newuserfile, $newaccount, $serversalt);
3557 # Re-encrypt the new password for transmission
3558 if($newaccounttext =~ /^(Password\:\s+)(\S+)\s*$/)
3560 my $passwordvalue = $1;
3561 my $reencryptedpassword = XOR_hex_strings($secretkey, $passwordvalue);
3562 my $encryptedpasswordline = "<span id='newaccount'>$reencryptedpassword</span>";
3563 $newaccounttext =~ s/^(Password\:\s+)(\S+)\s*$/\1$encryptedpasswordline/gim;
3565 return $newaccounttext;
3568 # Copy a Challenge ticket file to a new name which is the hash of the new $CHALLENGETICKET and the password
3569 sub copy_challenge_file #($oldchallengefile, $authorizationfile, $sessionpath) -> $CHALLENGETICKET
3571 my $oldchallengefile = shift || "";
3572 my $authorizationfile = shift || "";
3573 my $sessionpath = shift || "";
3574 $sessionpath =~ s!/+$!!g;
3576 # Get Login session ticket
3577 my $oldchallenge = read_ticket($oldchallengefile);
3578 return "" unless $oldchallenge;
3580 # Get Authorization (user) session file
3581 my $authorization = read_ticket($authorizationfile);
3582 return "" unless $authorization;
3583 my $storedpassword = $authorization->{'Password'}->[0];
3584 return "" unless $storedpassword;
3585 my $challengekey = $oldchallenge->{'Key'}->[0];
3586 return "" unless $challengekey;
3588 # Create Random Hash Salt
3589 my $NEWCHALLENGETICKET = get_random_hex();;
3590 my $newchallengefile = hash_string($challengekey.$NEWCHALLENGETICKET);
3591 return "" unless $newchallengefile;
3593 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3594 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3595 ${"CGIexecute::CHALLENGETICKET"} = $NEWCHALLENGETICKET;
3597 # Write Session Ticket
3598 open(OLDCHALLENGE, "<$oldchallengefile") || die "<$oldchallengefile: $!\n";
3599 my @OldChallengeLines = <OLDCHALLENGE>;
3600 close(OLDCHALLENGE);
3601 # Old file should now be removed
3602 unlink($oldchallengefile);
3604 open(SESSION, ">$sessionpath/$newchallengefile") || die "$sessionpath/$newchallengefile: $!\n";
3605 foreach $line (@OldChallengeLines)
3607 print SESSION $line;
3609 close(SESSION);
3611 return $NEWCHALLENGETICKET;
3614 sub create_login_file #($PasswordDir, $SessionDir, $IPaddress)
3616 my $PasswordDir = shift || "";
3617 my $SessionDir = shift || "";
3618 my $IPaddress = shift || "";
3620 # Create Login Ticket
3621 my $LOGINTICKET= get_random_hex ();
3623 # Create Random Hash Salt
3624 my $RANDOMSALT= get_random_hex();
3626 # Create SALT file if it does not exist
3627 # Remove this, including test account for life system
3628 unless(-d "$SessionDir")
3630 `mkdir -p "$SessionDir"`;
3632 unless(-d "$PasswordDir")
3634 `mkdir -p "$PasswordDir"`;
3636 # Create SERVERSALT and default test account
3637 my $SERVERSALT = "";
3638 unless(-s "$PasswordDir/SALT")
3640 $SERVERSALT= get_random_hex();
3641 open(SALTFILE, ">$PasswordDir/SALT") || die ">$PasswordDir/SALT: $!\n";
3642 print SALTFILE "$SERVERSALT\n";
3643 close(SALTFILE);
3645 # Update test account (should be removed in live system)
3646 my @alltestusers = ("test", "testip", "testchallenge", "admin");
3647 foreach my $testuser (@alltestusers)
3649 if(-s "$PasswordDir/$testuser")
3651 my $plainpassword = $testuser eq 'admin' ? "There is no password like more password" : "testing";
3652 my $storedpassword = hash_string(${plainpassword}.${testuser}.${SERVERSALT});
3653 # Encrypt the new password with the MasterKey
3654 my $authorization = read_ticket("$PasswordDir/$testuser") || return "";
3655 $authorization->{'Salt'} = [$SERVERSALT];
3656 set_password($authorization, $SERVERSALT, $plainpassword);
3657 write_ticket("$PasswordDir/$testuser", $authorization, $SERVERSALT);
3662 # Read in site Salt
3663 open(SALTFILE, "<$PasswordDir/SALT") || die "$PasswordDir/SALT: $!\n";
3664 $SERVERSALT=<SALTFILE>;
3665 close(SALTFILE);
3666 chomp($SERVERSALT);
3668 # Create login session ticket
3669 my $datetime = gmtime();
3670 my $timesec = time();
3671 my $loginticket = {};
3672 $loginticket->{Type} = ['LOGIN'];
3673 $loginticket->{IPaddress} = [$IPaddress];
3674 $loginticket->{Salt} = [$SERVERSALT];
3675 $loginticket->{Session} = [$LOGINTICKET];
3676 $loginticket->{Randomsalt} = [$RANDOMSALT];
3677 $loginticket->{Expires} = ['+600s'];
3678 $loginticket->{Date} = ["$datetime UTC"];
3679 $loginticket->{Time} = [$timesec];
3680 write_ticket("$SessionDir/$LOGINTICKET", $loginticket, $SERVERSALT);
3682 # Set global variables
3683 # $SERVERSALT
3684 $ENV{'SERVERSALT'} = $SERVERSALT;
3685 CGIexecute::defineCGIvariable('SERVERSALT', "");
3686 ${"CGIexecute::SERVERSALT"} = $SERVERSALT;
3688 # $SESSIONTICKET
3689 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3690 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3691 ${"CGIexecute::SESSIONTICKET"} = $SESSIONTICKET;
3693 # $RANDOMSALT
3694 $ENV{'RANDOMSALT'} = $RANDOMSALT;
3695 CGIexecute::defineCGIvariable('RANDOMSALT', "");
3696 ${"CGIexecute::RANDOMSALT"} = $RANDOMSALT;
3698 # $LOGINTICKET
3699 $ENV{'LOGINTICKET'} = $LOGINTICKET;
3700 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3701 ${"CGIexecute::LOGINTICKET"} = $LOGINTICKET;
3703 return $ENV{'LOGINTICKET'};
3706 sub create_session_file #($sessionfile, $loginfile, $authorizationfile, $path) -> Is $loginfile deleted? 0/1
3708 my $sessionfile = shift || "";
3709 my $loginfile = shift || "";
3710 my $authorizationfile = shift || "";
3711 my $path = shift || "";
3713 # Get Login session ticket
3714 my $loginticket = read_ticket($loginfile);
3715 return unlink($loginfile) unless $loginticket;
3717 # Get Authorization (user) session file
3718 my $authorization = read_ticket($authorizationfile);
3719 return unlink($loginfile) unless $authorization;
3721 # For a Session or a Challenge, we need a stored key
3722 my $sessionkey = "";
3723 my $secretkey = "";
3724 if($authorization->{'Session'} && $authorization->{'Session'}->[0] ne 'IPADDRESS')
3726 my $storedpassword = $authorization->{'Password'}->[0];
3727 my $loginticketid = $loginticket->{'Session'}->[0];
3728 my $randomsalt = $loginticket->{'Randomsalt'}->[0];
3729 $sessionkey = hash_string($storedpassword.$loginticketid);
3730 $secretkey = hash_string($storedpassword.$loginticketid.$randomsalt);
3732 # Get Session id
3733 my $sessionid = "";
3734 if($sessionfile =~ m!([^/]+)$!)
3736 $sessionid = $1;
3739 # Convert Authorization content to Session content
3740 my $sessionContent = {};
3741 my $SessionType = $authorization->{'Session'}->[0] ? $authorization->{'Session'}->[0] : "SESSION";
3742 $sessionContent->{Type} = [$SessionType];
3743 $sessionContent->{Username} = [lc($authorization->{'Username'}->[0])];
3744 $sessionContent->{Session} = [$sessionid];
3745 $sessionContent->{Time} = [time];
3746 # Limit communication to the login IP address, except for Tor like situations with VariableREMOTE_ADDR
3747 $sessionContent->{IPaddress} = ['.'];
3748 if($sessionContent->{Type}->[0] eq 'CHALLENGE' && grep(/^VariableREMOTE_ADDR$/, @{$authorization->{'Capabilities'}}))
3750 $sessionContent->{IPaddress} = $authorization->{'IPaddress'} if $authorization->{'IPaddress'};
3752 else
3754 $sessionContent->{IPaddress} = $loginticket->{'IPaddress'};
3756 $sessionContent->{Salt} = $authorization->{'Salt'};
3757 $sessionContent->{Randomsalt} = $loginticket->{'Randomsalt'};
3758 $sessionContent->{AllowedPaths} = $authorization->{'AllowedPaths'};
3759 $sessionContent->{DeniedPaths} = $authorization->{'DeniedPaths'};
3760 $sessionContent->{Expires} = $authorization->{'MaxLifetime'};
3761 $sessionContent->{Capabilities} = $authorization->{'Capabilities'};
3762 foreach my $pattern (keys(%TicketRequiredPatterns))
3764 if($path =~ m#$pattern#)
3766 my ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3767 push(@{$sessionContent->{Expires}}, $validtime);
3770 $sessionContent->{Key} = [$sessionkey] if $sessionkey;
3771 $sessionContent->{Secretkey} = [$secretkey] if $secretkey;
3772 $sessionContent->{Date} = [gmtime()." UTC"];
3774 # Write Session Ticket
3775 write_ticket($sessionfile, $sessionContent, $authorization->{'Salt'}->[0]);
3777 # Login file should now be removed
3778 return unlink($loginfile);
3781 sub check_ticket_validity # ($type, $ticketfile, $address, $path [, $unsigned])
3783 my $type = shift || "SESSION";
3784 my $ticketfile = shift || "";
3785 my $address = shift || "";
3786 my $path = shift || "";
3787 my $unsigned = shift || 0;
3789 # Is there a session ticket of this name?
3790 return 0 unless -s "$ticketfile";
3792 # There is a session ticket, is it linked to this IP address?
3793 my $ticket = read_ticket($ticketfile);
3794 unless($ticket)
3796 print STDERR "Ticket expired or empty: $ticketfile\n";
3797 return;
3800 # Is this the right type of ticket
3801 unless($ticket && $ticket->{'Type'}->[0] eq $type)
3803 print STDERR "Wrong ticket type: $ticket->{'Type'}->[0] eq $type\n";
3804 return;
3807 # Does the IP address match?
3808 my $IPmatches = @{$ticket->{"IPaddress"}} ? 0 : 1;
3809 for $IPpattern (@{$ticket->{"IPaddress"}})
3811 ++$IPmatches if $address =~ m#^$IPpattern#ig;
3813 if($address && ! $IPmatches)
3815 print STDERR "Wrong REMOTE ADDR for $ticket->{'Username'}->[0]: $ticket->{'IPaddress'}->[0] vs $address\n";
3816 return 0;
3819 # Is the path denied
3820 my $Pathmatches = 0;
3821 foreach $Pathpattern (@{$ticket->{"DeniedPaths"}})
3823 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3825 return 0 if @{$ticket->{"DeniedPaths"}} && $Pathmatches;
3827 # Is the path allowed
3828 $Pathmatches = 0;
3829 foreach $Pathpattern (@{$ticket->{"AllowedPaths"}})
3831 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3833 return 0 unless !@{$ticket->{"AllowedPaths"}} || $Pathmatches;
3835 # Check signature if not told to use an unsigned ticket (dangerous)
3836 my $Signature = TicketSignature($ticket, $ticket->{'Salt'}->[0]);
3837 if((! $unsigned) && $Signature && $Signature ne $ticket->{'Signature'}->[0])
3839 print STDERR "Invalid signature for $ticket->{'Type'}: $ticket->{'Username'}\n$ticketfile\n";
3840 return 0;
3843 # Make login values available (will also protect against resetting by query)
3844 $ENV{"LOGINUSERNAME"} = lc($ticket->{'Username'}->[0]);
3845 $ENV{"LOGINIPADDRESS"} = $address;
3846 $ENV{"LOGINPATH"} = $path;
3847 $ENV{"SESSIONTYPE"} = $type unless $type eq "PASSWORD";
3849 # Set Capabilities, if present
3850 if($ticket->{'Username'}->[0] && @{$ticket->{'Capabilities'}})
3852 $ENV{'CAPABILITIES'} = $ticket->{'Username'}->[0];
3853 CGIexecute::defineCGIvariableList('CAPABILITIES', "");
3854 @{"CGIexecute::CAPABILITIES"} = @{$ticket->{'Capabilities'}};
3855 # Capabilities should not be changed anymore by CGI query!
3857 # Capabilities are NOT to be set by the query
3858 CGIexecute::ProtectCGIvariable('CAPABILITIES');
3860 return 1;
3864 sub remove_expired_tickets # ($path) -> number of tickets removed
3866 my $path = shift || "";
3867 return 0 unless $path;
3868 $path =~ s!/+$!!g;
3869 my $removed_tickets = 0;
3870 my @ticketlist = glob("$path/*");
3871 foreach my $ticketfile (@ticketlist)
3873 my $ticket = read_ticket($ticketfile);
3874 unless($ticket)
3876 unlink $ticketfile;
3877 ++$removed_tickets;
3880 return $removed_tickets;
3883 sub set_password # ($ticket, $salt, $plainpassword) -> $password
3885 my $ticket = shift || "";
3886 my $salt = shift || "";
3887 my $plainpassword = shift || "";
3889 my $user = lc($ticket->{'Username'}->[0]);
3890 return "" unless $user;
3891 $salt = $ticket->{'Salt'}->[0] unless $salt;
3893 my $storedpassword = hash_string(${plainpassword}.${user}.${salt});
3894 $ticket->{'Password'} = [$storedpassword];
3895 $ticket->{'Salt'} = [$salt];
3897 return $ticket->{'Password'}->[0];
3900 sub write_ticket # ($ticketfile, $ticket, $salt [, $masterkey]) -> &%ticket
3902 my $ticketfile = shift || "";
3903 my $ticket = shift || "";
3904 my $salt = shift || "";
3905 my $masterkey = shift || $ENV{'CGIMasterKey'};
3907 # Encrypt password
3908 EncryptTicketWithMasterKey($ticket, $salt, $masterkey);
3910 # Sign the new ticket
3911 my $signature = SignTicketWithMasterkey($ticket, $salt, $masterkey);
3913 # Create ordered list with labels
3914 my @orderlist = ('Type', 'Username', 'Password', 'IPaddress', 'AllowedPaths', 'DeniedPaths',
3915 'Expires', 'Capabilities', 'Salt', 'Session', 'Randomsalt',
3916 'Date', 'Time', 'Signature', 'Key', 'Secretkey');
3917 my @labellist = keys(%{$ticket});
3918 foreach my $label (@orderlist)
3920 @labellist = grep(!/\b$label\b/, @labellist);
3923 # Create ticket in text
3924 my $TicketText = "";
3925 foreach my $label (@orderlist, @labellist)
3927 next unless exists($ticket->{$label}) && $ticket->{$label}->[0];
3928 foreach my $value (@{$ticket->{$label}})
3930 $TicketText .= "$label: $value\n";
3933 if($ticketfile)
3935 open(TICKET, ">$ticketfile") || die "$ticketfile: $!\n";
3936 print TICKET $TicketText;
3937 close(TICKET);
3940 return $TicketText;
3943 # Note, read_ticket will return 0 if the ticket has expired!
3944 sub read_ticket # ($ticketfile [, $salt, $masterkey]) -> &%ticket
3946 my $ticketfile = shift || "";
3947 my $serversalt = shift || "";
3948 my $masterkey = shift || $ENV{'CGIMasterKey'};
3950 my $ticket = {};
3951 if($ticketfile && -s $ticketfile)
3953 open(TICKETFILE, "<$ticketfile") || die "$ticketfile: $!\n";
3954 my @alllines = <TICKETFILE>;
3955 close(TICKETFILE);
3956 foreach my $currentline (@alllines)
3958 # Skip empty lines and comments
3959 next unless $currentline =~ /\S/;
3960 next if $currentline =~ /^\s*\#/;
3962 if($currentline =~ /^\s*(\S[^\:]+)\:\s+(.*)\s*$/)
3964 my $Label = $1;
3965 my $Value = $2;
3966 $ticket->{$Label} = () unless exists($ticket->{$Label});
3967 push(@{$ticket->{$Label}}, $Value);
3971 if($masterkey && exists($ticket->{'Password'}) && $ticket->{'Password'}->[0])
3973 # Use the ServerSalt stored in the ticket, if present
3974 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
3976 $serversalt = $ticket->{Salt}->[0];
3978 # Decrypt all passwords
3979 DecryptTicketWithMasterKey($ticket, $serversalt, $masterkey) ||
3980 die "Decryption failed: DecryptTicketWithMasterKey ($ticket, $serversalt)\n";
3983 # Check whether the ticket has expired
3984 if(exists($ticket->{Expires}))
3986 my $StartTime = 0;
3987 if(exists($ticket->{Time}) && $ticket->{Time}->[0] > 0)
3989 $StartTime = [(sort(@{$ticket->{Time}}))]->[0];
3991 else
3993 # Get SessionTicket file stats
3994 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)
3995 = stat($ticketfile);
3996 $StartTime = $ctime;
3998 foreach my $Value (@{$ticket->{'Expires'}})
4000 # Recalculate expire date from relative time
4001 if($Value =~ /^\+/)
4003 if($Value =~ /^\+(\d+)\s*d(ays)?\s*$/)
4005 $ExpireTime = 24*3600*$1;
4007 elsif($Value =~ /^\+(\d+)\s*m(inutes)?\s*$/)
4009 $ExpireTime = 60*$1;
4011 elsif($Value =~ /^\+(\d+)\s*h(ours)?\s*$/)
4013 $ExpireTime = 3600*$1;
4015 elsif($Value =~ /^\+(\d+)\s*s(econds)?\s*$/)
4017 $ExpireTime = $1;
4019 elsif($Value =~ /^\+(\d+)\s*$/)
4021 $ExpireTime = $1;
4024 my $absoluteTime = $Value =~ /^\+/ ? $StartTime + $ExpireTime : $Value;
4025 return 0 unless $absoluteTime > time;
4027 @{$ticket->{Expires}} = sort(@{$ticket->{Expires}});
4029 return $ticket;
4032 # Set up a valid ticket from a given text file
4033 # Use from command line. DO NOT USE ONLINE
4034 # Watch out for passwords that get stored in the history file
4036 # perl CGIscriptor.pl --managelogin [options] [files]
4037 # Options:
4038 # salt={file or saltvalue}
4039 # masterkey={file or plaintext}
4040 # newmasterkey={file or plaintext}
4041 # password={file or palintext}
4043 # Followed by one or more file names.
4044 # Options can be interspersed between filenames,
4045 # e.g., password='plaintext'
4046 # Note that passwords are only used once!
4048 sub setup_ticket_file # (@ARGV)
4050 # Stop when run on-line
4051 return if $ENV{'PATH_INFO'} || $ENV{'QUERY_STRING'};
4053 my %Settings = ();
4054 foreach my $input (@_)
4056 if($input =~ /^([\w]+)\=/)
4058 my $name = lc($1);
4059 my $value = $';
4060 chomp($value);
4062 if($value !~ m![^\w\.\~\/\:\-]! && $value !~ /^[\-\.]/ && -s "$value" && ! -d "$value" && $value =~ m![/\.\:\\]!)
4064 open(INPUTVALUE, "<$value") || die "$value: $!\n";
4065 $value = <INPUTVALUE>;
4066 chomp($value);
4069 $value =~ s/(^\'([^\']*)\'$)/\1/g;
4070 $value =~ s/(^\"([^\"]*)\"$)/\1/g;
4071 $Settings{$name} = $value;
4073 elsif($input !~ m![^\w\.\~\/\:\-]!i && $input !~ /^[\-\.]/i && -s $input)
4075 # We MUST have a salt
4076 $Settings{'salt'} = $ticket->{'Salt'}->[0] unless $Settings{'salt'};
4078 # Set the new masterkey to the old masterkey if there is no new masterkey
4079 $Settings{'newmasterkey'} = $Settings{'masterkey'} unless exists($Settings{'newmasterkey'});
4081 # Get the ticket
4082 my $ticket = read_ticket($input, $Settings{'salt'}, $Settings{'masterkey'});
4084 # Set a new password from plaintext
4085 $ticket->{'Salt'}->[0] = $Settings{'salt'} if $Settings{'salt'} && $Settings{'password'};
4086 set_password ($ticket, $Settings{'salt'}, $Settings{'password'}) if $Settings{'password'};
4087 # Write the ticket back to file
4088 write_ticket($input, $ticket, $Settings{'salt'}, $Settings{'newmasterkey'});
4090 # A password is only used once
4091 $Settings{'password'} = "";
4096 # Add a signature from $masterkey to a ticket in the label $signlabel
4097 sub SignTicketWithMasterkey # ($ticket, $serversalt [, $masterkey, $signlabel]) -> $Signature
4099 my $ticket = shift || return 0;
4100 my $serversalt = shift || "";
4101 my $masterkey = shift || $ENV{'CGIMasterKey'};
4102 my $signlabel = shift || 'Signature';
4104 my $Signature = TicketSignature($ticket, $serversalt, $masterkey);
4106 $ticket->{$signlabel} = [$Signature] if $Signature;
4108 return $Signature;
4111 # Determine ticket signature
4112 sub TicketSignature # ($ticket, $serversalt [, $masterkey]) -> $Signature
4114 my $ticket = shift || return 0;
4115 my $serversalt = shift || "";
4116 my $masterkey = shift || $ENV{'CGIMasterKey'};
4117 my $Signature = "";
4119 if($masterkey)
4121 # If the ServerSalt is not stored in the ticket, the SALT file has to be found
4122 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4124 $serversalt = $ticket->{Salt}->[0];
4126 # Sign
4127 if($serversalt)
4129 my $username = lc($ticket->{'Username'}->[0]);
4130 my $hash1 = hash_string(${masterkey}.${serversalt});
4131 my $CryptKey = hash_string($username.${'hash1'});
4132 my $SignText = "Type: ".$ticket->{'Type'}->[0]."\n";
4133 my @tmp = sort(@{$ticket->{'Username'}});
4134 $SignText .= "Username: @tmp\n";
4135 @tmp = sort(@{$ticket->{'IPaddress'}});
4136 $SignText .= "IPaddress: @tmp\n";
4137 @tmp = sort(@{$ticket->{'AllowedPaths'}});
4138 $SignText .= "AllowedPaths: @tmp\n";
4139 @tmp = sort(@{$ticket->{'DeniedPaths'}});
4140 $SignText .= "DeniedPaths: @tmp\n";
4141 @tmp = sort(@{$ticket->{'Session'}});
4142 $SignText .= "Session: @tmp\n";
4143 @tmp = sort(@{$ticket->{'Time'}});
4144 $SignText .= "Time: @tmp\n";
4145 @tmp = sort(@{$ticket->{'Expires'}});
4146 $SignText .= "Expires: @tmp\n";
4147 @tmp = sort(@{$ticket->{'Capabilities'}});
4148 $SignText .= "Capabilities: @tmp\n";
4149 @tmp = sort(@{$ticket->{'MaxLifetime'}});
4150 $SignText .= "MaxLifetime: @tmp\n";
4151 $Signature = HMAC_hex($CryptKey, $SignText);
4154 return $Signature;
4157 # Decrypts a password list IN PLACE
4158 sub DecryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4160 my $ticket = shift || return 0;
4161 my $serversalt = shift || "";
4162 my $masterkey = shift || $ENV{'CGIMasterKey'};
4164 if($masterkey && exists($ticket->{Password}) && $ticket->{Password}->[0])
4166 # If the ServerSalt is not given, read it from the the ticket
4167 if(! $serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4169 $serversalt = $ticket->{Salt}->[0];
4171 # Decrypt password(s)
4172 if($serversalt)
4174 my $hash1 = hash_string(${masterkey}.${serversalt});
4175 my $username = lc($ticket->{'Username'}->[0]);
4176 my $CryptKey = hash_string(${'hash1'}.$username);
4177 foreach my $password (@{$ticket->{Password}})
4179 $password = XOR_hex_strings($CryptKey,$password);
4183 return $ticket->{'Password'};
4185 sub EncryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4187 DecryptTicketWithMasterKey(@_);
4190 # Implement HMAC signature hash.
4191 # Blocksize is length in HEX characters, NOT bytes
4192 sub HMAC_hex # ($key, $message [, $blocksizehex]) -> $hex
4194 my $key = shift || "";
4195 my $message = shift || "";
4196 my $blocksizehex = shift || length($key);
4197 $key = hash_string($key) if length($key) > $blocksizehex;
4199 my $innerkey = XOR_hex_byte ($key, "36");
4200 my $outerkey = XOR_hex_byte ($key, "5c");
4201 my $innerhash = hash_string($innerkey.$message);
4202 my $outerhash = hash_string($outerkey.$innerhash);
4204 return $outerhash;
4207 # XOR input with equally long string of repeated 2 hex character (byte)
4208 # string. Input must have even number of hex characters
4209 sub XOR_hex_byte # ($hex1, $hexbyte) -> $hex
4211 my $hex1 = shift || "";
4212 my $hexbyte = shift || "";
4213 my $bytelength = length($hexbyte);
4214 my $hex2 = $hex1;
4215 $hex2 =~ s/.{$bytelength}/$hexbyte/ig;
4216 return XOR_hex_strings($hex1, $hex2);
4219 sub XOR_hex_strings # ($hex1, $hex2) -> $hex
4221 my $hex1 = shift || "";
4222 my $hex2 = shift || "";
4223 my @hex1list = split('', $hex1);
4224 my @hex2list = split('', $hex2);
4225 my @hexresultlist = ();
4226 for(my $i; $i < scalar(@hex1list); ++$i)
4228 my $d1 = hex($hex1list[$i]);
4229 my $d2 = hex($hex2list[$i]);
4230 my $dresult = ($d1 ^ $d2);
4231 $hexresultlist[$i] = sprintf("%x", $dresult);
4233 $hexresult = join('', @hexresultlist);
4234 return $hexresult;
4237 # End of Handle login access
4240 ############################################################################
4242 # Handle foreign interpreters (i.e., scripting languages)
4244 # Insert perl code to execute scripts in foreign scripting languages.
4245 # Actually, the scripts inside the <SCRIPT></SCRIPT> blocks are piped
4246 # into an interpreter.
4247 # The code presented here is fairly confusing because it
4248 # actually writes perl code code to the output.
4250 # A table with the file handles
4251 %SCRIPTINGINPUT = ();
4253 # A function to clean up Client delivered CGI parameter values
4254 # (i.e., quote all odd characters)
4255 %SHRUBcharacterTR =
4257 "\'" => '&#39;',
4258 "\`" => '&#96;',
4259 "\"" => '&quot;',
4260 '&' => '&amper;',
4261 "\\" => '&#92;'
4264 sub shrubCGIparameter # ($String) -> Cleaned string
4266 my $String = shift || "";
4268 # Change all quotes [`'"] into HTML character entities
4269 my ($Char, $Transcript) = ('&', $SHRUBcharacterTR{'&'});
4271 # Protect &
4272 $String =~ s/\Q$Char\E/$Transcript/isg if $Transcript;
4274 while( ($Char, $Transcript) = each %SHRUBcharacterTR)
4276 next if $Char eq '&';
4277 $String =~ s/\Q$Char\E/$Transcript/isg;
4280 # Replace newlines
4281 $String =~ s/[\n]/\\n/g;
4282 # Replace control characters with their backslashed octal ordinal numbers
4283 $String =~ s/([^\S \t])/(sprintf("\\0%o", ord($1)))/eisg; #
4284 $String =~ s/([\x00-\x08\x0A-\x1F])/(sprintf("\\0%o", ord($1)))/eisg; #
4286 return $String;
4290 # The initial open statements: Open a pipe to the foreign script interpreter
4291 sub OpenForeignScript # ($ContentType) -> $DirectivePrefix
4293 my $ContentType = lc(shift) || return "";
4294 my $NewDirective = "";
4296 return $NewDirective if($SCRIPTINGINPUT{$ContentType});
4298 # Construct a unique file handle name
4299 $SCRIPTINGFILEHANDLE = uc($ContentType);
4300 $SCRIPTINGFILEHANDLE =~ s/\W/\_/isg;
4301 $SCRIPTINGINPUT{$ContentType} = $SCRIPTINGFILEHANDLE
4302 unless $SCRIPTINGINPUT{$ContentType};
4304 # Create the relevant script: Open the pipe to the interpreter
4305 $NewDirective .= <<"BLOCKCGISCRIPTOROPEN";
4306 # Open interpreter for '$ContentType'
4307 # Open pipe to interpreter (if it isn't open already)
4308 open($SCRIPTINGINPUT{$ContentType}, "|$ScriptingLanguages{$ContentType}") || main::dieHandler(14, "$ContentType: \$!\\n");
4309 BLOCKCGISCRIPTOROPEN
4311 # Insert Initialization code and CGI variables
4312 $NewDirective .= InitializeForeignScript($ContentType);
4314 # Ready
4315 return $NewDirective;
4319 # The final closing code to stop the interpreter
4320 sub CloseForeignScript # ($ContentType) -> $DirectivePrefix
4322 my $ContentType = lc(shift) || return "";
4323 my $NewDirective = "";
4325 # Do nothing unless the pipe realy IS open
4326 return "" unless $SCRIPTINGINPUT{$ContentType};
4328 # Initial comment
4329 $NewDirective .= "\# Close interpreter for '$ContentType'\n";
4332 # Write the Postfix code
4333 $NewDirective .= CleanupForeignScript($ContentType);
4335 # Create the relevant script: Close the pipe to the interpreter
4336 $NewDirective .= <<"BLOCKCGISCRIPTORCLOSE";
4337 close($SCRIPTINGINPUT{$ContentType}) || main::dieHandler(15, \"$ContentType: \$!\\n\");
4338 select(STDOUT); \$|=1;
4340 BLOCKCGISCRIPTORCLOSE
4342 # Remove the file handler of the foreign script
4343 delete($SCRIPTINGINPUT{$ContentType});
4345 return $NewDirective;
4349 # The initialization code for the foreign script interpreter
4350 sub InitializeForeignScript # ($ContentType) -> $DirectivePrefix
4352 my $ContentType = lc(shift) || return "";
4353 my $NewDirective = "";
4355 # Add initialization code
4356 if($ScriptingInitialization{$ContentType})
4358 $NewDirective .= <<"BLOCKCGISCRIPTORINIT";
4359 # Initialization Code for '$ContentType'
4360 # Select relevant output filehandle
4361 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4363 # The Initialization code (if any)
4364 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}INITIALIZATIONCODE';
4365 $ScriptingInitialization{$ContentType}
4366 ${ContentType}INITIALIZATIONCODE
4368 BLOCKCGISCRIPTORINIT
4371 # Add all CGI variables defined
4372 if(exists($ScriptingCGIvariables{$ContentType}))
4374 # Start writing variable definitions to the Interpreter
4375 if($ScriptingCGIvariables{$ContentType})
4377 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEF";
4378 # CGI variables (from the %default_values table)
4379 print $SCRIPTINGINPUT{$ContentType} << '${ContentType}CGIVARIABLES';
4380 BLOCKCGISCRIPTORVARDEF
4383 my ($N, $V);
4384 foreach $N (keys(%default_values))
4386 # Determine whether the parameter has been defined
4387 # (the eval is a workaround to get at the variable value)
4388 next unless eval("defined(\$CGIexecute::$N)");
4390 # Get the value from the EXECUTION environment
4391 $V = eval("\$CGIexecute::$N");
4392 # protect control characters (i.e., convert them to \0.. form)
4393 $V = shrubCGIparameter($V);
4395 # Protect interpolated variables
4396 eval("\$CGIexecute::$N = '$V';") unless $ScriptingCGIvariables{$ContentType};
4398 # Print the actual declaration for this scripting language
4399 if($ScriptingCGIvariables{$ContentType})
4401 $NewDirective .= sprintf($ScriptingCGIvariables{$ContentType}, $N, $V);
4402 $NewDirective .= "\n";
4406 # Stop writing variable definitions to the Interpreter
4407 if($ScriptingCGIvariables{$ContentType})
4409 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEFEND";
4410 ${ContentType}CGIVARIABLES
4411 BLOCKCGISCRIPTORVARDEFEND
4416 $NewDirective .= << "BLOCKCGISCRIPTOREND";
4418 # Select STDOUT filehandle
4419 select(STDOUT); \$|=1;
4421 BLOCKCGISCRIPTOREND
4423 return $NewDirective;
4427 # The cleanup code for the foreign script interpreter
4428 sub CleanupForeignScript # ($ContentType) -> $DirectivePrefix
4430 my $ContentType = lc(shift) || return "";
4431 my $NewDirective = "";
4433 # Return if not needed
4434 return $NewDirective unless $ScriptingCleanup{$ContentType};
4436 # Create the relevant script: Open the pipe to the interpreter
4437 $NewDirective .= <<"BLOCKCGISCRIPTORSTOP";
4438 # Cleanup Code for '$ContentType'
4439 # Select relevant output filehandle
4440 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4441 # Print Cleanup code to foreign script
4442 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}SCRIPTSTOP';
4443 $ScriptingCleanup{$ContentType}
4444 ${ContentType}SCRIPTSTOP
4446 # Select STDOUT filehandle
4447 select(STDOUT); \$|=1;
4448 BLOCKCGISCRIPTORSTOP
4450 return $NewDirective;
4454 # The prefix code for each <script></script> block
4455 sub PrefixForeignScript # ($ContentType) -> $DirectivePrefix
4457 my $ContentType = lc(shift) || return "";
4458 my $NewDirective = "";
4460 # Return if not needed
4461 return $NewDirective unless $ScriptingPrefix{$ContentType};
4463 my $Quote = "\'";
4464 # If the CGIvariables parameter is defined, but empty, interpolate
4465 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4466 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4467 !$ScriptingCGIvariables{$ContentType};
4469 # Add initialization code
4470 $NewDirective .= <<"BLOCKCGISCRIPTORPREFIX";
4471 # Prefix Code for '$ContentType'
4472 # Select relevant output filehandle
4473 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4475 # The block Prefix code (if any)
4476 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}PREFIXCODE$Quote;
4477 $ScriptingPrefix{$ContentType}
4478 ${ContentType}PREFIXCODE
4479 # Select STDOUT filehandle
4480 select(STDOUT); \$|=1;
4481 BLOCKCGISCRIPTORPREFIX
4483 return $NewDirective;
4487 # The postfix code for each <script></script> block
4488 sub PostfixForeignScript # ($ContentType) -> $DirectivePrefix
4490 my $ContentType = lc(shift) || return "";
4491 my $NewDirective = "";
4493 # Return if not needed
4494 return $NewDirective unless $ScriptingPostfix{$ContentType};
4496 my $Quote = "\'";
4497 # If the CGIvariables parameter is defined, but empty, interpolate
4498 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4499 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4500 !$ScriptingCGIvariables{$ContentType};
4502 # Create the relevant script: Open the pipe to the interpreter
4503 $NewDirective .= <<"BLOCKCGISCRIPTORPOSTFIX";
4504 # Postfix Code for '$ContentType'
4505 # Select filehandle to interpreter
4506 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4507 # Print postfix code to foreign script
4508 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SCRIPTPOSTFIX$Quote;
4509 $ScriptingPostfix{$ContentType}
4510 ${ContentType}SCRIPTPOSTFIX
4511 # Select STDOUT filehandle
4512 select(STDOUT); \$|=1;
4513 BLOCKCGISCRIPTORPOSTFIX
4515 return $NewDirective;
4518 sub InsertForeignScript # ($ContentType, $directive, @SRCfile) -> $NewDirective
4520 my $ContentType = lc(shift) || return "";
4521 my $directive = shift || return "";
4522 my @SRCfile = @_;
4523 my $NewDirective = "";
4525 my $Quote = "\'";
4526 # If the CGIvariables parameter is defined, but empty, interpolate
4527 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4528 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4529 !$ScriptingCGIvariables{$ContentType};
4531 # Create the relevant script
4532 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4533 # Insert Code for '$ContentType'
4534 # Select filehandle to interpreter
4535 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4536 BLOCKCGISCRIPTORINSERT
4538 # Use SRC feature files
4539 my $ThisSRCfile;
4540 while($ThisSRCfile = shift(@_))
4542 # Handle blocks
4543 if($ThisSRCfile =~ /^\s*\{\s*/)
4545 my $Block = $';
4546 $Block = $` if $Block =~ /\s*\}\s*$/;
4547 $NewDirective .= <<"BLOCKCGISCRIPTORSRCBLOCK";
4548 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SRCBLOCKCODE$Quote;
4549 $Block
4550 ${ContentType}SRCBLOCKCODE
4551 BLOCKCGISCRIPTORSRCBLOCK
4553 next;
4556 # Handle files
4557 $NewDirective .= <<"BLOCKCGISCRIPTORSRCFILES";
4558 # Read $ThisSRCfile
4559 open(SCRIPTINGSOURCE, "<$ThisSRCfile") || main::dieHandler(16, "$ThisSRCfILE: \$!");
4560 while(<SCRIPTINGSOURCE>)
4562 print $SCRIPTINGINPUT{$ContentType} \$_;
4564 close(SCRIPTINGSOURCE);
4566 BLOCKCGISCRIPTORSRCFILES
4570 # Add the directive
4571 if($directive)
4573 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4574 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}DIRECTIVECODE$Quote;
4575 $directive
4576 ${ContentType}DIRECTIVECODE
4577 BLOCKCGISCRIPTORINSERT
4581 $NewDirective .= <<"BLOCKCGISCRIPTORSELECT";
4582 # Select STDOUT filehandle
4583 select(STDOUT); \$|=1;
4584 BLOCKCGISCRIPTORSELECT
4586 # Ready
4587 return $NewDirective;
4590 sub CloseAllForeignScripts # Call CloseForeignScript on all open scripts
4592 my $ContentType;
4593 foreach $ContentType (keys(%SCRIPTINGINPUT))
4595 my $directive = CloseForeignScript($ContentType);
4596 print STDERR "\nDirective $CGI_Date: ", $directive;
4597 CGIexecute->evaluate($directive);
4602 # End of handling foreign (external) scripting languages.
4604 ############################################################################
4606 # A subroutine to handle "nested" quotes, it cuts off the leading
4607 # item or quoted substring
4608 # E.g.,
4609 # ' A_word and more words' -> @('A_word', ' and more words')
4610 # '"quoted string" The rest' -> @('quoted string', ' The rest')
4611 # (this is needed for parsing the <TAGS> and their attributes)
4612 my $SupportedQuotes = "\'\"\`\(\{\[";
4613 my %QuotePairs = ('('=>')','['=>']','{'=>'}'); # Brackets
4614 sub ExtractQuotedItem # ($String) -> @($QuotedString, $RestOfString)
4616 my @Result = ();
4617 my $String = shift || return @Result;
4619 if($String =~ /^\s*([\w\/\-\.]+)/is)
4621 push(@Result, $1, $');
4623 elsif($String =~ /^\s*(\\?)([\Q$SupportedQuotes\E])/is)
4625 my $BackSlash = $1 || "";
4626 my $OpenQuote = $2;
4627 my $CloseQuote = $OpenQuote;
4628 $CloseQuote = $QuotePairs{$OpenQuote} if $QuotePairs{$OpenQuote};
4630 if($BackSlash)
4632 $String =~ /^\s*\\\Q$OpenQuote\E/i;
4633 my $Onset = $';
4634 $Onset =~ /\\\Q$CloseQuote\E/i;
4635 my $Rest = $';
4636 my $Item = $`;
4637 push(@Result, $Item, $Rest);
4640 else
4642 $String =~ /^\s*\Q$OpenQuote\E([^\Q$CloseQuote\E]*)\Q$CloseQuote\E/i;
4643 push(@Result, $1, $');
4646 else
4648 push(@Result, "", $String);
4650 return @Result;
4653 # Now, start with the real work
4655 # Control the output of the Content-type: text/html\n\n message
4656 my $SupressContentType = 0;
4658 # Process a file
4659 sub ProcessFile # ($file_path)
4661 my $file_path = shift || return 0;
4664 # Generate a unique file handle (for recursions)
4665 my @SRClist = ();
4666 my $FileHandle = "file";
4667 my $n = 0;
4668 while(!eof($FileHandle.$n)) {++$n;};
4669 $FileHandle .= $n;
4671 # Start HTML output
4672 # Use the default Content-type if this is NOT a raw file
4673 unless(($RawFilePattern && $ENV{'PATH_INFO'} =~ m@($RawFilePattern)$@i)
4674 || $SupressContentType)
4676 $ENV{'PATH_INFO'} =~ m@($FilePattern)$@i;
4677 my $ContentType = $ContentTypeTable{$1};
4678 print "Content-type: $ContentType\n";
4679 if(%SETCOOKIELIST && keys(%SETCOOKIELIST))
4681 foreach my $name (keys(%SETCOOKIELIST))
4683 my $value = $SETCOOKIELIST{$name};
4684 print "Set-Cookie: $name=$value\n";
4686 # Cookies are set only ONCE
4687 %SETCOOKIELIST = ();
4689 print "\n";
4690 $SupressContentType = 1; # Content type has been printed
4694 # Get access to the actual data. This can be from RAM (by way of an
4695 # environment variable) or by opening a file.
4697 # Handle the use of RAM images (file-data is stored in the
4698 # $CGI_FILE_CONTENTS environment variable)
4699 # Note that this environment variable will be cleared, i.e., it is strictly for
4700 # single-use only!
4701 if($ENV{$CGI_FILE_CONTENTS})
4703 # File has been read already
4704 $_ = $ENV{$CGI_FILE_CONTENTS};
4705 # Sorry, you have to do the reading yourself (dynamic document creation?)
4706 # NOTE: you must read the whole document at once
4707 if($_ eq '-')
4709 $_ = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
4711 else # Clear environment variable
4713 $ENV{$CGI_FILE_CONTENTS} = '-';
4716 # Open Only PLAIN TEXT files (or STDIN) and NO executable files (i.e., scripts).
4717 # THIS IS A SECURITY FEATURE!
4718 elsif($file_path eq '-' || (-e "$file_path" && -r _ && -T _ && -f _ && ! (-x _ || -X _) ))
4720 open($FileHandle, $file_path) || dieHandler(17, "<h2>File not found</h2>\n");
4721 push(@OpenFiles, $file_path);
4722 $_ = <$FileHandle>; # Read first line
4724 else
4726 print "<h2>File not found</h2>\n";
4727 dieHandler(18, "$file_path\n");
4730 $| = 1; # Flush output buffers
4732 # Initialize variables
4733 my $METAarguments = ""; # The CGI arguments from the latest META tag
4734 my @METAvalues = (); # The ''-quoted CGI values from the latest META tag
4735 my $ClosedTag = 0; # <TAG> </TAG> versus <TAG/>
4738 # Send document to output
4739 # Process the requested document.
4740 # Do a loop BEFORE reading input again (this catches the RAM/Database
4741 # type of documents).
4742 do {
4745 # Handle translations if needed
4747 performTranslation(\$_) if $TranslationPaths;
4749 # Catch <SCRIPT LANGUAGE="PERL" TYPE="text/ssperl" > directives in $_
4750 # There can be more than 1 <SCRIPT> or META tags on a line
4751 while(/\<\s*(SCRIPT|META|DIV|INS)\s/is)
4753 my $directive = "";
4754 # Store rest of line
4755 my $Before = $`;
4756 my $ScriptTag = $&;
4757 my $After = $';
4758 my $TagType = uc($1);
4759 # The before part can be send to the output
4760 print $Before;
4762 # Read complete Tag from after and/or file
4763 until($After =~ /([^\\])\>/)
4765 $After .= <$FileHandle>;
4766 performTranslation(\$After) if $TranslationPaths;
4769 if($After =~ /([^\\])\>/)
4771 $ScriptTag .= $`.$&; # Keep the Script Tag intact
4772 $After = $';
4774 else
4776 dieHandler(19, "Closing > not found\n");
4779 # The tag could be closed by />, we handle this in the XML way
4780 # and don't process any content (we ignore whitespace)
4781 $ClosedTag = ($ScriptTag =~ m@[^\\]/\s*\>\s*$@) ? 1 : 0;
4784 # TYPE or CLASS?
4785 my $TypeName = ($TagType =~ /META/is) ? "CONTENT" : "TYPE";
4786 $TypeName = "CLASS" if $TagType eq 'DIV' || $TagType eq 'INS';
4788 # Parse <SCRIPT> or <META> directive
4789 # If NOT (TYPE|CONTENT)="text/ssperl" (i.e., $ServerScriptContentType),
4790 # send the line to the output and go to the next loop
4791 my $CurrentContentType = "";
4792 if($ScriptTag =~ /(^|\s)$TypeName\s*=\s*/is)
4794 my ($Type) = ExtractQuotedItem($');
4795 $Type =~ /^\s*([\w\/\-]+)\s*[\,\;]?/;
4796 $CurrentContentType = lc($1); # Note: mime-types are "case-less"
4797 # CSS classes are aliases of $ServerScriptContentType
4798 if($TypeName eq "CLASS" && $CurrentContentType eq $ServerScriptContentClass)
4800 $CurrentContentType = $ServerScriptContentType;
4805 # Not a known server-side content type, print and continue
4806 unless(($CurrentContentType =~
4807 /$ServerScriptContentType|$ShellScriptContentType/is) ||
4808 $ScriptingLanguages{$CurrentContentType})
4810 print $ScriptTag;
4811 $_ = $After;
4812 next;
4816 # A known server-side content type, evaluate
4818 # First, handle \> and \<
4819 $ScriptTag =~ s/\\\>/\>/isg;
4820 $ScriptTag =~ s/\\\</\</isg;
4822 # Extract the CGI, SRC, ID, IF and UNLESS attributes
4823 my %ScriptTagAttributes = ();
4824 while($ScriptTag =~ /(^|\s)(CGI|IF|UNLESS|SRC|ID)\s*=\s*/is)
4826 my $Attribute = $2;
4827 my $Rest = $';
4828 my $Value = "";
4829 ($Value, $ScriptTag) = ExtractQuotedItem($Rest);
4830 $ScriptTagAttributes{uc($Attribute)} = $Value;
4834 # The attribute used to define the CGI variables
4835 # Extract CGI-variables from
4836 # <META CONTENT="text/ssperl; CGI='' SRC=''">
4837 # <SCRIPT TYPE='text/ssperl' CGI='' SRC=''>
4838 # <DIV CLASS='ssperl' CGI='' SRC='' ID=""> tags
4839 # <INS CLASS='ssperl' CGI='' SRC='' ID=""> tags
4840 if($ScriptTagAttributes{'CGI'})
4842 @ARGV = (); # Reset ARGV
4843 $ARGC = 0;
4844 $METAarguments = ""; # Reset the META CGI arguments
4845 @METAvalues = ();
4846 my $Meta_CGI = $ScriptTagAttributes{'CGI'};
4848 # Process default values of variables ($<name> = 'default value')
4849 # Allowed quotes are '', "", ``, (), [], and {}
4850 while($Meta_CGI =~ /(^\s*|[^\\])([\$\@\%]?)([\w\-]+)\s*/is)
4852 my $varType = $2 || '$'; # Variable or list
4853 my $name = $3; # The Name
4854 my $default = "";
4855 $Meta_CGI = $';
4857 if($Meta_CGI =~ /^\s*\=\s*/is)
4859 # Locate (any) default value
4860 ($default, $Meta_CGI) = ExtractQuotedItem($'); # Cut the parameter from the CGI
4862 $RemainingTag = $Meta_CGI;
4865 # Define CGI (or ENV) variable, initalize it from the
4866 # Query string or the default value
4868 # Also construct the @ARGV and @_ arrays. This allows other (SRC=) Perl
4869 # scripts to access the CGI arguments defined in the META tag
4870 # (Not for CGI inside <SCRIPT> tags)
4871 if($varType eq '$')
4873 CGIexecute::defineCGIvariable($name, $default)
4874 || dieHandler(20, "INVALID CGI name/value pair ($name, $default)\n");
4875 push(@METAvalues, "'".${"CGIexecute::$name"}."'");
4876 # Add value to the @ARGV list
4877 push(@ARGV, ${"CGIexecute::$name"});
4878 ++$ARGC;
4880 elsif($varType eq '@')
4882 CGIexecute::defineCGIvariableList($name, $default)
4883 || dieHandler(21, "INVALID CGI name/value list pair ($name, $default)\n");
4884 push(@METAvalues, "'".join("'", @{"CGIexecute::$name"})."'");
4885 # Add value to the @ARGV list
4886 push(@ARGV, @{"CGIexecute::$name"});
4887 $ARGC = scalar(@CGIexecute::ARGV);
4889 elsif($varType eq '%')
4891 CGIexecute::defineCGIvariableHash($name, $default)
4892 || dieHandler(22, "INVALID CGI name/value hash pair ($name, $default)\n");
4893 my @PairList = map {"$_ => ".${"CGIexecute::$name"}{$_}} keys(%{"CGIexecute::$name"});
4894 push(@METAvalues, "'".join("'", @PairList)."'");
4895 # Add value to the @ARGV list
4896 push(@ARGV, %{"CGIexecute::$name"});
4897 $ARGC = scalar(@CGIexecute::ARGV);
4900 # Store the values for internal and later use
4901 $METAarguments .= "$varType".$name.","; # A string of CGI variable names
4903 push(@METAvalues, "\'".eval("\"$varType\{CGIexecute::$name\}\"")."\'"); # ALWAYS add '-quotes around values
4908 # The IF (conditional execution) Attribute
4909 # Evaluate the condition and stop unless it evaluates to true
4910 if($ScriptTagAttributes{'IF'})
4912 my $IFcondition = $ScriptTagAttributes{'IF'};
4914 # Convert SCRIPT calls, ./<script>
4915 $IFcondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
4917 # Convert FILE calls, ~/<file>
4918 $IFcondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
4920 # Block execution if necessary
4921 unless(CGIexecute->evaluate($IFcondition))
4923 %ScriptTagAttributes = ();
4924 $CurrentContentType = "";
4928 # The UNLESS (conditional execution) Attribute
4929 # Evaluate the condition and stop if it evaluates to true
4930 if($ScriptTagAttributes{'UNLESS'})
4932 my $UNLESScondition = $ScriptTagAttributes{'UNLESS'};
4934 # Convert SCRIPT calls, ./<script>
4935 $UNLESScondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
4937 # Convert FILE calls, ~/<file>
4938 $UNLESScondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
4940 # Block execution if necessary
4941 if(CGIexecute->evaluate($UNLESScondition))
4943 %ScriptTagAttributes = ();
4944 $CurrentContentType = "";
4948 # The SRC (Source File) Attribute
4949 # Extract any source script files and add them in
4950 # front of the directive
4951 # The SRC list should be emptied
4952 @SRClist = ();
4953 my $SRCtag = "";
4954 my $Prefix = 1;
4955 my $PrefixDirective = "";
4956 my $PostfixDirective = "";
4957 # There is a SRC attribute
4958 if($ScriptTagAttributes{'SRC'})
4960 $SRCtag = $ScriptTagAttributes{'SRC'};
4961 # Remove "file://" prefixes
4962 $SRCtag =~ s@([^\w\/\\]|^)file\://([^\s\/\@\=])@$1$2@gis;
4963 # Expand script filenames "./Script"
4964 $SRCtag =~ s@([^\w\/\\]|^)\./([^\s\/\@\=])@$1$SCRIPT_SUB/$2@gis;
4965 # Expand script filenames "~/Script"
4966 $SRCtag =~ s@([^\w\/\\]|^)\~/([^\s\/\@\=])@$1$HOME_SUB/$2@gis;
4969 # File source tags
4970 while($SRCtag =~ /\S/is)
4972 my $SRCdirective = "";
4974 # Pseudo file, just a switch to go from PREFIXING to POSTFIXING
4975 # SRC files
4976 if($SRCtag =~ /^[\s\;\,]*(POSTFIX|PREFIX)([^$FileAllowedChars]|$)/is)
4978 my $InsertionPlace = $1;
4979 $SRCtag = $2.$';
4981 $Prefix = $InsertionPlace =~ /POSTFIX/i ? 0 : 1;
4982 # Go to next round
4983 next;
4985 # {}-blocks are just evaluated by "do"
4986 elsif($SRCtag =~ /^[\s\;\,]*\{/is)
4988 my $SRCblock = $';
4989 if($SRCblock =~ /\}[\s\;\,]*([^\}]*)$/is)
4991 $SRCblock = $`;
4992 $SRCtag = $1.$';
4993 # SAFEqx shell script blocks
4994 if($CurrentContentType =~ /$ShellScriptContentType/is)
4996 # Handle ''-quotes inside the script
4997 $SRCblock =~ s/[\']/\\$&/gis;
4999 $SRCblock = "print do { SAFEqx(\'".$SRCblock."\'); };'';";
5000 $SRCdirective .= $SRCblock."\n";
5002 # do { SRCblocks }
5003 elsif($CurrentContentType =~ /$ServerScriptContentType/is)
5005 $SRCblock = "print do { $SRCblock };'';";
5006 $SRCdirective .= $SRCblock."\n";
5008 else # The interpreter should handle this
5010 push(@SRClist, "{ $SRCblock }");
5014 else
5015 { dieHandler(23, "Closing \} missing\n");};
5017 # Files are processed as Text or Executable files
5018 elsif($SRCtag =~ /[\s\;\,]*([$FileAllowedChars]+)[\;\,\s]*/is)
5020 my $SrcFile = $1;
5021 $SRCtag = $';
5023 # We are handling one of the external interpreters
5024 if($ScriptingLanguages{$CurrentContentType})
5026 push(@SRClist, $SrcFile);
5028 # We are at the start of a DIV tag, just load all SRC files and/or URL's
5029 elsif($TagType eq 'DIV' || $TagType eq 'INS') # All files are prepended in DIV's
5031 # $SrcFile is a URL pointing to an HTTP or FTP server
5032 if($SrcFile =~ m!^([a-z]+)\://!)
5034 my $URLoutput = CGIscriptor::read_url($SrcFile);
5035 $SRCdirective .= $URLoutput;
5037 # SRC file is an existing file
5038 elsif(-e "$SrcFile")
5040 open(DIVSOURCE, "<$SrcFile") || dieHandler(24, "<$SrcFile: $!\n");
5041 my $Content;
5042 while(sysread(DIVSOURCE, $Content, 1024) > 0)
5044 $SRCdirective .= $Content;
5046 close(DIVSOURCE);
5049 # Executable files are executed as
5050 # `$SrcFile 'ARGV[0]' 'ARGV[1]'`
5051 elsif(-x "$SrcFile")
5053 $SRCdirective .= "print \`$SrcFile @METAvalues\`;'';\n";
5055 # Handle 'standard' files, using ProcessFile
5056 elsif((-T "$SrcFile" || $ENV{$CGI_FILE_CONTENTS})
5057 && $SrcFile =~ m@($FilePattern)$@) # A recursion
5060 # Do not process still open files because it can lead
5061 # to endless recursions
5062 if(grep(/^$SrcFile$/, @OpenFiles))
5063 { dieHandler(25, "$SrcFile allready opened (endless recursion)\n")};
5064 # Prepare meta arguments
5065 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5066 # Process the file
5067 $SRCdirective .= "main::ProcessFile(\'$SrcFile\');'';\n";
5069 elsif($SrcFile =~ m!^([a-z]+)\://!) # URL's are loaded and printed
5071 $SRCdirective .= GET_URL($SrcFile);
5073 elsif(-T "$SrcFile") # Textfiles are "do"-ed (Perl execution)
5075 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5076 $SRCdirective .= "do \'$SrcFile\';'';\n";
5078 else # This one could not be resolved (should be handled by BinaryMapFile)
5080 $SRCdirective .= 'print "'.$SrcFile.' cannot be used"'."\n";
5085 # Postfix or Prefix
5086 if($Prefix)
5088 $PrefixDirective .= $SRCdirective;
5090 else
5092 $PostfixDirective .= $SRCdirective;
5095 # The prefix should be handled immediately
5096 $directive .= $PrefixDirective;
5097 $PrefixDirective = "";
5101 # Handle the content of the <SCRIPT></SCRIPT> tags
5102 # Do not process the content of <SCRIPT/>
5103 if($TagType =~ /SCRIPT/is && !$ClosedTag) # The <SCRIPT> TAG
5105 my $EndScriptTag = "";
5107 # Execute SHELL scripts with SAFEqx()
5108 if($CurrentContentType =~ /$ShellScriptContentType/is)
5110 $directive .= "SAFEqx(\'";
5113 # Extract Program
5114 while($After !~ /\<\s*\/SCRIPT[^\>]*\>/is && !eof($FileHandle))
5116 $After .= <$FileHandle>;
5117 performTranslation(\$After) if $TranslationPaths;
5120 if($After =~ /\<\s*\/SCRIPT[^\>]*\>/is)
5122 $directive .= $`;
5123 $EndScriptTag = $&;
5124 $After = $';
5126 else
5128 dieHandler(26, "Missing </SCRIPT> end tag in $ENV{'PATH_INFO'}\n");
5131 # Process only when content should be executed
5132 if($CurrentContentType)
5135 # Remove all comments from Perl scripts
5136 # (NOT from OS shell scripts)
5137 $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
5138 if $CurrentContentType =~ /$ServerScriptContentType/i;
5140 # Convert SCRIPT calls, ./<script>
5141 $directive =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5143 # Convert FILE calls, ~/<file>
5144 $directive =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5146 # Execute SHELL scripts with SAFEqx(), closing bracket
5147 if($CurrentContentType =~ /$ShellScriptContentType/i)
5149 # Handle ''-quotes inside the script
5150 $directive =~ /SAFEqx\(\'/;
5151 $directive = $`.$&;
5152 my $Executable = $';
5153 $Executable =~ s/[\']/\\$&/gs;
5155 $directive .= $Executable."\');"; # Closing bracket
5158 else
5160 $directive = "";
5163 # Handle the content of the <DIV></DIV> tags
5164 # Do not process the content of <DIV/>
5165 elsif(($TagType eq 'DIV' || $TagType eq 'INS') && !$ClosedTag) # The <DIV> TAGs
5167 my $EndScriptTag = "";
5169 # Extract Text
5170 while($After !~ /\<\s*\/$TagType[^\>]*\>/is && !eof($FileHandle))
5172 $After .= <$FileHandle>;
5173 performTranslation(\$After) if $TranslationPaths;
5176 if($After =~ /\<\s*\/$TagType[^\>]*\>/is)
5178 $directive .= $`;
5179 $EndScriptTag = $&;
5180 $After = $';
5182 else
5184 dieHandler(27, "Missing </$TagType> end tag in $ENV{'PATH_INFO'}\n");
5187 # Add the Postfixed directives (but only when it contains something printable)
5188 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5189 $PostfixDirective = "";
5192 # Process only when content should be handled
5193 if($CurrentContentType)
5196 # Get the name (ID), and clean it (i.e., remove anything that is NOT part of
5197 # a valid Perl name). Names should not contain $, but we can handle it.
5198 my $name = $ScriptTagAttributes{'ID'};
5199 $name =~ /^\s*[\$\@\%]?([\w\-]+)/;
5200 $name = $1;
5202 # Assign DIV contents to $NAME value OUTSIDE the CGI values!
5203 CGIexecute::defineCGIexecuteVariable($name, $directive);
5204 $directive = "";
5207 # Nothing to execute
5208 $directive = "";
5212 # Handle Foreign scripting languages
5213 if($ScriptingLanguages{$CurrentContentType})
5215 my $newDirective = "";
5216 $newDirective .= OpenForeignScript($CurrentContentType); # Only if not already done
5217 $newDirective .= PrefixForeignScript($CurrentContentType);
5218 $newDirective .= InsertForeignScript($CurrentContentType, $directive, @SRClist);
5219 $newDirective .= PostfixForeignScript($CurrentContentType);
5220 $newDirective .= CloseForeignScript($CurrentContentType); # This shouldn't be necessary
5222 $newDirective .= '"";';
5224 $directive = $newDirective;
5228 # Add the Postfixed directives (but only when it contains something printable)
5229 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5230 $PostfixDirective = "";
5233 # EXECUTE the script and print the results
5235 # Use this to debug the program
5236 # print STDERR "Directive $CGI_Date: \n", $directive, "\n\n";
5238 my $Result = CGIexecute->evaluate($directive) if $directive; # Evaluate as PERL code
5239 $Result =~ s/\n$//g; # Remove final newline
5241 # Print the Result of evaluating the directive
5242 # (this will handle LARGE, >64 kB output)
5243 my $BytesWritten = 1;
5244 while($Result && $BytesWritten)
5246 $BytesWritten = syswrite(STDOUT, $Result, 64);
5247 $Result = substr($Result, $BytesWritten);
5249 # print $Result; # Could be used instead of above code
5251 # Store result if wanted, i.e., if $CGIscriptorResults has been
5252 # defined in a <META> tag.
5253 push(@CGIexecute::CGIscriptorResults, $Result)
5254 if exists($default_values{'CGIscriptorResults'});
5256 # Process the rest of the input line (this could contain
5257 # another directive)
5258 $_ = $After;
5260 print $_;
5261 } while(<$FileHandle>); # Read and Test AFTER first loop!
5263 close ($FileHandle);
5264 dieHandler(28, "Error in recursion\n") unless pop(@OpenFiles) == $file_path;
5268 ###############################################################################
5270 # Call the whole package
5272 sub Handle_Request
5274 my $file_path = "";
5276 # Initialization Code
5277 Initialize_Request();
5279 # SECURITY: ACCESS CONTROL
5280 Access_Control();
5282 # Read the POST part of the query, if there is one
5283 Get_POST_part_of_query();
5285 # Start (HTML) output and logging
5286 $file_path = Initialize_output();
5288 # Check login access or divert to login procedure
5289 $Use_Login = Log_In_Access();
5290 $file_path = $Use_Login if $Use_Login;
5292 # Record which files are still open (to avoid endless recursions)
5293 my @OpenFiles = ();
5295 # Record whether the default HTML ContentType has already been printed
5296 # but only if the SERVER uses HTTP or some other protocol that might interpret
5297 # a content MIME type.
5299 $SupressContentType = !("$ENV{'SERVER_PROTOCOL'}" =~ /($ContentTypeServerProtocols)/i);
5301 # Process the specified file
5302 ProcessFile($file_path) if $file_path ne $SS_PUB;
5304 # Cleanup all open external (foreign) interpreters
5305 CloseAllForeignScripts();
5308 "" # SUCCESS
5311 # Make a single call to handle an (empty) request
5312 Handle_Request();
5315 # END OF PACKAGE MAIN
5318 ####################################################################################
5320 # The CGIEXECUTE PACKAGE
5322 ####################################################################################
5324 # Isolate the evaluation of directives as PERL code from the rest of the program.
5325 # Remember that each package has its own name space.
5326 # Note that only the FIRST argument of execute->evaluate is actually evaluated,
5327 # all other arguments are accessible inside the first argument as $_[0] to $_[$#_].
5329 package CGIexecute;
5331 sub evaluate
5333 my $self = shift;
5334 my $directive = shift;
5335 $directive = eval($directive);
5336 warn $@ if $@; # Write an error message to STDERR
5337 $directive; # Return value of directive
5341 # defineCGIexecuteVariable($name [, $value]) -> 0/1
5343 # Define and intialize variables inside CGIexecute
5344 # Does no sanity checking, for internal use only
5346 sub defineCGIexecuteVariable # ($name [, $value]) -> 0/1
5348 my $name = shift || return 0; # The Name
5349 my $value = shift || ""; # The value
5351 ${$name} = $value;
5353 return 1;
5356 # Protect certain CGI variables values when set internally
5357 # If not defined internally, there will be no variable set AT ALL
5358 my %CGIprotectedVariable = ();
5359 sub ProtectCGIvariable # ($name) -> 0/1
5361 my $name = shift || "";
5362 return 0 unless $name && $name =~ /\w/;
5364 ++$CGIprotectedVariable{$name};
5366 return $CGIprotectedVariable{$name};
5369 # defineCGIvariable($name [, $default]) -> 0/1
5371 # Define and intialize CGI variables
5372 # Tries (in order) $ENV{$name}, the Query string and the
5373 # default value.
5374 # Removes all '-quotes etc.
5376 sub defineCGIvariable # ($name [, $default]) -> 0/1
5378 my $name = shift || return 0; # The Name
5379 my $default = shift || ""; # The default value
5381 # Protect variables set internally
5382 return 1 if !$name || exists($CGIprotectedVariable{$name});
5384 # Remove \-quoted characters
5385 $default =~ s/\\(.)/$1/g;
5386 # Store default values
5387 $::default_values{$name} = $default if $default;
5389 # Process variables
5390 my $temp = undef;
5391 # If there is a user supplied value, it replaces the
5392 # default value.
5394 # Environment values have precedence
5395 if(exists($ENV{$name}))
5397 $temp = $ENV{$name};
5399 # Get name and its value from the query string
5400 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5402 $temp = ::YOUR_CGIPARSE($name);
5404 # Defined values must exist for security
5405 elsif(!exists($::default_values{$name}))
5407 $::default_values{$name} = undef;
5410 # SECURITY, do not allow '- and `-quotes in
5411 # client values.
5412 # Remove all existing '-quotes
5413 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5414 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5415 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5416 # If $temp is empty, use the default value (if it exists)
5417 unless($temp =~ /\S/ || length($temp) > 0) # I.e., $temp is empty
5419 $temp = $::default_values{$name};
5420 # Remove all existing '-quotes
5421 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5422 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5423 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5425 else # Store current CGI values and remove defaults
5427 $::default_values{$name} = $temp;
5429 # Define the CGI variable and its value (in the execute package)
5430 ${$name} = $temp;
5432 # return SUCCES
5433 return 1;
5436 sub defineCGIvariableList # ($name [, $default]) -> 0/1)
5438 my $name = shift || return 0; # The Name
5439 my $default = shift || ""; # The default value
5441 # Protect variables set internally
5442 return 1 if !$name || exists($CGIprotectedVariable{$name});
5444 # Defined values must exist for security
5445 if(!exists($::default_values{$name}))
5447 $::default_values{$name} = $default;
5450 my @temp = ();
5453 # For security:
5454 # Environment values have precedence
5455 if(exists($ENV{$name}))
5457 push(@temp, $ENV{$name});
5459 # Get name and its values from the query string
5460 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5462 push(@temp, ::YOUR_CGIPARSE($name, 1)); # Extract LIST
5464 else
5466 push(@temp, $::default_values{$name});
5470 # SECURITY, do not allow '- and `-quotes in
5471 # client values.
5472 # Remove all existing '-quotes
5473 @temp = map {s/([\r\f]+\n)/\n/g; $_} @temp; # Only \n is allowed
5474 @temp = map {s/[\']/&#8217;/igs; $_} @temp; # Remove all single quotes
5475 @temp = map {s/[\`]/&#8216;/igs; $_} @temp; # Remove all backtick quotes
5477 # Store current CGI values and remove defaults
5478 $::default_values{$name} = $temp[0];
5480 # Define the CGI variable and its value (in the execute package)
5481 @{$name} = @temp;
5483 # return SUCCES
5484 return 1;
5487 sub defineCGIvariableHash # ($name [, $default]) -> 0/1) Note: '$name{""} = $default';
5489 my $name = shift || return 0; # The Name
5490 my $default = shift || ""; # The default value
5492 # Protect variables set internally
5493 return 1 if !$name || exists($CGIprotectedVariable{$name});
5495 # Defined values must exist for security
5496 if(!exists($::default_values{$name}))
5498 $::default_values{$name} = $default;
5501 my %temp = ();
5504 # For security:
5505 # Environment values have precedence
5506 if(exists($ENV{$name}))
5508 $temp{""} = $ENV{$name};
5510 # Get name and its values from the query string
5511 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5513 %temp = ::YOUR_CGIPARSE($name, -1); # Extract HASH table
5515 elsif($::default_values{$name} ne "")
5517 $temp{""} = $::default_values{$name};
5521 # SECURITY, do not allow '- and `-quotes in
5522 # client values.
5523 # Remove all existing '-quotes
5524 my $Key;
5525 foreach $Key (keys(%temp))
5527 $temp{$Key} =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5528 $temp{$Key} =~ s/[\']/&#8217;/igs; # Remove all single quotes
5529 $temp{$Key} =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5532 # Store current CGI values and remove defaults
5533 $::default_values{$name} = $temp{""};
5535 # Define the CGI variable and its value (in the execute package)
5536 %{$name} = ();
5537 my $tempKey;
5538 foreach $tempKey (keys(%temp))
5540 ${$name}{$tempKey} = $temp{$tempKey};
5543 # return SUCCES
5544 return 1;
5548 # SAFEqx('CommandString')
5550 # A special function that is a safe alternative to backtick quotes (and qx//)
5551 # with client-supplied CGI values. All CGI variables are surrounded by
5552 # single ''-quotes (except between existing \'\'-quotes, don't try to be
5553 # too smart). All variables are then interpolated. Simple (@) lists are
5554 # expanded with join(' ', @List), and simple (%) hash tables expanded
5555 # as a list of "key=value" pairs. Complex variables, e.g., @$var, are
5556 # evaluated in a scalar context (e.g., as scalar(@$var)). All occurrences of
5557 # $@% that should NOT be interpolated must be preceeded by a "\".
5558 # If the first line of the String starts with "#! interpreter", the
5559 # remainder of the string is piped into interpreter (after interpolation), i.e.,
5560 # open(INTERPRETER, "|interpreter");print INTERPRETER remainder;
5561 # just like in UNIX. There are some problems with quotes. Be carefull in
5562 # using them. You do not have access to the output of any piped (#!)
5563 # process! If you want such access, execute
5564 # <SCRIPT TYPE="text/osshell">echo "script"|interpreter</SCRIPT> or
5565 # <SCRIPT TYPE="text/ssperl">$resultvar = SAFEqx('echo "script"|interpreter');
5566 # </SCRIPT>.
5568 # SAFEqx ONLY WORKS WHEN THE STRING ITSELF IS SURROUNDED BY SINGLE QUOTES
5569 # (SO THAT IT IS NOT INTERPOLATED BEFORE IT CAN BE PROTECTED)
5570 sub SAFEqx # ('String') -> result of executing qx/"String"/
5572 my $CommandString = shift;
5573 my $NewCommandString = "";
5575 # Only interpolate when required (check the On/Off switch)
5576 unless($CGIscriptor::NoShellScriptInterpolation)
5579 # Handle existing single quotes around CGI values
5580 while($CommandString =~ /\'[^\']+\'/s)
5582 my $CurrentQuotedString = $&;
5583 $NewCommandString .= $`;
5584 $CommandString = $'; # The remaining string
5585 # Interpolate CGI variables between quotes
5586 # (e.g., '$CGIscriptorResults[-1]')
5587 $CurrentQuotedString =~
5588 s/(^|[^\\])([\$\@])((\w*)([\{\[][\$\@\%]?[\:\w\-]+[\}\]])*)/if(exists($main::default_values{$4})){
5589 "$1".eval("$2$3")}else{"$&"}/egs;
5591 # Combine result with previous result
5592 $NewCommandString .= $CurrentQuotedString;
5594 $CommandString = $NewCommandString.$CommandString;
5596 # Select known CGI variables and surround them with single quotes,
5597 # then interpolate all variables
5598 $CommandString =~
5599 s/(^|[^\\])([\$\@\%]+)((\w*)([\{\[][\w\:\$\"\-]+[\}\]])*)/
5600 if($2 eq '$' && exists($main::default_values{$4}))
5601 {"$1\'".eval("\$$3")."\'";}
5602 elsif($2 eq '@'){$1.join(' ', @{"$3"});}
5603 elsif($2 eq '%'){my $t=$1;map {$t.=" $_=".${"$3"}{$_}}
5604 keys(%{"$3"});$t}
5605 else{$1.eval("${2}$3");
5606 }/egs;
5608 # Remove backslashed [$@%]
5609 $CommandString =~ s/\\([\$\@\%])/$1/gs;
5612 # Debugging
5613 # return $CommandString;
5615 # Handle UNIX style "#! shell command\n" constructs as
5616 # a pipe into the shell command. The output cannot be tapped.
5617 my $ReturnValue = "";
5618 if($CommandString =~ /^\s*\#\!([^\f\n\r]+)[\f\n\r]/is)
5620 my $ShellScripts = $';
5621 my $ShellCommand = $1;
5622 open(INTERPRETER, "|$ShellCommand") || dieHandler(29, "\'$ShellCommand\' PIPE not opened: &!\n");
5623 select(INTERPRETER);$| = 1;
5624 print INTERPRETER $ShellScripts;
5625 close(INTERPRETER);
5626 select(STDOUT);$| = 1;
5628 # Shell scripts which are redirected to an existing named pipe.
5629 # The output cannot be tapped.
5630 elsif($CGIscriptor::ShellScriptPIPE)
5632 CGIscriptor::printSAFEqxPIPE($CommandString);
5634 else # Plain ``-backtick execution
5636 # Execute the commands
5637 $ReturnValue = qx/$CommandString/;
5639 return $ReturnValue;
5642 ####################################################################################
5644 # The CGIscriptor PACKAGE
5646 ####################################################################################
5648 # Isolate the evaluation of CGIscriptor functions, i.e., those prefixed with
5649 # "CGIscriptor::"
5651 package CGIscriptor;
5654 # The Interpolation On/Off switch
5655 my $NoShellScriptInterpolation = undef;
5656 # The ShellScript redirection pipe
5657 my $ShellScriptPIPE = undef;
5659 # Open a named PIPE for SAFEqx to receive ALL shell scripts
5660 sub RedirectShellScript # ('CommandString')
5662 my $CommandString = shift || undef;
5664 if($CommandString)
5666 $ShellScriptPIPE = "ShellScriptNamedPipe";
5667 open($ShellScriptPIPE, "|$CommandString")
5668 || main::dieHandler(30, "\'|$CommandString\' PIPE open failed: $!\n");
5670 else
5672 close($ShellScriptPIPE);
5673 $ShellScriptPIPE = undef;
5675 return $ShellScriptPIPE;
5678 # Print to redirected shell script pipe
5679 sub printSAFEqxPIPE # ("String") -> print return value
5681 my $String = shift || undef;
5683 select($ShellScriptPIPE); $| = 1;
5684 my $returnvalue = print $ShellScriptPIPE ($String);
5685 select(STDOUT); $| = 1;
5687 return $returnvalue;
5690 # a pointer to CGIexecute::SAFEqx
5691 sub SAFEqx # ('String') -> result of qx/"String"/
5693 my $CommandString = shift;
5694 return CGIexecute::SAFEqx($CommandString);
5698 # a pointer to CGIexecute::defineCGIvariable
5699 sub defineCGIvariable # ($name[, $default]) ->0/1
5701 my $name = shift;
5702 my $default = shift;
5703 return CGIexecute::defineCGIvariable($name, $default);
5707 # a pointer to CGIexecute::defineCGIvariable
5708 sub defineCGIvariableList # ($name[, $default]) ->0/1
5710 my $name = shift;
5711 my $default = shift;
5712 return CGIexecute::defineCGIvariableList($name, $default);
5716 # a pointer to CGIexecute::defineCGIvariable
5717 sub defineCGIvariableHash # ($name[, $default]) ->0/1
5719 my $name = shift;
5720 my $default = shift;
5721 return CGIexecute::defineCGIvariableHash($name, $default);
5725 # Decode URL encoded arguments
5726 sub URLdecode # (URL encoded input) -> string
5728 my $output = "";
5729 my $char;
5730 my $Value;
5731 foreach $Value (@_)
5733 my $EncodedValue = $Value; # Do not change the loop variable
5734 # Convert all "+" to " "
5735 $EncodedValue =~ s/\+/ /g;
5736 # Convert all hexadecimal codes (%FF) to their byte values
5737 while($EncodedValue =~ /\%([0-9A-F]{2})/i)
5739 $output .= $`.chr(hex($1));
5740 $EncodedValue = $';
5742 $output .= $EncodedValue; # The remaining part of $Value
5744 $output;
5747 # Encode arguments as URL codes.
5748 sub URLencode # (input) -> URL encoded string
5750 my $output = "";
5751 my $char;
5752 my $Value;
5753 foreach $Value (@_)
5755 my @CharList = split('', $Value);
5756 foreach $char (@CharList)
5758 if($char =~ /\s/)
5759 { $output .= "+";}
5760 elsif($char =~ /\w\-/)
5761 { $output .= $char;}
5762 else
5764 $output .= uc(sprintf("%%%2.2x", ord($char)));
5768 $output;
5771 # Extract the value of a CGI variable from the URL-encoded $string
5772 # Also extracts the data blocks from a multipart request. Does NOT
5773 # decode the multipart blocks
5774 sub CGIparseValue # (ValueName [, URL_encoded_QueryString [, \$QueryReturnReference]]) -> Decoded value
5776 my $ValueName = shift;
5777 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5778 my $ReturnReference = shift || undef;
5779 my $output = "";
5781 if($QueryString =~ /(^|\&)$ValueName\=([^\&]*)(\&|$)/)
5783 $output = URLdecode($2);
5784 $$ReturnReference = $' if ref($ReturnReference);
5786 # Get multipart POST or PUT methods
5787 elsif($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
5789 my $MultipartType = $2;
5790 my $BoundaryString = $3;
5791 # Remove the boundary-string
5792 my $temp = $QueryString;
5793 $temp =~ /^\Q--$BoundaryString\E/m;
5794 $temp = $';
5796 # Identify the newline character(s), this is the first character in $temp
5797 my $NewLine = "\r\n"; # Actually, this IS the correct one
5798 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
5800 # Is this correct??? I have to check.
5801 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
5802 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
5803 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
5804 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
5807 # search through all data blocks
5808 while($temp =~ /^\Q--$BoundaryString\E/m)
5810 my $DataBlock = $`;
5811 $temp = $';
5812 # Get the empty line after the header
5813 $DataBlock =~ /$NewLine$NewLine/;
5814 $Header = $`;
5815 $output = $';
5816 my $Header = $`;
5817 $output = $';
5819 # Remove newlines from the header
5820 $Header =~ s/$NewLine/ /g;
5822 # Look whether this block is the one you are looking for
5823 # Require the quotes!
5824 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
5826 my $i;
5827 for($i=length($NewLine); $i; --$i)
5829 chop($output);
5831 # OK, get out
5832 last;
5834 # reinitialize the output
5835 $output = "";
5837 $$ReturnReference = $temp if ref($ReturnReference);
5839 elsif($QueryString !~ /(^|\&)$ValueName\=/) # The value simply isn't there
5841 return undef;
5842 $$ReturnReference = undef if ref($ReturnReference);
5844 else
5846 print "ERROR: $ValueName $main::ENV{'CONTENT_TYPE'}\n";
5848 return $output;
5852 # Get a list of values for the same ValueName. Uses CGIparseValue
5854 sub CGIparseValueList # (ValueName [, URL_encoded_QueryString]) -> List of decoded values
5856 my $ValueName = shift;
5857 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5858 my @output = ();
5859 my $RestQueryString;
5860 my $Value;
5861 while($QueryString &&
5862 (($Value = CGIparseValue($ValueName, $QueryString, \$RestQueryString))
5863 || defined($Value)))
5865 push(@output, $Value);
5866 $QueryString = $RestQueryString; # QueryString is consumed!
5868 # ready, return list with values
5869 return @output;
5872 sub CGIparseValueHash # (ValueName [, URL_encoded_QueryString]) -> Hash table of decoded values
5874 my $ValueName = shift;
5875 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5876 my $RestQueryString;
5877 my %output = ();
5878 while($QueryString && $QueryString =~ /(^|\&)$ValueName([\w]*)\=/)
5880 my $Key = $2;
5881 my $Value = CGIparseValue("$ValueName$Key", $QueryString, \$RestQueryString);
5882 $output{$Key} = $Value;
5883 $QueryString = $RestQueryString; # QueryString is consumed!
5885 # ready, return list with values
5886 return %output;
5889 sub CGIparseForm # ([URL_encoded_QueryString]) -> Decoded Form (NO multipart)
5891 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5892 my $output = "";
5894 $QueryString =~ s/\&/\n/g;
5895 $output = URLdecode($QueryString);
5897 $output;
5900 # Extract the header of a multipart CGI variable from the POST input
5901 sub CGIparseHeader # (ValueName [, URL_encoded_QueryString]) -> Decoded value
5903 my $ValueName = shift;
5904 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5905 my $output = "";
5907 if($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
5909 my $MultipartType = $2;
5910 my $BoundaryString = $3;
5911 # Remove the boundary-string
5912 my $temp = $QueryString;
5913 $temp =~ /^\Q--$BoundaryString\E/m;
5914 $temp = $';
5916 # Identify the newline character(s), this is the first character in $temp
5917 my $NewLine = "\r\n"; # Actually, this IS the correct one
5918 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
5920 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
5921 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
5922 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
5923 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
5926 # search through all data blocks
5927 while($temp =~ /^\Q--$BoundaryString\E/m)
5929 my $DataBlock = $`;
5930 $temp = $';
5931 # Get the empty line after the header
5932 $DataBlock =~ /$NewLine$NewLine/;
5933 $Header = $`;
5934 my $Header = $`;
5936 # Remove newlines from the header
5937 $Header =~ s/$NewLine/ /g;
5939 # Look whether this block is the one you are looking for
5940 # Require the quotes!
5941 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
5943 $output = $Header;
5944 last;
5946 # reinitialize the output
5947 $output = "";
5950 return $output;
5954 # Checking variables for security (e.g., file names and email addresses)
5955 # File names are tested against the $::FileAllowedChars and $::BlockPathAccess variables
5956 sub CGIsafeFileName # FileName -> FileName or ""
5958 my $FileName = shift || "";
5959 return "" if $FileName =~ m?[^$::FileAllowedChars]?;
5960 return "" if $FileName =~ m!(^|/|\:)[\-\.]!;
5961 return "" if $FileName =~ m@\.\.\Q$::DirectorySeparator\E@; # Higher directory not allowed
5962 return "" if $FileName =~ m@\Q$::DirectorySeparator\E\.\.@; # Higher directory not allowed
5963 return "" if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@; # Invisible (blocked) file
5965 return $FileName;
5968 sub CGIsafeEmailAddress # email -> email or ""
5970 my $Email = shift || "";
5971 return "" unless $Email =~ m/^[\w\.\-]+[\@][\w\.\-\:]+$/;
5972 return $Email;
5975 # Get a URL from the web. Needs main::GET_URL($URL) function
5976 # (i.e., curl, snarf, or wget)
5977 sub read_url # ($URL) -> page/file
5979 my $URL = shift || return "";
5981 # Get the commands to read the URL, do NOT add a print command
5982 my $URL_command = main::GET_URL($URL, 1);
5983 # execute the commands, i.e., actually read it
5984 my $URLcontent = CGIexecute->evaluate($URL_command);
5986 # Ready, return the content.
5987 return $URLcontent;
5990 ################################################>>>>>>>>>>Start Remove
5992 # BrowseAllDirs(Directory, indexfile)
5994 # usage:
5995 # <SCRIPT TYPE='text/ssperl'>
5996 # CGIscriptor::BrowseAllDirs('Sounds', 'index.html', '\.wav$')
5997 # </SCRIPT>
5999 # Allows to browse all directories. Stops at '/'. If the directory contains
6000 # an indexfile, eg, index.html, that file will be used instead. Files must match
6001 # the $Pattern, if it is given. Default is
6002 # CGIscriptor::BrowseAllDirs('/', 'index.html', '')
6004 sub BrowseAllDirs # (Directory, indexfile, $Pattern) -> Print HTML code
6006 my $Directory = shift || '/';
6007 my $indexfile = shift || 'index.html';
6008 my $Pattern = shift || '';
6009 $Directory =~ s!/$!!g;
6011 # If the index directory exists, use that one
6012 if(-s "$::CGI_HOME$Directory/$indexfile")
6014 return main::ProcessFile("$::CGI_HOME$Directory/$indexfile");
6017 # No indexfile, continue
6018 my @DirectoryList = glob("$::CGI_HOME$Directory");
6019 $CurrentDirectory = shift(@DirectoryList);
6020 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
6021 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
6022 print "<h1>";
6023 print "$CurrentDirectory" if $CurrentDirectory;
6024 print "</h1>\n";
6026 opendir(BROWSE, "$::CGI_HOME$Directory") || main::dieHandler(31, "$::CGI_HOME$Directory $!");
6027 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
6029 # Print directories
6030 my $file;
6031 print "<pre><ul TYPE='NONE'>\n";
6032 foreach $file (@AllFiles)
6034 next unless -d "$::CGI_HOME$Directory/$file";
6035 # Check whether this file should be visible
6036 next if $::BlockPathAccess &&
6037 "$Directory/$file/" =~ m@$::BlockPathAccess@;
6038 print "<dt><a href='$Directory/$file'>$file</a></dt>\n";
6040 print "</ul></pre>\n";
6042 # Print files
6043 print "<pre><ul TYPE='CIRCLE'>\n";
6044 my $TotalSize = 0;
6045 foreach $file (@AllFiles)
6047 next if $file =~ /^\./;
6048 next if -d "$::CGI_HOME$Directory/$file";
6049 next if -l "$::CGI_HOME$Directory/$file";
6050 # Check whether this file should be visible
6051 next if $::BlockPathAccess &&
6052 "$Directory/$file" =~ m@$::BlockPathAccess@;
6054 if(!$Pattern || $file =~ m@$Pattern@)
6056 my $Date = localtime($^T - (-M "$::CGI_HOME$Directory/$file")*3600*24);
6057 my $Size = -s "$::CGI_HOME$Directory/$file";
6058 $Size = sprintf("%6.0F kB", $Size/1024);
6059 my $Type = `file $::CGI_HOME$Directory/$file`;
6060 $Type =~ s@\s*$::CGI_HOME$Directory/$file\s*\:\s*@@ig;
6061 chomp($Type);
6063 print "<li>";
6064 print "<a href='$Directory/$file'>";
6065 printf("%-40s", "$file</a>");
6066 print "\t$Size\t$Date\t$Type";
6067 print "</li>\n";
6070 print "</ul></pre>";
6072 return 1;
6076 ################################################
6078 # BrowseDirs(RootDirectory [, Pattern, Start])
6080 # usage:
6081 # <SCRIPT TYPE='text/ssperl'>
6082 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', 'Speech', 'DIRECTORY')
6083 # </SCRIPT>
6085 # Allows to browse subdirectories. Start should be relative to the RootDirectory,
6086 # e.g., the full path of the directory 'Speech' is '~/Sounds/Speech'.
6087 # Only files which fit /$Pattern/ and directories are displayed.
6088 # Directories down or up the directory tree are supplied with a
6089 # GET request with the name of the CGI variable in the fourth argument (default
6090 # is 'BROWSEDIRS'). So the correct call for a subdirectory could be:
6091 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', $DIRECTORY, 'DIRECTORY')
6093 sub BrowseDirs # (RootDirectory [, Pattern, Start, CGIvariable, HTTPserver]) -> Print HTML code
6095 my $RootDirectory = shift; # || return 0;
6096 my $Pattern = shift || '\S';
6097 my $Start = shift || "";
6098 my $CGIvariable = shift || "BROWSEDIRS";
6099 my $HTTPserver = shift || '';
6101 $Start = CGIscriptor::URLdecode($Start); # Sometimes, too much has been encoded
6102 $Start =~ s@//+@/@g;
6103 $Start =~ s@[^/]+/\.\.@@ig;
6104 $Start =~ s@^\.\.@@ig;
6105 $Start =~ s@/\.$@@ig;
6106 $Start =~ s!/+$!!g;
6107 $Start .= "/" if $Start;
6109 my @Directory = glob("$::CGI_HOME/$RootDirectory/$Start");
6110 $CurrentDirectory = shift(@Directory);
6111 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
6112 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
6113 print "<h1>";
6114 print "$CurrentDirectory" if $CurrentDirectory;
6115 print "</h1>\n";
6116 opendir(BROWSE, "$::CGI_HOME/$RootDirectory/$Start") || main::dieHandler(31, "$::CGI_HOME/$RootDirectory/$Start $!");
6117 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
6119 # Print directories
6120 my $file;
6121 print "<pre><ul TYPE='NONE'>\n";
6122 foreach $file (@AllFiles)
6124 next unless -d "$::CGI_HOME/$RootDirectory/$Start$file";
6125 # Check whether this file should be visible
6126 next if $::BlockPathAccess &&
6127 "/$RootDirectory/$Start$file/" =~ m@$::BlockPathAccess@;
6129 my $NewURL = $Start ? "$Start$file" : $file;
6130 $NewURL = CGIscriptor::URLencode($NewURL);
6131 print "<dt><a href='";
6132 print "$ENV{SCRIPT_NAME}" if $ENV{SCRIPT_NAME} !~ m@[^\w+\-/]@;
6133 print "$ENV{PATH_INFO}?$CGIvariable=$NewURL'>$file</a></dt>\n";
6135 print "</ul></pre>\n";
6137 # Print files
6138 print "<pre><ul TYPE='CIRCLE'>\n";
6139 my $TotalSize = 0;
6140 foreach $file (@AllFiles)
6142 next if $file =~ /^\./;
6143 next if -d "$::CGI_HOME/$RootDirectory/$Start$file";
6144 next if -l "$::CGI_HOME/$RootDirectory/$Start$file";
6145 # Check whether this file should be visible
6146 next if $::BlockPathAccess &&
6147 "$::CGI_HOME/$RootDirectory/$Start$file" =~ m@$::BlockPathAccess@;
6149 if($file =~ m@$Pattern@)
6151 my $Date = localtime($^T - (-M "$::CGI_HOME/$RootDirectory/$Start$file")*3600*24);
6152 my $Size = -s "$::CGI_HOME/$RootDirectory/$Start$file";
6153 $Size = sprintf("%6.0F kB", $Size/1024);
6154 my $Type = `file $::CGI_HOME/$RootDirectory/$Start$file`;
6155 $Type =~ s@\s*$::CGI_HOME/$RootDirectory/$Start$file\s*\:\s*@@ig;
6156 chomp($Type);
6158 print "<li>";
6159 if($HTTPserver =~ /^\s*[\.\~]\s*$/)
6161 print "<a href='$RootDirectory/$Start$file'>";
6163 elsif($HTTPserver)
6165 print "<a href='$HTTPserver/$RootDirectory/$Start$file'>";
6167 printf("%-40s", "$file</a>") if $HTTPserver;
6168 printf("%-40s", "$file") unless $HTTPserver;
6169 print "\t$Size\t$Date\t$Type";
6170 print "</li>\n";
6173 print "</ul></pre>";
6175 return 1;
6179 # ListDocs(Pattern [,ListType])
6181 # usage:
6182 # <SCRIPT TYPE=text/ssperl>
6183 # CGIscriptor::ListDocs("/*", "dl");
6184 # </SCRIPT>
6186 # This subroutine is very usefull to manage collections of independent
6187 # documents. The resulting list will display the tree-like directory
6188 # structure. If this routine is too slow for online use, you can
6189 # store the result and use a link to that stored file.
6191 # List HTML and Text files with title and first header (HTML)
6192 # or filename and first meaningfull line (general text files).
6193 # The listing starts at the ServerRoot directory. Directories are
6194 # listed recursively.
6196 # You can change the list type (default is dl).
6197 # e.g.,
6198 # <dt><a href=<file.html>>title</a>
6199 # <dd>First Header
6200 # <dt><a href=<file.txt>>file.txt</a>
6201 # <dd>First meaningfull line of text
6203 sub ListDocs # ($Pattern [, prefix]) e.g., ("/Books/*", [, "dl"])
6205 my $Pattern = shift;
6206 $Pattern =~ /\*/;
6207 my $ListType = shift || "dl";
6208 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
6209 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
6210 my @FileList = glob("$::CGI_HOME$Pattern");
6211 my ($FileName, $Path, $Link);
6213 # Print List markers
6214 print "<$ListType>\n";
6216 # Glob all files
6217 File: foreach $FileName (@FileList)
6219 # Check whether this file should be visible
6220 next if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@;
6222 # Recursively list files in all directories
6223 if(-d $FileName)
6225 $FileName =~ m@([^/]*)$@;
6226 my $DirName = $1;
6227 print "<$Prefix>$DirName\n";
6228 $Pattern =~ m@([^/]*)$@;
6229 &ListDocs("$`$DirName/$1", $ListType);
6230 next;
6232 # Use textfiles
6233 elsif(-T "$FileName")
6235 open(TextFile, $FileName) || next;
6237 # Ignore all other file types
6238 else
6239 { next;};
6241 # Get file path for link
6242 $FileName =~ /$::CGI_HOME/;
6243 print "<$Prefix><a href=$URL_root$'>";
6244 # Initialize all variables
6245 my $Line = "";
6246 my $TitleFound = 0;
6247 my $Caption = "";
6248 my $Title = "";
6249 # Read file and step through
6250 while(<TextFile>)
6252 chop $_;
6253 $Line = $_;
6254 # HTML files
6255 if($FileName =~ /\.ht[a-zA-Z]*$/i)
6257 # Catch Title
6258 while(!$Title)
6260 if($Line =~ m@<title>([^<]*)</title>@i)
6262 $Title = $1;
6263 $Line = $';
6265 else
6267 $Line .= <TextFile> || goto Print;
6268 chop $Line;
6271 # Catch First Header
6272 while(!$Caption)
6274 if($Line =~ m@</h1>@i)
6276 $Caption = $`;
6277 $Line = $';
6278 $Caption =~ m@<h1>@i;
6279 $Caption = $';
6280 $Line = $`.$Caption.$Line;
6282 else
6284 $Line .= <TextFile> || goto Print;
6285 chop $Line;
6289 # Other text files
6290 else
6292 # Title equals file name
6293 $FileName =~ /([^\/]+)$/;
6294 $Title = $1;
6295 # Catch equals First Meaningfull line
6296 while(!$Caption)
6298 if($Line =~ /[A-Z]/ &&
6299 ($Line =~ /subject|title/i || $Line =~ /^[\w,\.\s\?\:]+$/)
6300 && $Line !~ /Newsgroup/ && $Line !~ /\:\s*$/)
6302 $Line =~ s/\<[^\>]+\>//g;
6303 $Caption = $Line;
6305 else
6307 $Line = <TextFile> || goto Print;
6311 Print: # Print title and subject
6312 print "$Title</a>\n";
6313 print "<dd>$Caption\n" if $ListType eq "dl";
6314 $TitleFound = 0;
6315 $Caption = "";
6316 close TextFile;
6317 next File;
6320 # Print Closing List Marker
6321 print "</$ListType>\n";
6322 ""; # Empty return value
6326 # HTMLdocTree(Pattern [,ListType])
6328 # usage:
6329 # <SCRIPT TYPE=text/ssperl>
6330 # CGIscriptor::HTMLdocTree("/Welcome.html", "dl");
6331 # </SCRIPT>
6333 # The following subroutine is very usefull for checking large document
6334 # trees. Starting from the root (s), it reads all files and prints out
6335 # a nested list of links to all attached files. Non-existing or misplaced
6336 # files are flagged. This is quite a file-i/o intensive routine
6337 # so you would not like it to be accessible to everyone. If you want to
6338 # use the result, save the whole resulting page to disk and use a link
6339 # to this file.
6341 # HTMLdocTree takes an HTML file or file pattern and constructs nested lists
6342 # with links to *local* files (i.e., only links to the local server are
6343 # followed). The list entries are the document titles.
6344 # If the list type is <dl>, the first <H1> header is used too.
6345 # For each file matching the pattern, a list is made recursively of all
6346 # HTML documents that are linked from it and are stored in the same directory
6347 # or a sub-directory. Warnings are given for missing files.
6348 # The listing starts for the ServerRoot directory.
6349 # You can change the default list type <dl> (<dl>, <ul>, <ol>).
6351 %LinkUsed = ();
6353 sub HTMLdocTree # ($Pattern [, listtype])
6354 # e.g., ("/Welcome.html", [, "ul"])
6356 my $Pattern = shift;
6357 my $ListType = shift || "dl";
6358 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
6359 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
6360 my ($Filename, $Path, $Link);
6361 my %LocalLinks = {};
6363 # Read files (glob them for expansion of wildcards)
6364 my @FileList = glob("$::CGI_HOME$Pattern");
6365 foreach $Path (@FileList)
6367 # Get URL_path
6368 $Path =~ /$::CGI_HOME/;
6369 my $URL_path = $';
6370 # Check whether this file should be visible
6371 next if $::BlockPathAccess && $URL_path =~ m@$::BlockPathAccess@;
6373 my $Title = $URL_path;
6374 my $Caption = "";
6375 # Current file should not be used again
6376 ++$LinkUsed{$URL_path};
6377 # Open HTML doc
6378 unless(open(TextFile, $Path))
6380 print "<$Prefix>$Title <blink>(not found)</blink><br>\n";
6381 next;
6383 while(<TextFile>)
6385 chop $_;
6386 $Line = $_;
6387 # Catch Title
6388 while($Line =~ m@<title>@i)
6390 if($Line =~ m@<title>([^<]*)</title>@i)
6392 $Title = $1;
6393 $Line = $';
6395 else
6397 $Line .= <TextFile>;
6398 chop $Line;
6401 # Catch First Header
6402 while(!$Caption && $Line =~ m@<h1>@i)
6404 if($Line =~ m@</h[1-9]>@i)
6406 $Caption = $`;
6407 $Line = $';
6408 $Caption =~ m@<h1>@i;
6409 $Caption = $';
6410 $Line = $`.$Caption.$Line;
6412 else
6414 $Line .= <TextFile>;
6415 chop $Line;
6418 # Catch and print Links
6419 while($Line =~ m@<a href\=([^>]*)>@i)
6421 $Link = $1;
6422 $Line = $';
6423 # Remove quotes
6424 $Link =~ s/\"//g;
6425 # Remove extras
6426 $Link =~ s/[\#\?].*$//g;
6427 # Remove Servername
6428 if($Link =~ m@(http://|^)@i)
6430 $Link = $';
6431 # Only build tree for current server
6432 next unless $Link =~ m@$::ENV{'SERVER_NAME'}|^/@;
6433 # Remove server name and port
6434 $Link =~ s@^[^\/]*@@g;
6436 # Store the current link
6437 next if $LinkUsed{$Link} || $Link eq $URL_path;
6438 ++$LinkUsed{$Link};
6439 ++$LocalLinks{$Link};
6443 close TextFile;
6444 print "<$Prefix>";
6445 print "<a href=http://";
6446 print "$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}$URL_path>";
6447 print "$Title</a>\n";
6448 print "<br>$Caption\n"
6449 if $Caption && $Caption ne $Title && $ListType =~ /dl/i;
6450 print "<$ListType>\n";
6451 foreach $Link (keys(%LocalLinks))
6453 &HTMLdocTree($Link, $ListType);
6455 print "</$ListType>\n";
6459 ###########################<<<<<<<<<<End Remove
6461 # Make require happy
6464 =head1 NAME
6466 CGIscriptor -
6468 =head1 DESCRIPTION
6470 A flexible HTML 4 compliant script/module for CGI-aware
6471 embeded Perl, shell-scripts, and other scripting languages,
6472 executed at the server side.
6474 =head1 README
6476 Executes embeded Perl code in HTML pages with easy
6477 access to CGI variables. Also processes embeded shell
6478 scripts and scripts in any other language with an
6479 interactive interpreter (e.g., in-line Python, Tcl,
6480 Ruby, Awk, Lisp, Xlispstat, Prolog, M4, R, REBOL, Praat,
6481 sh, bash, csh, ksh).
6483 CGIscriptor is very flexible and hides all the specifics
6484 and idiosyncrasies of correct output and CGI coding and naming.
6485 CGIscriptor complies with the W3C HTML 4.0 recommendations.
6487 This Perl program will run on any WWW server that runs
6488 Perl scripts, just add a line like the following to your
6489 srm.conf file (Apache example):
6491 ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
6493 URL's that refer to http://www.your.address/SHTML/... will
6494 now be handled by CGIscriptor.pl, which can use a private
6495 directory tree (default is the DOCUMENT_ROOT directory tree,
6496 but it can be anywhere).
6498 =head1 PREREQUISITES
6501 =head1 COREQUISITES
6504 =pod OSNAMES
6506 Linux, *BSD, *nix, MS WinXP
6508 =pod SCRIPT CATEGORIES
6510 Servers
6514 =cut