3 # (configure the first line to contain YOUR path to perl 5.000+)
11 # perl 5.0 or higher (see: "http://www.perl.org/")
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
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.
58 # Configuration, copyright notice, and user manual follow the next
61 ############################################################################
63 # Changes (document ALL changes with date, name and email here):
64 # 31 Mar 2013 - Added support for Digest::SHA
65 # 13 Mar 2013 - Changed password hash
66 # 10 Jul 2012 - Version 2.4
67 # 11 Jun 2012 - Securing CGIvariable setting. Made
68 # 'if($ENV{QUERY_STRING} =~ /$name/)' into elsif in
69 # defineCGIvariable/List/Hash to give precedence to ENV{$name}
70 # This was a very old security bug. Added ProtectCGIvariable($name).
71 # 06 Jun 2012 - Added IP only session types after login.
72 # 31 May 2012 - Session ticket system added for handling login sessions.
73 # 29 May 2012 - CGIsafeFileName does not accept filenames starting with '.'
74 # 29 May 2012 - Added CGIscriptor::BrowseAllDirs to handle browsing directories
76 # 22 May 2012 - Added Access control with Session Tickets linked to
77 # IP Address and PATH_INFO.
78 # 21 May 2012 - Corrected the links generated by CGIscriptor::BrowseDirs
79 # Will link to current base URL when the HTTP server is '.' or '~'
80 # 29 Oct 2009 - Adapted David A. Wheeler's suggestion about filenames:
81 # CGIsafeFileName does not accept filenames starting with '-'
82 # (http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html)
83 # 08 Oct 2009 - Some corrections in the README.txt file, eg, new email address
84 # 28 Jan 2005 - Added a file selector to performTranslation.
85 # Changed %TranslationTable to @TranslationTable
86 # and patterns to lists.
87 # 27 Jan 2005 - Added a %TranslationTable with associated
88 # performTranslation(\$text) function to allow
89 # run changes in the web pages. Say, to translate
90 # legacy pages with <%=...%> delimiters to the new
91 # <SCRIPT TYPE=..></SCRIPT> format.
92 # 27 Jan 2005 - Small bug of extra '\n' in output removed from the
93 # Other Languages Code.
94 # 10 May 2004 - Belated upload of latest version (2.3) to CPAN
95 # 07 Oct 2003 - Corrected error '\s' -> '\\s' in rebol scripting
97 # 07 Oct 2003 - Corrected omitted INS tags in <DIV><INS> handling
98 # 20 May 2003 - Added a --help switch to print the manual.
99 # 06 Mar 2003 - Adapted the blurb at the end of the file.
100 # 03 Mar 2003 - Added a user definable dieHandler function to catch all
101 # "die" calls. Also "enhanced" the STDERR printout.
102 # 10 Feb 2003 - Split off the reading of the POST part of a query
103 # from Initialize_output. This was suggested by Gerd Franke
104 # to allow for the catching of the file_path using a
105 # POST based lookup. That is, he needed the POST part
106 # to change the file_path.
107 # 03 Feb 2003 - %{$name}; => %{$name} = (); in defineCGIvariableHash.
108 # 03 Feb 2003 - \1 better written as $1 in
109 # $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
110 # 29 Jan 2003 - This makes "CLASS="ssperl" CSS-compatible Gerd Franke
112 # $ServerScriptContentClass = "ssperl";
113 # changed in ProcessFile():
114 # unless(($CurrentContentType =~
115 # 28 Jan 2003 - Added 'INS' Tag! Gerd Franke
116 # 20 Dec 2002 - Removed useless $Directoryseparator variable.
117 # Update comments and documentation.
118 # 18 Dec 2002 - Corrected bug in Accept/Reject processing.
120 # 24 Jul 2002 - Added .htaccess documentation (from Gerd Franke)
121 # Also added a note that RawFilePattern can be a
122 # complete file name.
123 # 19 Mar 2002 - Added SRC pseudo-files PREFIX and POSTFIX. These
124 # switch to prepending or to appending the content
125 # of the SRC attribute. Default is prefixing. You
126 # can add as many of these switches as you like.
127 # 13 Mar 2002 - Do not search for tag content if a tag closes with
128 # />, i.e., <DIV ... /> will be handled the XML/XHTML way.
129 # 25 Jan 2002 - Added 'curl' and 'snarf' to SRC attribute URL handling
131 # 25 Jan 2002 - Found a bug in SAFEqx, now executes qx() in a scalar context
132 # (i.o. a list context). This is necessary for binary results.
133 # 24 Jan 2002 - Disambiguated -T $SRCfile to -T "$SRCfile" (and -e) and
134 # changed the order of if/elsif to allow removing these
135 # conditions in systems with broken -T functions.
136 # (I also removed a spurious ')' bracket)
137 # 17 Jan 2002 - Changed DIV tag SRC from <SOURCE> to sysread(SOURCE,...)
138 # to support binary files.
139 # 17 Jan 2002 - Removed WhiteSpace from $FileAllowedCharacters.
140 # 17 Jan 2002 - Allow "file://" prefix in SRC attribute. It is simply
141 # stipped from the path.
142 # 15 Jan 2002 - Version 2.2
143 # 15 Jan 2002 - Debugged and completed URL support (including
144 # CGIscriptor::read_url() function)
145 # 07 Jan 2002 - Added automatic (magic) URL support to the SRC attribute
146 # with the main::GET_URL function. Uses wget -O underlying.
147 # 04 Jan 2002 - Added initialization of $NewDirective in InsertForeignScript
148 # (i.e., my $NewDirective = "";) to clear old output
149 # (this was a realy anoying bug).
150 # 03 Jan 2002 - Added a <DIV CLASS='text/ssperl' ID='varname'></DIV>
151 # tags that assign the body text as-is (literally)
152 # to $varname. Allows standard HTML-tools to handle
153 # Cascading Style Sheet templates. This implements a
154 # design by Gerd Franke (franke@roo.de).
155 # 03 Jan 2002 - I finaly gave in and allowed SRC files to expand ~/.
156 # 12 Oct 2001 - Normalized spelling of "CGIsafFileName" in documentation.
157 # 09 Oct 2001 - Added $ENV{'CGI_BINARY_FILE'} to log files to
158 # detect unwanted indexing of TAR files by webcrawlers.
159 # 10 Sep 2001 - Added $YOUR_SCRIPTS directory to @INC for 'require'.
160 # 22 Aug 2001 - Added .txt (Content-type: text/plain) as a default
161 # processed file type. Was processed via BinaryMapFile.
162 # 31 May 2001 - Changed =~ inside CGIsafeEmailAddress that was buggy.
163 # 29 May 2001 - Updated $CGI_HOME to point to $ENV{DOCUMENT_ROOT} io
164 # the root of PATH_TRANSLATED. DOCUMENT_ROOT can now
165 # be manipulated to achieve a "Sub Root".
166 # NOTE: you can have $YOUR_HTML_FILES != DOCUMENT_ROOT
167 # 28 May 2001 - Changed CGIscriptor::BrowsDirs function for security
168 # and debugging (it now works).
169 # 21 May 2001 - defineCGIvariableHash will ADD values to existing
170 # hashes,instead of replacing existing hashes.
171 # 17 May 2001 - Interjected a '&' when pasting POST to GET data
172 # 24 Apr 2001 - Blocked direct requests for BinaryMapFile.
173 # 16 Aug 2000 - Added hash table extraction for CGI parameters with
174 # CGIparseValueHash (used with structured parameters).
175 # Use: CGI='%<CGI-partial-name>' (fill in your name in <>)
176 # Will collect all <CGI-partial-name><key>=value pairs in
177 # $<CGI-partial-name>{<key>} = value;
178 # 16 Aug 2000 - Adapted SAFEqx to protect @PARAMETER values.
179 # 09 Aug 2000 - Added support for non-filesystem input by way of
180 # the CGI_FILE_CONTENTS and CGI_DATA_ACCESS_CODE
181 # environment variables.
182 # 26 Jul 2000 - On the command-line, file-path '-' indicates STDIN.
183 # This allows CGIscriptor to be used in pipes.
184 # Default, $BLOCK_STDIN_HTTP_REQUEST=1 will block this
185 # in an HTTP request (i.e., in a web server).
186 # 26 Jul 2000 - Blocked 'Content-type: text/html' if the SERVER_PROTOCOL
187 # is not HTTP or another protocol. Changed the default
188 # source directory to DOCUMENT_ROOT (i.o. the incorrect
190 # 24 Jul 2000 - -slim Command-line argument added to remove all
191 # comments, security, etc.. Updated documentation.
192 # 05 Jul 2000 - Added IF and UNLESS attributes to make the
193 # execution of all <META> and <SCRIPT> code
195 # 05 Jul 2000 - Rewrote and isolated the code for extracting
196 # quoted items from CGI and SRC attributes.
197 # Now all attributes expect the same set of
198 # quotes: '', "", ``, (), {}, [] and the same
199 # preceded by a \, e.g., "\((aap)\)" will be
200 # extracted as "(aap)".
201 # 17 Jun 2000 - Construct @ARGV list directly in CGIexecute
202 # name-space (i.o. by evaluation) from
203 # CGI attributes to prevent interference with
204 # the processing for non perl scripts.
205 # Changed CGIparseValueList to prevent runaway
207 # 16 Jun 2000 - Added a direct (interpolated) display mode
208 # (text/ssdisplay) and a user log mode
210 # 06 Jun 2000 - Replace "print $Result" with a syswrite loop to
211 # allow large string output.
212 # 02 Jun 2000 - Corrected shrubCGIparameter($CGI_VALUE) to realy
213 # remove all control characters. Changed Interpreter
214 # initialization to shrub interpolated CGI parameters.
215 # Added 'text/ssmailto' interpreter script.
216 # 22 May 2000 - Changed some of the comments
217 # 09 May 2000 - Added list extraction for CGI parameters with
218 # CGIparseValueList (used with multiple selections).
219 # Use: CGI='@<CGI-parameter>' (fill in your name in <>)
220 # 09 May 2000 - Added a 'Not Present' condition to CGIparseValue.
221 # 27 Apr 2000 - Updated documentation to reflect changes.
222 # 27 Apr 2000 - SRC attribute "cleaned". Supported for external
224 # 27 Apr 2000 - CGI attribute can be used in <SCRIPT> tag.
225 # 27 Apr 2000 - Gprolog, M4 support added.
226 # 26 Apr 2000 - Lisp (rep) support added.
227 # 20 Apr 2000 - Use of external interpreters now functional.
228 # 20 Apr 2000 - Removed bug from extracting Content types (RegExp)
229 # 10 Mar 2000 - Qualified unconditional removal of '#' that preclude
230 # the use of $#foo, i.e., I changed
231 # s/[^\\]\#[^\n\f\r]*([\n\f\r])/\1/g
233 # s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/\1/g
234 # 03 Mar 2000 - Added a '$BlockPathAccess' variable to "hide"
235 # things like, e.g., CVS information in CVS subtrees
236 # 10 Feb 2000 - URLencode/URLdecode have been made case-insensitive
237 # 10 Feb 2000 - Added a BrowseDirs function (CGIscriptor package)
238 # 01 Feb 2000 - A BinaryMapFile in the ~/ directory has precedence
239 # over a "burried" BinaryMapFile.
240 # 04 Oct 1999 - Added two functions to check file names and email addresses
241 # (CGIscriptor::CGIsafeFileName and
242 # CGIscriptor::CGIsafeEmailAddress)
243 # 28 Sept 1999 - Corrected bug in sysread call for reading POST method
244 # to allow LONG posts.
245 # 28 Sept 1999 - Changed CGIparseValue to handle multipart/form-data.
246 # 29 July 1999 - Refer to BinaryMapFile from CGIscriptor directory, if
247 # this directory exists.
248 # 07 June 1999 - Limit file-pattern matching to LAST extension
249 # 04 June 1999 - Default text/html content type is printed only once.
250 # 18 May 1999 - Bug in replacement of ~/ and ./ removed.
251 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
252 # 15 May 1999 - Changed the name of the execute package to CGIexecute.
253 # Changed the processing of the Accept and Reject file.
254 # Added a full expression evaluation to Access Control.
255 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
256 # 27 Apr 1999 - Brought CGIscriptor under the GNU GPL. Made CGIscriptor
257 # Version 1.1 a module that can be called with 'require "CGIscriptor.pl"'.
258 # Requests are serviced by "Handle_Request()". CGIscriptor
259 # can still be called as a isolated perl script and a shell
261 # Changed the "factory default setting" so that it will run
262 # from the DOCUMENT_ROOT directory.
263 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
264 # 29 Mar 1999 - Remove second debugging STDERR switch. Moved most code
265 # to subroutines to change CGIscriptor into a module.
266 # Added mapping to process unsupported file types (e.g., binary
267 # pictures). See $BinaryMapFile.
268 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
269 # 24 Sept 1998 - Changed text of license (Rob van Son, R.J.J.H.vanSon@gmail.com)
270 # Removed a double setting of filepatterns and maximum query
271 # size. Changed email address. Removed some typos from the
273 # 02 June 1998 - Bug fixed in URLdecode. Changing the foreach loop variable
274 # caused quiting CGIscriptor.(Rob van Son, R.J.J.H.vanSon@gmail.com)
275 # 02 June 1998 - $SS_PUB and $SS_SCRIPT inserted an extra /, removed.
276 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
282 # It is not possible to use operators or variables to construct variable names,
283 # e.g., $bar = \@{$foo}; won't work. However, eval('$bar = \@{'.$foo.'};');
284 # will indeed work. If someone could tell me why, I would be obliged.
287 ############################################################################
289 # OBLIGATORY USER CONFIGURATION
291 # Configure the directories where all user files can be found (this
292 # is the equivalent of the server root directory of a WWW-server).
293 # These directories can be located ANYWHERE. For security reasons, it is
294 # better to locate them outside the WWW-tree of your HTTP server, unless
295 # CGIscripter handles ALL requests.
297 # For convenience, the defaults are set to the root of the WWW server.
298 # However, this might not be safe!
301 # $YOUR_HTML_FILES = "/usr/pub/WWW/SHTML"; # or SS_PUB as environment var
302 # (patch to use the parent directory of CGIscriptor as document root, should be removed)
303 if($ENV{'SCRIPT_FILENAME'}) # && $ENV{'SCRIPT_FILENAME'} !~ /\Q$ENV{'DOCUMENT_ROOT'}\E/)
305 $ENV{'DOCUMENT_ROOT'} = $ENV{'SCRIPT_FILENAME'};
306 $ENV{'DOCUMENT_ROOT'} =~ s@
/CGIscriptor
.*$@
@ig;
309 # Just enter your own directory path here
310 $YOUR_HTML_FILES = $ENV{'DOCUMENT_ROOT'}; # default is the DOCUMENT_ROOT
312 # ./ script files (recommended to be different from the previous)
313 # $YOUR_SCRIPTS = "/usr/pub/WWW/scripts"; # or SS_SCRIPT as environment var
314 $YOUR_SCRIPTS = $YOUR_HTML_FILES; # This might be a SECURITY RISK
316 # End of obligatory user configuration
317 # (note: there is more non-essential user configuration below)
319 ############################################################################
321 # OPTIONAL USER CONFIGURATION (all values are used CASE INSENSITIVE)
323 # Script content-types: TYPE="Content-type" (user defined mime-type)
324 $ServerScriptContentType = "text/ssperl"; # Server Side Perl scripts
325 # CSS require a simple class
326 $ServerScriptContentClass = $ServerScriptContentType =~ m!/! ?
327 $' : "ssperl"; # Server Side Perl CSS classes
329 $ShellScriptContentType = "text/osshell"; # OS shell scripts
330 # # (Server Side perl ``-execution)
332 # Accessible file patterns, block any request that doesn't match
.
333 # Matches any file with the extension .(s)htm(l), .txt, or .xmr
334 # (\. is used in regexp)
335 # Note: die unless $PATH_INFO =~ m@($FilePattern)$@is;
336 $FilePattern = ".shtml|.htm|.html|.xml|.xmr|.txt|.js|.css";
338 # The table with the content type MIME types
339 # (allows to differentiate MIME types, if needed)
342 '.html' => 'text/html',
343 '.shtml' => 'text/html',
344 '.htm' => 'text/html',
345 '.xml' => 'text/xml',
346 '.txt' => 'text/plain',
347 '.js' => 'text/plain',
348 '.css' => 'text/plain'
352 # File pattern post-processing
353 $FilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
355 # SHAsum command needed for Authorization and Login
356 # (note, these have to be accessible in the HTML pages, ie, the CGIexecute environment)
357 my $shasum = "shasum -a 256";
358 if(qx{uname
} =~ /Darwin/)
360 $shasum = "shasum-5.12 -a 256" unless `which shasum`;
362 my $SHASUMCMD = $shasum.' |cut -f 1 -d" "';
363 $ENV{"SHASUMCMD"} = $SHASUMCMD;
364 my $RANDOMHASHCMD = 'dd bs=1 count=64 if=/dev/urandom 2>/dev/null | '.$shasum.' -b |cut -f 1 -d" "';
365 $ENV{"RANDOMHASHCMD"} = $RANDOMHASHCMD;
367 # Hash a string, return hex of hash
368 sub hash_string_cmd
# ($string) -> hex_hash
370 my $string = shift || "";
371 # Catch nasty \'-quotes, embed them in '..'"'"'..'
372 $string =~ s/\'/\'\"\'\"\'/isg;
373 my $hash = `printf '%s' '$string'| $ENV{"SHASUMCMD"}`;
378 # Note that you CANNOT replace $RANDOMHASHCMD with a call using hash_string_cmd
379 # as the output of /dev/urandom breaks string handling in Perl.
380 # Generate random hex hash
381 sub get_random_hex_cmd
# () -> hex
383 # Create Random Hash Salt
384 open(URANDOM
, "$RANDOMHASHCMD |") || die "URANDOM; $RANDOMHASHCMD | $!\n";
385 my $RANDOMSALT= <URANDOM
>;
393 # You can use Digest::SHA (SHA.pm), you need sha256_hex
394 # See http://search.cpan.org/~mshelor/Digest-SHA-5.84/lib/Digest/SHA.pm
395 # > sudo CPAN -i Digest
397 # The following code will check whether Digest::SHA is available and then
398 # use the appropriate function calls.
400 $shaDigestLoaded = (eval("require Digest::SHA;1;") eq "1") ?
1 : 0;
402 sub hash_string_Digest
# ($string) -> hex_hash
404 my $string = shift || "";
405 my $digest = Digest
::SHA
::sha256_hex
($string);
410 sub get_random_hex_Digest
# () -> hex
412 my $randomstring = "";
413 # Create Random Hash Salt
414 open(URANDOM
, "</dev/urandom") || die "/dev/urandom: $!\n";
415 read URANDOM
, $randomstring, 64 || die "No random bytes read: $!\n";
417 my $RANDOMSALT= hash_string_Digest
($randomstring);
422 # The final functions
423 sub hash_string
# ($string) -> hex_hash
426 { return hash_string_Digest
(@_) }
428 { return hash_string_cmd
(@_);};
431 sub get_random_hex
# () -> hex
434 { return get_random_hex_Digest
() }
436 { return get_random_hex_cmd
();};
439 ######################################################################
441 # File patterns of files which are handled by session tickets.
442 %TicketRequiredPatterns = (
443 '^/Private(/|$)' => "Private/.Sessions\tPrivate/.Passwords\t/Private/Login.html\t+36000"
445 # Used to set cookies, only session cookies supported
446 my %SETCOOKIELIST = ();
448 # Session Ticket Directory: Private/.Sessions
449 # Password Directory: Private/.Passwords
450 # Login page (url path): /Private/Login.html
451 # Expiration time (s): +3600
452 # +<seconds> = relative time <seconds> is absolute date-time
455 # Set up a valid ticket from a given text file
456 # Use from command line. DO NOT USE ONLINE
457 # Watch out for passwords that get stored in the history file
459 # perl CGIscriptor.pl --managelogin [options] [files]
461 # salt={file or saltvalue}
462 # masterkey={file or plaintext}
463 # newmasterkey={file or plaintext}
464 # password={file or palintext}
466 # Followed by one or more file names.
467 # Options can be interspersed between filenames,
468 # e.g., password='plaintext'
469 # Note that passwords are only used once!
471 if($ARGV[0] =~ /^\-\-managelogin/i)
473 my @arguments = @ARGV;
475 setup_ticket_file
(@arguments);
476 # Should be run on the command line
482 # Raw files must contain their own Content-type (xmr <- x-multipart-replace).
483 # THIS IS A SUBSET OF THE FILES DEFINED IN $FilePattern
484 $RawFilePattern = ".xmr";
485 # (In principle, this could contain a full file specification, e.g.,
486 # ".xmr|relocated.html")
488 # Raw File pattern post-processing
489 $RawFilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
491 # Server protocols for which "Content-type: text/html\n\n" should be printed
492 # (you should not bother with these, except for HTTP, they are mostly imaginary)
493 $ContentTypeServerProtocols = 'HTTP|MAIL|MIME';
495 # Block access to all (sub-) paths and directories that match the
496 # following (URL) path (is used as:
497 # 'die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;' )
498 $BlockPathAccess = '/(CVS|\.git)/'; # Protect CVS and .git information
500 # All (blocked) other file-types can be mapped to a single "binary-file"
501 # processor (a kind of pseudo-file path). This can either be an error
502 # message (e.g., "illegal file") or contain a script that serves binary
504 # Note: the real file path wil be stored in $ENV{CGI_BINARY_FILE}.
505 $BinaryMapFile = "/BinaryMapFile.xmr";
506 # Allow for the addition of a CGIscriptor directory
507 # Note that a BinaryMapFile in the root "~/" directory has precedence
508 $BinaryMapFile = "/CGIscriptor".$BinaryMapFile
509 if ! -e
"$YOUR_HTML_FILES".$BinaryMapFile
510 && -e
"$YOUR_HTML_FILES/CGIscriptor".$BinaryMapFile;
513 # List of all characters that are allowed in file names and paths.
514 # All requests containing illegal characters are blocked. This
515 # blocks most tricks (e.g., adding "\000", "\n", or other control
516 # characters, also blocks URI's using %FF)
517 # THIS IS A SECURITY FEATURE
518 # (this is also used to parse filenames in SRC= features, note the
519 # '-quotes, they are essential)
520 $FileAllowedChars = '\w\.\~\/\:\*\?\-'; # Covers Unix and Mac, but NO spaces
522 # Maximum size of the Query (number of characters clients can send
523 # covers both GET & POST combined)
524 $MaximumQuerySize = 2**20 - 1; # = 2**14 - 1
527 # Embeded URL get function used in SRC attributes and CGIscriptor::read_url
528 # (returns a string with the PERL code to transfer the URL contents, e.g.,
529 # "SAFEqx(\'curl \"http://www.fon.hum.uva.nl\"\')")
530 # "SAFEqx(\'wget --quiet --output-document=- \"http://www.fon.hum.uva.nl\"\')")
531 # Be sure to handle <BASE HREF='URL'> and allow BOTH
532 # direct printing GET_URL($URL [, 0]) and extracting the content of
533 # the $URL for post-processing GET_URL($URL, 1).
534 # You get the WHOLE file, including HTML header.
535 # The shell command Use $URL where the URL should go
536 # ('wget', 'snarf' or 'curl', uncomment the one you would like to use)
537 my $GET_URL_shell_command = 'wget --quiet --output-document=- $URL';
538 #my $GET_URL_shell_command = 'snarf $URL -';
539 #my $GET_URL_shell_command = 'curl $URL';
541 sub GET_URL
# ($URL, $ValueNotPrint) -> content_of_url
543 my $URL = shift || return;
544 my $ValueNotPrint = shift || 0;
546 # Check URL for illegal characters
547 return "print '<h1>Illegal URL<h1>'\"\n\";" if $URL =~ /[^$FileAllowedChars\%]/;
549 # Include URL in final command
550 my $CurrentCommand = $GET_URL_shell_command;
551 $CurrentCommand =~ s/\$URL/$URL/g;
553 # Print to STDOUT or return a value
554 my $BlockPrint = "print STDOUT ";
555 $BlockPrint = "" if $ValueNotPrint;
557 my $Commands = <<"GETURLCODE";
562 # Simple, using shell command
563 \$Page = SAFEqx('$CurrentCommand');
565 # Add a BASE tage to the header
566 \$Page =~ s!\\</head!\\<base href='$URL'\\>\\</head!ig unless \$Page =~ m!\\<base!;
568 # Print the URL value, or return it as a value
575 # As files can get rather large (and binary), you might want to use
576 # some more intelligent reading procedure, e.g.,
578 # # open(URLHANDLE, '/usr/bin/wget --quiet --output-document=- "$URL"|') || die "wget: \$!";
579 # #open(URLHANDLE, '/usr/bin/snarf "$URL" -|') || die "snarf: \$!";
580 # open(URLHANDLE, '/usr/bin/curl "$URL"|') || die "curl: \$!";
582 # while(sysread(URLHANDLE,\$text, 1024) > 0)
586 # close(URLHANDLE) || die "\$!";
587 # However, this doesn't work with the CGIexecute->evaluate() function.
588 # You get an error: 'No child processes at (eval 16) line 15, <file0> line 8.'
590 # You can forget the next two variables, they are only needed when
591 # you don't want to use a regular file system (i.e., with open)
592 # but use some kind of database/RAM image for accessing (generating)
595 # Name of the environment variable that contains the file contents
596 # when reading directly from Database/RAM. When this environment variable,
597 # $ENV{$CGI_FILE_CONTENTS}, is not false, no real file will be read.
598 $CGI_FILE_CONTENTS = 'CGI_FILE_CONTENTS';
599 # Uncomment the following if you want to force the use of the data access code
600 # $ENV{$CGI_FILE_CONTENTS} = '-'; # Force use of $ENV{$CGI_DATA_ACCESS_CODE}
602 # Name of the environment variable that contains the RAM access perl
603 # code needed to read additional "files", i.e.,
604 # $ENV{$CGI_FILE_CONTENTS} = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
605 # When $ENV{$CGI_FILE_CONTENTS} eq '-', this code is executed to generate the data.
606 $CGI_DATA_ACCESS_CODE = 'CGI_DATA_ACCESS_CODE';
608 # You can, of course, fill this yourself, e.g.,
609 # $ENV{$CGI_DATA_ACCESS_CODE} =
610 # 'open(INPUT, "<$_[0]"); while(<INPUT>){print;};close(INPUT);'
615 # Suppress error messages, this can be changed for debugging or error-logging
616 #open(STDERR, "/dev/null"); # (comment out for use in debugging)
618 # SPECIAL: Remove Comments, security, etc. if the command line is
619 # '>CGIscriptor.pl -slim >slimCGIscriptor.pl'
620 $TrimDownCGIscriptor = 1 if $ARGV[0] =~ /^\-slim/i;
622 # If CGIscriptor is used from the command line, the command line
623 # arguments are interpreted as the file (1st) and the Query String (rest).
625 $ENV{'PATH_INFO'} = shift(@ARGV) unless exists($ENV{'PATH_INFO'}) || grep(/\-\-help/i, @ARGV);
626 $ENV{'QUERY_STRING'} = join("&", @ARGV) unless exists($ENV{'QUERY_STRING'});
629 # Handle bail-outs in a user definable way.
630 # Catch Die and replace it with your own function.
631 # Ends with a call to "die $_[0];"
633 sub dieHandler
# ($ErrorCode, "Message", @_) -> DEAD
635 my $ErrorCode = shift;
636 my $ErrorMessage = shift;
638 # Place your own reporting functions here
640 # Now, kill everything (default)
641 print STDERR
"$ErrorCode: $ErrorMessage\n";
646 # End of optional user configuration
647 # (note: there is more non-essential user configuration below)
649 if(grep(/\-\-help/i, @ARGV))
651 print << 'ENDOFPREHELPTEXT2';
653 ###############################################################################
655 # Author and Copyright (c):
656 # Rob van Son, © 1995,1996,1997,1998,1999,2000,2001,2002-2012
659 # Institute of Phonetic Sciences & IFOTT/ACLS
660 # University of Amsterdam
661 # Email: R.J.J.H.vanSon@gmail.com
662 # Email: R.J.J.H.vanSon@gmail.com
663 # WWW : http://www.fon.hum.uva.nl/rob/
665 # License for use and disclaimers
667 # CGIscriptor merges plain ASCII HTML files transparantly
668 # with CGI variables, in-line PERL code, shell commands,
669 # and executable scripts in other scripting languages.
671 # This program is free software; you can redistribute it and/or
672 # modify it under the terms of the GNU General Public License
673 # as published by the Free Software Foundation; either version 2
674 # of the License, or (at your option) any later version.
676 # This program is distributed in the hope that it will be useful,
677 # but WITHOUT ANY WARRANTY; without even the implied warranty of
678 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
679 # GNU General Public License for more details.
681 # You should have received a copy of the GNU General Public License
682 # along with this program; if not, write to the Free Software
683 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
687 # Rob van Son (R.J.J.H.vanSon@gmail.com)
688 # Gerd Franke franke@roo.de (designed the <DIV> behaviour)
690 #######################################################
693 #######################################################>>>>>>>>>>Start Remove
695 # You can skip the following code, it is an auto-splice
698 # Construct a slimmed down version of CGIscriptor
699 # (i.e., CGIscriptor.pl -slim > slimCGIscriptor.pl)
701 if($TrimDownCGIscriptor)
703 open(CGISCRIPTOR
, "<CGIscriptor.pl")
704 || dieHandler
(1, "<CGIscriptor.pl not slimmed down: $!\n");
706 my $SKIPComments = 0;
715 $SKIPtext = 1 if /[\>]{10}Start Remove/;
716 $SKIPComments = 1 if $SKIPtext == 1;
719 $SKIPline = 1 if $SKIPtext || ($SKIPComments && /^\s*\#/);
721 ++$PrintCount unless $SKIPline;
723 print STDOUT
$_ unless $SKIPline;
726 $SKIPtext = 0 if /[\<]{10}End Remove/;
729 print STDERR
"\# Printed $PrintCount out of $LineCount lines\n";
733 #######################################################
735 if(grep(/\-\-help/i, @ARGV))
737 print << 'ENDOFHELPTEXT';
741 # CGIscriptor merges plain ASCII HTML files transparantly and safely
742 # with CGI variables, in-line PERL code, shell commands, and executable
743 # scripts in many languages (on-line and real-time). It combines the
744 # "ease of use" of HTML files with the versatillity of specialized
745 # scripts and PERL programs. It hides all the specifics and
746 # idiosyncrasies of correct output and CGI coding and naming. Scripts
747 # do not have to be aware of HTML, HTTP, or CGI conventions just as HTML
748 # files can be ignorant of scripts and the associated values. CGIscriptor
749 # complies with the W3C HTML 4.0 recommendations.
750 # In addition to its use as a WWW embeded CGI processor, it can
751 # be used as a command-line document preprocessor (text-filter).
753 # THIS IS HOW IT WORKS
755 # The aim of CGIscriptor is to execute "plain" scripts inside a text file
756 # using any required CGIparameters and environment variables. It
757 # is optimized to transparantly process HTML files inside a WWW server.
758 # The native language is Perl, but many other scripting languages
761 # CGIscriptor reads text files from the requested input file (i.e., from
762 # $YOUR_HTML_FILES$PATH_INFO) and writes them to <STDOUT> (i.e., the
763 # client requesting the service) preceded by the obligatory
764 # "Content-type: text/html\n\n" or "Content-type: text/plain\n\n" string
765 # (except for "raw" files which supply their own Content-type message
766 # and only if the SERVER_PROTOCOL supports HTTP, MAIL, or MIME).
768 # When CGIscriptor encounters an embedded script, indicated by an HTML4 tag
770 # <SCRIPT TYPE="text/ssperl" [CGI="$VAR='default value'"] [SRC="ScriptSource"]>
776 # <SCRIPT TYPE="text/osshell" [CGI="$name='default value'"] [SRC="ScriptSource"]>
780 # construct (anything between []-brackets is optional, other MIME-types
781 # and scripting languages are supported), the embedded script is removed
782 # and both the contents of the source file (i.e., "do 'ScriptSource'")
783 # AND the script are evaluated as a PERL program (i.e., by eval()),
784 # shell script (i.e., by a "safe" version of `Command`, qx) or an external
785 # interpreter. The output of the eval() function takes the place of the
786 # original <SCRIPT></SCRIPT> construct in the output string. Any CGI
787 # parameters declared by the CGI attribute are available as simple perl
788 # variables, and can subsequently be made available as variables to other
789 # scripting languages (e.g., bash, python, or lisp).
791 # Example: printing "Hello World"
792 # <HTML><HEAD><TITLE>Hello World</TITLE>
794 # <H1><SCRIPT TYPE="text/ssperl">"Hello World"</SCRIPT></H1>
797 # Save this in a file, hello.html, in the directory you indicated with
798 # $YOUR_HTML_FILES and access http://your_server/SHTML/hello.html
799 # (or to whatever name you use as an alias for CGIscriptor.pl).
800 # This is realy ALL you need to do to get going.
802 # You can use any values that are delivered in CGI-compliant form (i.e.,
803 # the "?name=value" type URL additions) transparently as "$name" variables
804 # in your scripts IFF you have declared them in the CGI attribute of
805 # a META or SCRIPT tag before e.g.:
806 # <META CONTENT="text/ssperl; CGI='$name = `default value`'
807 # [SRC='ScriptSource']">
809 # <SCRIPT TYPE="text/ssperl" CGI="$name = 'default value'"
810 # [SRC='ScriptSource']>
811 # After such a 'CGI' attribute, you can use $name as an ordinary PERL variable
812 # (the ScriptSource file is immediately evaluated with "do 'ScriptSource'").
813 # The CGIscriptor script allows you to write ordinary HTML files which will
814 # include dynamic CGI aware (run time) features, such as on-line answers
815 # to specific CGI requests, queries, or the results of calculations.
817 # For example, if you wanted to answer questions of clients, you could write
818 # a Perl program called "Answer.pl" with a function "AnswerQuestion()"
819 # that prints out the answer to requests given as arguments. You then write
820 # an HTML page "Respond.html" containing the following fragment:
823 # The Answer to your question
824 # <META CONTENT="text/ssperl; CGI='$Question'">
825 # <h3><SCRIPT TYPE="text/ssperl">$Question</SCRIPT></h3>
827 # <h3><SCRIPT TYPE="text/ssperl" SRC="./PATH/Answer.pl">
828 # AnswerQuestion($Question);
831 # <FORM ACTION=Respond.html METHOD=GET>
832 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
833 # <INPUT TYPE=SUBMIT VALUE="Ask">
836 # The output could look like the following (in HTML-speak):
839 # The Answer to your question
840 # <h3>What is the capital of the Netherlands?</h3>
844 # <FORM ACTION=Respond.html METHOD=GET>
845 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
846 # <INPUT TYPE=SUBMIT VALUE="Ask">
848 # Note that the function "Answer.pl" does know nothing about CGI or HTML,
849 # it just prints out answers to arguments. Likewise, the text has no
850 # provisions for scripts or CGI like constructs. Also, it is completely
851 # trivial to extend this "program" to use the "Answer" later in the page
852 # to call up other information or pictures/sounds. The final text never
853 # shows any cue as to what the original "source" looked like, i.e.,
854 # where you store your scripts and how they are called.
856 # There are some extra's. The argument of the files called in a SRC= tag
857 # can access the CGI variables declared in the preceding META tag from
858 # the @ARGV array. Executable files are called as:
859 # `file '$ARGV[0]' ... ` (e.g., `Answer.pl \'$Question\'`;)
860 # The files called from SRC can even be (CGIscriptor) html files which are
861 # processed in-line. Furthermore, the SRC= tag can contain a perl block
862 # that is evaluated. That is,
863 # <META CONTENT="text/ssperl; CGI='$Question' SRC='{$Question}'">
864 # will result in the evaluation of "print do {$Question};" and the VALUE
865 # of $Question will be printed. Note that these "SRC-blocks" can be
866 # preceded and followed by other file names, but only a single block is
867 # allowed in a SRC= tag.
869 # One of the major hassles of dynamic WWW pages is the fact that several
870 # mutually incompatible browsers and platforms must be supported. For example,
871 # the way sound is played automatically is different for Netscape and
872 # Internet Explorer, and for each browser it is different again on
873 # Unix, MacOS, and Windows. Realy dangerous is processing user-supplied
874 # (form-) values to construct email addresses, file names, or database
875 # queries. All Apache WWW-server exploits reported in the media are
876 # based on faulty CGI-scripts that didn't check their user-data properly.
878 # There is no panacee for these problems, but a lot of work and problems
879 # can be saved by allowing easy and transparent control over which
880 # <SCRIPT></SCRIPT> blocks are executed on what CGI-data. CGIscriptor
881 # supplies such a method in the form of a pair of attributes:
882 # IF='...condition..' and UNLESS='...condition...'. When added to a
883 # script tag, the whole block (including the SRC attribute) will be
884 # ignored if the condition is false (IF) or true (UNLESS).
885 # For example, the following block will NOT be evaluated if the value
886 # of the CGI variable FILENAME is NOT a valid filename:
888 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
889 # IF='CGIscriptor::CGIsafeFileName($FILENAME)'>
893 # (the function CGIsafeFileName(String) returns an empty string ("")
894 # if the String argument is not a valid filename).
895 # The UNLESS attribute is the mirror image of IF.
897 # A user manual follows the HTML 4 and security paragraphs below.
899 ##########################################################################
903 # In general, CGIscriptor.pl complies with the HTML 4 recommendations of
904 # the W3C. This means that any software to manage Web sites will be able
905 # to handle CGIscriptor files, as will web agents.
907 # All script code should be placed between <SCRIPT></SCRIPT> tags, the
908 # script type is indicated with TYPE="mime-type", the LANGUAGE
909 # feature is ignored, and a SRC feature is implemented. All CGI specific
910 # features are delegated to the CGI attribute.
912 # However, the behavior deviates from the W3C recommendations at some
913 # points. Most notably:
914 # 0- The scripts are executed at the server side, invissible to the
915 # client (i.e., the browser)
916 # 1- The mime-types are personal and idiosyncratic, but can be adapted.
917 # 2- Code in the body of a <SCRIPT></SCRIPT> tag-pair is still evaluated
918 # when a SRC feature is present.
919 # 3- The SRC attribute reads a list of files.
920 # 4- The files in a SRC attribute are processed according to file type.
921 # 5- The SRC attribute evaluates inline Perl code.
922 # 6- Processed META, DIV, INS tags are removed from the output
924 # 7- All attributes of the processed META tags, except CONTENT, are ignored
925 # (i.e., deleted from the output).
926 # 8- META tags can be placed ANYWHERE in the document.
927 # 9- Through the SRC feature, META tags can have visible output in the
929 # 10- The CGI attribute that declares CGI parameters, can be used
930 # inside the <SCRIPT> tag.
931 # 11- Use of an extended quote set, i.e., '', "", ``, (), {}, []
932 # and their \-slashed combinations: \'\', \"\", \`\`, \(\),
934 # 12- IF and UNLESS attributes to <SCRIPT>, <META>, <DIV>, <INS> tags.
935 # 13- <DIV> tags cannot be nested, DIV tags are not
936 # rendered with new-lines.
937 # 14- The XML style <TAG .... /> is recognized and handled correctly.
938 # (i.e., no content is processed)
940 # The reasons for these choices are:
941 # You can still write completely HTML4 compliant documents. CGIscriptor
942 # will not force you to write "deviant" code. However, it allows you to
943 # do so (which is, in fact, just as bad). The prime design principle
944 # was to allow users to include plain Perl code. The code itself should
945 # be "enhancement free". Therefore, extra features were needed to
946 # supply easy access to CGI and Web site components. For security
947 # reasons these have to be declared explicitly. The SRC feature
948 # transparently manages access to external files, especially the safe
949 # use of executable files.
950 # The CGI attribute handles the declarations of external (CGI) variables
951 # in the SCRIPT and META tag's.
952 # EVERYTHING THE CGI ATTRIBUTE AND THE META TAG DO CAN BE DONE INSIDE
953 # A <SCRIPT></SCRIPT> TAG CONSTRUCT.
955 # The reason for the IF, UNLESS, and SRC attributes (and their Perl code
956 # evaluation) were build into the META and SCRIPT tags is part laziness,
957 # part security. The SRC blocks allows more compact documents and easier
958 # debugging. The values of the CGI variables can be immediately screened
959 # for security by IF or UNLESS conditions, and even SRC attributes (e.g.,
960 # email addresses and file names), and a few commands can be called
961 # without having to add another Perl TAG pair. This is especially important
962 # for documents that require the use of other (more restricted) "scripting"
963 # languages and facilities that lag transparent control structures.
965 ##########################################################################
969 # Your WWW site is a few keystrokes away from a few hundred million internet
970 # users. A fair percentage of these users knows more about your computer
971 # than you do. And some of these just might have bad intentions.
973 # To ensure uncompromized operation of your server and platform, several
974 # features are incorporated in CGIscriptor.pl to enhance security.
975 # First of all, you should check the source of this program. No security
976 # measures will help you when you download programs from anonymous sources.
977 # If you want to use THIS file, please make sure that it is uncompromized.
978 # The best way to do this is to contact the source and try to determine
979 # whether s/he is reliable (and accountable).
981 # BE AWARE THAT ANY PROGRAMMER CAN CHANGE THIS PROGRAM IN SUCH A WAY THAT
982 # IT WILL SET THE DOORS TO YOUR SYSTEM WIDE OPEN
984 # I would like to ask any user who finds bugs that could compromise
985 # security to report them to me (and any other bug too,
986 # Email: R.J.J.H.vanSon@gmail.com or ifa@hum.uva.nl).
991 # The inner workings of the HTML source files are completely hidden
992 # from the client. Only the HTTP header and the ever changing content
993 # of the output distinguish it from the output of a plain, fixed HTML
994 # file. Names, structures, and arguments of the "embedded" scripts
995 # are invisible to the client. Error output is suppressed except
996 # during debugging (user configurable).
998 # 2 Separate directory trees
999 # Directories containing Inline text and script files can reside on
1000 # separate trees, distinct from those of the HTTP server. This means
1001 # that NEITHER the text files, NOR the script files can be read by
1002 # clients other than through CGIscriptor.pl, UNLESS they are
1003 # EXPLICITELY made available.
1005 # 3 Requests are NEVER "evaluated"
1006 # All client supplied values are used as literal values (''-quoted).
1007 # Client supplied ''-quotes are ALWAYS removed. Therefore, as long as the
1008 # embedded scripts do NOT themselves evaluate these values, clients CANNOT
1009 # supply executable commands. Be sure to AVOID scripts like:
1011 # <META CONTENT="text/ssperl; CGI='$UserValue'">
1012 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 $UserValue`;</SCRIPT>
1014 # These are a recipe for disaster. However, the following quoted
1015 # form should be save (but is still not adviced):
1017 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 \'$UserValue\'`;</SCRIPT>
1019 # A special function, SAFEqx(), will automatically do exactly this,
1020 # e.g., SAFEqx('ls -1 $UserValue') will execute `ls -1 \'$UserValue\'`
1021 # with $UserValue interpolated. I recommend to use SAFEqx() instead
1022 # of backticks whenever you can. The OS shell scripts inside
1024 # <SCRIPT TYPE="text/osshell">ls -1 $UserValue</SCRIPT>
1026 # are handeld by SAFEqx and automatically ''-quoted.
1028 # 4 Logging of requests
1029 # All requests can be logged separate from the Host server. The level of
1030 # detail is user configurable: Including or excluding the actual queries.
1031 # This allows for the inspection of (im-) proper use.
1033 # 5 Access control: Clients
1034 # The Remote addresses can be checked against a list of authorized
1035 # (i.e., accepted) or non-authorized (i.e., rejected) clients. Both
1036 # REMOTE_HOST and REMOTE_ADDR are tested so clients without a proper
1037 # HOST name can be (in-) excluded by their IP-address. Client patterns
1038 # containing all numbers and dots are considered IP-addresses, all others
1039 # domain names. No wild-cards or regexp's are allowed, only partial
1041 # Matching of names is done from the back to the front (domain first,
1042 # i.e., $REMOTE_HOST =~ /\Q$pattern\E$/is), so including ".edu" will
1043 # accept or reject all clients from the domain EDU. Matching of
1044 # IP-addresses is done from the front to the back (domain first, i.e.,
1045 # $REMOTE_ADDR =~ /^\Q$pattern\E/is), so including "128." will (in-)
1046 # exclude all clients whose IP-address starts with 128.
1047 # There are two special symbols: "-" matches HOSTs with no name and "*"
1048 # matches ALL HOSTS/clients.
1049 # For those needing more expressional power, lines starting with
1050 # "-e" are evaluated by the perl eval() function. E.g.,
1051 # '-e $REMOTE_HOST =~ /\.edu$/is;' will accept/reject clients from the
1054 # 6 Access control: Files
1055 # In principle, CGIscriptor could read ANY file in the directory
1056 # tree as discussed in 1. However, for security reasons this is
1057 # restricted to text files. It can be made more restricted by entering
1058 # a global file pattern (e.g., ".html"). This is done by default.
1059 # For each client requesting access, the file pattern(s) can be made
1060 # more restrictive than the global pattern by entering client specific
1061 # file patterns in the Access Control files (see 5).
1062 # For example: if the ACCEPT file contained the lines
1066 # Then all clients could request paths containing "DEMO" or "demo", e.g.
1067 # "/my/demo/file.html" ($PATH_INFO =~ /\Q$pattern\E/), Clients from
1068 # *.hum.uva.nl could also request paths containing "LET or "let", e.g.
1069 # "/my/let/file.html", and clients from the local cluster
1070 # 145.18.230.[0-9]+ could access ALL files.
1071 # Again, for those needing more expressional power, lines starting with
1072 # "-e" are evaluated. For instance:
1073 # '-e $REMOTE_HOST =~ /\.edu$/is && $PATH_INFO =~ m@/DEMO/@is;'
1074 # will accept/reject requests for files from the directory "/demo/" from
1075 # clients from the domain '.edu'.
1077 # 7 Access control: Server side session tickets
1078 # Specific paths can be controlled by Session Tickets which must be
1079 # present as a SESSIONTICKET=<value> CGI variable in the request. These paths
1080 # are defined in %TicketRequiredPatterns as pairs of:
1081 # ('regexp' => 'SessionPath\tPasswordPath\tLogin.html\tExpiration').
1082 # Session Tickets are stored in a separate directory (SessionPath, e.g.,
1083 # "Private/.Session") as files with the exact same name of the SESSIONTICKET
1084 # CGI. The following is an example:
1086 # IPaddress: 127.0.0.1
1087 # AllowedPaths: ^/Private/Name/
1091 # Other content can follow.
1093 # It is adviced that Session Tickets should be deleted
1094 # after some (idle) time. The IP address should be the IP number at login, and
1095 # the SESSIONTICKET will be rejected if it is presented from another IP address.
1096 # AllowedPaths and DeniedPaths are perl regexps. Be careful how they match. Make sure to delimit
1097 # the names to prevent access to overlapping names, eg, "^/Private/Rob" will also
1098 # match "^/Private/Robert", however, "^/Private/Rob/" will not. Expires is the
1099 # time the ticket will remain valid after creation (file ctime). Time can be given
1100 # in s[econds] (default), m[inutes], h[hours], or d[ays], eg, "24h" means 24 hours.
1101 # None of these need be present, but the Ticket must have a non-zero size.
1103 # Next to Session Tickets, there are two other type of ticket files:
1104 # - LOGIN tickets store information about a current login request
1105 # - PASSWORD ticket store account information to authorize login requests
1107 # 8 Query length limiting
1108 # The length of the Query string can be limited. If CONTENT_LENGTH is larger
1109 # than this limit, the request is rejected. The combined length of the
1110 # Query string and the POST input is checked before any processing is done.
1111 # This will prevent clients from overloading the scripts.
1112 # The actual, combined, Query Size is accessible as a variable through
1113 # $CGI_Content_Length.
1115 # 9 Illegal filenames, paths, and protected directories
1116 # One of the primary security concerns in handling CGI-scripts is the
1117 # use of "funny" characters in the requests that con scripts in executing
1118 # malicious commands. Examples are inserting ';', null bytes, or <newline>
1119 # characters in URL's and filenames, followed by executable commands. A
1120 # special variable $FileAllowedChars stores a string of all allowed
1121 # characters. Any request that translates to a filename with a character
1122 # OUTSIDE this set will be rejected.
1123 # In general, all (readable files) in the DocumentRoot tree are accessible.
1124 # This might not be what you want. For instance, your DocumentRoot directory
1125 # might be the working directory of a CVS project and contain sensitive
1126 # information (e.g., the password to get to the repository). You can block
1127 # access to these subdirectories by adding the corresponding patterns to
1128 # the $BlockPathAccess variable. For instance, $BlockPathAccess = '/CVS/'
1129 # will block any request that contains '/CVS/' or:
1130 # die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;
1132 #10 The execution of code blocks can be controlled in a transparent way
1133 # by adding IF or UNLESS conditions in the tags themselves. That is,
1134 # a simple check of the validity of filenames or email addresses can
1135 # be done before any code is executed.
1137 ###############################################################################
1139 # USER MANUAL (sort of)
1141 # CGIscriptor removes embedded scripts, indicated by an HTML 4 type
1142 # <SCRIPT TYPE='text/ssperl'> </SCRIPT> or <SCRIPT TYPE='text/osshell'>
1143 # </SCRIPT> constructs. CGIscriptor also recognizes XML-type
1144 # <SCRIPT TYPE='text/ssperl'/> constructs. These are usefull when
1145 # the necessary code is already available in the TAG itself (e.g.,
1146 # using external files). The contents of the directive are executed by
1147 # the PERL eval() and `` functions (in a separate name space). The
1148 # result of the eval() function replaces the <SCRIPT> </SCRIPT> construct
1149 # in the output file. You can use the values that are delivered in
1150 # CGI-compliant form (i.e., the "?name=value&.." type URL additions)
1151 # transparently as "$name" variables in your directives after they are
1152 # defined in a <META> or <SCRIPT> tag.
1153 # If you define the variable "$CGIscriptorResults" in a CGI attribute, all
1154 # subsequent <SCRIPT> and <META> results (including the defining
1155 # tag) will also be pushed onto a stack: @CGIscriptorResults. This list
1156 # behaves like any other, ordinary list and can be manipulated.
1158 # Both GET and POST requests are accepted. These two methods are treated
1159 # equal. Variables, i.e., those values that are determined when a file is
1160 # processed, are indicated in the CGI attribute by $<name> or $<name>=<default>
1161 # in which <name> is the name of the variable and <default> is the value
1162 # used when there is NO current CGI value for <name> (you can use
1163 # white-spaces in $<name>=<default> but really DO make sure that the
1164 # default value is followed by white space or is quoted). Names can contain
1165 # any alphanumeric characters and _ (i.e., names match /[\w]+/).
1166 # If the Content-type: is 'multipart/*', the input is treated as a
1167 # MIME multipart message and automatically delimited. CGI variables get
1168 # the "raw" (i.e., undecoded) body of the corresponding message part.
1170 # Variables can be CGI variables, i.e., those from the QUERY_STRING,
1171 # environment variables, e.g., REMOTE_USER, REMOTE_HOST, or REMOTE_ADDR,
1172 # or predefined values, e.g., CGI_Decoded_QS (The complete, decoded,
1173 # query string), CGI_Content_Length (the length of the decoded query
1174 # string), CGI_Year, CGI_Month, CGI_Time, and CGI_Hour (the current
1177 # All these are available when defined in a CGI attribute. All environment
1178 # variables are accessible as $ENV{'name'}. So, to access the REMOTE_HOST
1179 # and the REMOTE_USER, use, e.g.:
1181 # <SCRIPT TYPE='text/ssperl'>
1182 # ($ENV{'REMOTE_HOST'}||"-")." $ENV{'REMOTE_USER'}"
1185 # (This will print a "-" if REMOTE_HOST is not known)
1186 # Another way to do this is:
1188 # <META CONTENT="text/ssperl; CGI='$REMOTE_HOST = - $REMOTE_USER'">
1189 # <SCRIPT TYPE='text/ssperl'>"$REMOTE_HOST $REMOTE_USER"</SCRIPT>
1191 # <META CONTENT='text/ssperl; CGI="$REMOTE_HOST = - $REMOTE_USER"
1192 # SRC={"$REMOTE_HOST $REMOTE_USER\n"}'>
1194 # This is possible because ALL environment variables are available as
1195 # CGI variables. The environment variables take precedence over CGI
1196 # names in case of a "name clash". For instance:
1197 # <META CONTENT="text/ssperl; CGI='$HOME' SRC={$HOME}">
1198 # Will print the current HOME directory (environment) irrespective whether
1199 # there is a CGI variable from the query
1200 # (e.g., Where do you live? <INPUT TYPE="TEXT" NAME="HOME">)
1201 # THIS IS A SECURITY FEATURE. It prevents clients from changing
1202 # the values of defined environment variables (e.g., by supplying
1203 # a bogus $REMOTE_ADDR). Although $ENV{} is not changed by the META tags,
1204 # it would make the use of declared variables insecure. You can still
1205 # access CGI variables after a name clash with
1206 # CGIscriptor::CGIparseValue(<name>).
1208 # Some CGI variables are present several times in the query string
1209 # (e.g., from multiple selections). These should be defined as
1210 # @VARIABLENAME=default in the CGI attribute. The list @VARIABLENAME
1211 # will contain ALL VARIABLENAME values from the query, or a single
1212 # default value. If there is an ENVIRONMENT variable of the
1213 # same name, it will be used instead of the default AND the query
1214 # values. The corresponding function is
1215 # CGIscriptor::CGIparseValueList(<name>)
1217 # CGI variables collected in a @VARIABLENAME list are unordered.
1218 # When more structured variables are needed, a hash table can be used.
1219 # A variable defined as %VARIABLE=default will collect all
1220 # CGI-parameters whose name start with 'VARIABLE' in a hash table with
1221 # the remainder of the name as a key. For instance, %PERSON will
1222 # collect PERSONname='John Doe', PERSONbirthdate='01 Jan 00', and
1223 # PERSONspouse='Alice' into a hash table %PERSON such that $PERSON{'spouse'}
1224 # equals 'Alice'. Any default value or environment value will be stored
1225 # under the "" key. If there is an ENVIRONMENT variable of the same name,
1226 # it will be used instead of the default AND the query values. The
1227 # corresponding function is CGIscriptor::CGIparseValueHash(<name>)
1229 # This method of first declaring your environment and CGI variables
1230 # before being able to use them in the scripts might seem somewhat
1231 # clumsy, but it protects you from inadvertedly printing out the values of
1232 # system environment variables when their names coincide with those used
1233 # in the CGI forms. It also prevents "clients" from supplying CGI
1234 # parameter values for your private variables.
1235 # THIS IS A SECURITY FEATURE!
1238 # NON-HTML CONTENT TYPES
1240 # Normally, CGIscriptor prints the standard "Content-type: text/html\n\n"
1241 # message before anything is printed. This has been extended to include
1242 # plain text (.txt) files, for which the Content-type (MIME type)
1243 # 'text/plain' is printed. In all other respects, text files are treated
1244 # as HTML files (this can be switched off by removing '.txt' from the
1245 # $FilePattern variable) . When the content type should be something else,
1246 # e.g., with multipart files, use the $RawFilePattern (.xmr, see also next
1247 # item). CGIscriptor will not print a Content-type message for this file
1248 # type (which must supply its OWN Content-type message). Raw files must
1249 # still conform to the <SCRIPT></SCRIPT> and <META> tag specifications.
1254 # CGIscriptor is intended to process HTML and text files only. You can
1255 # create documents of any mime-type on-the-fly using "raw" text files,
1256 # e.g., with the .xmr extension. However, CGIscriptor will not process
1257 # binary files of any type, e.g., pictures or sounds. Given the sheer
1258 # number of formats, I do not have any intention to do so. However,
1259 # an escape route has been provided. You can construct a genuine raw
1260 # (.xmr) text file that contains the perl code to service any file type
1261 # you want. If the global $BinaryMapFile variable contains the path to
1262 # this file (e.g., /BinaryMapFile.xmr), this file will be called
1263 # whenever an unsupported (non-HTML) file type is requested. The path
1264 # to the requested binary file is stored in $ENV('CGI_BINARY_FILE')
1265 # and can be used like any other CGI-variable. Servicing binary files
1266 # then becomes supplying the correct Content-type (e.g., print
1267 # "Content-type: image/jpeg\n\n";) and reading the file and writing it
1268 # to STDOUT (e.g., using sysread() and syswrite()).
1273 # All attributes of a META tag are ignored, except the
1274 # CONTENT='text/ssperl; CGI=" ... " [SRC=" ... "]' attribute. The string
1275 # inside the quotes following the CONTENT= indication (white-space is
1276 # ignored, "" '' `` (){}[]-quote pairs are allowed, plus their \ versions)
1277 # MUST start with any of the CGIscriptor mime-types (e.g.: text/ssperl or
1278 # text/osshell) and a comma or semicolon.
1279 # The quoted string following CGI= contains a white-space separated list
1280 # of declarations of the CGI (and Environment) values and default values
1281 # used when no CGI values are supplied by the query string.
1283 # If the default value is a longer string containing special characters,
1284 # possibly spanning several lines, the string must be enclosed in quotes.
1285 # You may use any pair of quotes or brackets from the list '', "", ``, (),
1286 # [], or {} to distinguish default values (or preceded by \, e.g., \(...\)
1287 # is different from (...)). The outermost pair will always be used and any
1288 # other quotes inside the string are considered to be part of the string
1293 # will result in $Value getting the default value: ['this'
1295 # (NOTE that the newline is part of the default value!).
1297 # Internally, for defining and initializing CGI (ENV) values, the META
1298 # and SCRIPT tags use the functions "defineCGIvariable($name, $default)"
1299 # (scalars) and "defineCGIvariableList($name, $default)" (lists).
1300 # These functions can be used inside scripts as
1301 # "CGIscriptor::defineCGIvariable($name, $default)" and
1302 # "CGIscriptor::defineCGIvariableList($name, $default)".
1303 # "CGIscriptor::defineCGIvariableHash($name, $default)".
1305 # The CGI attribute will be processed exactly identical when used inside
1306 # the <SCRIPT> tag. However, this use is not according to the
1307 # HTML 4.0 specifications of the W3C.
1312 # There is a problem when constructing html files containing
1313 # server-side perl scripts with standard HTML tools. These
1314 # tools will refuse to process any text between <SCRIPT></SCRIPT>
1315 # tags. This is quite annoying when you want to use large
1316 # HTML templates where you will fill in values.
1318 # For this purpose, CGIscriptor will read the neutral
1319 # <DIV CLASS="ssperl" ID="varname"></DIV> or
1320 # <INS CLASS="ssperl" ID="varname"></INS>
1321 # tag (in Cascading Style Sheet manner) Note that
1322 # "varname" has NO '$' before it, it is a bare name.
1323 # Any text between these <DIV ...></DIV> or
1324 # <INS ...></INS>tags will be assigned to '$varname'
1325 # as is (e.g., as a literal).
1326 # No processing or interpolation will be performed.
1327 # There is also NO nesting possible. Do NOT nest a
1328 # </DIV> inside a <DIV></DIV>! Moreover, neither INS nor
1329 # DIV tags do ensure a block structure in the final
1330 # rendering (i.e., no empty lines).
1332 # Note that <DIV CLASS="ssperl" ID="varname"/>
1333 # is handled the XML way. No content is processed,
1334 # but varname is defined, and any SRC directives are
1337 # You can use $varname like any other variable name.
1338 # However, $varname is NOT a CGI variable and will be
1339 # completely internal to your script. There is NO
1340 # interaction between $varname and the outside world.
1342 # To interpolate a DIV derived text, you can use:
1343 # $varname =~ s/([\]])/\\\1/g; # Mark ']'-quotes
1344 # $varname = eval("qq[$varname]"); # Interpolate all values
1346 # The DIV tags will process IF, UNLESS, CGI and
1347 # SRC attributes. The SRC files will be pre-pended to the
1348 # body text of the tag. SRC blocks are NOT executed.
1350 # CONDITIONAL PROCESSING: THE 'IF' AND 'UNLESS' ATTRIBUTES
1352 # It is often necessary to include code-blocks that should be executed
1353 # conditionally, e.g., only for certain browsers or operating system.
1354 # Furthermore, quite often sanity and security checks are necessary
1355 # before user (form) data can be processed, e.g., with respect to
1356 # email addresses and filenames.
1358 # Checks added to the code are often difficult to find, interpret or
1359 # maintain and in general mess up the code flow. This kind of confussion
1361 # Also, for many of the supported "foreign" scripting languages, adding
1362 # these checks is cumbersome or even impossible.
1364 # As a uniform method for asserting the correctness of "context", two
1365 # attributes are added to all supported tags: IF and UNLESS.
1366 # They both evaluate their value and block execution when the
1367 # result is <FALSE> (IF) or <TRUE> (UNLESS) in Perl, e.g.,
1368 # UNLESS='$NUMBER \> 100;' blocks execution if $NUMBER <= 100. Note that
1369 # the backslash in the '\>' is removed and only used to differentiate
1370 # this conditional '>' from the tag-closing '>'. For symmetry, the
1371 # backslash in '\<' is also removed. Inside these conditionals,
1372 # ~/ and ./ are expanded to their respective directory root paths.
1374 # For example, the following tag will be ignored when the filename is
1377 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
1378 # IF='CGIscriptor::CGIsafeFileName($FILENAME);'>
1382 # The IF and UNLESS values must be quoted. The same quotes are supported
1383 # as with the other attributes. The SRC attribute is ignored when IF and
1384 # UNLESS block execution.
1386 # NOTE: 'IF' and 'UNLESS' always evaluate perl code.
1389 # THE MAGIC SOURCE ATTRIBUTE (SRC=)
1391 # The SRC attribute inside tags accepts a list of filenames and URL's
1392 # separated by "," comma's (or ";" semicolons).
1393 # ALL the variable values defined in the CGI attribute are available
1394 # in @ARGV as if the file or block was executed from the command line,
1395 # in the exact order in which they were declared in the preceding CGI
1398 # First, a SRC={}-block will be evaluated as if the code inside the
1399 # block was part of a <SCRIPT></SCRIPT> construct, i.e.,
1400 # "print do { code };'';" or `code` (i.e., SAFEqx('code)).
1401 # Only a single block is evaluated. Note that this is processed less
1402 # efficiently than <SCRIPT> </SCRIPT> blocks. Type of evaluation
1403 # depends on the content-type: Perl for text/ssperl and OS shell for
1404 # text/osshell. For other mime types (scripting languages), anything in
1405 # the source block is put in front of the code block "inside" the tag.
1407 # Second, executable files (i.e., -x filename != 0) are evaluated as:
1408 # print `filename \'$ARGV[0]\' \'$ARGV[1]\' ...`
1409 # That is, you can actually call executables savely from the SRC tag.
1411 # Third, text files that match the file pattern, used by CGIscriptor to
1412 # check whether files should be processed ($FilePattern), are
1413 # processed in-line (i.e., recursively) by CGIscriptor as if the code
1414 # was inserted in the original source file. Recursions, i.e., calling
1415 # a file inside itself, are blocked. If you need them, you have to code
1416 # them explicitely using "main::ProcessFile($file_path)".
1418 # Fourth, Perl text files (i.e., -T filename != 0) are evaluated as:
1419 # "do FileName;'';".
1421 # Last, URL's (i.e., starting with 'HTTP://', 'FTP://', 'GOPHER://',
1422 # 'TELNET://', 'WHOIS://' etc.) are loaded
1423 # and printed. The loading and handling of <BASE> and document header
1424 # is done by a command generated by main::GET_URL($URL [, 0]). You can enter your
1425 # own code (default is curl, wget, or snarf and some post-processing to add a <BASE> tag).
1427 # There are two pseudo-file names: PREFIX and POSTFIX. These implement
1428 # a switch from prefixing the SRC code/files (PREFIX, default) before the
1429 # content of the tag to appending the code after the content of the tag
1430 # (POSTFIX). The switches are done in the order in which the PREFIX and
1431 # POSTFIX labels are encountered. You can mix PREFIX and POSTFIX labels
1432 # in any order with the SRC files. Note that the ORDER of file execution
1433 # is determined for prefixed and postfixed files seperately.
1435 # File paths can be preceded by the URL protocol prefix "file://". This
1436 # is simply STRIPPED from the name.
1440 # "http://cgi-bin/Action_Forms.pl/Statistics/Sign_Test.html?positive=8&negative=22
1441 # will result in printing "${SS_PUB}/Statistics/Sign_Test.html"
1442 # With QUERY_STRING = "positive=8&negative=22"
1444 # on encountering the lines:
1445 # <META CONTENT="text/osshell; CGI='$positive=11 $negative=3'">
1446 # <b><SCRIPT LANGUAGE=PERL TYPE="text/ssperl" SRC="./Statistics/SignTest.pl">
1449 # This line will be processed as:
1450 # "<b>`${SS_SCRIPT}/Statistics/SignTest.pl '8' '22'`</b><p>"
1452 # In which "${SS_SCRIPT}/Statistics/SignTest.pl" is an executable script,
1453 # This line will end up printed as:
1454 # "<b>p <= 0.0161</b><p>"
1456 # Note that the META tag itself will never be printed, and is invisible to
1457 # the outside world.
1459 # The SRC files in a DIV or INS tag will be added (pre-pended) to the body
1460 # of the <DIV></DIV> tag. Blocks are NOT executed! If you do not
1461 # need any content, you can use the <DIV...../> format.
1464 # THE CGISCRIPTOR ROOT DIRECTORIES ~/ AND ./
1466 # Inside <SCRIPT></SCRIPT> tags, filepaths starting
1467 # with "~/" are replaced by "$YOUR_HTML_FILES/", this way files in the
1468 # public directories can be accessed without direct reference to the
1469 # actual paths. Filepaths starting with "./" are replaced by
1470 # "$YOUR_SCRIPTS/" and this should only be used for scripts.
1472 # Note: this replacement can seriously affect Perl scripts. Watch
1473 # out for constructs like $a =~ s/aap\./noot./g, use
1474 # $a =~ s@aap\.@noot.@g instead.
1476 # CGIscriptor.pl will assign the values of $SS_PUB and $SS_SCRIPT
1477 # (i.e., $YOUR_HTML_FILES and $YOUR_SCRIPTS) to the environment variables
1478 # $SS_PUB and $SS_SCRIPT. These can be accessed by the scripts that are
1480 # Values not preceded by $, ~/, or ./ are used as literals
1483 # OS SHELL SCRIPT EVALUATION (CONTENT-TYPE=TEXT/OSSHELL)
1485 # OS scripts are executed by a "safe" version of the `` operator (i.e.,
1486 # SAFEqx(), see also below) and any output is printed. CGIscriptor will
1487 # interpolate the script and replace all user-supplied CGI-variables by
1488 # their ''-quoted values (actually, all variables defined in CGI attributes
1489 # are quoted). Other Perl variables are interpolated in a simple fasion,
1490 # i.e., $scalar by their value, @list by join(' ', @list), and %hash by
1491 # their name=value pairs. Complex references, e.g., @$variable, are all
1492 # evaluated in a scalar context. Quotes should be used with care.
1493 # NOTE: the results of the shell script evaluation will appear in the
1494 # @CGIscriptorResults stack just as any other result.
1495 # All occurrences of $@% that should NOT be interpolated must be
1496 # preceeded by a "\". Interpolation can be switched off completely by
1497 # setting $CGIscriptor::NoShellScriptInterpolation = 1
1498 # (set to 0 or undef to switch interpolation on again)
1500 # <SCRIPT TYPE="text/ssperl">
1501 # $CGIscriptor::NoShellScriptInterpolation = 1;
1505 # RUN TIME TRANSLATION OF INPUT FILES
1507 # Allows general and global conversions of files using Regular Expressions.
1508 # Very handy (but costly) to rewrite legacy pages to a new format.
1509 # Select files to use it on with
1510 # my $TranslationPaths = 'filepattern';
1511 # This is costly. For efficiency, define:
1512 # $TranslationPaths = ''; when not using translations.
1513 # Accepts general regular expressions: [$pattern, $replacement]
1516 # my $TranslationPaths = 'filepattern'; # Pattern matching PATH_INFO
1518 # push(@TranslationTable, ['pattern', 'replacement']);
1519 # e.g. (for Ruby Rails):
1520 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
1521 # push(@TranslationTable, ['%>', '</SCRIPT>']);
1524 # my $currentRegExp;
1525 # foreach $currentRegExp (@TranslationTable)
1527 # my ($pattern, $replacement) = @$currentRegExp;
1528 # $$text =~ s!$pattern!$replacement!msg;
1532 # EVALUATION OF OTHER SCRIPTING LANGUAGES
1534 # Adding a MIME-type and an interpreter command to
1535 # %ScriptingLanguages automatically will catch any other
1536 # scripting language in the standard
1537 # <SCRIPT TYPE="[mime]"></SCRIPT> manner.
1538 # E.g., adding: $ScriptingLanguages{'text/sspython'} = 'python';
1539 # will actually execute the folowing code in an HTML page
1540 # (ignore 'REMOTE_HOST' for the moment):
1541 # <SCRIPT TYPE="text/sspython">
1543 # x = ["A","real","python","script","Hello","World","and", REMOTE_HOST]
1544 # print x[4:8] # Prints the list ["Hello","World","and", REMOTE_HOST]
1547 # The script code is NOT interpolated by perl, EXCEPT for those
1548 # interpreters that cannot handle variables themselves.
1549 # Currently, several interpreters are pre-installed:
1551 # Perl test - "text/testperl" => 'perl',
1552 # Python - "text/sspython" => 'python',
1553 # Ruby - "text/ssruby" => 'ruby',
1554 # Tcl - "text/sstcl" => 'tcl',
1555 # Awk - "text/ssawk" => 'awk -f-',
1556 # Gnu Lisp - "text/sslisp" => 'rep | tail +5 '.
1557 # "| egrep -v '> |^rep. |^nil\\\$'",
1558 # XLispstat - "text/xlispstat" => 'xlispstat | tail +7 '.
1559 # "| egrep -v '> \\\$|^NIL'",
1560 # Gnu Prolog- "text/ssprolog" => 'gprolog',
1561 # M4 macro's- "text/ssm4" => 'm4',
1562 # Born shell- "text/sh" => 'sh',
1563 # Bash - "text/bash" => 'bash',
1564 # C-shell - "text/csh" => 'csh',
1565 # Korn shell- "text/ksh" => 'ksh',
1566 # Praat - "text/sspraat" => "praat - | sed 's/Praat > //g'",
1567 # R - "text/ssr" => "R --vanilla --slave | sed 's/^[\[0-9\]*] //g'",
1568 # REBOL - "text/ssrebol" =>
1569 # "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\s*\[> \]* //g'",
1570 # PostgreSQL- "text/postgresql" => 'psql 2>/dev/null',
1573 # Note that the "value" of $ScriptingLanguages{mime} must be a command
1574 # that reads Standard Input and writes to standard output. Any extra
1575 # output of interactive interpreters (banners, echo's, prompts)
1576 # should be removed by piping the output through 'tail', 'grep',
1577 # 'sed', or even 'awk' or 'perl'.
1579 # For access to CGI variables there is a special hashtable:
1580 # %ScriptingCGIvariables.
1581 # CGI variables can be accessed in three ways.
1582 # 1. If the mime type is not present in %ScriptingCGIvariables,
1583 # nothing is done and the script itself should parse the relevant
1584 # environment variables.
1585 # 2. If the mime type IS present in %ScriptingCGIvariables, but it's
1586 # value is empty, e.g., $ScriptingCGIvariables{"text/sspraat"} = '';,
1587 # the script text is interpolated by perl. That is, all $var, @array,
1588 # %hash, and \-slashes are replaced by their respective values.
1589 # 3. In all other cases, the CGI and environment variables are added
1590 # in front of the script according to the format stored in
1591 # %ScriptingCGIvariables. That is, the following (pseudo-)code is
1592 # executed for each CGI- or Environment variable defined in the CGI-tag:
1593 # printf(INTERPRETER, $ScriptingCGIvariables{$mime}, $CGI_NAME, $CGI_VALUE);
1595 # For instance, "text/testperl" => '$%s = "%s";' defines variable
1596 # definitions for Perl, and "text/sspython" => '%s = "%s"' for Python
1597 # (note that these definitions are not save, the real ones contain '-quotes).
1599 # THIS WILL NOT WORK FOR @VARIABLES, the (empty) $VARIABLES will be used
1602 # The $CGI_VALUE parameters are "shrubed" of all control characters
1603 # and quotes (by &shrubCGIparameter($CGI_VALUE)) for the options 2 and 3.
1604 # Control characters are replaced by \0<octal ascii value> (the exception
1605 # is \015, the newline, which is replaced by \n) and quotes
1606 # and backslashes by their HTML character
1607 # value (' -> ' ` -> ` " -> " \ -> \ & -> &er;).
1609 # if a client would supply the string value (in standard perl, e.g.,
1610 # \n means <newline>)
1611 # "/dev/null';\nrm -rf *;\necho '"
1612 # it would be processed as
1613 # '/dev/null';\nrm -rf *;\necho ''
1614 # (e.g., sh or bash would process the latter more according to your
1616 # If your intepreter requires different protection measures, you will
1617 # have to supply these in %main::SHRUBcharacterTR (string => translation),
1618 # e.g., $SHRUBcharacterTR{"\'"} = "'";
1620 # Currently, the following definitions are used:
1621 # %ScriptingCGIvariables = (
1622 # "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value' (for testing)
1623 # "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
1624 # "text/ssruby" => '@%s = "%s"', # Ruby @VAR = "value"
1625 # "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
1626 # "text/ssawk" => '%s = "%s";', # Awk VAR = "value";
1627 # "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
1628 # "text/xlispstat" => '(setq %s "%s")', # Xlispstat (setq VAR "value")
1629 # "text/ssprolog" => '', # Gnu prolog (interpolated)
1630 # "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
1631 # "text/sh" => "\%s='\%s';", # Born shell VAR='value';
1632 # "text/bash" => "\%s='\%s';", # Born again shell VAR='value';
1633 # "text/csh" => "\$\%s = '\%s';", # C shell $VAR = 'value';
1634 # "text/ksh" => "\$\%s = '\%s';", # Korn shell $VAR = 'value';
1635 # "text/sspraat" => '', # Praat (interpolation)
1636 # "text/ssr" => '%s <- "%s";', # R VAR <- "value";
1637 # "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
1638 # "text/postgresql" => '', # PostgreSQL (interpolation)
1642 # Four tables allow fine-tuning of interpreter with code that should be
1643 # added before and after each code block:
1645 # Code added before each script block
1646 # %ScriptingPrefix = (
1647 # "text/testperl" => "\# Prefix Code;", # Perl script testing
1648 # "text/ssm4" => 'divert(0)' # M4 macro's (open STDOUT)
1650 # Code added at the end of each script block
1651 # %ScriptingPostfix = (
1652 # "text/testperl" => "\# Postfix Code;", # Perl script testing
1653 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1655 # Initialization code, inserted directly after opening (NEVER interpolated)
1656 # %ScriptingInitialization = (
1657 # "text/testperl" => "\# Initialization Code;", # Perl script testing
1658 # "text/ssawk" => 'BEGIN {', # Server Side awk scripts
1659 # "text/sslisp" => '(prog1 nil ', # Lisp (rep)
1660 # "text/xlispstat" => '(prog1 nil ', # xlispstat
1661 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1663 # Cleanup code, inserted before closing (NEVER interpolated)
1664 # %ScriptingCleanup = (
1665 # "text/testperl" => "\# Cleanup Code;", # Perl script testing
1666 # "text/sspraat" => 'Quit',
1667 # "text/ssawk" => '};', # Server Side awk scripts
1668 # "text/sslisp" => '(princ "\n" standard-output)).' # Closing print to rep
1669 # "text/xlispstat" => '(print "" *standard-output*)).' # Closing print to xlispstat
1670 # "text/postgresql" => '\q',
1674 # The SRC attribute is NOT magical for these interpreters. In short,
1675 # all code inside a source file or {} block is written verbattim
1676 # to the interpreter. No (pre-)processing or executional magic is done.
1678 # A serious shortcomming of the described mechanism for handling other
1679 # (scripting) languages, with respect to standard perl scripts
1680 # (i.e., 'text/ssperl'), is that the code is only executed when
1681 # the pipe to the interpreter is closed. So the pipe has to be
1682 # closed at the end of each block. This means that the state of the
1683 # interpreter (e.g., all variable values) is lost after the closing of
1684 # the next </SCRIPT> tag. The standard 'text/ssperl' scripts retain
1685 # all values and definitions.
1687 # APPLICATION MIME TYPES
1689 # To ease some important auxilliary functions from within the
1690 # html pages I have added them as MIME types. This uses
1691 # the mechanism that is also used for the evaluation of
1692 # other scripting languages, with interpolation of CGI
1693 # parameters (and perl-variables). Actually, these are
1694 # defined exactly like any other "scripting language".
1696 # text/ssdisplay: display some (HTML) text with interpolated
1697 # variables (uses `cat`).
1698 # text/sslogfile: write (append) the interpolated block to the file
1699 # mentioned on the first, non-empty line
1700 # (the filename can be preceded by 'File: ',
1701 # note the space after the ':',
1702 # uses `awk .... >> <filename>`).
1703 # text/ssmailto: send email directly from within the script block.
1704 # The first line of the body must contain
1705 # To:Name@Valid.Email.Address
1706 # (note: NO space between 'To:' and the email adres)
1707 # For other options see the mailto man pages.
1708 # It works by directly sending the (interpolated)
1709 # content of the text block to a pipe into the
1710 # Linux program 'mailto'.
1712 # In these script blocks, all Perl variables will be
1713 # replaced by their values. All CGI variables are cleaned before
1714 # they are used. These CGI variables must be redefined with a
1715 # CGI attribute to restore their original values.
1716 # In general, this will be more secure than constructing
1717 # e.g., your own email command lines. For instance, Mailto will
1718 # not execute any odd (forged) email addres, but just stops
1719 # when the email address is invalid and awk will construct
1720 # any filename you give it (e.g. '<File;rm\\\040-f' would end up
1721 # as a "valid" UNIX filename). Note that it will also gladly
1722 # store this file anywhere (/../../../etc/passwd will work!).
1723 # Use the CGIscriptor::CGIsafeFileName() function to clean the
1726 # SHELL SCRIPT PIPING
1728 # If a shell script starts with the UNIX style "#! <shell command> \n"
1729 # line, the rest of the shell script is piped into the indicated command,
1731 # open(COMMAND, "| command");print COMMAND $RestOfScript;
1733 # In many ways this is equivalent to the MIME-type profiling for
1734 # evaluating other scripting languages as discussed above. The
1735 # difference breaks down to convenience. Shell script piping is a
1736 # "raw" implementation. It allows you to control all aspects of
1737 # execution. Using the MIME-type profiling is easier, but has a
1738 # lot of defaults built in that might get in the way. Another
1739 # difference is that shell script piping uses the SAFEqx() function,
1740 # and MIME-type profiling does not.
1742 # Execution of shell scripts is under the control of the Perl Script blocks
1743 # in the document. The MIME-type triggered execution of <SCRIPT></SCRIPT>
1744 # blocks can be simulated easily. You can switch to a different shell,
1745 # e.g. tcl, completely by executing the following Perl commands inside
1748 # <SCRIPT TYPE="text/ssperl">
1749 # $main::ShellScriptContentType = "text/ssTcl"; # Yes, you can do this
1750 # CGIscriptor::RedirectShellScript('/usr/bin/tcl'); # Pipe to Tcl
1751 # $CGIscriptor::NoShellScriptInterpolation = 1;
1754 # After this script is executed, CGIscriptor will parse scripts of
1755 # TYPE="text/ssTcl" and pipe their contents into '|/usr/bin/tcl'
1756 # WITHOUT interpolation (i.e., NO substitution of Perl variables).
1757 # The crucial function is :
1758 # CGIscriptor::RedirectShellScript('/usr/bin/tcl')
1759 # After executing this function, all shell scripts AND all
1760 # calls to SAFEqx()) are piped into '|/usr/bin/tcl'. If the argument
1761 # of RedirectShellScript is empty, e.g., '', the original (default)
1764 # The standard output, STDOUT, of any pipe is send to the client.
1765 # Currently, you should be carefull with quotes in such a piped script.
1766 # The results of a pipe is NOT put on the @CGIscriptorResults stack.
1767 # As a result, you do not have access to the output of any piped (#!)
1768 # process! If you want such access, execute
1769 # <SCRIPT TYPE="text/osshell">echo "script"|command</SCRIPT>
1771 # <SCRIPT TYPE="text/ssperl">
1772 # $resultvar = SAFEqx('echo "script"|command');
1775 # Safety is never complete. Although SAFEqx() prevents some of the
1776 # most obvious forms of attacks and security slips, it cannot prevent
1777 # them all. Especially, complex combinations of quotes and intricate
1778 # variable references cannot be handled safely by SAFEqx. So be on
1782 # PERL CODE EVALUATION (CONTENT-TYPE=TEXT/SSPERL)
1784 # All PERL scripts are evaluated inside a PERL package. This package
1785 # has a separate name space. This isolated name space protects the
1786 # CGIscriptor.pl program against interference from user code. However,
1787 # some variables, e.g., $_, are global and cannot be protected. You are
1788 # advised NOT to use such global variable names. You CAN write
1789 # directives that directly access the variables in the main program.
1790 # You do so at your own risk (there is definitely enough rope available
1791 # to hang yourself). The behavior of CGIscriptor becomes undefined if
1792 # you change its private variables during run time. The PERL code
1793 # directives are used as in:
1794 # $Result = eval($directive); print $Result;'';
1795 # ($directive contains all text between <SCRIPT></SCRIPT>).
1796 # That is, the <directive> is treated as ''-quoted string and
1797 # the result is treated as a scalar. To prevent the VALUE of the code
1798 # block from appearing on the client's screen, end the directive with
1799 # ';""</SCRIPT>'. Evaluated directives return the last value, just as
1800 # eval(), blocks, and subroutines, but only as a scalar.
1802 # IMPORTANT: All PERL variables defined are persistent. Each <SCRIPT>
1803 # </SCRIPT> construct is evaluated as a {}-block with associated scope
1804 # (e.g., for "my $var;" declarations). This means that values assigned
1805 # to a PERL variable can be used throughout the document unless they
1806 # were declared with "my". The following will actually work as intended
1807 # (note that the ``-quotes in this example are NOT evaluated, but used
1808 # as simple quotes):
1810 # <META CONTENT="text/ssperl; CGI=`$String='abcdefg'`">
1812 # <SCRIPT TYPE=text/ssperl>@List = split('', $String);</SCRIPT>
1814 # <SCRIPT TYPE=text/ssperl>join(", ", @List[1..$#List]);</SCRIPT>
1816 # The first <SCRIPT TYPE=text/ssperl></SCRIPT> construct will return the
1817 # value scalar(@List), the second <SCRIPT TYPE=text/ssperl></SCRIPT>
1818 # construct will print the elements of $String separated by commas, leaving
1819 # out the first element, i.e., $List[0].
1821 # Another warning: './' and '~/' are ALWAYS replaced by the values of
1822 # $YOUR_SCRIPTS and $YOUR_HTML_FILES, respectively . This can interfere
1823 # with pattern matching, e.g., $a =~ s/aap\./noot\./g will result in the
1824 # evaluations of $a =~ s/aap\\${YOUR_SCRIPTS}noot\\${YOUR_SCRIPTS}g. Use
1825 # s@<regexp>.@<replacement>.@g instead.
1828 # SERVER SIDE SESSIONS AND ACCESS CONTROL (LOGIN)
1830 # An infrastructure for user acount authorization and file access control
1831 # is available. Each request is matched against a list of URL path patterns.
1832 # If the request matches, a Session Ticket is required to access the URL.
1833 # This Session Ticket should be present as a CGI parameter or Cookie, eg:
1835 # CGI: SESSIONTICKET=<value>
1836 # Cookie: CGIscriptorSESSION=<value>
1838 # The example implementation stores Session Tickets as files in a local
1839 # directory. To create Session Tickets, a Login request must be given
1840 # with a LOGIN=<value> CGI parameter, a user name and a (doubly hashed)
1841 # password. The user name and (singly hashed) password are stored in a
1842 # PASSWORD ticket with the same name as the user account (name cleaned up
1845 # The example session model implements 4 functions:
1847 # The password is hashed with the user name and server side salt, and then
1848 # hashed with a random salt. Client and Server both perform these actions
1849 # and the Server only grants access if restults are the same. The server
1850 # side only stores the password hashed with the user name and
1851 # server side salt. Neither the plain password, nor the hashed password is
1852 # ever exchanged. Only values hashed with the one-time salt are exchanged.
1854 # For every access to a restricted URL, the Session Ticket is checked before
1855 # access is granted. There are three session modes. The first uses a fixed
1856 # Session Ticket that is stored as a cookie value in the browser (actually,
1857 # as a sessionStorage value). The second uses only the IP address at login
1858 # to authenticate requests. The third
1859 # is a Challenge mode, where the client has to calculate the value of the
1860 # next one-time Session Ticket from a value derived from the password and
1863 # A new password is hashed with the user name and server side salt, and
1864 # then encrypted (XORed)
1865 # with the old password hashed with the user name and salt. That value is
1866 # exchanged and XORed with the stored old hashed(password+username+salt).
1867 # Again, the stored password value is never exchanged unencrypted.
1869 # The text of a new account (Type: PASSWORD) file is constructed from
1870 # the new username (CGI: NEWUSERNAME, converted to lowercase) and
1871 # hashed new password (CGI: NEWPASSWORD). The same process is used to encrypt
1872 # the new password as is used for the Password Change function.
1873 # Again, the stored password value is never exchanged unencrypted.
1874 # Some default setting are encoded. For display in the browser, the new password
1875 # is reencrypted (XORed) with a special key, the old password hash
1876 # hashed with a session specific random hex value sent initially with the
1877 # session login ticket ($RANDOMSALT).
1878 # For example for user "NewUser" and password "NewPassword" with filename
1883 # Password: 19afeadfba8d5dcd252e157fafd3010859f8762b87682b6b6cdb3e565194fa91
1884 # IPaddress: 127\.0\.0\.1
1885 # AllowedPaths: ^/Private/[\w\-]+\.html?
1886 # AllowedPaths: ^/Private/newuser/
1887 # Salt: e93cf858a1d5626bf095ea5c25df990dfa969ff5a5dc908b22c9a5229b525f65
1889 # Date: Fri Jun 29 12:46:22 2012
1891 # Signature: 676c35d3aa63540293ea5442f12872bfb0a22665b504f58f804582493b6ef04e
1893 # The password is created with the commands:
1895 # printf '%s' 'NewPasswordnewuser970e68017413fb0ea84d7fe3c463077636dd6d53486910d4a53c693dd4109b1a'|shasum -a 256
1897 # If the CPAN mudule Digest is installed, it is used instead of the commands.
1898 # However, the password account files are protected against unauthorized change.
1899 # To obtain a valid Password account, the following command should be given:
1901 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
1902 # masterkey='Sherlock investigates oleander curry in Bath' \
1903 # password='NewPassword' \
1904 # Private/.Passwords/newuser
1909 # The session authentication mechanism is based on the exchange of ticket
1910 # identifiers. A ticket identifier is just a string of characters, a name
1911 # or a random 64 character hexadecimal string. Ticket identifiers should be
1912 # "safe" filenames (except user names). There are four types of tickets:
1913 # PASSWORD: User account descriptors, including a user name and password
1914 # LOGIN: Temporary anonymous tickets used during login
1915 # IPADDRESS: Authentication tokens that allow access based on the IP address of the request
1916 # SESSION: Reusable authentication tokens
1917 # CHALLENGE: One-time authentication tokens
1918 # All tickets can have an expiration date in the form of a time duration
1919 # from creation, in seconds, minutes, hours, or days (+duration[smhd]).
1920 # An absolute time can be given in seconds since the epoch of the server host.
1921 # Note that expiration times of CHALLENGE authentication tokens are calculated
1922 # from the last access time. Accounts can include a maximal lifetime
1923 # for session tickets (MaxLifetime).
1925 # A Login page should create a LOGIN ticket file locally and send a
1926 # server specific salt, a Random salt, and a LOGIN ticket
1927 # identifier. The server side compares the username and hashed password,
1928 # actually hashed(hashed(password+serversalt)+Random salt) from the client with
1929 # the values it calculates from the stored Random salt from the LOGIN
1930 # ticket and the hashed(password+serversalt) from the PASSWORD ticket. If
1931 # successful, a new SESSION ticket is generated as a (double) hash sum of the stored
1932 # password and the LOGIN ticket. This SESSION ticket should also be
1933 # generated by the client and stored as sessionStorage and cookie values
1934 # as needed. The Username, IP address and Path are available as
1935 # $LoginUsername, $LoginIPaddress, and $LoginPath, respectively.
1937 # The CHALLENGE protocol stores the single hashed version of the SESSION tickets.
1938 # However, this value is not exchanged, but kept secret in the JavaScript
1939 # sessionStorage object. Instead, every page returned from the
1940 # server will contain a one-time Challenge value ($CHALLENGETICKET) which
1941 # has to be hashed with the stored value to return the current ticket
1944 # In the current example implementation, all random values are created as
1945 # full, 256 bit SHA256 hash values (Hex strings) of 64 bytes read from
1951 # A limited level of authorization tuning is build into the login system.
1952 # Each account file (PASSWORD ticket file) can contain a number of
1953 # Capabilities lines. These control special priveliges. The
1954 # Capabilities can be checked inside the HTML pages as part of the
1955 # ticket information. Two privileges are handled internally:
1956 # CreateUser and VariableREMOTE_ADDR.
1957 # CreateUser allows the logged in user to create a new user account.
1958 # With VariableREMOTE_ADDR, the session of the logged in user is
1959 # not limited to the Remote IP address from which the inital log-in took
1960 # place. Sessions can hop from one apparant (proxy) IP address to another,
1961 # e.g., when using Tor. Any IPaddress patterns given in the PASSWORD
1962 # ticket file remain in effect during the session. For security reasons,
1963 # the VariableREMOTE_ADDR capability is only effective if the session
1964 # type is CHALLENGE.
1967 # Security considerations with Session tickets
1969 # For strong security, please use end-to-end encryption. This can be
1970 # achieved using a VPN (Virtual Private Network), SSH tunnel, or a HTTPS
1971 # capable server with OpenSSL. The session ticket system of CGIscriptor.pl
1972 # is intended to be used as a simple authentication mechanism WITHOUT
1973 # END-TO-END ENCRYPTION. The authenticating mechanism tries to use some
1974 # simple means to protect the authentication process from eavesdropping.
1975 # For this it uses a secure hash function, SHA256. For all practial purposes,
1976 # it is impossible to "decrypt" a SHA256 sum. But this login scheme is
1977 # only as secure as your browser. Which, in general, is not very secure.
1979 # One fundamental weakness of the implemented procedure is that the Client
1980 # obtains the code to encrypt the passwords from the server. It is the JavaScript
1981 # code in the HTML pages. An attacker who could place himself between Server
1982 # and Client, a man in the middle attack (MITM), could change the code to
1983 # reveal the plaintext password and other information. There is no
1984 # real protection against this attack without end-to-end encryption and
1985 # authentication. A simple, but rather cumbersome, way to check for such
1986 # attacks would be to store known good copys of the pages (downloaded
1987 # with a browser or automatically with curl or wget) and
1988 # then use other tools to download new pages at random intervals and compare
1989 # them to the old pages. For instance, the following line would remove
1990 # the variable ticket codes and give a fixed SHA256 sum for the original
1991 # Login.html page+code:
1992 # curl http://localhost:8080/Private/index.html | \
1993 # sed 's/=\"[a-z0-9]\{64\}\"/=""/g' | shasum -a 256
1994 # A simple diff command between old and new files should give only
1995 # differences in half a dozen lines, where only hexadecimal salt values
1996 # will actually differ.
1998 # A sort of solution for the MITM attack problem that might protect at
1999 # least the plaintext password would be to run a trusted web
2000 # page from local storage to handle password input. The solution would be
2001 # to add a hidden iFrame tag loading the untrusted page from the URL and
2002 # extract the needed ticket and salt values. Then run the stored, trusted,
2003 # code with these values. It is not (yet) possible to set the
2004 # required session storage inside the browser, so this method only works
2005 # for IPADDRESS sessions and plain SESSION tickets. There are many
2006 # security problems with this "solution".
2008 # Humans tend to reuse passwords. A compromise of a site running
2009 # CGIscriptor.pl could therefore lead to a compromise of user accounts at
2010 # other sites. Therefore, plain text passwords are never stored, used, or
2011 # exchanged. Instead, the plain password and user name are "encrypted" with
2012 # a server site salt value. Actually, all are concatenated and hashed
2013 # with a one-way secure hash function (SHA256) into a single string.
2014 # Whenever the word "password" is used, this hash sum is meant. Note that
2015 # the salts are generated from /dev/urandom. You should check whether the
2016 # implementation of /dev/urandom on your platform is secure before
2017 # relying on it. This might be a problem when running CGIscriptor under
2018 # Cygwin on MS Windows.
2019 # Note: no attempt is made to slow down the password hash, so bad
2020 # passwords can be cracked by brute force
2022 # As the (hashed) passwords are all that is needed to identify at the site,
2023 # these should not be stored in this form. A site specific passphrase
2024 # can be entered as an environment variable ($ENV{'CGIMasterKey'}). This
2025 # phrase is hashed with the server site salt and the result is hashed with
2026 # the user name and then XORed with the password when it is stored. Also, to
2027 # detect changes to the account (PASSWORD) and session tickets, a
2028 # (HMAC) hash of some of the contents of the ticket with the server salt and
2029 # CGIMasterKey is stored in each ticket.
2031 # Creating a valid (hashed) password, encrypt it with the CGIMasterKey and
2032 # construct a signature of the ticket are non-trivial. This has to be redone
2033 # with every change of the ticket file or CGIMasterKey change. CGIscriptor
2034 # can do this from the command line with the command:
2036 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
2037 # masterkey='Sherlock investigates oleander curry in Bath' \
2038 # password='There is no password like more password' \
2041 # CGIscriptor will exit after this command with the first option being
2042 # --managelogin. Options have the form:
2044 # salt=[file or string]
2045 # Server salt value to use io the value
2046 # stored in the ticket file. Will replace the stored value if a new
2047 # password is given. If you change the server salt, you have to
2048 # reset all the passwords. There is absolutely no procedure known
2049 # to recover plaintext passwords, except asking the account holders.
2050 # You are strongly adviced to make a backup before you apply such a change
2051 # masterkey=[file or string]
2052 # CGIMasterKey used to read and decrypt the ticket
2053 # newmasterkey=[file or string]
2054 # CGIMasterKey used to encrypt, sign,
2055 # and write the ticket. Defaults to the masterkey. If you change
2056 # the masterkey, you will have to reset all the accounts. You are strongly
2057 # adviced to make a backup before you apply such a change
2058 # password=[file or string]
2059 # New plaintext password
2061 # When the value of an option is a existing file path, the first line of
2062 # that file is used. Options are followed by one or more paths plus names
2063 # of existing ticket files. Each password option is only used for a single
2064 # ticket file. It is most definitely a bad idea to use a password that is
2065 # identical to an existing filepath, as the file will be read instead. Be
2066 # aware that the name of the file should be a cleaned up version of the
2067 # Username. This will not be checked.
2069 # For the authentication and a change of password, the (old) password
2070 # is used to "encrypt" a random one-time token or the new password,
2071 # respectively. For authentication, decryption is not needed, so a secure
2072 # hash function (SHA256) is used to create a one-way hash sum "encryption".
2073 # A new password must be decrypted. New passwords are encryped by XORing
2074 # them with the old password.
2076 # Strong Passwords: It is so easy
2077 # If you only could see what you are typing
2079 # Your password might be vulnerable to brute force guessing
2080 # (https://en.wikipedia.org/wiki/Brute_force_attack).
2081 # Protections against such attacks are costly in terms of code
2082 # complexity, bugs, and execution time. However, there is a very
2083 # simple and secure counter measure. See the XKCD comic
2084 # (http://xkcd.com/936/). The phrase, "There is no password like more
2085 # password" would be both much easier to remember, and still stronger
2086 # than "h4]D%@m:49", at least before this phrase was pasted as an
2087 # example on the Internet.
2089 # For the procedures used at this site, a basic computer setup can
2090 # check in the order of a billion passwords per second. You need a
2091 # password (or phrase) strength in the order of 56 bits to be a
2092 # little secure (one year on a single computer). Please be so kind
2093 # and add the name of your favorite flower, dish, fictional
2094 # character, or small town to your password. Say, Oleander, Curry,
2095 # Sherlock, or Bath, UK (each adds ~12 bits) or even the phrase "Sherlock
2096 # investigates oleander curry in Bath" (adds > 56 bits, note that
2097 # oleander is poisonous, so do not try this curry at home). That
2098 # would be more effective than adding a thousand rounds of encryption.
2099 # Typing long passwords without seeing what you are typing is
2100 # problematic. So a button should be included to make password
2106 # Client side JavaScript code definitions. Variable names starting with '$'
2107 # are CGIscriptor CGI variables. Some of the hashes could be strengthened
2108 # by switching to HMAC signatures. However, the security issues of
2109 # maintaining parallel functions for HMAC in both Perl and Javascript seem
2110 # to be more serious than the attack vectors against the hashes. But HMAC
2111 # is indeed used for the ticket signatures.
2114 # HashPlaintextPassword() {
2115 # var plaintextpassword = document.getElementById('PASSWORD');
2116 # var serversalt = document.getElementById('SERVERSALT');
2117 # var username = document.getElementById('CGIUSERNAME');
2118 # return hex_sha256(plaintextpassword.value+username.value.toLowerCase()+serversalt.value);
2120 # var randomsalt = $RANDOMSALT; // From CGIscriptor
2121 # var loginticket = $LOGINTICKET; // From CGIscriptor
2122 # // Hash plaintext password
2123 # var password = HashPlaintextPassword();
2124 # // Authorize login
2125 # var hashedpassword = hex_sha256(randomsalt+password);
2127 # var sessionticket = hex_sha256(loginticket+password);
2128 # sessionStorage.setItem("CGIscriptorPRIVATE", sessionticket);
2129 # // Secretkey for encrypting new passwords, acts like a one-time pad
2130 # // Is set anew with every login, ie, also whith password changes
2131 # // and for each create new user request
2132 # var secretkey = hex_sha256(password+loginticket+randomsalt);
2133 # sessionStorage.setItem("CGIscriptorSECRET", secretkey);
2135 # // For a SESSION type request
2136 # sessionticket = hex_sha256(sessionStorage.getItem("CGIscriptorPRIVATE"));
2137 # createCookie("CGIscriptorSESSION",sessionticket, 0, "");
2139 // For a CHALLENGE type request
2140 # var sessionset = "$CHALLENGETICKET"; // From CGIscriptor
2141 # var sessionkey = sessionStorage.getItem("CGIscriptorPRIVATE");
2142 # sessionticket = hex_sha256(sessionset+sessionkey);
2143 # createCookie("CGIscriptorCHALLENGE",sessionticket, 0, "");
2145 # // For transmitting a new password
2146 # HashPlaintextNewPassword() {
2147 # var plaintextpassword = document.getElementById('NEWPASSWORD');
2148 # var serversalt = document.getElementById('SERVERSALT');
2149 # var username = document.getElementById('NEWUSERNAME');
2150 # return hex_sha256(plaintextpassword.value+username.value.toLowerCase()+serversalt.value);
2153 # var newpassword = document.getElementById('NEWPASSWORD');
2154 # var newpasswordrep = document.getElementById('NEWPASSWORDREP');
2155 # // Hash plaintext password
2156 # newpassword.value = HashPlaintextNewPassword();
2157 # var secretkey = sessionStorage.getItem("CGIscriptorSECRET");
2159 # var encrypted = XOR_hex_strings(secretkey, newpassword.value);
2160 # newpassword.value = encrypted;
2161 # newpasswordrep.value = encrypted;
2163 # // XOR of hexadecimal strings of equal length
2164 # function XOR_hex_strings(hex1, hex2) {
2165 # var resultHex = "";
2166 # var maxlength = Math.max(hex1.length, hex2.length);
2168 # for(var i=0; i < maxlength; ++i) {
2169 # var h1 = hex1.charAt(i);
2171 # var h2 = hex2.charAt(i);
2173 # var d1 = parseInt(h1,16);
2174 # var d2 = parseInt(h2,16);
2175 # var resultD = d1^d2;
2176 # resultHex = resultHex+resultD.toString(16);
2181 # Password encryption based on $ENV{'CGIMasterKey'}.
2182 # Server side Perl code:
2184 # # Password encryption
2185 # my $masterkey = $ENV{'CGIMasterKey'}
2186 # my $hash1 = hash_string($masterkey.$serversalt);
2187 # my $CryptKey = hash_string($username.$hash1);
2188 # $password = XOR_hex_strings($CryptKey,$password);
2190 # # Key for HMAC signing
2191 # my $hash1 = hash_string($masterkey.$serversalt);
2192 # my $HMACKey = hash_string($username.$hash1);
2198 # A CGIscriptor package is attached to the bottom of this file. With
2199 # this package you can personalize your version of CGIscriptor by
2200 # including often used perl routines. These subroutines can be
2201 # accessed by prefixing their names with CGIscriptor::, e.g.,
2202 # <SCRIPT LANGUAGE=PERL TYPE=text/ssperl>
2203 # CGIscriptor::ListDocs("/Books/*") # List all documents in /Books
2205 # It already contains some useful subroutines for Document Management.
2206 # As it is a separate package, it has its own namespace, isolated from
2207 # both the evaluator and the main program. To access variables from
2208 # the document <SCRIPT></SCRIPT> blocks, use $CGIexecute::<var>.
2210 # Currently, the following functions are implemented
2211 # (precede them with CGIscriptor::, see below for more information)
2212 # - SAFEqx ('String') -> result of qx/"String"/ # Safe application of ``-quotes
2213 # Is used by text/osshell Shell scripts. Protects all CGI
2214 # (client-supplied) values with single quotes before executing the
2215 # commands (one of the few functions that also works WITHOUT CGIscriptor::
2217 # - defineCGIvariable ($name[, $default) -> 0/1 (i.e., failure/success)
2218 # Is used by the META tag to define and initialize CGI and ENV
2219 # name/value pairs. Tries to obtain an initializing value from (in order):
2222 # The default value given (if any)
2223 # (one of the few functions that also works WITHOUT CGIscriptor::
2225 # - CGIsafeFileName (FileName) -> FileName or ""
2226 # Check a string against the Allowed File Characters (and ../ /..).
2227 # Returns an empty string for unsafe filenames.
2228 # - CGIsafeEmailAddress (Email) -> Email or ""
2229 # Check a string against correct email address pattern.
2230 # Returns an empty string for unsafe addresses.
2231 # - RedirectShellScript ('CommandString') -> FILEHANDLER or undef
2232 # Open a named PIPE for SAFEqx to receive ALL shell scripts
2233 # - URLdecode (URL encoded string) -> plain string # Decode URL encoded argument
2234 # - URLencode (plain string) -> URL encoded string # Encode argument as URL code
2235 # - CGIparseValue (ValueName [, URL_encoded_QueryString]) -> Decoded value
2236 # Extract the value of a CGI variable from the global or a private
2237 # URL-encoded query (multipart POST raw, NOT decoded)
2238 # - CGIparseValueList (ValueName [, URL_encoded_QueryString])
2239 # -> List of decoded values
2240 # As CGIparseValue, but now assembles ALL values of ValueName into a list.
2241 # - CGIparseHeader (ValueName [, URL_encoded_QueryString]) -> Header
2242 # Extract the header of a multipart CGI variable from the global or a private
2243 # URL-encoded query ("" when not a multipart variable or absent)
2244 # - CGIparseForm ([URL_encoded_QueryString]) -> Decoded Form
2245 # Decode the complete global URL-encoded query or a private
2247 # - read_url(URL) # Returns the page from URL (with added base tag, both FTP and HTTP)
2248 # Uses main::GET_URL(URL, 1) to get at the command to read the URL.
2249 # - BrowseDirs(RootDirectory [, Pattern, Startdir, CGIname]) # print browsable directories
2250 # - ListDocs(Pattern [,ListType]) # Prints a nested HTML directory listing of
2251 # all documents, e.g., ListDocs("/*", "dl");.
2252 # - HTMLdocTree(Pattern [,ListType]) # Prints a nested HTML listing of all
2253 # local links starting from a given document, e.g.,
2254 # HTMLdocTree("/Welcome.html", "dl");
2257 # THE RESULTS STACK: @CGISCRIPTORRESULTS
2259 # If the pseudo-variable "$CGIscriptorResults" has been defined in a
2260 # META tag, all subsequent SCRIPT and META results are pushed
2261 # on the @CGIscriptorResults stack. This list is just another
2262 # Perl variable and can be used and manipulated like any other list.
2263 # $CGIscriptorResults[-1] is always the last result.
2264 # This is only of limited use, e.g., to use the results of an OS shell
2265 # script inside a Perl script. Will NOT contain the results of Pipes
2266 # or code from MIME-profiling.
2269 # USEFULL CGI PREDEFINED VARIABLES (DO NOT ASSIGN TO THESE)
2271 # $CGI_HOME - The DocumentRoot directory
2272 # $CGI_Decoded_QS - The complete decoded Query String
2273 # $CGI_Content_Length - The ACTUAL length of the Query String
2274 # $CGI_Date - Current date and time
2275 # $CGI_Year $CGI_Month $CGI_Day $CGI_WeekDay - Current Date
2276 # $CGI_Time - Current Time
2277 # $CGI_Hour $CGI_Minutes $CGI_Seconds - Current Time, split
2279 # $CGI_GMTYear $CGI_GMTMonth $CGI_GMTDay $CGI_GMTWeekDay $CGI_GMTYearDay
2280 # $CGI_GMTHour $CGI_GMTMinutes $CGI_GMTSeconds $CGI_GMTisdst
2283 # USEFULL CGI ENVIRONMENT VARIABLES
2285 # Variables accessible (in APACHE) as $ENV{<name>}
2286 # (see: "http://hoohoo.ncsa.uiuc.edu/cgi/env.html"):
2288 # QUERY_STRING - The query part of URL, that is, everything that follows the
2290 # PATH_INFO - Extra path information given after the script name
2291 # PATH_TRANSLATED - Extra pathinfo translated through the rule system.
2292 # (This doesn't always make sense.)
2293 # REMOTE_USER - If the server supports user authentication, and the script is
2294 # protected, this is the username they have authenticated as.
2295 # REMOTE_HOST - The hostname making the request. If the server does not have
2296 # this information, it should set REMOTE_ADDR and leave this unset
2297 # REMOTE_ADDR - The IP address of the remote host making the request.
2298 # REMOTE_IDENT - If the HTTP server supports RFC 931 identification, then this
2299 # variable will be set to the remote user name retrieved from
2300 # the server. Usage of this variable should be limited to logging
2302 # AUTH_TYPE - If the server supports user authentication, and the script
2303 # is protected, this is the protocol-specific authentication
2304 # method used to validate the user.
2305 # CONTENT_TYPE - For queries which have attached information, such as HTTP
2306 # POST and PUT, this is the content type of the data.
2307 # CONTENT_LENGTH - The length of the said content as given by the client.
2308 # SERVER_SOFTWARE - The name and version of the information server software
2309 # answering the request (and running the gateway).
2310 # Format: name/version
2311 # SERVER_NAME - The server's hostname, DNS alias, or IP address as it
2312 # would appear in self-referencing URLs
2313 # GATEWAY_INTERFACE - The revision of the CGI specification to which this
2314 # server complies. Format: CGI/revision
2315 # SERVER_PROTOCOL - The name and revision of the information protocol this
2316 # request came in with. Format: protocol/revision
2317 # SERVER_PORT - The port number to which the request was sent.
2318 # REQUEST_METHOD - The method with which the request was made. For HTTP,
2319 # this is "GET", "HEAD", "POST", etc.
2320 # SCRIPT_NAME - A virtual path to the script being executed, used for
2321 # self-referencing URLs.
2322 # HTTP_ACCEPT - The MIME types which the client will accept, as given by
2323 # HTTP headers. Other protocols may need to get this
2324 # information from elsewhere. Each item in this list should
2325 # be separated by commas as per the HTTP spec.
2326 # Format: type/subtype, type/subtype
2327 # HTTP_USER_AGENT - The browser the client is using to send the request.
2328 # General format: software/version library/version.
2331 # INSTRUCTIONS FOR RUNNING CGIscriptor ON UNIX
2333 # CGIscriptor.pl will run on any WWW server that runs Perl scripts, just add
2334 # a line like the following to your srm.conf file (Apache example):
2336 # ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
2338 # URL's that refer to http://www.your.address/SHTML/... will now be handled
2339 # by CGIscriptor.pl, which can use a private directory tree (default is the
2340 # DOCUMENT_ROOT directory tree, but it can be anywhere, see manual).
2342 # If your hosting ISP won't let you add ScriptAlias lines you can use
2343 # the following "rewrite"-based "scriptalias" in .htaccess
2344 # (from Gerd Franke)
2348 # RewriteCond %{REQUEST_FILENAME} .html$
2349 # RewriteCond %{SCRIPT_FILENAME} !cgiscriptor.pl$
2350 # RewriteCond %{REQUEST_FILENAME} -f
2351 # RewriteRule ^(.*)$ /cgi-bin/cgiscriptor.pl/$1?&%{QUERY_STRING}
2353 # Everthing with the extension ".html" and not including "cgiscriptor.pl"
2354 # in the url and where the file "path/filename.html" exists is redirected
2355 # to "/cgi.bin/cgiscriptor.pl/path/filename.html?query".
2356 # The user configuration should get the same path-level as the
2359 # # Just enter your own directory path here
2360 # $YOUR_HTML_FILES = "$ENV{'DOCUMENT_ROOT'}";
2361 # # use DOCUMENT_ROOT only, if .htaccess lies in the root-directory.
2363 # If this .htaccess goes in a specific directory, the path to this
2364 # directory must be added to $ENV{'DOCUMENT_ROOT'}.
2366 # The CGIscriptor file contains all documentation as comments. These
2367 # comments can be removed to speed up loading (e.g., `egrep -v '^#'
2368 # CGIscriptor.pl` > leanScriptor.pl). A bare bones version of
2369 # CGIscriptor.pl, lacking documentation, most comments, access control,
2370 # example functions etc. (but still with the copyright notice and some
2371 # minimal documentation) can be obtained by calling CGIscriptor.pl on the
2372 # command line with the '-slim' command line argument, e.g.,
2374 # >CGIscriptor.pl -slim > slimCGIscriptor.pl
2376 # CGIscriptor.pl can be run from the command line with <path> and <query> as
2377 # arguments, as `CGIscriptor.pl <path> <query>`, inside a perl script
2378 # with 'do CGIscriptor.pl' after setting $ENV{PATH_INFO}
2379 # and $ENV{QUERY_STRING}, or CGIscriptor.pl can be loaded with 'require
2380 # "/real-path/CGIscriptor.pl"'. In the latter case, requests are processed
2381 # by 'Handle_Request();' (again after setting $ENV{PATH_INFO} and
2382 # $ENV{QUERY_STRING}).
2384 # Using the command line execution option, CGIscriptor.pl can be used as a
2385 # document (meta-)preprocessor. If the first argument is '-', STDIN will be read.
2388 # > cat MyDynamicDocument.html | CGIscriptor.pl - '[QueryString]' > MyStaticFile.html
2390 # This command line will produce a STATIC file with the DYNAMIC content of
2391 # MyDocument.html "interpolated".
2393 # This option would be very dangerous when available over the internet.
2394 # If someone could sneak a 'http://www.your.domain/-' URL past your
2395 # server, CGIscriptor could EXECUTE any POSTED contend.
2396 # Therefore, for security reasons, STDIN will NOT be read
2397 # if ANY of the HTTP server environment variables is set (e.g.,
2398 # SERVER_PORT, SERVER_PROTOCOL, SERVER_NAME, SERVER_SOFTWARE,
2399 # HTTP_USER_AGENT, REMOTE_ADDR).
2400 # This block on processing STDIN on HTTP requests can be lifted by setting
2401 # $BLOCK_STDIN_HTTP_REQUEST = 0;
2402 # In the security configuration. Butbe carefull when doing this.
2403 # It can be very dangerous.
2405 # Running demo's and more information can be found at
2406 # http://www.fon.hum.uva.nl/~rob/OSS/OSS.html
2408 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site or
2409 # CPAN that can use CGIscriptor.pl as the base of a µWWW server and
2410 # demonstrates its use.
2413 # PROCESSING NON-FILESYSTEM DATA
2415 # Normally, HTTP (WWW) requests map onto file that can be accessed
2416 # using the perl open() function. That is, the web server runs on top of
2417 # some directory structure. However, we can envission (and put to good
2418 # use) other systems that do not use a normal file system. The whole CGI
2419 # was developed to make dynamic document generation possible.
2421 # A special case is where we want to have it both: A normal web server
2422 # with normal "file data", but not a normal files system. For instance,
2423 # we want or normal Web Site to run directly from a RAM hash table or
2424 # other database, instead of from disk. But we do NOT want to code the
2425 # whole site structure in CGI.
2427 # CGIscriptor can do this. If the web server fills an environment variable
2428 # $ENV{'CGI_FILE_CONTENT'} with the content of the "file", then the content
2429 # of this variable is processed instead of opening a file. If this environment
2430 # variable has the value '-', the content of another environment variable,
2431 # $ENV{'CGI_DATA_ACCESS_CODE'} is executed as:
2432 # eval("\@_ = ($file_path); do {$ENV{'CGI_DATA_ACCESS_CODE'}};")
2433 # and the result is processed as if it was the content of the requested
2435 # (actually, the names of the environment variables are user configurable,
2436 # they are stored in the local variables $CGI_FILE_CONTENT and
2437 # $CGI_DATA_ACCESS_CODE)
2439 # When using this mechanism, the SRC attribute mechanism will only partially work.
2440 # Only the "recursive" calls to CGIscriptor (the ProcessFile() function)
2441 # will work, the automagical execution of SRC files won't. (In this case,
2442 # the SRC attribute won't work either for other scripting languages)
2445 # NON-UNIX PLATFORMS
2447 # CGIscriptor.pl was mainly developed and tested on UNIX. However, as I
2448 # coded part of the time on an Apple Macintosh under MacPerl, I made sure
2449 # CGIscriptor did run under MacPerl (with command line options). But only
2450 # as an independend script, not as part of a HTTP server. I have used it
2451 # under Apache in Windows XP.
2456 ###############################################################################
2458 # SECURITY CONFIGURATION
2460 # Special configurations related to SECURITY
2461 # (i.e., optional, see also environment variables below)
2464 # Log Clients and the requested paths (Redundant when loging Queries)
2466 $ClientLog = "./Client.log"; # (uncomment for use)
2468 # Format: Localtime | REMOTE_USER REMOTE_IDENT REMOTE_HOST REMOTE_ADDRESS \
2469 # PATH_INFO CONTENT_LENGTH (actually, the real query+post length)
2471 # Log Clients and the queries, the CGIQUERYDECODE is required if you want
2472 # to log queries. If you log Queries, the loging of Clients is redundant
2473 # (note that queries can be quite long, so this might not be a good idea)
2475 #$QueryLog = "./Query.log"; # (uncomment for use)
2478 # the Access files should contain Hostnames or IP addresses,
2479 # i.e. REMOTE_HOST or REMOTE_ADDR, each on a separate line
2480 # optionally followed by one ore more file patterns, e.g., "edu /DEMO".
2481 # Matching is done "domain first". For example ".edu" matches all
2482 # clients whose "name" ends in ".edu" or ".EDU". The file pattern
2483 # "/DEMO" matches all paths that contain the strings "/DEMO" or "/demo"
2484 # (both matchings are done case-insensitive).
2485 # The name special symbol "-" matches ALL clients who do not supply a
2486 # REMOTE_HOST name, "*" matches all clients.
2487 # Lines starting with '-e' are evaluated. A non-zero return value indicates
2488 # a match. You can use $REMOTE_HOST, $REMOTE_ADDR, and $PATH_INFO. These
2489 # lines are evaluated in the program's own name-space. So DO NOT assign to
2492 # Accept the following users (remove comment # and adapt filename)
2493 $CGI_Accept = -s
"$YOUR_SCRIPTS/ACCEPT.lis" ?
"$YOUR_SCRIPTS/ACCEPT.lis" : ''; # (uncomment for use)
2495 # Reject requests from the following users (remove comment # and
2496 # adapt filename, this is only of limited use)
2497 $CGI_Reject = -s
"$YOUR_SCRIPTS/REJECT.lis" ?
"$YOUR_SCRIPTS/REJECT.lis" : ''; # (uncomment for use)
2499 # Empty lines or comment lines starting with '#' are ignored in both
2500 # $CGI_Accept and $CGI_Reject.
2502 # Block STDIN (i.e., '-') requests when servicing an HTTP request
2503 # Comment this out if you realy want to use STDIN in an on-line web server
2504 $BLOCK_STDIN_HTTP_REQUEST = 1;
2507 # End of security configuration
2509 ##################################################<<<<<<<<<<End Remove
2511 # PARSING CGI VALUES FROM THE QUERY STRING (USER CONFIGURABLE)
2513 # The CGI parse commands. These commands extract the values of the
2514 # CGI variables from the URL encoded Query String.
2515 # If you want to use your own CGI decoders, you can call them here
2516 # instead, using your own PATH and commenting/uncommenting the
2519 # CGI parse command for individual values
2520 # (if $List > 0, returns a list value, if $List < 0, a hash table, this is optional)
2521 sub YOUR_CGIPARSE
# ($Name [, $List]) -> Decoded value
2524 my $List = shift || 0;
2525 # Use one of the following by uncommenting
2526 if(!$List) # Simple value
2528 return CGIscriptor
::CGIparseValue
($Name) ;
2530 elsif($List < 0) # Hash tables
2532 return CGIscriptor
::CGIparseValueHash
($Name); # Defined in CGIscriptor below
2536 return CGIscriptor
::CGIparseValueList
($Name); # Defined in CGIscriptor below
2539 # return `/PATH/cgiparse -value $Name`; # Shell commands
2540 # require "/PATH/cgiparse.pl"; return cgivalue($Name); # Library
2543 sub YOUR_CGIQUERYDECODE
2545 # Use one of the following by uncommenting
2546 return CGIscriptor
::CGIparseForm
(); # Defined in CGIscriptor below
2547 # return `/PATH/cgiparse -form`; # Shell commands
2548 # require "/PATH/cgiparse.pl"; return cgiform(); # Library
2551 # End of configuration
2553 #######################################################################
2555 # Translating input files.
2556 # Allows general and global conversions of files using Regular Expressions
2557 # Translations are applied in the order of definition.
2560 # my $TranslationPaths = 'pattern'; # Pattern matching PATH_INFO
2562 # push(@TranslationTable, ['pattern', 'replacement']);
2563 # e.g. (for Ruby Rails):
2564 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2565 # push(@TranslationTable, ['%>', '</SCRIPT>']);
2568 # my $currentRegExp;
2569 # foreach $currentRegExp (keys(%TranslationTable))
2571 # my $currentRegExp;
2572 # foreach $currentRegExp (@TranslationTable)
2574 # my ($pattern, $replacement) = @$currentRegExp;
2575 # $$text =~ s!$pattern!$replacement!msg;
2579 # Configuration section
2581 #######################################################################
2583 # The file paths on which to apply the translation
2584 my $TranslationPaths = ''; # NO files
2585 #$TranslationPaths = '.'; # ANY file
2586 # $TranslationPaths = '\.html'; # HTML files
2588 my @TranslationTable = ();
2590 push(@TranslationTable, ['\<\s*CGI\s+([^\>])*\>', '\<SCRIPT TYPE=\"text/ssperl\"\>$1\<\/SCRIPT>']);
2592 push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2593 push(@TranslationTable, ['%>', '</SCRIPT>']);
2595 sub performTranslation
# (\$text)
2597 my $text = shift || return;
2598 if(@TranslationTable && $TranslationPaths && $ENV{'PATH_INFO'} =~ m!$TranslationPaths!)
2601 foreach $currentRegExp (@TranslationTable)
2603 my ($pattern, $replacement) = @
$currentRegExp;
2604 $$text =~ s!$pattern!$replacement!msg;
2609 #######################################################################
2611 # Seamless access to other (Scripting) Languages
2612 # TYPE='text/ss<interpreter>'
2614 # Configuration section
2616 #######################################################################
2618 # OTHER SCRIPTING LANGUAGES AT THE SERVER SIDE (MIME => OScommand)
2619 # Yes, it realy is this simple! (unbelievable, isn't it)
2620 # NOTE: Some interpreters require some filtering to obtain "clean" output
2622 %ScriptingLanguages = (
2623 "text/testperl" => 'perl', # Perl for testing
2624 "text/sspython" => 'python', # Python
2625 "text/ssruby" => 'ruby', # Ruby
2626 "text/sstcl" => 'tcl', # TCL
2627 "text/ssawk" => 'awk -f-', # Awk
2628 "text/sslisp" => # lisp (rep, GNU)
2629 'rep | tail +4 '."| egrep -v '> |^rep. |^nil\\\$'",
2630 "text/xlispstat" => # xlispstat
2631 'xlispstat | tail +7 ' ."| egrep -v '> \\\$|^NIL'",
2632 "text/ssprolog" => # Prolog (GNU)
2633 "gprolog | tail +4 | sed 's/^| ?- //'",
2634 "text/ssm4" => 'm4', # M4 macro's
2635 "text/sh" => 'sh', # Born shell
2636 "text/bash" => 'bash', # Born again shell
2637 "text/csh" => 'csh', # C shell
2638 "text/ksh" => 'ksh', # Korn shell
2639 "text/sspraat" => # Praat (sound/speech analysis)
2640 "praat - | sed 's/Praat > //g'",
2642 "R --vanilla --slave | sed 's/^[\[0-9\]*] //'",
2643 "text/ssrebol" => # REBOL
2644 "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\\s*\[> \]* //'",
2645 "text/postgresql" => 'psql 2>/dev/null',
2647 # Not real scripting, but the use of other applications
2648 "text/ssmailto" => "awk 'NF||F{F=1;print \\\$0;}'|mailto >/dev/null", # Send mail from server
2649 "text/ssdisplay" => 'cat', # Display, (interpolation)
2650 "text/sslogfile" => # Log to file, (interpolation)
2651 "awk 'NF||L {if(!L){L=tolower(\\\$1)~/^file:\\\$/ ? \\\$2 : \\\$1;}else{print \\\$0 >> L;};}'",
2656 # To be able to access the CGI variables in your script, they
2657 # should be passed to the scripting language in a readable form
2658 # Here you can enter how they should be printed (the first %s
2659 # is replaced by the NAME of the CGI variable as it apears in the
2660 # META tag, the second by its VALUE).
2661 # For Perl this would be:
2662 # "text/testperl" => '$%s = "%s";',
2663 # which would be executed as
2664 # printf('$%s = "%s";', $CGI_NAME, $CGI_VALUE);
2666 # If the hash table value doesn't exist, nothing is done
2667 # (you have to parse the Environment variables yourself).
2668 # If it DOES exist but is empty (e.g., "text/sspraat" => '',)
2669 # Perl string interpolation of variables (i.e., $var, @array,
2670 # %hash) is performed. This means that $@%\ must be protected
2673 %ScriptingCGIvariables = (
2674 "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value'; (for testing)
2675 "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
2676 "text/ssruby" => '@%s = "%s"', # Ruby @VAR = 'value'
2677 "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
2678 "text/ssawk" => '%s = "%s";', # Awk VAR = 'value';
2679 "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
2680 "text/xlispstat" => '(setq %s "%s")', # xlispstat (setq VAR "value")
2681 "text/ssprolog" => '', # Gnu prolog (interpolated)
2682 "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
2683 "text/sh" => "\%s='\%s'", # Born shell VAR='value'
2684 "text/bash" => "\%s='\%s'", # Born again shell VAR='value'
2685 "text/csh" => "\$\%s='\%s';", # C shell $VAR = 'value';
2686 "text/ksh" => "\$\%s='\%s';", # Korn shell $VAR = 'value';
2688 "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
2689 "text/sspraat" => '', # Praat (interpolation)
2690 "text/ssr" => '%s <- "%s";', # R VAR <- "value";
2691 "text/postgresql" => '', # PostgreSQL (interpolation)
2693 # Not real scripting, but the use of other applications
2694 "text/ssmailto" => '', # MAILTO, (interpolation)
2695 "text/ssdisplay" => '', # Display, (interpolation)
2696 "text/sslogfile" => '', # Log to file, (interpolation)
2701 # If you want something added in front or at the back of each script
2702 # block as send to the interpreter add it here.
2703 # mime => "string", e.g., "text/sspython" => "python commands"
2704 %ScriptingPrefix = (
2705 "text/testperl" => "\# Prefix Code;", # Perl script testing
2706 "text/ssm4" => 'divert(0)', # M4 macro's (open STDOUT)
2710 # If you want something added at the end of each script block
2711 %ScriptingPostfix = (
2712 "text/testperl" => "\# Postfix Code;", # Perl script testing
2713 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2717 # If you need initialization code, directly after opening
2718 %ScriptingInitialization = (
2719 "text/testperl" => "\# Initialization Code;", # Perl script testing
2720 "text/ssawk" => 'BEGIN {', # Server Side awk scripts (VAR = "value")
2721 "text/sslisp" => '(prog1 nil ', # Lisp (rep)
2722 "text/xlispstat" => '(prog1 nil ', # xlispstat
2723 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2727 # If you need cleanup code before closing
2728 %ScriptingCleanup = (
2729 "text/testperl" => "\# Cleanup Code;", # Perl script testing
2730 "text/sspraat" => 'Quit',
2731 "text/ssawk" => '};', # Server Side awk scripts (VAR = "value")
2732 "text/sslisp" => '(princ "\n" standard-output)).', # Closing print to rep
2733 "text/xlispstat" => '(print ""))', # Closing print to xlispstat
2734 "text/postgresql" => '\q', # quit psql
2735 "text/ssdisplay" => "", # close cat
2740 # End of configuration for foreign scripting languages
2742 ###############################################################################
2744 # Initialization Code
2747 sub Initialize_Request
2749 ###############################################################################
2751 # ENVIRONMENT VARIABLES
2753 # Use environment variables to configure CGIscriptor on a temporary basis.
2754 # If you define any of the configurable variables as environment variables,
2755 # these are used instead of the "hard coded" values above.
2757 $SS_PUB = $ENV{'SS_PUB'} || $YOUR_HTML_FILES;
2758 $SS_SCRIPT = $ENV{'SS_SCRIPT'} || $YOUR_SCRIPTS;
2761 # Substitution strings, these are used internally to handle the
2762 # directory separator strings, e.g., '~/' -> 'SS_PUB:' (Mac)
2763 $HOME_SUB = $SS_PUB;
2764 $SCRIPT_SUB = $SS_SCRIPT;
2767 # Make sure all script are reliably loaded
2768 push(@INC, $SS_SCRIPT);
2771 # Add the directory separator to the "home" directories.
2772 # (This is required for ~/ and ./ substitution)
2773 $HOME_SUB .= '/' if $HOME_SUB;
2774 $SCRIPT_SUB .= '/' if $SCRIPT_SUB;
2776 $CGI_HOME = $ENV{'DOCUMENT_ROOT'};
2777 $ENV{'PATH_TRANSLATED'} =~ /$ENV{'PATH_INFO'}/is;
2778 $CGI_HOME = $` unless $ENV{'DOCUMENT_ROOT'}; # Get the DOCUMENT_ROOT directory
2779 $default_values{'CGI_HOME'} = $CGI_HOME;
2780 $ENV{'HOME'} = $CGI_HOME;
2781 # Set SS_PUB and SS_SCRIPT as Environment variables (make them available
2783 $ENV{'SS_PUB'} = $SS_PUB unless $ENV{'SS_PUB'};
2784 $ENV{'SS_SCRIPT'} = $SS_SCRIPT unless $ENV{'SS_SCRIPT'};
2786 $FilePattern = $ENV{'FilePattern'} || $FilePattern;
2787 $MaximumQuerySize = $ENV{'MaximumQuerySize'} || $MaximumQuerySize;
2788 $ClientLog = $ENV{'ClientLog'} || $ClientLog;
2789 $QueryLog = $ENV{'QueryLog'} || $QueryLog;
2790 $CGI_Accept = $ENV{'CGI_Accept'} || $CGI_Accept;
2791 $CGI_Reject = $ENV{'CGI_Reject'} || $CGI_Reject;
2794 $CGI_Accept =~ s@^\~/@$HOME_SUB@g if $CGI_Accept;
2795 $CGI_Reject =~ s@^\~/@$HOME_SUB@g if $CGI_Reject;
2796 $ClientLog =~ s@^\~/@$HOME_SUB@g if $ClientLog;
2797 $QueryLog =~ s@^\~/@$HOME_SUB@g if $QueryLog;
2799 $CGI_Accept =~ s@^\./@$SCRIPT_SUB@g if $CGI_Accept;
2800 $CGI_Reject =~ s@^\./@$SCRIPT_SUB@g if $CGI_Reject;
2801 $ClientLog =~ s@^\./@$SCRIPT_SUB@g if $ClientLog;
2802 $QueryLog =~ s@^\./@$SCRIPT_SUB@g if $QueryLog;
2804 @CGIscriptorResults = (); # A stack of results
2806 # end of Environment variables
2808 #############################################################################
2810 # Define and Store "standard" values
2812 # BEFORE doing ANYTHING check the size of Query String
2813 length($ENV{'QUERY_STRING'}) <= $MaximumQuerySize || dieHandler(2, "QUERY TOO LONG\n");
2815 # The Translated Query String and the Actual length of the (decoded)
2817 if($ENV{'QUERY_STRING'})
2819 # If this can contain '`"-quotes, be carefull to use it QUOTED
2820 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2821 $default_values{CGI_Content_Length} = length($default_values{CGI_Decoded_QS});
2824 # Get the current Date and time and store them as default variables
2827 $LocalTime = localtime;
2829 # CGI_Year CGI_Month CGI_Day CGI_WeekDay CGI_Time
2830 # CGI_Hour CGI_Minutes CGI_Seconds
2832 $default_values{CGI_Date} = $LocalTime;
2833 ($default_values{CGI_WeekDay},
2834 $default_values{CGI_Month},
2835 $default_values{CGI_Day},
2836 $default_values{CGI_Time},
2837 $default_values{CGI_Year}) = split(' ', $LocalTime);
2838 ($default_values{CGI_Hour},
2839 $default_values{CGI_Minutes},
2840 $default_values{CGI_Seconds}) = split(':', $default_values{CGI_Time});
2843 # CGI_GMTYear CGI_GMTMonth CGI_GMTDay CGI_GMTWeekDay CGI_GMTYearDay
2844 # CGI_GMTHour CGI_GMTMinutes CGI_GMTSeconds CGI_GMTisdst
2846 ($default_values{CGI_GMTSeconds},
2847 $default_values{CGI_GMTMinutes},
2848 $default_values{CGI_GMTHour},
2849 $default_values{CGI_GMTDay},
2850 $default_values{CGI_GMTMonth},
2851 $default_values{CGI_GMTYear},
2852 $default_values{CGI_GMTWeekDay},
2853 $default_values{CGI_GMTYearDay},
2854 $default_values{CGI_GMTisdst}) = gmtime;
2858 # End of Initialize Request
2860 ###################################################################
2862 # SECURITY: ACCESS CONTROL
2864 # Check the credentials of each client (use pattern matching, domain first).
2865 # This subroutine will kill-off (die) the current process whenever access
2870 # >>>>>>>>>>Start Remove
2874 # Only accept clients which are authorized, reject all unnamed clients
2875 # if REMOTE_HOST is given.
2876 # If file patterns are given, check whether the user is authorized for
2880 # Use local variables, REMOTE_HOST becomes '-' if undefined
2881 my $REMOTE_HOST = $ENV{REMOTE_HOST} || '-';
2882 my $REMOTE_ADDR = $ENV{REMOTE_ADDR};
2883 my $PATH_INFO = $ENV{'PATH_INFO'};
2885 open(CGI_Accept, "<$CGI_Accept") || dieHandler(3, "$CGI_Accept: $!\n");
2889 next unless /\S/; # Skip empty lines
2890 next if /^\s*\#/; # Skip comments
2895 my $Accept = $'; # Get the expression
2896 $NoAccess &&= eval($Accept); # evaluate the expresion
2900 my ($Accept, @FilePatternList) = split;
2901 if($Accept eq '*' # Always match
2902 ||$REMOTE_HOST =~ /\Q$Accept\E$/is # REMOTE_HOST matches
2904 $Accept =~ /^[0-9\.]+$/
2905 && $REMOTE_ADDR =~ /^\Q$Accept\E/ # IP address matches
2909 if($FilePatternList[0])
2911 foreach $Pattern (@FilePatternList)
2913 # Check whether this patterns is accepted
2914 $NoAccess &&= ($PATH_INFO !~ m@\Q$Pattern\E@is);
2919 $NoAccess = 0; # No file patterns -> Accepted
2924 last unless $NoAccess;
2927 if($NoAccess){ dieHandler(4, "No Access
: $PATH_INFO\n");};
2933 # Reject named clients, accept all unnamed clients
2936 # Use local variables, REMOTE_HOST becomes '-' if undefined
2937 my $REMOTE_HOST = $ENV{'REMOTE_HOST'} || '-';
2938 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
2939 my $PATH_INFO = $ENV{'PATH_INFO'};
2941 open(CGI_Reject, "<$CGI_Reject") || dieHandler(5, "$CGI_Reject: $!\n");
2945 next unless /\S/; # Skip empty lines
2946 next if /^\s*\#/; # Skip comments
2951 my $Reject = $'; # Get the expression
2952 $NoAccess ||= eval($Reject); # evaluate the expresion
2956 my ($Reject, @FilePatternList) = split;
2957 if($Reject eq '*' # Always match
2958 ||$REMOTE_HOST =~ /\Q$Reject\E$/is # REMOTE_HOST matches
2959 ||($Reject =~ /^[0-9\.]+$/
2960 && $REMOTE_ADDR =~ /^\Q$Reject\E/is # IP address matches
2964 if($FilePatternList[0])
2966 foreach $Pattern (@FilePatternList)
2968 $NoAccess ||= ($PATH_INFO =~ m@\Q$Pattern\E@is);
2973 $NoAccess = 1; # No file patterns -> Rejected
2980 if($NoAccess){ dieHandler(6, "Request rejected
: $PATH_INFO\n");};
2983 ##########################################################<<<<<<<<<<End Remove
2988 # Does the filename contain any illegal characters (e.g., |, >, or <)
2989 dieHandler(7, "Illegal request
: $ENV{'PATH_INFO'}\n") if $ENV{'PATH_INFO'} =~ /[^$FileAllowedChars]/;
2990 # Does the pathname contain an illegal (blocked) "directory
"
2991 dieHandler(8, "Illegal request
: $ENV{'PATH_INFO'}\n") if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@; # Access is blocked
2992 # Does the pathname contain a direct referencer to BinaryMapFile
2993 dieHandler(9, "Illegal request
: $ENV{'PATH_INFO'}\n") if $BinaryMapFile && $ENV{'PATH_INFO'} =~ m@\Q$BinaryMapFile\E@; # Access is blocked
2995 # SECURITY: Is PATH_INFO allowed?
2996 if($FilePattern && $ENV{'PATH_INFO'} && $ENV{'PATH_INFO'} ne '-' &&
2997 ($ENV{'PATH_INFO'} !~ m@($FilePattern)$@is))
2999 # Unsupported file types can be processed by a special raw-file
3002 $ENV{'CGI_BINARY_FILE'} = $ENV{'PATH_INFO'};
3003 $ENV{'PATH_INFO'} = $BinaryMapFile;
3007 dieHandler(10, "Illegal file
\n");
3013 # End of Security Access Control
3016 ############################################################################
3018 # Get the POST part of the query and add it to the QUERY_STRING.
3021 sub Get_POST_part_of_query
3024 # If POST, Read data from stdin to QUERY_STRING
3025 if($ENV{'REQUEST_METHOD'} =~ /POST/is)
3027 # SECURITY: Check size of Query String
3028 $ENV{'CONTENT_LENGTH'} <= $MaximumQuerySize || dieHandler
(11, "Query too long: $ENV{'CONTENT_LENGTH'}\n"); # Query too long
3030 my $SystemRead = $ENV{'CONTENT_LENGTH'};
3031 $ENV{'QUERY_STRING'} .= '&' if length($ENV{'QUERY_STRING'}) > 0;
3032 while($SystemRead > 0)
3034 $QueryRead = sysread(STDIN
, $Post, $SystemRead); # Limit length
3035 $ENV{'QUERY_STRING'} .= $Post;
3036 $SystemRead -= $QueryRead;
3038 # Update decoded Query String
3039 $default_values{CGI_Decoded_QS
} = YOUR_CGIQUERYDECODE
();
3040 $default_values{CGI_Content_Length
} =
3041 length($default_values{CGI_Decoded_QS
});
3045 # End of getting POST part of query
3048 ############################################################################
3050 # Start (HTML) output and logging
3051 # (if there are irregularities, it can kill the current process)
3054 sub Initialize_output
3056 # Construct the REAL file path (except for STDIN on the command line)
3057 my $file_path = $ENV{'PATH_INFO'} ne '-' ?
$SS_PUB . $ENV{'PATH_INFO'} : '-';
3058 $file_path =~ s/\?.*$//; # Remove query
3059 # This is only necessary if your server does not catch ../ directives
3060 $file_path !~ m@\
.\
./@ || dieHandler(12, "Illegal ../ Construct
\n"); # SECURITY: Do not allow ../ constructs
3062 # Block STDIN use (-) if CGIscriptor is servicing a HTTP request
3063 if($file_path eq '-')
3065 dieHandler(13, "STDIN request
in On Line
system\n") if $BLOCK_STDIN_HTTP_REQUEST
3066 && ($ENV{'SERVER_SOFTWARE'}
3067 || $ENV{'SERVER_NAME'}
3068 || $ENV{'GATEWAY_INTERFACE'}
3069 || $ENV{'SERVER_PROTOCOL'}
3070 || $ENV{'SERVER_PORT'}
3071 || $ENV{'REMOTE_ADDR'}
3072 || $ENV{'HTTP_USER_AGENT'});
3079 open(ClientLog, ">>$ClientLog");
3080 print ClientLog "$LocalTime | ",
3081 ($ENV{REMOTE_USER} || "-"), " ",
3082 ($ENV{REMOTE_IDENT} || "-"), " ",
3083 ($ENV{REMOTE_HOST} || "-"), " ",
3084 $ENV{REMOTE_ADDR}, " ",
3085 $ENV{PATH_INFO}, " ",
3086 $ENV{'CGI_BINARY_FILE'}, " ",
3087 ($default_values{CGI_Content_Length} || "-"),
3093 open(QueryLog, ">>$QueryLog");
3094 print QueryLog "$LocalTime\n",
3095 ($ENV{REMOTE_USER} || "-"), " ",
3096 ($ENV{REMOTE_IDENT} || "-"), " ",
3097 ($ENV{REMOTE_HOST} || "-"), " ",
3098 $ENV{REMOTE_ADDR}, ": ",
3099 $ENV{PATH_INFO}, " ",
3100 $ENV{'CGI_BINARY_FILE'}, "\n";
3102 # Write Query to Log file
3103 print QueryLog $default_values{CGI_Decoded_QS}, "\n\n";
3107 # Return the file path
3111 # End of Initialize output
3114 ############################################################################
3116 # Handle login access
3118 # Access is based on a valid session ticket.
3119 # Session tickets should be dependend on user name
3120 # and IP address. The patterns of URLs for which a
3121 # session ticket is needed and the login URL are stored in
3122 # %TicketRequiredPatterns as:
3123 # 'RegEx pattern' -> 'SessionPath\tPasswordPath\tLogin URL\tExpiration'
3126 sub Log_In_Access # () -> 0 = Access Allowed, Login page if access is not allowed
3128 # No patterns, no login
3129 goto Return unless %TicketRequiredPatterns;
3131 # Get and initialize values (watch out for stuff processed by BinaryMap files)
3132 my ($SessionPath, $PasswordsPath, $Login, $valid_duration) = ("", "", "", 0);
3133 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3134 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
3135 goto Return if $REMOTE_ADDR =~ /[^0-9\.]/;
3136 # Extract TICKETs, starting with returned cookies
3137 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3138 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3139 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3140 if($ENV{'COOKIE_JAR'})
3142 my $CurrentCookieJar = $ENV{'COOKIE_JAR'};
3143 $CurrentCookieJar =~ s/\w+\=\-\s*(\;\s*|$)//isg;
3144 if($CurrentCookieJar =~ /\s*CGIscriptorLOGIN\=\s*([^\;]+)/)
3146 ${"CGIexecute
::LOGINTICKET
"} = $1;
3148 if($CurrentCookieJar =~ /\s*CGIscriptorCHALLENGE\=\s*([^\;]+)/ && $1 ne '-')
3150 ${"CGIexecute
::CHALLENGETICKET
"} = $1;
3152 if($CurrentCookieJar =~ /\s*CGIscriptorSESSION\=\s*([^\;]+)/ && $1 ne '-')
3154 ${"CGIexecute
::SESSIONTICKET
"} = $1;
3157 # Get and check the tickets. Tickets are restricted to word-characters (alphanumeric+_+.)
3158 my $LOGINTICKET = ${"CGIexecute
::LOGINTICKET
"};
3159 goto Return if ($LOGINTICKET && $LOGINTICKET =~ /[^\w\.]/isg);
3160 my $SESSIONTICKET = ${"CGIexecute
::SESSIONTICKET
"};
3161 goto Return if ($SESSIONTICKET && $SESSIONTICKET =~ /[^\w\.]/isg);
3162 my $CHALLENGETICKET = ${"CGIexecute
::CHALLENGETICKET
"};
3163 goto Return if ($CHALLENGETICKET && $CHALLENGETICKET =~ /[^\w\.]/isg);
3164 # Look for a LOGOUT message
3165 my $LOGOUT = $ENV{QUERY_STRING} =~ /(^|\&)LOGOUT([\=\&]|$)/;
3166 # Username and password
3167 CGIexecute::defineCGIvariable('CGIUSERNAME', "");
3168 my $username = lc(${"CGIexecute
::CGIUSERNAME
"});
3169 goto Return if $username =~ m!^[^\w]!isg || $username =~ m![^\w \-]!isg;
3170 my $userfile = lc($username);
3171 $userfile =~ s/[^\w]/_/isg;
3172 CGIexecute::defineCGIvariable('PASSWORD', "");
3173 my $password = ${"CGIexecute
::PASSWORD
"};
3174 CGIexecute::defineCGIvariable('NEWUSERNAME', "");
3175 my $newuser = lc(${"CGIexecute
::NEWUSERNAME
"});
3176 CGIexecute::defineCGIvariable('NEWPASSWORD', "");
3177 my $newpassword = ${"CGIexecute
::NEWPASSWORD
"};
3179 foreach my $pattern (keys(%TicketRequiredPatterns))
3181 # Check BOTH the real PATH_INFO and the CGI_BINARY_FILE variable
3182 if($ENV{'PATH_INFO'} =~ m#$pattern# || $ENV{'CGI_BINARY_FILE'} =~ m#$pattern#)
3184 # Fall through a sieve of requirements
3185 ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3186 # If a LOGOUT is present, remove everything
3187 if($LOGOUT && !$LOGINTICKET)
3189 unlink "$SessionPath/$LOGINTICKET" if $LOGINTICKET && (-s "$SessionPath/$LOGINTICKET");
3191 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
3192 $SESSIONTICKET = "";
3193 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
3194 $CHALLENGETICKET = "";
3195 unlink "$SessionPath/$REMOTE_ADDR" if (-s "$SessionPath/$REMOTE_ADDR");
3196 $CHALLENGETICKET = "";
3199 # Is there a change password request?
3200 if($newuser && $LOGINTICKET && $username)
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, ".", 1);
3207 goto Login unless $ticket_valid;
3209 my ($sessiontype, $currentticket) = ("", "");
3210 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE
", $CHALLENGETICKET);}
3211 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION
", $SESSIONTICKET);}
3212 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS
", $REMOTE_ADDR);
3216 goto Login unless (-s "$SessionPath/$currentticket");
3217 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3218 goto Login unless $ticket_valid;
3221 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath);
3222 goto Login unless $TMPTICKET;
3224 # Create a new user account
3225 CGIexecute::defineCGIvariable('NEWSESSION', "");
3226 my $newsession = ${"CGIexecute
::NEWSESSION
"};
3227 my $newaccount = create_newuser("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket",
3228 "$PasswordsPath/$userfile", $password, $newuser, $newpassword, $newsession);
3229 CGIexecute::defineCGIvariable('NEWACCOUNTTEXT', $newaccount);
3230 ${CGIexecute::NEWACCOUNTTEXT} = $newaccount;
3231 # NEWACCOUNTTEXT is NOT to be set by the query
3232 CGIexecute::ProtectCGIvariable('NEWACCOUNTTEXT');
3238 # Is there a change password request?
3239 elsif($newpassword && $LOGINTICKET && $username)
3241 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3242 goto Login unless (-s "$PasswordsPath/$userfile");
3243 my $ticket_valid = check_ticket_validity("PASSWORD
", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3244 goto Login unless $ticket_valid;
3245 $ticket_valid = check_ticket_validity("LOGIN
", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".", 1);
3246 goto Login unless $ticket_valid;
3248 my ($sessiontype, $currentticket) = ("", "");
3249 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE
", $CHALLENGETICKET);}
3250 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION
", $SESSIONTICKET);}
3251 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS
", $REMOTE_ADDR);
3255 goto Login unless (-s "$SessionPath/$currentticket");
3256 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3257 goto Login unless $ticket_valid;
3260 change_password("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket", "$PasswordsPath/$userfile", $password, $newpassword);
3261 # After a change of password, you have to login again for a CHALLENGE
3262 if($CHALLENGETICKET){$CHALLENGETICKET = "";};
3266 # Is there a login ticket of this name?
3269 my $tickets_removed = remove_expired_tickets($SessionPath);
3270 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3271 goto Login unless (-s "$PasswordsPath/$userfile");
3272 my $ticket_valid = check_ticket_validity("PASSWORD
", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3273 goto Login unless $ticket_valid;
3274 $ticket_valid = check_ticket_validity("LOGIN
", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".");
3275 goto Login unless $ticket_valid;
3277 # Remove any lingering tickets
3278 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
3279 $SESSIONTICKET = "";
3280 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
3281 $CHALLENGETICKET = "";
3285 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath);
3288 my $authorization = read_ticket("$PasswordsPath/$userfile");
3289 goto Login unless $authorization;
3290 # Session type is read from the userfile
3291 if($authorization->{"Session
"} && $authorization->{"Session
"}->[0] eq "CHALLENGE
")
3293 # Create New Random CHALLENGETICKET
3294 $CHALLENGETICKET = $TMPTICKET;
3295 create_session_file("$SessionPath/$CHALLENGETICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3297 elsif($authorization->{"Session
"} && $authorization->{"Session
"}->[0] eq "IPADDRESS
")
3299 create_session_file("$SessionPath/$REMOTE_ADDR", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3303 # Extra hash to protect CHALLENGETICKET use
3304 $SESSIONTICKET = hash_string($TMPTICKET);
3305 create_session_file("$SessionPath/$SESSIONTICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3306 $SETCOOKIELIST{"CGIscriptorSESSION
"} = "-";
3307 $TMPTICKET = $SESSIONTICKET;
3310 # Login ticket file has been used, remove it
3313 # Is there a session ticket of this name?
3315 if($CHALLENGETICKET)
3317 # Do not log into a CHALLENGE account if the SESSION cookie is present
3318 # Uncomment when $SESSIONTICKET does not receive an extra hash
3319 #goto Login if $SESSIONTICKET =~ /\S/;
3320 goto Login unless (-s "$SessionPath/$CHALLENGETICKET");
3321 my $ticket_valid = check_ticket_validity("CHALLENGE
", "$SessionPath/$CHALLENGETICKET", $REMOTE_ADDR, $PATH_INFO);
3322 goto Login unless $ticket_valid;
3324 my $oldchallenge = read_ticket("$SessionPath/$CHALLENGETICKET");
3325 goto Login unless $oldchallenge;
3326 # Check whether the login still exists
3327 my $userfile = lc($oldchallenge->{"Username
"}->[0]);
3328 $userfile =~ s/[^\w]/_/isg;
3329 goto Login unless (-s "$PasswordsPath/$userfile");
3331 $ticket_valid = check_ticket_validity("PASSWORD
", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3332 goto Login unless $ticket_valid;
3334 my $NEWCHALLENGETICKET = "";
3335 $NEWCHALLENGETICKET = copy_challenge_file("$SessionPath/$CHALLENGETICKET", "$PasswordsPath/$userfile", $SessionPath);
3336 # Sessionticket is available to scripts, do NOT set the cookie
3337 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3341 elsif(-s "$SessionPath/$REMOTE_ADDR")
3343 my $ticket_valid = check_ticket_validity("IPADDRESS
", "$SessionPath/$REMOTE_ADDR", $REMOTE_ADDR, $PATH_INFO);
3344 goto Login unless $ticket_valid;
3345 # Check whether the login still exists
3346 my $currentsessionticket = read_ticket("$SessionPath/$REMOTE_ADDR");
3347 my $userfile = lc($currentsessionticket->{"Username
"}->[0]);
3348 $userfile =~ s/[^\w]/_/isg;
3349 goto Login unless (-s "$PasswordsPath/$userfile");
3351 $ticket_valid = check_ticket_validity("PASSWORD
", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3352 goto Login unless $ticket_valid;
3357 elsif($SESSIONTICKET)
3359 goto Login unless (-s "$SessionPath/$SESSIONTICKET");
3360 my $ticket_valid = check_ticket_validity("SESSION
", "$SessionPath/$SESSIONTICKET", $REMOTE_ADDR, $PATH_INFO);
3361 goto Login unless $ticket_valid;
3363 # Check whether the login still exists
3364 my $currentsessionticket = read_ticket("$SessionPath/$SESSIONTICKET");
3365 my $userfile = lc($currentsessionticket->{"Username
"}->[0]);
3366 $userfile =~ s/[^\w]/_/isg;
3367 goto Login unless (-s "$PasswordsPath/$userfile");
3369 $ticket_valid = check_ticket_validity("PASSWORD
", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3370 goto Login unless $ticket_valid;
3372 # Sessionticket is available to scripts
3373 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3382 # The Masterkey should NOT be accessible by the parsed files
3383 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3387 create_login_file($PasswordsPath, $SessionPath, $REMOTE_ADDR);
3388 # Note, cookies are set only ONCE
3389 $SETCOOKIELIST{"CGIscriptorLOGIN
"} = "-";
3390 # The Masterkey should NOT be accessible by the parsed files
3391 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3392 return "$YOUR_HTML_FILES/$Login";
3395 sub authorize_login # ($loginfile, $authorizationfile, $password, $SessionPath) => SESSIONTICKET First two arguments are file paths
3397 my $loginfile = shift || "";
3398 my $authorizationfile = shift || "";
3399 my $password = shift || "";
3400 my $SessionPath = shift || "";
3402 # Get Login session ticket
3403 my $loginticket = read_ticket($loginfile);
3404 return 0 unless $loginticket;
3405 # Get User credentials for authorization
3406 my $authorization = read_ticket($authorizationfile);
3407 return 0 unless $authorization;
3410 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3411 return "" unless $Randomsalt;
3413 my $storedpassword = $authorization->{'Password'}->[0];
3414 return "" unless $storedpassword;
3415 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3416 return "" unless $password eq $Hashedpassword;
3418 # Extract Session Ticket
3419 my $loginsession = $loginticket->{'Session'}->[0];
3420 my $sessionticket = hash_string($storedpassword.$loginsession);
3421 chomp($sessionticket);
3422 $sessionticket = "" if -x "$SessionPath/$sessionticket";
3424 # No lingering password variables
3425 $Hashedpassword = $Randomsalt;
3426 $password = $Randomsalt;
3427 $authorization->{'Password'}->[0] = $Randomsalt;
3429 return $sessionticket;
3432 sub change_password # ($loginfile, $sessionfile, $authorizationfile, $password, $newpassword) First three arguments are file paths
3434 my $loginfile = shift || "";
3435 my $sessionfile = shift || "";
3436 my $authorizationfile = shift || "";
3437 my $password = shift || "";
3438 my $newpassword = shift || "";
3439 # Get Login session ticket
3440 my $loginticket = read_ticket($loginfile);
3441 return "" unless $loginticket;
3442 # Login ticket file has been used, remove it
3445 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3446 return "" unless $Randomsalt;
3447 my $LoginID = $loginticket->{'Session'}->[0];
3448 return "" unless $LoginID;
3450 # Get session ticket
3451 my $sessionticket = read_ticket($sessionfile);
3452 return "" unless $sessionticket;
3454 # Get User credentials for authorization
3455 my $authorization = read_ticket($authorizationfile);
3456 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3458 my $storedpassword = $authorization->{'Password'}->[0];
3459 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3460 return "" unless $password eq $Hashedpassword;
3461 my $secretkey = hash_string($storedpassword.$LoginID.$Randomsalt);
3463 # Decrypt the $newpassword
3464 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3465 return "" unless $decryptedPassword;
3466 # Authorization succeeded, change password
3467 $authorization->{'Password'}->[0] = $decryptedPassword;
3469 write_ticket($authorizationfile, $authorization, $authorization->{'Salt'}->[0]);
3471 # No lingering password variables
3472 $decryptedPassword = $Randomsalt;
3473 $secretkey = $Randomsalt;
3474 $storedpassword = $Randomsalt;
3475 $Hashedpassword = $Randomsalt;
3476 $authorization->{'Password'}->[0] = $Randomsalt;
3478 return $newpassword;
3480 # First three arguments are file paths
3481 sub create_newuser # ($loginfile, $sessionfile, $authorizationfile, $password, $newuser, $newpassword, $newsession) -> account text
3483 my $loginfile = shift || "";
3484 my $sessionfile = shift || "";
3485 my $authorizationfile = shift || "";
3486 my $password = shift || "";
3487 my $newuser = shift || "";
3488 my $newpassword = shift || "";
3489 my $newsession = shift || "";
3491 # Get Login session ticket
3492 my $loginticket = read_ticket($loginfile);
3493 return "" unless $loginticket;
3494 # Login ticket file has been used, remove it
3497 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3498 return "" unless $Randomsalt;
3499 my $LoginID = $loginticket->{'Session'}->[0];
3500 return "" unless $LoginID;
3502 # Get session ticket
3503 my $sessionticket = read_ticket($sessionfile);
3504 return "" unless $sessionticket;
3505 # Get User credentials for authorization
3506 my $authorization = read_ticket($authorizationfile);
3507 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3508 my $sessionkey = $sessionticket->{'Key'}->[0];
3509 my $serversalt = $authorization->{'Salt'}->[0];
3510 return "" unless $serversalt;
3512 my $storedpassword = $authorization->{'Password'}->[0];
3513 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3514 return "" unless $password eq $Hashedpassword;
3515 my $secretkey = hash_string($storedpassword.$LoginID.$Randomsalt);
3517 # Decrypt the $newpassword
3518 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3519 return "" unless $decryptedPassword;
3521 # Authorization succeeded, create new account
3522 my $newaccount = {};
3523 $newaccount->{'Type'} = ['PASSWORD'];
3524 $newaccount->{'Username'} = [$newuser];
3525 $newaccount->{'Password'} = [$decryptedPassword];
3526 $newaccount->{'Salt'} = [$serversalt];
3527 $newaccount->{'Session'} = ['SESSION'];
3528 if($newsession eq 'IPADDRESS'){$newaccount->{'Session'} = ['IPADDRESS'];};
3529 if($newsession eq 'CHALLENGE'){$newaccount->{'Session'} = ['CHALLENGE'];};
3530 my $timesec = time();
3531 my $gmt_date = gmtime();
3532 $newaccount->{'Time'} = [$timesec];
3533 $newaccount->{'Date'} = [$gmt_date];
3536 my $NewAllowedPaths = "";
3537 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3538 my $currentRoot = "";
3539 $currentRoot = $1 if $PATH_INFO =~ m!^([\w\-\. /]+)!isg;
3540 $currentRoot =~ s![^/]+$!!isg;
3543 $currentRoot .= '/' unless $currentRoot =~ m!/$!;
3544 my $newpath = "^".${currentRoot}.'[\w\-]+\.html?';
3545 $NewAllowedPaths .= 'AllowedPaths: ^'.${currentRoot}.'[\w\-]+\.html?'."\n";
3546 $newaccount->{'AllowedPaths'} = [$newpath];
3550 # Tricky PATH_INFO, deny all
3551 $NewAllowedPaths .= "DeniedPaths
: ^/\n";
3552 $newaccount->{'DeniedPaths'} = ["DeniedPaths
: ^/\n"];
3555 # Construct home directory path
3556 my $FullHomeDirectoryPath = "";
3557 my $currentHome = lc($newuser);
3558 if($currentHome && $currentHome !~ /^\s*\#/)
3560 $currentHome =~ s![^\w]!_!isg;
3561 my $newpath = "^${currentRoot
}$currentHome/";
3562 push(@{$newaccount->{'AllowedPaths'}}, $newpath);
3563 # Create home directory
3564 $FullHomeDirectoryPath = $ENV{'HOME'}.${currentRoot}.$currentHome;
3568 CGIexecute::defineCGIvariable('ALLOWEDPATHS', "");
3569 my $allowedpaths = ${"CGIexecute
::ALLOWEDPATHS
"};
3570 if($allowedpaths && $allowedpaths !~ /^\s*\#/)
3572 $allowedpaths =~ s![^\^\w\./\;\+\*\?\[\]\$]!!isg;
3573 my @pathlist = split(/\;/, $allowedpaths);
3574 foreach my $entry (@pathlist)
3576 push(@{$newaccount->{'AllowedPaths'}}, "^".${currentRoot}.$entry);
3580 # Allowed IP addresses
3581 CGIexecute::defineCGIvariable('IPADDRESS', "");
3582 my $ipaddress = ${"CGIexecute
::IPADDRESS
"};
3583 if($ipaddress && $ipaddress !~ /^\s*\#/)
3585 $ipaddress =~ s![^\d\.\;]!!isg;
3586 my @iplist = split(/\;/, $ipaddress);
3587 foreach my $entry (@iplist)
3589 next unless $entry =~ /\d/;
3590 next if $entry =~ /^\s*\#/;
3591 $entry =~ s/\./\\./g;
3592 push(@{$newaccount->{'IPaddress'}}, $entry);
3596 # Sign the new ticket
3597 my $Signature = SignTicketWithMasterkey($newaccount, $newaccount->{'Salt'}->[0]);
3600 my $datetime = gmtime();
3601 my $newuserfile = "";
3602 if(grep(/^CreateUser$/, @{$authorization->{'Capabilities'}}))
3604 my $newuserfilename = lc($newuser);
3605 $newuserfilename =~ s/[^\w]/_/isg;
3606 $newuserfile = $authorizationfile;
3607 $newuserfile =~ s![^/]*$!!isg;
3608 $newuserfile .= $newuserfilename;
3613 elsif($FullHomeDirectoryPath && !(-d $FullHomeDirectoryPath || -s $FullHomeDirectoryPath))
3615 if(-d "$ENV{'HOME'}${currentRoot
}.SkeletonDir
")
3617 `cp -r '$ENV{'HOME'}${currentRoot}.SkeletonDir' '$FullHomeDirectoryPath'`;
3619 elsif(-d "$ENV{'HOME'}${currentRoot
}SkeletonDir
")
3621 `cp -r '$ENV{'HOME'}${currentRoot}SkeletonDir' '$FullHomeDirectoryPath'`;
3623 elsif(-s "$ENV{'HOME'}${currentRoot
}UserIndex
.html
")
3625 mkdir $FullHomeDirectoryPath;
3626 `cp '$ENV{'HOME'}${currentRoot}UserIndex.html' '$FullHomeDirectoryPath/index.html'`;
3628 elsif(-s "$ENV{'HOME'}${currentRoot
}index.html
")
3630 mkdir $FullHomeDirectoryPath;
3631 `cp '$ENV{'HOME'}${currentRoot}index.html' '$FullHomeDirectoryPath/index.html'`;
3637 my $newaccounttext = write_ticket($newuserfile, $newaccount, $serversalt);
3639 # Re-encrypt the new password for transmission
3640 if($newaccounttext =~ /^(Password\:\s+)(\S+)\s*$/)
3642 my $passwordvalue = $1;
3643 my $reencryptedpassword = XOR_hex_strings($secretkey, $passwordvalue);
3644 my $encryptedpasswordline = "<span id
='newaccount'>$reencryptedpassword</span
>";
3645 $newaccounttext =~ s/^(Password\:\s+)(\S+)\s*$/\1$encryptedpasswordline/gim;
3647 # No lingering passwords
3648 $passwordvalue = $serversalt;
3650 return $newaccounttext;
3653 # Copy a Challenge ticket file to a new name which is the hash of the new $CHALLENGETICKET and the password
3654 sub copy_challenge_file #($oldchallengefile, $authorizationfile, $sessionpath) -> $CHALLENGETICKET
3656 my $oldchallengefile = shift || "";
3657 my $authorizationfile = shift || "";
3658 my $sessionpath = shift || "";
3659 $sessionpath =~ s!/+$!!g;
3661 # Get Login session ticket
3662 my $oldchallenge = read_ticket($oldchallengefile);
3663 return "" unless $oldchallenge;
3665 # Get Authorization (user) session file
3666 my $authorization = read_ticket($authorizationfile);
3667 return "" unless $authorization;
3668 my $storedpassword = $authorization->{'Password'}->[0];
3669 return "" unless $storedpassword;
3670 my $challengekey = $oldchallenge->{'Key'}->[0];
3671 return "" unless $challengekey;
3673 # Create Random Hash Salt
3674 my $NEWCHALLENGETICKET = get_random_hex();;
3675 my $newchallengefile = hash_string($challengekey.$NEWCHALLENGETICKET);
3676 return "" unless $newchallengefile;
3678 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3679 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3680 ${"CGIexecute
::CHALLENGETICKET
"} = $NEWCHALLENGETICKET;
3682 # Write Session Ticket
3683 open(OLDCHALLENGE, "<$oldchallengefile") || die "<$oldchallengefile: $!\n";
3684 my @OldChallengeLines = <OLDCHALLENGE>;
3685 close(OLDCHALLENGE);
3686 # Old file should now be removed
3687 unlink($oldchallengefile);
3689 open(SESSION, ">$sessionpath/$newchallengefile") || die "$sessionpath/$newchallengefile: $!\n";
3690 foreach $line (@OldChallengeLines)
3692 print SESSION $line;
3696 # No lingering passwords
3697 $storedpassword = $oldchallenge;
3699 return $NEWCHALLENGETICKET;
3702 sub create_login_file #($PasswordDir, $SessionDir, $IPaddress)
3704 my $PasswordDir = shift || "";
3705 my $SessionDir = shift || "";
3706 my $IPaddress = shift || "";
3708 # Create Login Ticket
3709 my $LOGINTICKET= get_random_hex ();
3711 # Create Random Hash Salt
3712 my $RANDOMSALT= get_random_hex();
3714 # Create SALT file if it does not exist
3715 # Remove this, including test account for life system
3716 unless(-d "$SessionDir")
3718 `mkdir -p "$SessionDir"`;
3720 unless(-d "$PasswordDir")
3722 `mkdir -p "$PasswordDir"`;
3724 # Create SERVERSALT and default test account
3725 my $SERVERSALT = "";
3726 unless(-s "$PasswordDir/SALT
")
3728 $SERVERSALT= get_random_hex();
3729 open(SALTFILE, ">$PasswordDir/SALT") || die ">$PasswordDir/SALT
: $!\n";
3730 print SALTFILE "$SERVERSALT\n";
3733 # Update test account (should be removed in live system)
3734 my @alltestusers = ("test
", "testip
", "testchallenge
", "admin
");
3735 foreach my $testuser (@alltestusers)
3737 if(-s "$PasswordDir/$testuser")
3739 my $plainpassword = $testuser eq 'admin' ? "There is
no password like more password
" : "testing
";
3741 my $storedpassword = hash_string(${plainpassword}.${testuser}.${SERVERSALT});
3742 # Encrypt the new password with the MasterKey
3743 my $authorization = read_ticket("$PasswordDir/$testuser") || return "";
3744 $authorization->{'Salt'} = [$SERVERSALT];
3745 $authorization->{'Type'} = ['INACTIVE PASSWORD'] if $testuser eq 'admin';
3746 set_password($authorization, $SERVERSALT, $plainpassword);
3747 write_ticket("$PasswordDir/$testuser", $authorization, $SERVERSALT);
3748 # No lingering passwords
3749 $storedpassword = $SERVERSALT;
3750 $plainpassword = $SERVERSALT;
3756 open(SALTFILE, "<$PasswordDir/SALT") || die "$PasswordDir/SALT
: $!\n";
3757 $SERVERSALT=<SALTFILE>;
3761 # Create login session ticket
3762 my $datetime = gmtime();
3763 my $timesec = time();
3764 my $loginticket = {};
3765 $loginticket->{Type} = ['LOGIN'];
3766 $loginticket->{IPaddress} = [$IPaddress];
3767 $loginticket->{Salt} = [$SERVERSALT];
3768 $loginticket->{Session} = [$LOGINTICKET];
3769 $loginticket->{Randomsalt} = [$RANDOMSALT];
3770 $loginticket->{Expires} = ['+600s'];
3771 $loginticket->{Date} = ["$datetime UTC
"];
3772 $loginticket->{Time} = [$timesec];
3773 write_ticket("$SessionDir/$LOGINTICKET", $loginticket, $SERVERSALT);
3775 # Set global variables
3777 $ENV{'SERVERSALT'} = $SERVERSALT;
3778 CGIexecute::defineCGIvariable('SERVERSALT', "");
3779 ${"CGIexecute
::SERVERSALT
"} = $SERVERSALT;
3782 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3783 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3784 ${"CGIexecute
::SESSIONTICKET
"} = $SESSIONTICKET;
3787 $ENV{'RANDOMSALT'} = $RANDOMSALT;
3788 CGIexecute::defineCGIvariable('RANDOMSALT', "");
3789 ${"CGIexecute
::RANDOMSALT
"} = $RANDOMSALT;
3792 $ENV{'LOGINTICKET'} = $LOGINTICKET;
3793 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3794 ${"CGIexecute
::LOGINTICKET
"} = $LOGINTICKET;
3796 return $ENV{'LOGINTICKET'};
3799 sub create_session_file #($sessionfile, $loginfile, $authorizationfile, $path) -> Is $loginfile deleted? 0/1
3801 my $sessionfile = shift || "";
3802 my $loginfile = shift || "";
3803 my $authorizationfile = shift || "";
3804 my $path = shift || "";
3806 # Get Login session ticket
3807 my $loginticket = read_ticket($loginfile);
3808 return unlink($loginfile) unless $loginticket;
3810 # Get Authorization (user) session file
3811 my $authorization = read_ticket($authorizationfile);
3812 return unlink($loginfile) unless $authorization;
3814 # For a Session or a Challenge, we need a stored key
3815 my $sessionkey = "";
3817 if($authorization->{'Session'} && $authorization->{'Session'}->[0] ne 'IPADDRESS')
3819 my $storedpassword = $authorization->{'Password'}->[0];
3820 my $loginticketid = $loginticket->{'Session'}->[0];
3821 my $randomsalt = $loginticket->{'Randomsalt'}->[0];
3822 $sessionkey = hash_string($storedpassword.$loginticketid);
3823 $secretkey = hash_string($storedpassword.$loginticketid.$randomsalt);
3824 # No lingering passwords
3825 $storedpassword = $loginticketid;
3829 if($sessionfile =~ m!([^/]+)$!)
3834 # Convert Authorization content to Session content
3835 my $sessionContent = {};
3836 my $SessionType = $authorization->{'Session'}->[0] ? $authorization->{'Session'}->[0] : "SESSION
";
3837 $sessionContent->{Type} = [$SessionType];
3838 $sessionContent->{Username} = [lc($authorization->{'Username'}->[0])];
3839 $sessionContent->{Session} = [$sessionid];
3840 $sessionContent->{Time} = [time];
3841 # Limit communication to the login IP address, except for Tor like situations with VariableREMOTE_ADDR
3842 $sessionContent->{IPaddress} = ['.'];
3843 if($sessionContent->{Type}->[0] eq 'CHALLENGE' && grep(/^VariableREMOTE_ADDR$/, @{$authorization->{'Capabilities'}}))
3845 $sessionContent->{IPaddress} = $authorization->{'IPaddress'} if $authorization->{'IPaddress'};
3849 $sessionContent->{IPaddress} = $loginticket->{'IPaddress'};
3851 $sessionContent->{Salt} = $authorization->{'Salt'};
3852 $sessionContent->{Randomsalt} = $loginticket->{'Randomsalt'};
3853 $sessionContent->{AllowedPaths} = $authorization->{'AllowedPaths'};
3854 $sessionContent->{DeniedPaths} = $authorization->{'DeniedPaths'};
3855 $sessionContent->{Expires} = $authorization->{'MaxLifetime'};
3856 $sessionContent->{Capabilities} = $authorization->{'Capabilities'};
3857 foreach my $pattern (keys(%TicketRequiredPatterns))
3859 if($path =~ m#$pattern#)
3861 my ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3862 push(@{$sessionContent->{Expires}}, $validtime);
3865 $sessionContent->{Key} = [$sessionkey] if $sessionkey;
3866 $sessionContent->{Secretkey} = [$secretkey] if $secretkey;
3867 $sessionContent->{Date} = [gmtime()." UTC
"];
3869 # Write Session Ticket
3870 write_ticket($sessionfile, $sessionContent, $authorization->{'Salt'}->[0]);
3872 # Login file should now be removed
3873 return unlink($loginfile);
3876 sub check_ticket_validity # ($type, $ticketfile, $address, $path [, $unsigned])
3878 my $type = shift || "SESSION
";
3879 my $ticketfile = shift || "";
3880 my $address = shift || "";
3881 my $path = shift || "";
3882 my $unsigned = shift || 0;
3884 # Is there a session ticket of this name?
3885 return 0 unless -s "$ticketfile";
3887 # There is a session ticket, is it linked to this IP address?
3888 my $ticket = read_ticket($ticketfile);
3891 print STDERR "Ticket expired
or empty
: $ticketfile\n";
3895 # Is this the right type of ticket
3896 unless($ticket && $ticket->{'Type'}->[0] eq $type)
3898 print STDERR "Wrong ticket type
: $ticket->{'Type'}->[0] eq $type\n";
3902 # Does the IP address match?
3903 my $IPmatches = @{$ticket->{"IPaddress
"}} ? 0 : 1;
3904 for $IPpattern (@{$ticket->{"IPaddress
"}})
3906 ++$IPmatches if $address =~ m#^$IPpattern#ig;
3908 if($address && ! $IPmatches)
3910 print STDERR "Wrong REMOTE ADDR
for $ticket->{'Username'}->[0]: $ticket->{'IPaddress'}->[0] vs
$address\n";
3914 # Is the path denied
3915 my $Pathmatches = 0;
3916 foreach $Pathpattern (@{$ticket->{"DeniedPaths
"}})
3918 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3920 return 0 if @{$ticket->{"DeniedPaths
"}} && $Pathmatches;
3922 # Is the path allowed
3924 foreach $Pathpattern (@{$ticket->{"AllowedPaths
"}})
3926 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3928 return 0 unless !@{$ticket->{"AllowedPaths
"}} || $Pathmatches;
3930 # Check signature if not told to use an unsigned ticket (dangerous)
3931 my $Signature = TicketSignature($ticket, $ticket->{'Salt'}->[0]);
3932 if((! $unsigned) && $Signature && $Signature ne $ticket->{'Signature'}->[0])
3934 print STDERR "Invalid signature
for $ticket->{'Type'}: $ticket->{'Username'}\n$ticketfile\n";
3938 # Make login values available (will also protect against resetting by query)
3939 $ENV{"LOGINUSERNAME
"} = lc($ticket->{'Username'}->[0]);
3940 $ENV{"LOGINIPADDRESS
"} = $address;
3941 $ENV{"LOGINPATH
"} = $path;
3942 $ENV{"SESSIONTYPE
"} = $type unless $type eq "PASSWORD
";
3944 # Set Capabilities, if present
3945 if($ticket->{'Username'}->[0] && @{$ticket->{'Capabilities'}})
3947 $ENV{'CAPABILITIES'} = $ticket->{'Username'}->[0];
3948 CGIexecute::defineCGIvariableList('CAPABILITIES', "");
3949 @{"CGIexecute
::CAPABILITIES
"} = @{$ticket->{'Capabilities'}};
3950 # Capabilities should not be changed anymore by CGI query!
3952 # Capabilities are NOT to be set by the query
3953 CGIexecute::ProtectCGIvariable('CAPABILITIES');
3959 sub remove_expired_tickets # ($path) -> number of tickets removed
3961 my $path = shift || "";
3962 return 0 unless $path;
3964 my $removed_tickets = 0;
3965 my @ticketlist = glob("$path/*");
3966 foreach my $ticketfile (@ticketlist)
3968 my $ticket = read_ticket($ticketfile);
3975 return $removed_tickets;
3978 sub set_password # ($ticket, $salt, $plainpassword) -> $password
3980 my $ticket = shift || "";
3981 my $salt = shift || "";
3982 my $plainpassword = shift || "";
3984 my $user = lc($ticket->{'Username'}->[0]);
3985 return "" unless $user;
3986 $salt = $ticket->{'Salt'}->[0] unless $salt;
3988 my $storedpassword = hash_string(${plainpassword}.${user}.${salt});
3989 $ticket->{'Password'} = [$storedpassword];
3990 $ticket->{'Salt'} = [$salt];
3991 # No lingering passwords
3992 $storedpassword = $salt;
3993 $plainpassword = $salt;
3995 return $ticket->{'Password'}->[0];
3998 sub write_ticket # ($ticketfile, $ticket, $salt [, $masterkey]) -> &%ticket
4000 my $ticketfile = shift || "";
4001 my $ticket = shift || "";
4002 my $salt = shift || "";
4003 my $masterkey = shift || $ENV{'CGIMasterKey'};
4006 EncryptTicketWithMasterKey($ticket, $salt, $masterkey);
4008 # Sign the new ticket
4009 my $signature = SignTicketWithMasterkey($ticket, $salt, $masterkey);
4011 # Create ordered list with labels
4012 my @orderlist = ('Type', 'Username', 'Password', 'IPaddress', 'AllowedPaths', 'DeniedPaths',
4013 'Expires', 'Capabilities', 'Salt', 'Session', 'Randomsalt',
4014 'Date', 'Time', 'Signature', 'Key', 'Secretkey');
4015 my @labellist = keys(%{$ticket});
4016 foreach my $label (@orderlist)
4018 @labellist = grep(!/\b$label\b/, @labellist);
4021 # Create ticket in text
4022 my $TicketText = "";
4023 foreach my $label (@orderlist, @labellist)
4025 next unless exists($ticket->{$label}) && $ticket->{$label}->[0];
4026 foreach my $value (@{$ticket->{$label}})
4028 $TicketText .= "$label: $value\n";
4033 open(TICKET, ">$ticketfile") || die "$ticketfile: $!\n";
4034 print TICKET $TicketText;
4041 # Note, read_ticket will return 0 if the ticket has expired!
4042 sub read_ticket # ($ticketfile [, $salt, $masterkey]) -> &%ticket
4044 my $ticketfile = shift || "";
4045 my $serversalt = shift || "";
4046 my $masterkey = shift || $ENV{'CGIMasterKey'};
4049 if($ticketfile && -s $ticketfile)
4051 open(TICKETFILE, "<$ticketfile") || die "$ticketfile: $!\n";
4052 my @alllines = <TICKETFILE>;
4054 foreach my $currentline (@alllines)
4056 # Skip empty lines and comments
4057 next unless $currentline =~ /\S/;
4058 next if $currentline =~ /^\s*\#/;
4060 if($currentline =~ /^\s*(\S[^\:]+)\:\s+(.*)\s*$/)
4064 $ticket->{$Label} = () unless exists($ticket->{$Label});
4065 push(@{$ticket->{$Label}}, $Value);
4069 if($masterkey && exists($ticket->{'Password'}) && $ticket->{'Password'}->[0])
4071 # Use the ServerSalt stored in the ticket, if present
4072 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4074 $serversalt = $ticket->{Salt}->[0];
4076 # Decrypt all passwords
4077 DecryptTicketWithMasterKey($ticket, $serversalt, $masterkey) ||
4078 die "Decryption failed
: DecryptTicketWithMasterKey
($ticket, $serversalt)\n";
4081 # Check whether the ticket has expired
4082 if(exists($ticket->{Expires}))
4085 if(exists($ticket->{Time}) && $ticket->{Time}->[0] > 0)
4087 $StartTime = [(sort(@{$ticket->{Time}}))]->[0];
4091 # Get SessionTicket file stats
4092 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)
4093 = stat($ticketfile);
4094 $StartTime = $ctime;
4096 foreach my $Value (@{$ticket->{'Expires'}})
4098 # Recalculate expire date from relative time
4101 if($Value =~ /^\+(\d+)\s*d(ays)?\s*$/)
4103 $ExpireTime = 24*3600*$1;
4105 elsif($Value =~ /^\+(\d+)\s*m(inutes)?\s*$/)
4107 $ExpireTime = 60*$1;
4109 elsif($Value =~ /^\+(\d+)\s*h(ours)?\s*$/)
4111 $ExpireTime = 3600*$1;
4113 elsif($Value =~ /^\+(\d+)\s*s(econds)?\s*$/)
4117 elsif($Value =~ /^\+(\d+)\s*$/)
4122 my $absoluteTime = $Value =~ /^\+/ ? $StartTime + $ExpireTime : $Value;
4123 return 0 unless $absoluteTime > time;
4125 @{$ticket->{Expires}} = sort(@{$ticket->{Expires}});
4130 # Set up a valid ticket from a given text file
4131 # Use from command line. DO NOT USE ONLINE
4132 # Watch out for passwords that get stored in the history file
4134 # perl CGIscriptor.pl --managelogin [options] [files]
4136 # salt={file or saltvalue}
4137 # masterkey={file or plaintext}
4138 # newmasterkey={file or plaintext}
4139 # password={file or palintext}
4141 # Followed by one or more file names.
4142 # Options can be interspersed between filenames,
4143 # e.g., password='plaintext'
4144 # Note that passwords are only used once!
4146 sub setup_ticket_file # (@ARGV)
4148 # Stop when run on-line
4149 return if $ENV{'PATH_INFO'} || $ENV{'QUERY_STRING'};
4152 foreach my $input (@_)
4154 if($input =~ /^([\w]+)\=/)
4160 if($value !~ m![^\w\.\~\/\:\-]! && $value !~ /^[\-\.]/ && -s "$value" && ! -d "$value")
4162 # Warn about reading a value from file
4163 print STDERR "Read
'$name' from
: '$value'\n";
4164 open(INPUTVALUE, "<$value") || die "$value: $!\n";
4165 $value = <INPUTVALUE>;
4169 $value =~ s/(^\'([^\']*)\'$)/\1/g;
4170 $value =~ s/(^\"([^\"]*)\"$)/\1/g;
4171 $Settings{$name} = $value;
4173 elsif($input !~ m![^\w\.\~\/\:\-]!i && $input !~ /^[\-\.]/i && -s $input)
4175 # We MUST have a salt
4176 $Settings{'salt'} = $ticket->{'Salt'}->[0] unless $Settings{'salt'};
4178 # Set the new masterkey to the old masterkey if there is no new masterkey
4179 $Settings{'newmasterkey'} = $Settings{'masterkey'} unless exists($Settings{'newmasterkey'});
4182 my $ticket = read_ticket($input, $Settings{'salt'}, $Settings{'masterkey'});
4184 # Set a new password from plaintext
4185 $ticket->{'Salt'}->[0] = $Settings{'salt'} if $Settings{'salt'} && $Settings{'password'};
4186 set_password ($ticket, $Settings{'salt'}, $Settings{'password'}) if $Settings{'password'};
4187 # Write the ticket back to file
4188 write_ticket($input, $ticket, $Settings{'salt'}, $Settings{'newmasterkey'});
4190 # A password is only used once
4191 $Settings{'password'} = "";
4196 # Add a signature from $masterkey to a ticket in the label $signlabel
4197 sub SignTicketWithMasterkey # ($ticket, $serversalt [, $masterkey, $signlabel]) -> $Signature
4199 my $ticket = shift || return 0;
4200 my $serversalt = shift || "";
4201 my $masterkey = shift || $ENV{'CGIMasterKey'};
4202 my $signlabel = shift || 'Signature';
4204 my $Signature = TicketSignature($ticket, $serversalt, $masterkey);
4206 $ticket->{$signlabel} = [$Signature] if $Signature;
4211 # Determine ticket signature
4212 sub TicketSignature # ($ticket, $serversalt [, $masterkey]) -> $Signature
4214 my $ticket = shift || return 0;
4215 my $serversalt = shift || "";
4216 my $masterkey = shift || $ENV{'CGIMasterKey'};
4221 # If the ServerSalt is not stored in the ticket, the SALT file has to be found
4222 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4224 $serversalt = $ticket->{Salt}->[0];
4229 my $username = lc($ticket->{'Username'}->[0]);
4230 my $hash1 = hash_string(${masterkey}.${serversalt});
4231 # The order of $username.$hash1 should be different than in DecryptTicketWithMasterKey
4232 my $CryptKey = hash_string($username.${'hash1'});
4233 my $SignText = "Type
: ".$ticket->{'Type'}->[0]."\n";
4234 my @tmp = sort(@{$ticket->{'Username'}});
4235 $SignText .= "Username
: @tmp\n";
4236 @tmp = sort(@{$ticket->{'IPaddress'}});
4237 $SignText .= "IPaddress
: @tmp\n";
4238 @tmp = sort(@{$ticket->{'AllowedPaths'}});
4239 $SignText .= "AllowedPaths
: @tmp\n";
4240 @tmp = sort(@{$ticket->{'DeniedPaths'}});
4241 $SignText .= "DeniedPaths
: @tmp\n";
4242 @tmp = sort(@{$ticket->{'Session'}});
4243 $SignText .= "Session
: @tmp\n";
4244 @tmp = sort(@{$ticket->{'Time'}});
4245 $SignText .= "Time
: @tmp\n";
4246 @tmp = sort(@{$ticket->{'Expires'}});
4247 $SignText .= "Expires
: @tmp\n";
4248 @tmp = sort(@{$ticket->{'Capabilities'}});
4249 $SignText .= "Capabilities
: @tmp\n";
4250 @tmp = sort(@{$ticket->{'MaxLifetime'}});
4251 $SignText .= "MaxLifetime
: @tmp\n";
4252 $Signature = HMAC_hex($CryptKey, $SignText);
4258 # Decrypts a password list IN PLACE
4259 sub DecryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4261 my $ticket = shift || return 0;
4262 my $serversalt = shift || "";
4263 my $masterkey = shift || $ENV{'CGIMasterKey'};
4265 if($masterkey && exists($ticket->{Password}) && $ticket->{Password}->[0])
4267 # If the ServerSalt is not given, read it from the the ticket
4268 if(! $serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4270 $serversalt = $ticket->{Salt}->[0];
4272 # Decrypt password(s)
4275 my $hash1 = hash_string(${masterkey}.${serversalt});
4276 my $username = lc($ticket->{'Username'}->[0]);
4277 # The order of $hash1.$username should be different than in TicketSignature
4278 my $CryptKey = hash_string(${'hash1'}.$username);
4279 foreach my $password (@{$ticket->{Password}})
4281 $password = XOR_hex_strings($CryptKey,$password);
4285 return $ticket->{'Password'};
4287 sub EncryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4289 DecryptTicketWithMasterKey(@_);
4292 # Implement HMAC signature hash.
4293 # Blocksize is length in HEX characters, NOT bytes
4294 sub HMAC_hex # ($key, $message [, $blocksizehex]) -> $hex
4296 my $key = shift || "";
4297 my $message = shift || "";
4298 my $blocksizehex = shift || length($key);
4299 $key = hash_string($key) if length($key) > $blocksizehex;
4301 my $innerkey = XOR_hex_byte ($key, "36");
4302 my $outerkey = XOR_hex_byte ($key, "5c
");
4303 my $innerhash = hash_string($innerkey.$message);
4304 my $outerhash = hash_string($outerkey.$innerhash);
4309 # XOR input with equally long string of repeated 2 hex character (byte)
4310 # string. Input must have even number of hex characters
4311 sub XOR_hex_byte # ($hex1, $hexbyte) -> $hex
4313 my $hex1 = shift || "";
4314 my $hexbyte = shift || "";
4315 my $bytelength = length($hexbyte);
4317 $hex2 =~ s/.{$bytelength}/$hexbyte/ig;
4318 return XOR_hex_strings($hex1, $hex2);
4321 sub XOR_hex_strings # ($hex1, $hex2) -> $hex
4323 my $hex1 = shift || "";
4324 my $hex2 = shift || "";
4325 my @hex1list = split('', $hex1);
4326 my @hex2list = split('', $hex2);
4327 my @hexresultlist = ();
4328 for(my $i; $i < scalar(@hex1list); ++$i)
4330 my $d1 = hex($hex1list[$i]);
4331 my $d2 = hex($hex2list[$i]);
4332 my $dresult = ($d1 ^ $d2);
4333 $hexresultlist[$i] = sprintf("%x", $dresult);
4335 $hexresult = join('', @hexresultlist);
4339 # End of Handle login access
4342 ############################################################################
4344 # Handle foreign interpreters (i.e., scripting languages)
4346 # Insert perl code to execute scripts in foreign scripting languages.
4347 # Actually, the scripts inside the <SCRIPT></SCRIPT> blocks are piped
4348 # into an interpreter.
4349 # The code presented here is fairly confusing because it
4350 # actually writes perl code code to the output.
4352 # A table with the file handles
4353 %SCRIPTINGINPUT = ();
4355 # A function to clean up Client delivered CGI parameter values
4356 # (i.e., quote all odd characters)
4366 sub shrubCGIparameter # ($String) -> Cleaned string
4368 my $String = shift || "";
4370 # Change all quotes [`'"] into HTML character entities
4371 my ($Char, $Transcript) = ('&', $SHRUBcharacterTR{'&'});
4374 $String =~ s/\Q$Char\E/$Transcript/isg if $Transcript;
4376 while( ($Char, $Transcript) = each %SHRUBcharacterTR)
4378 next if $Char eq '&';
4379 $String =~ s/\Q$Char\E/$Transcript/isg;
4383 $String =~ s/[\n]/\\n/g;
4384 # Replace control characters with their backslashed octal ordinal numbers
4385 $String =~ s/([^\S \t])/(sprintf("\\0%o", ord($1)))/eisg; #
4386 $String =~ s/([\x00-\x08\x0A-\x1F])/(sprintf("\\0%o", ord($1)))/eisg; #
4392 # The initial open statements: Open a pipe to the foreign script interpreter
4393 sub OpenForeignScript # ($ContentType) -> $DirectivePrefix
4395 my $ContentType = lc(shift) || return "";
4396 my $NewDirective = "";
4398 return $NewDirective if($SCRIPTINGINPUT{$ContentType});
4400 # Construct a unique file handle name
4401 $SCRIPTINGFILEHANDLE = uc($ContentType);
4402 $SCRIPTINGFILEHANDLE =~ s/\W/\_/isg;
4403 $SCRIPTINGINPUT{$ContentType} = $SCRIPTINGFILEHANDLE
4404 unless $SCRIPTINGINPUT{$ContentType};
4406 # Create the relevant script: Open the pipe to the interpreter
4407 $NewDirective .= <<"BLOCKCGISCRIPTOROPEN";
4408 # Open interpreter for '$ContentType'
4409 # Open pipe to interpreter (if it isn't
open already
)
4410 open($SCRIPTINGINPUT{$ContentType}, "|$ScriptingLanguages{$ContentType}") || main
::dieHandler
(14, "$ContentType: \$!\\n");
4411 BLOCKCGISCRIPTOROPEN
4413 # Insert Initialization code and CGI variables
4414 $NewDirective .= InitializeForeignScript
($ContentType);
4417 return $NewDirective;
4421 # The final closing code to stop the interpreter
4422 sub CloseForeignScript
# ($ContentType) -> $DirectivePrefix
4424 my $ContentType = lc(shift) || return "";
4425 my $NewDirective = "";
4427 # Do nothing unless the pipe realy IS open
4428 return "" unless $SCRIPTINGINPUT{$ContentType};
4431 $NewDirective .= "\# Close interpreter for '$ContentType'\n";
4434 # Write the Postfix code
4435 $NewDirective .= CleanupForeignScript
($ContentType);
4437 # Create the relevant script: Close the pipe to the interpreter
4438 $NewDirective .= <<"BLOCKCGISCRIPTORCLOSE";
4439 close($SCRIPTINGINPUT{$ContentType}) || main::dieHandler(15, \"$ContentType: \$!\\n\");
4440 select(STDOUT); \$|=1;
4442 BLOCKCGISCRIPTORCLOSE
4444 # Remove the file handler of the foreign script
4445 delete($SCRIPTINGINPUT{$ContentType});
4447 return $NewDirective;
4451 # The initialization code for the foreign script interpreter
4452 sub InitializeForeignScript
# ($ContentType) -> $DirectivePrefix
4454 my $ContentType = lc(shift) || return "";
4455 my $NewDirective = "";
4457 # Add initialization code
4458 if($ScriptingInitialization{$ContentType})
4460 $NewDirective .= <<"BLOCKCGISCRIPTORINIT";
4461 # Initialization Code for '$ContentType'
4462 # Select relevant output filehandle
4463 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4465 # The Initialization code (if any)
4466 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}INITIALIZATIONCODE';
4467 $ScriptingInitialization{$ContentType}
4468 ${ContentType}INITIALIZATIONCODE
4470 BLOCKCGISCRIPTORINIT
4473 # Add all CGI variables defined
4474 if(exists($ScriptingCGIvariables{$ContentType}))
4476 # Start writing variable definitions to the Interpreter
4477 if($ScriptingCGIvariables{$ContentType})
4479 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEF";
4480 # CGI variables (from the %default_values table)
4481 print $SCRIPTINGINPUT{$ContentType} << '${ContentType}CGIVARIABLES';
4482 BLOCKCGISCRIPTORVARDEF
4486 foreach $N (keys(%default_values))
4488 # Determine whether the parameter has been defined
4489 # (the eval is a workaround to get at the variable value)
4490 next unless eval("defined(\$CGIexecute::$N)");
4492 # Get the value from the EXECUTION environment
4493 $V = eval("\$CGIexecute::$N");
4494 # protect control characters (i.e., convert them to \0.. form)
4495 $V = shrubCGIparameter
($V);
4497 # Protect interpolated variables
4498 eval("\$CGIexecute::$N = '$V';") unless $ScriptingCGIvariables{$ContentType};
4500 # Print the actual declaration for this scripting language
4501 if($ScriptingCGIvariables{$ContentType})
4503 $NewDirective .= sprintf($ScriptingCGIvariables{$ContentType}, $N, $V);
4504 $NewDirective .= "\n";
4508 # Stop writing variable definitions to the Interpreter
4509 if($ScriptingCGIvariables{$ContentType})
4511 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEFEND";
4512 ${ContentType}CGIVARIABLES
4513 BLOCKCGISCRIPTORVARDEFEND
4518 $NewDirective .= << "BLOCKCGISCRIPTOREND";
4520 # Select STDOUT filehandle
4521 select(STDOUT
); \
$|=1;
4525 return $NewDirective;
4529 # The cleanup code for the foreign script interpreter
4530 sub CleanupForeignScript
# ($ContentType) -> $DirectivePrefix
4532 my $ContentType = lc(shift) || return "";
4533 my $NewDirective = "";
4535 # Return if not needed
4536 return $NewDirective unless $ScriptingCleanup{$ContentType};
4538 # Create the relevant script: Open the pipe to the interpreter
4539 $NewDirective .= <<"BLOCKCGISCRIPTORSTOP";
4540 # Cleanup Code for '$ContentType'
4541 # Select relevant output filehandle
4542 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4543 # Print Cleanup code to foreign script
4544 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}SCRIPTSTOP';
4545 $ScriptingCleanup{$ContentType}
4546 ${ContentType}SCRIPTSTOP
4548 # Select STDOUT filehandle
4549 select(STDOUT); \$|=1;
4550 BLOCKCGISCRIPTORSTOP
4552 return $NewDirective;
4556 # The prefix code for each <script></script> block
4557 sub PrefixForeignScript
# ($ContentType) -> $DirectivePrefix
4559 my $ContentType = lc(shift) || return "";
4560 my $NewDirective = "";
4562 # Return if not needed
4563 return $NewDirective unless $ScriptingPrefix{$ContentType};
4566 # If the CGIvariables parameter is defined, but empty, interpolate
4567 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4568 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4569 !$ScriptingCGIvariables{$ContentType};
4571 # Add initialization code
4572 $NewDirective .= <<"BLOCKCGISCRIPTORPREFIX";
4573 # Prefix Code for '$ContentType'
4574 # Select relevant output filehandle
4575 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4577 # The block Prefix code (if any)
4578 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}PREFIXCODE$Quote;
4579 $ScriptingPrefix{$ContentType}
4580 ${ContentType}PREFIXCODE
4581 # Select STDOUT filehandle
4582 select(STDOUT); \$|=1;
4583 BLOCKCGISCRIPTORPREFIX
4585 return $NewDirective;
4589 # The postfix code for each <script></script> block
4590 sub PostfixForeignScript
# ($ContentType) -> $DirectivePrefix
4592 my $ContentType = lc(shift) || return "";
4593 my $NewDirective = "";
4595 # Return if not needed
4596 return $NewDirective unless $ScriptingPostfix{$ContentType};
4599 # If the CGIvariables parameter is defined, but empty, interpolate
4600 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4601 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4602 !$ScriptingCGIvariables{$ContentType};
4604 # Create the relevant script: Open the pipe to the interpreter
4605 $NewDirective .= <<"BLOCKCGISCRIPTORPOSTFIX";
4606 # Postfix Code for '$ContentType'
4607 # Select filehandle to interpreter
4608 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4609 # Print postfix code to foreign script
4610 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SCRIPTPOSTFIX$Quote;
4611 $ScriptingPostfix{$ContentType}
4612 ${ContentType}SCRIPTPOSTFIX
4613 # Select STDOUT filehandle
4614 select(STDOUT); \$|=1;
4615 BLOCKCGISCRIPTORPOSTFIX
4617 return $NewDirective;
4620 sub InsertForeignScript
# ($ContentType, $directive, @SRCfile) -> $NewDirective
4622 my $ContentType = lc(shift) || return "";
4623 my $directive = shift || return "";
4625 my $NewDirective = "";
4628 # If the CGIvariables parameter is defined, but empty, interpolate
4629 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4630 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4631 !$ScriptingCGIvariables{$ContentType};
4633 # Create the relevant script
4634 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4635 # Insert Code for '$ContentType'
4636 # Select filehandle to interpreter
4637 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4638 BLOCKCGISCRIPTORINSERT
4640 # Use SRC feature files
4642 while($ThisSRCfile = shift(@_))
4645 if($ThisSRCfile =~ /^\s*\{\s*/)
4648 $Block = $` if $Block =~ /\s*\}\s*$/;
4649 $NewDirective .= <<"BLOCKCGISCRIPTORSRCBLOCK";
4650 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SRCBLOCKCODE$Quote;
4652 ${ContentType}SRCBLOCKCODE
4653 BLOCKCGISCRIPTORSRCBLOCK
4659 $NewDirective .= <<"BLOCKCGISCRIPTORSRCFILES";
4661 open(SCRIPTINGSOURCE, "<$ThisSRCfile") || main::dieHandler(16, "$ThisSRCfILE: \$!");
4662 while(<SCRIPTINGSOURCE>)
4664 print $SCRIPTINGINPUT{$ContentType} \$_;
4666 close(SCRIPTINGSOURCE);
4668 BLOCKCGISCRIPTORSRCFILES
4675 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4676 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}DIRECTIVECODE$Quote;
4678 ${ContentType}DIRECTIVECODE
4679 BLOCKCGISCRIPTORINSERT
4683 $NewDirective .= <<"BLOCKCGISCRIPTORSELECT";
4684 # Select STDOUT filehandle
4685 select(STDOUT); \$|=1;
4686 BLOCKCGISCRIPTORSELECT
4689 return $NewDirective;
4692 sub CloseAllForeignScripts
# Call CloseForeignScript on all open scripts
4695 foreach $ContentType (keys(%SCRIPTINGINPUT))
4697 my $directive = CloseForeignScript
($ContentType);
4698 print STDERR
"\nDirective $CGI_Date: ", $directive;
4699 CGIexecute
->evaluate($directive);
4704 # End of handling foreign (external) scripting languages.
4706 ############################################################################
4708 # A subroutine to handle "nested" quotes, it cuts off the leading
4709 # item or quoted substring
4711 # ' A_word and more words' -> @('A_word', ' and more words')
4712 # '"quoted string" The rest' -> @('quoted string', ' The rest')
4713 # (this is needed for parsing the <TAGS> and their attributes)
4714 my $SupportedQuotes = "\'\"\`\(\{\[";
4715 my %QuotePairs = ('('=>')','['=>']','{'=>'}'); # Brackets
4716 sub ExtractQuotedItem
# ($String) -> @($QuotedString, $RestOfString)
4719 my $String = shift || return @Result;
4721 if($String =~ /^\s*([\w\/\
-\
.]+)/is
)
4723 push(@Result, $1, $');
4725 elsif($String =~ /^\s*(\\?)([\Q$SupportedQuotes\E])/is)
4727 my $BackSlash = $1 || "";
4729 my $CloseQuote = $OpenQuote;
4730 $CloseQuote = $QuotePairs{$OpenQuote} if $QuotePairs{$OpenQuote};
4734 $String =~ /^\s*\\\Q$OpenQuote\E/i;
4736 $Onset =~ /\\\Q$CloseQuote\E/i;
4739 push(@Result, $Item, $Rest);
4744 $String =~ /^\s*\Q$OpenQuote\E([^\Q$CloseQuote\E]*)\Q$CloseQuote\E/i;
4745 push(@Result, $1, $');
4750 push(@Result, "", $String);
4755 # Now, start with the real work
4757 # Control the output of the Content-type: text/html\n\n message
4758 my $SupressContentType = 0;
4761 sub ProcessFile
# ($file_path)
4763 my $file_path = shift || return 0;
4766 # Generate a unique file handle (for recursions)
4768 my $FileHandle = "file";
4770 while(!eof($FileHandle.$n)) {++$n;};
4774 # Use the default Content-type if this is NOT a raw file
4775 unless(($RawFilePattern && $ENV{'PATH_INFO'} =~ m@
($RawFilePattern)$@i)
4776 || $SupressContentType)
4778 $ENV{'PATH_INFO'} =~ m@
($FilePattern)$@i;
4779 my $ContentType = $ContentTypeTable{$1};
4780 print "Content-type: $ContentType\n";
4781 if(%SETCOOKIELIST && keys(%SETCOOKIELIST))
4783 foreach my $name (keys(%SETCOOKIELIST))
4785 my $value = $SETCOOKIELIST{$name};
4786 print "Set-Cookie: $name=$value\n";
4788 # Cookies are set only ONCE
4789 %SETCOOKIELIST = ();
4792 $SupressContentType = 1; # Content type has been printed
4796 # Get access to the actual data. This can be from RAM (by way of an
4797 # environment variable) or by opening a file.
4799 # Handle the use of RAM images (file-data is stored in the
4800 # $CGI_FILE_CONTENTS environment variable)
4801 # Note that this environment variable will be cleared, i.e., it is strictly for
4803 if($ENV{$CGI_FILE_CONTENTS})
4805 # File has been read already
4806 $_ = $ENV{$CGI_FILE_CONTENTS};
4807 # Sorry, you have to do the reading yourself (dynamic document creation?)
4808 # NOTE: you must read the whole document at once
4811 $_ = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
4813 else # Clear environment variable
4815 $ENV{$CGI_FILE_CONTENTS} = '-';
4818 # Open Only PLAIN TEXT files (or STDIN) and NO executable files (i.e., scripts).
4819 # THIS IS A SECURITY FEATURE!
4820 elsif($file_path eq '-' || (-e
"$file_path" && -r _
&& -T _
&& -f _
&& ! (-x _
|| -X _
) ))
4822 open($FileHandle, $file_path) || dieHandler
(17, "<h2>File not found</h2>\n");
4823 push(@OpenFiles, $file_path);
4824 $_ = <$FileHandle>; # Read first line
4828 print "<h2>File not found</h2>\n";
4829 dieHandler
(18, "$file_path\n");
4832 $| = 1; # Flush output buffers
4834 # Initialize variables
4835 my $METAarguments = ""; # The CGI arguments from the latest META tag
4836 my @METAvalues = (); # The ''-quoted CGI values from the latest META tag
4837 my $ClosedTag = 0; # <TAG> </TAG> versus <TAG/>
4840 # Send document to output
4841 # Process the requested document.
4842 # Do a loop BEFORE reading input again (this catches the RAM/Database
4843 # type of documents).
4847 # Handle translations if needed
4849 performTranslation
(\
$_) if $TranslationPaths;
4851 # Catch <SCRIPT LANGUAGE="PERL" TYPE="text/ssperl" > directives in $_
4852 # There can be more than 1 <SCRIPT> or META tags on a line
4853 while(/\<\s*(SCRIPT|META|DIV|INS)\s/is)
4856 # Store rest of line
4860 my $TagType = uc($1);
4861 # The before part can be send to the output
4864 # Read complete Tag from after and/or file
4865 until($After =~ /([^\\])\>/)
4867 $After .= <$FileHandle>;
4868 performTranslation(\$After) if $TranslationPaths;
4871 if($After =~ /([^\\])\>/)
4873 $ScriptTag .= $`.$&; # Keep the Script Tag intact
4878 dieHandler(19, "Closing > not found\n");
4881 # The tag could be closed by />, we handle this in the XML way
4882 # and don't process any content
(we ignore whitespace
)
4883 $ClosedTag = ($ScriptTag =~ m@
[^\\]/\s
*\
>\s
*$@
) ?
1 : 0;
4887 my $TypeName = ($TagType =~ /META/is) ?
"CONTENT" : "TYPE";
4888 $TypeName = "CLASS" if $TagType eq 'DIV' || $TagType eq 'INS';
4890 # Parse <SCRIPT> or <META> directive
4891 # If NOT (TYPE|CONTENT)="text/ssperl" (i.e., $ServerScriptContentType),
4892 # send the line to the output and go to the next loop
4893 my $CurrentContentType = "";
4894 if($ScriptTag =~ /(^|\s)$TypeName\s*=\s*/is)
4896 my ($Type) = ExtractQuotedItem
($');
4897 $Type =~ /^\s*([\w\/\-]+)\s*[\,\;]?/;
4898 $CurrentContentType = lc($1); # Note: mime-types are "case-less"
4899 # CSS classes are aliases of $ServerScriptContentType
4900 if($TypeName eq "CLASS" && $CurrentContentType eq $ServerScriptContentClass)
4902 $CurrentContentType = $ServerScriptContentType;
4907 # Not a known server-side content type, print and continue
4908 unless(($CurrentContentType =~
4909 /$ServerScriptContentType|$ShellScriptContentType/is) ||
4910 $ScriptingLanguages{$CurrentContentType})
4918 # A known server-side content type, evaluate
4920 # First, handle \> and \<
4921 $ScriptTag =~ s/\\\>/\>/isg;
4922 $ScriptTag =~ s/\\\</\</isg;
4924 # Extract the CGI, SRC, ID, IF and UNLESS attributes
4925 my %ScriptTagAttributes = ();
4926 while($ScriptTag =~ /(^|\s)(CGI|IF|UNLESS|SRC|ID)\s*=\s*/is)
4931 ($Value, $ScriptTag) = ExtractQuotedItem
($Rest);
4932 $ScriptTagAttributes{uc($Attribute)} = $Value;
4936 # The attribute used to define the CGI variables
4937 # Extract CGI-variables from
4938 # <META CONTENT="text/ssperl; CGI='' SRC=''">
4939 # <SCRIPT TYPE='text/ssperl' CGI='' SRC=''>
4940 # <DIV CLASS='ssperl' CGI='' SRC='' ID=""> tags
4941 # <INS CLASS='ssperl' CGI='' SRC='' ID=""> tags
4942 if($ScriptTagAttributes{'CGI'})
4944 @ARGV = (); # Reset ARGV
4946 $METAarguments = ""; # Reset the META CGI arguments
4948 my $Meta_CGI = $ScriptTagAttributes{'CGI'};
4950 # Process default values of variables ($<name> = 'default value')
4951 # Allowed quotes are '', "", ``, (), [], and {}
4952 while($Meta_CGI =~ /(^\s*|[^\\])([\$\@\%]?)([\w\-]+)\s*/is)
4954 my $varType = $2 || '$'; # Variable or list
4955 my $name = $3; # The Name
4959 if($Meta_CGI =~ /^\s*\=\s*/is)
4961 # Locate (any) default value
4962 ($default, $Meta_CGI) = ExtractQuotedItem($'); # Cut the parameter from the CGI
4964 $RemainingTag = $Meta_CGI;
4967 # Define CGI (or ENV) variable, initalize it from the
4968 # Query string or the default value
4970 # Also construct the @ARGV and @_ arrays. This allows other (SRC=) Perl
4971 # scripts to access the CGI arguments defined in the META tag
4972 # (Not for CGI inside <SCRIPT> tags)
4975 CGIexecute
::defineCGIvariable
($name, $default)
4976 || dieHandler
(20, "INVALID CGI name/value pair ($name, $default)\n");
4977 push(@METAvalues, "'".${"CGIexecute::$name"}."'");
4978 # Add value to the @ARGV list
4979 push(@ARGV, ${"CGIexecute::$name"});
4982 elsif($varType eq '@')
4984 CGIexecute
::defineCGIvariableList
($name, $default)
4985 || dieHandler
(21, "INVALID CGI name/value list pair ($name, $default)\n");
4986 push(@METAvalues, "'".join("'", @
{"CGIexecute::$name"})."'");
4987 # Add value to the @ARGV list
4988 push(@ARGV, @
{"CGIexecute::$name"});
4989 $ARGC = scalar(@CGIexecute::ARGV
);
4991 elsif($varType eq '%')
4993 CGIexecute
::defineCGIvariableHash
($name, $default)
4994 || dieHandler
(22, "INVALID CGI name/value hash pair ($name, $default)\n");
4995 my @PairList = map {"$_ => ".${"CGIexecute::$name"}{$_}} keys(%{"CGIexecute::$name"});
4996 push(@METAvalues, "'".join("'", @PairList)."'");
4997 # Add value to the @ARGV list
4998 push(@ARGV, %{"CGIexecute::$name"});
4999 $ARGC = scalar(@CGIexecute::ARGV
);
5002 # Store the values for internal and later use
5003 $METAarguments .= "$varType".$name.","; # A string of CGI variable names
5005 push(@METAvalues, "\'".eval("\"$varType\{CGIexecute::$name\}\"")."\'"); # ALWAYS add '-quotes around values
5010 # The IF (conditional execution) Attribute
5011 # Evaluate the condition and stop unless it evaluates to true
5012 if($ScriptTagAttributes{'IF'})
5014 my $IFcondition = $ScriptTagAttributes{'IF'};
5016 # Convert SCRIPT calls, ./<script>
5017 $IFcondition =~ s@
([\W
]|^)\
./([\S
])@
$1$SCRIPT_SUB$2@g;
5019 # Convert FILE calls, ~/<file>
5020 $IFcondition =~ s@
([\W
])\
~/([\S
])@
$1$HOME_SUB$2@g;
5022 # Block execution if necessary
5023 unless(CGIexecute
->evaluate($IFcondition))
5025 %ScriptTagAttributes = ();
5026 $CurrentContentType = "";
5030 # The UNLESS (conditional execution) Attribute
5031 # Evaluate the condition and stop if it evaluates to true
5032 if($ScriptTagAttributes{'UNLESS'})
5034 my $UNLESScondition = $ScriptTagAttributes{'UNLESS'};
5036 # Convert SCRIPT calls, ./<script>
5037 $UNLESScondition =~ s@
([\W
]|^)\
./([\S
])@
$1$SCRIPT_SUB$2@g;
5039 # Convert FILE calls, ~/<file>
5040 $UNLESScondition =~ s@
([\W
])\
~/([\S
])@
$1$HOME_SUB$2@g;
5042 # Block execution if necessary
5043 if(CGIexecute
->evaluate($UNLESScondition))
5045 %ScriptTagAttributes = ();
5046 $CurrentContentType = "";
5050 # The SRC (Source File) Attribute
5051 # Extract any source script files and add them in
5052 # front of the directive
5053 # The SRC list should be emptied
5057 my $PrefixDirective = "";
5058 my $PostfixDirective = "";
5059 # There is a SRC attribute
5060 if($ScriptTagAttributes{'SRC'})
5062 $SRCtag = $ScriptTagAttributes{'SRC'};
5063 # Remove "file://" prefixes
5064 $SRCtag =~ s@
([^\w\
/\\]|^)file\://([^\s\/\@\
=])@
$1$2@gis;
5065 # Expand script filenames "./Script"
5066 $SRCtag =~ s@
([^\w\
/\\]|^)\./([^\s\
/\@\=])@$1$SCRIPT_SUB/$2@gis;
5067 # Expand script filenames "~/Script"
5068 $SRCtag =~ s@
([^\w\
/\\]|^)\~/([^\s\
/\@\=])@$1$HOME_SUB/$2@gis;
5072 while($SRCtag =~ /\S/is)
5074 my $SRCdirective = "";
5076 # Pseudo file, just a switch to go from PREFIXING to POSTFIXING
5078 if($SRCtag =~ /^[\s\;\,]*(POSTFIX|PREFIX)([^$FileAllowedChars]|$)/is)
5080 my $InsertionPlace = $1;
5083 $Prefix = $InsertionPlace =~ /POSTFIX/i ? 0 : 1;
5087 # {}-blocks are just evaluated by "do"
5088 elsif($SRCtag =~ /^[\s\;\,]*\{/is)
5091 if($SRCblock =~ /\}[\s\;\,]*([^\}]*)$/is)
5095 # SAFEqx shell script blocks
5096 if($CurrentContentType =~ /$ShellScriptContentType/is)
5098 # Handle ''-quotes inside the script
5099 $SRCblock =~ s/[\']/\\$&/gis;
5101 $SRCblock = "print do { SAFEqx(\'".$SRCblock."\'); };'';";
5102 $SRCdirective .= $SRCblock."\n";
5105 elsif($CurrentContentType =~ /$ServerScriptContentType/is)
5107 $SRCblock = "print do { $SRCblock };'';";
5108 $SRCdirective .= $SRCblock."\n";
5110 else # The interpreter should handle this
5112 push(@SRClist, "{ $SRCblock }");
5117 { dieHandler(23, "Closing \} missing\n");};
5119 # Files are processed as Text or Executable files
5120 elsif($SRCtag =~ /[\s\;\,]*([$FileAllowedChars]+)[\;\,\s]*/is)
5125 # We are handling one of the external interpreters
5126 if($ScriptingLanguages{$CurrentContentType})
5128 push(@SRClist, $SrcFile);
5130 # We are at the start of a DIV tag, just load all SRC files and/or URL's
5131 elsif($TagType eq 'DIV' || $TagType eq 'INS') # All files are prepended in DIV's
5133 # $SrcFile is a URL pointing to an HTTP or FTP server
5134 if($SrcFile =~ m!^([a-z]+)\://!)
5136 my $URLoutput = CGIscriptor::read_url($SrcFile);
5137 $SRCdirective .= $URLoutput;
5139 # SRC file is an existing file
5140 elsif(-e "$SrcFile")
5142 open(DIVSOURCE, "<$SrcFile") || dieHandler(24, "<$SrcFile: $!\n");
5144 while(sysread(DIVSOURCE, $Content, 1024) > 0)
5146 $SRCdirective .= $Content;
5151 # Executable files are executed as
5152 # `$SrcFile 'ARGV[0]' 'ARGV[1]'`
5153 elsif(-x "$SrcFile")
5155 $SRCdirective .= "print \`$SrcFile @METAvalues\
`;'';\n";
5157 # Handle 'standard' files, using ProcessFile
5158 elsif((-T "$SrcFile" || $ENV{$CGI_FILE_CONTENTS})
5159 && $SrcFile =~ m@($FilePattern)$@) # A recursion
5162 # Do not process still open files because it can lead
5163 # to endless recursions
5164 if(grep(/^$SrcFile$/, @OpenFiles))
5165 { dieHandler(25, "$SrcFile allready opened (endless recursion)\n")};
5166 # Prepare meta arguments
5167 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5169 $SRCdirective .= "main::ProcessFile(\'$SrcFile\');'';\n";
5171 elsif($SrcFile =~ m!^([a-z]+)\://!) # URL's are loaded and printed
5173 $SRCdirective .= GET_URL($SrcFile);
5175 elsif(-T "$SrcFile") # Textfiles are "do"-ed (Perl execution)
5177 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5178 $SRCdirective .= "do \'$SrcFile\';'';\n";
5180 else # This one could not be resolved (should be handled by BinaryMapFile)
5182 $SRCdirective .= 'print "'.$SrcFile.' cannot be used"'."\n";
5190 $PrefixDirective .= $SRCdirective;
5194 $PostfixDirective .= $SRCdirective;
5197 # The prefix should be handled immediately
5198 $directive .= $PrefixDirective;
5199 $PrefixDirective = "";
5203 # Handle the content of the <SCRIPT></SCRIPT> tags
5204 # Do not process the content of <SCRIPT/>
5205 if($TagType =~ /SCRIPT/is && !$ClosedTag) # The <SCRIPT> TAG
5207 my $EndScriptTag = "";
5209 # Execute SHELL scripts with SAFEqx()
5210 if($CurrentContentType =~ /$ShellScriptContentType/is)
5212 $directive .= "SAFEqx(\'";
5216 while($After !~ /\<\s*\/SCRIPT[^\>]*\>/is && !eof($FileHandle))
5218 $After .= <$FileHandle>;
5219 performTranslation(\$After) if $TranslationPaths;
5222 if($After =~ /\<\s*\/SCRIPT[^\>]*\>/is)
5230 dieHandler(26, "Missing </SCRIPT> end tag in $ENV{'PATH_INFO
'}\n");
5233 # Process only when content should be executed
5234 if($CurrentContentType)
5237 # Remove all comments from Perl scripts
5238 # (NOT from OS shell scripts)
5239 $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
5240 if $CurrentContentType =~ /$ServerScriptContentType/i;
5242 # Convert SCRIPT calls, ./<script>
5243 $directive =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5245 # Convert FILE calls, ~/<file>
5246 $directive =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5248 # Execute SHELL scripts with SAFEqx(), closing bracket
5249 if($CurrentContentType =~ /$ShellScriptContentType/i)
5251 # Handle ''-quotes inside the script
5252 $directive =~ /SAFEqx\(\'/;
5254 my $Executable = $';
5255 $Executable =~ s/[\']/\\$&/gs;
5257 $directive .= $Executable."\');"; # Closing bracket
5265 # Handle the content of the <DIV></DIV> tags
5266 # Do not process the content of <DIV/>
5267 elsif(($TagType eq 'DIV' || $TagType eq 'INS') && !$ClosedTag) # The <DIV> TAGs
5269 my $EndScriptTag = "";
5272 while($After !~ /\<\s*\/$TagType[^\
>]*\
>/is
&& !eof($FileHandle))
5274 $After .= <$FileHandle>;
5275 performTranslation
(\
$After) if $TranslationPaths;
5278 if($After =~ /\<\s*\/$TagType[^\
>]*\
>/is
)
5286 dieHandler(27, "Missing </$TagType> end tag in $ENV{'PATH_INFO'}\n");
5289 # Add the Postfixed directives (but only when it contains something printable)
5290 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5291 $PostfixDirective = "";
5294 # Process only when content should be handled
5295 if($CurrentContentType)
5298 # Get the name (ID), and clean it (i.e., remove anything that is NOT part of
5299 # a valid Perl name). Names should not contain $, but we can handle it.
5300 my $name = $ScriptTagAttributes{'ID'};
5301 $name =~ /^\s*[\$\@\%]?([\w\-]+)/;
5304 # Assign DIV contents to $NAME value OUTSIDE the CGI values!
5305 CGIexecute::defineCGIexecuteVariable($name, $directive);
5309 # Nothing to execute
5314 # Handle Foreign scripting languages
5315 if($ScriptingLanguages{$CurrentContentType})
5317 my $newDirective = "";
5318 $newDirective .= OpenForeignScript($CurrentContentType); # Only if not already done
5319 $newDirective .= PrefixForeignScript($CurrentContentType);
5320 $newDirective .= InsertForeignScript($CurrentContentType, $directive, @SRClist);
5321 $newDirective .= PostfixForeignScript($CurrentContentType);
5322 $newDirective .= CloseForeignScript($CurrentContentType); # This shouldn't be necessary
5324 $newDirective .= '"";';
5326 $directive = $newDirective;
5330 # Add the Postfixed directives (but only when it contains something printable)
5331 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5332 $PostfixDirective = "";
5335 # EXECUTE the script and print the results
5337 # Use this to debug the program
5338 # print STDERR "Directive $CGI_Date: \n", $directive, "\n\n";
5340 my $Result = CGIexecute->evaluate($directive) if $directive; # Evaluate as PERL code
5341 $Result =~ s/\n$//g; # Remove final newline
5343 # Print the Result of evaluating the directive
5344 # (this will handle LARGE, >64 kB output)
5345 my $BytesWritten = 1;
5346 while($Result && $BytesWritten)
5348 $BytesWritten = syswrite(STDOUT, $Result, 64);
5349 $Result = substr($Result, $BytesWritten);
5351 # print $Result; # Could be used instead of above code
5353 # Store result if wanted, i.e., if $CGIscriptorResults has been
5354 # defined in a <META> tag.
5355 push(@CGIexecute::CGIscriptorResults, $Result)
5356 if exists($default_values{'CGIscriptorResults'});
5358 # Process the rest of the input line (this could contain
5359 # another directive)
5363 } while(<$FileHandle>); # Read and Test AFTER first loop!
5365 close ($FileHandle);
5366 dieHandler(28, "Error in recursion\n") unless pop(@OpenFiles) == $file_path;
5370 ###############################################################################
5372 # Call the whole package
5378 # Initialization Code
5379 Initialize_Request();
5381 # SECURITY: ACCESS CONTROL
5384 # Read the POST part of the query, if there is one
5385 Get_POST_part_of_query();
5387 # Start (HTML) output and logging
5388 $file_path = Initialize_output();
5390 # Check login access or divert to login procedure
5391 $Use_Login = Log_In_Access();
5392 $file_path = $Use_Login if $Use_Login;
5394 # Record which files are still open (to avoid endless recursions)
5397 # Record whether the default HTML ContentType has already been printed
5398 # but only if the SERVER uses HTTP or some other protocol that might interpret
5399 # a content MIME type.
5401 $SupressContentType = !("$ENV{'SERVER_PROTOCOL'}" =~ /($ContentTypeServerProtocols)/i);
5403 # Process the specified file
5404 ProcessFile($file_path) if $file_path ne $SS_PUB;
5406 # Cleanup all open external (foreign) interpreters
5407 CloseAllForeignScripts();
5413 # Make a single call to handle an (empty) request
5417 # END OF PACKAGE MAIN
5420 ####################################################################################
5422 # The CGIEXECUTE PACKAGE
5424 ####################################################################################
5426 # Isolate the evaluation of directives as PERL code from the rest of the program.
5427 # Remember that each package has its own name space.
5428 # Note that only the FIRST argument of execute->evaluate is actually evaluated,
5429 # all other arguments are accessible inside the first argument as $_[0] to $_[$#_].
5436 my $directive = shift;
5437 $directive = eval($directive);
5438 warn $@ if $@; # Write an error message to STDERR
5439 $directive; # Return value of directive
5443 # defineCGIexecuteVariable($name [, $value]) -> 0/1
5445 # Define and intialize variables inside CGIexecute
5446 # Does no sanity checking, for internal use only
5448 sub defineCGIexecuteVariable # ($name [, $value]) -> 0/1
5450 my $name = shift || return 0; # The Name
5451 my $value = shift || ""; # The value
5458 # Protect certain CGI variables values when set internally
5459 # If not defined internally, there will be no variable set AT ALL
5460 my %CGIprotectedVariable = ();
5461 sub ProtectCGIvariable # ($name) -> 0/1
5463 my $name = shift || "";
5464 return 0 unless $name && $name =~ /\w/;
5466 ++$CGIprotectedVariable{$name};
5468 return $CGIprotectedVariable{$name};
5471 # defineCGIvariable($name [, $default]) -> 0/1
5473 # Define and intialize CGI variables
5474 # Tries (in order) $ENV{$name}, the Query string and the
5476 # Removes all '-quotes etc.
5478 sub defineCGIvariable # ($name [, $default]) -> 0/1
5480 my $name = shift || return 0; # The Name
5481 my $default = shift || ""; # The default value
5483 # Protect variables set internally
5484 return 1 if !$name || exists($CGIprotectedVariable{$name});
5486 # Remove \-quoted characters
5487 $default =~ s/\\(.)/$1/g;
5488 # Store default values
5489 $::default_values{$name} = $default if $default;
5493 # If there is a user supplied value, it replaces the
5496 # Environment values have precedence
5497 if(exists($ENV{$name}))
5499 $temp = $ENV{$name};
5501 # Get name and its value from the query string
5502 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5504 $temp = ::YOUR_CGIPARSE($name);
5506 # Defined values must exist for security
5507 elsif(!exists($::default_values{$name}))
5509 $::default_values{$name} = undef;
5512 # SECURITY, do not allow '- and `-quotes
in
5514 # Remove all existing '-quotes
5515 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5516 $temp =~ s/[\']/’/igs; # Remove all single quotes
5517 $temp =~ s/[\`]/‘/igs; # Remove all backtick quotes
5518 # If $temp is empty, use the default value (if it exists)
5519 unless($temp =~ /\S/ || length($temp) > 0) # I.e., $temp is empty
5521 $temp = $::default_values
{$name};
5522 # Remove all existing '-quotes
5523 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5524 $temp =~ s/[\']/’/igs; # Remove all single quotes
5525 $temp =~ s/[\`]/‘/igs; # Remove all backtick quotes
5527 else # Store current CGI values and remove defaults
5529 $::default_values
{$name} = $temp;
5531 # Define the CGI variable and its value (in the execute package)
5538 sub defineCGIvariableList
# ($name [, $default]) -> 0/1)
5540 my $name = shift || return 0; # The Name
5541 my $default = shift || ""; # The default value
5543 # Protect variables set internally
5544 return 1 if !$name || exists($CGIprotectedVariable{$name});
5546 # Defined values must exist for security
5547 if(!exists($::default_values
{$name}))
5549 $::default_values
{$name} = $default;
5556 # Environment values have precedence
5557 if(exists($ENV{$name}))
5559 push(@temp, $ENV{$name});
5561 # Get name and its values from the query string
5562 elsif($ENV{QUERY_STRING
} =~ /$name/) # $name is in the query string
5564 push(@temp, ::YOUR_CGIPARSE
($name, 1)); # Extract LIST
5568 push(@temp, $::default_values
{$name});
5572 # SECURITY, do not allow '- and `-quotes in
5574 # Remove all existing '-quotes
5575 @temp = map {s/([\r\f]+\n)/\n/g; $_} @temp; # Only \n is allowed
5576 @temp = map {s/[\']/’/igs; $_} @temp; # Remove all single quotes
5577 @temp = map {s/[\`]/‘/igs; $_} @temp; # Remove all backtick quotes
5579 # Store current CGI values and remove defaults
5580 $::default_values
{$name} = $temp[0];
5582 # Define the CGI variable and its value (in the execute package)
5589 sub defineCGIvariableHash
# ($name [, $default]) -> 0/1) Note: '$name{""} = $default';
5591 my $name = shift || return 0; # The Name
5592 my $default = shift || ""; # The default value
5594 # Protect variables set internally
5595 return 1 if !$name || exists($CGIprotectedVariable{$name});
5597 # Defined values must exist for security
5598 if(!exists($::default_values
{$name}))
5600 $::default_values
{$name} = $default;
5607 # Environment values have precedence
5608 if(exists($ENV{$name}))
5610 $temp{""} = $ENV{$name};
5612 # Get name and its values from the query string
5613 elsif($ENV{QUERY_STRING
} =~ /$name/) # $name is in the query string
5615 %temp = ::YOUR_CGIPARSE
($name, -1); # Extract HASH table
5617 elsif($::default_values
{$name} ne "")
5619 $temp{""} = $::default_values
{$name};
5623 # SECURITY, do not allow '- and `-quotes in
5625 # Remove all existing '-quotes
5627 foreach $Key (keys(%temp))
5629 $temp{$Key} =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5630 $temp{$Key} =~ s/[\']/’/igs; # Remove all single quotes
5631 $temp{$Key} =~ s/[\`]/‘/igs; # Remove all backtick quotes
5634 # Store current CGI values and remove defaults
5635 $::default_values
{$name} = $temp{""};
5637 # Define the CGI variable and its value (in the execute package)
5640 foreach $tempKey (keys(%temp))
5642 ${$name}{$tempKey} = $temp{$tempKey};
5650 # SAFEqx('CommandString')
5652 # A special function that is a safe alternative to backtick quotes (and qx//)
5653 # with client-supplied CGI values. All CGI variables are surrounded by
5654 # single ''-quotes (except between existing \'\'-quotes, don't try to be
5655 # too smart). All variables are then interpolated. Simple (@) lists are
5656 # expanded with join(' ', @List), and simple (%) hash tables expanded
5657 # as a list of "key=value" pairs. Complex variables, e.g., @$var, are
5658 # evaluated in a scalar context (e.g., as scalar(@$var)). All occurrences of
5659 # $@% that should NOT be interpolated must be preceeded by a "\".
5660 # If the first line of the String starts with "#! interpreter", the
5661 # remainder of the string is piped into interpreter (after interpolation), i.e.,
5662 # open(INTERPRETER, "|interpreter");print INTERPRETER remainder;
5663 # just like in UNIX. There are some problems with quotes. Be carefull in
5664 # using them. You do not have access to the output of any piped (#!)
5665 # process! If you want such access, execute
5666 # <SCRIPT TYPE="text/osshell">echo "script"|interpreter</SCRIPT> or
5667 # <SCRIPT TYPE="text/ssperl">$resultvar = SAFEqx('echo "script"|interpreter');
5670 # SAFEqx ONLY WORKS WHEN THE STRING ITSELF IS SURROUNDED BY SINGLE QUOTES
5671 # (SO THAT IT IS NOT INTERPOLATED BEFORE IT CAN BE PROTECTED)
5672 sub SAFEqx
# ('String') -> result of executing qx/"String"/
5674 my $CommandString = shift;
5675 my $NewCommandString = "";
5677 # Only interpolate when required (check the On/Off switch)
5678 unless($CGIscriptor::NoShellScriptInterpolation
)
5681 # Handle existing single quotes around CGI values
5682 while($CommandString =~ /\'[^\']+\'/s)
5684 my $CurrentQuotedString = $&;
5685 $NewCommandString .= $`;
5686 $CommandString = $'; # The remaining string
5687 # Interpolate CGI variables between quotes
5688 # (e.g., '$CGIscriptorResults[-1]')
5689 $CurrentQuotedString =~
5690 s/(^|[^\\])([\$\@])((\w*)([\{\[][\$\@\%]?[\:\w\-]+[\}\]])*)/if(exists($main::default_values{$4})){
5691 "$1".eval("$2$3")}else{"$&"}/egs;
5693 # Combine result with previous result
5694 $NewCommandString .= $CurrentQuotedString;
5696 $CommandString = $NewCommandString.$CommandString;
5698 # Select known CGI variables and surround them with single quotes,
5699 # then interpolate all variables
5701 s/(^|[^\\])([\$\@\%]+)((\w*)([\{\[][\w\:\$\"\-]+[\}\]])*)/
5702 if($2 eq '$' && exists($main::default_values{$4}))
5703 {"$1\'".eval("\$$3")."\'";}
5704 elsif($2 eq '@'){$1.join(' ', @{"$3"});}
5705 elsif($2 eq '%'){my $t=$1;map {$t.=" $_=".${"$3"}{$_}}
5707 else{$1.eval("${2}$3");
5710 # Remove backslashed [$@%]
5711 $CommandString =~ s/\\([\$\@\%])/$1/gs;
5715 # return $CommandString;
5717 # Handle UNIX style "#! shell command\n" constructs as
5718 # a pipe into the shell command. The output cannot be tapped.
5719 my $ReturnValue = "";
5720 if($CommandString =~ /^\s*\#\!([^\f\n\r]+)[\f\n\r]/is)
5722 my $ShellScripts = $';
5723 my $ShellCommand = $1;
5724 open(INTERPRETER, "|$ShellCommand") || dieHandler(29, "\'$ShellCommand\' PIPE not opened: &!\n");
5725 select(INTERPRETER);$| = 1;
5726 print INTERPRETER $ShellScripts;
5728 select(STDOUT);$| = 1;
5730 # Shell scripts which are redirected to an existing named pipe.
5731 # The output cannot be tapped.
5732 elsif($CGIscriptor::ShellScriptPIPE)
5734 CGIscriptor::printSAFEqxPIPE($CommandString);
5736 else # Plain ``-backtick execution
5738 # Execute the commands
5739 $ReturnValue = qx/$CommandString/;
5741 return $ReturnValue;
5744 ####################################################################################
5746 # The CGIscriptor PACKAGE
5748 ####################################################################################
5750 # Isolate the evaluation of CGIscriptor functions, i.e., those prefixed with
5753 package CGIscriptor;
5756 # The Interpolation On/Off switch
5757 my $NoShellScriptInterpolation = undef;
5758 # The ShellScript redirection pipe
5759 my $ShellScriptPIPE = undef;
5761 # Open a named PIPE for SAFEqx to receive ALL shell scripts
5762 sub RedirectShellScript # ('CommandString')
5764 my $CommandString = shift || undef;
5768 $ShellScriptPIPE = "ShellScriptNamedPipe";
5769 open($ShellScriptPIPE, "|$CommandString")
5770 || main::dieHandler(30, "\'|$CommandString\' PIPE open failed: $!\n");
5774 close($ShellScriptPIPE);
5775 $ShellScriptPIPE = undef;
5777 return $ShellScriptPIPE;
5780 # Print to redirected shell script pipe
5781 sub printSAFEqxPIPE # ("String") -> print return value
5783 my $String = shift || undef;
5785 select($ShellScriptPIPE); $| = 1;
5786 my $returnvalue = print $ShellScriptPIPE ($String);
5787 select(STDOUT); $| = 1;
5789 return $returnvalue;
5792 # a pointer to CGIexecute::SAFEqx
5793 sub SAFEqx # ('String') -> result of qx/"String"/
5795 my $CommandString = shift;
5796 return CGIexecute::SAFEqx($CommandString);
5800 # a pointer to CGIexecute::defineCGIvariable
5801 sub defineCGIvariable # ($name[, $default]) ->0/1
5804 my $default = shift;
5805 return CGIexecute::defineCGIvariable($name, $default);
5809 # a pointer to CGIexecute::defineCGIvariable
5810 sub defineCGIvariableList # ($name[, $default]) ->0/1
5813 my $default = shift;
5814 return CGIexecute::defineCGIvariableList($name, $default);
5818 # a pointer to CGIexecute::defineCGIvariable
5819 sub defineCGIvariableHash # ($name[, $default]) ->0/1
5822 my $default = shift;
5823 return CGIexecute::defineCGIvariableHash($name, $default);
5827 # Decode URL encoded arguments
5828 sub URLdecode # (URL encoded input) -> string
5835 my $EncodedValue = $Value; # Do not change the loop variable
5836 # Convert all "+" to " "
5837 $EncodedValue =~ s/\+/ /g;
5838 # Convert all hexadecimal codes (%FF) to their byte values
5839 while($EncodedValue =~ /\%([0-9A-F]{2})/i)
5841 $output .= $`.chr(hex($1));
5844 $output .= $EncodedValue; # The remaining part of $Value
5849 # Encode arguments as URL codes.
5850 sub URLencode # (input) -> URL encoded string
5857 my @CharList = split('', $Value);
5858 foreach $char (@CharList)
5862 elsif($char =~ /\w\-/)
5863 { $output .= $char;}
5866 $output .= uc(sprintf("%%%2.2x", ord($char)));
5873 # Extract the value of a CGI variable from the URL-encoded $string
5874 # Also extracts the data blocks from a multipart request. Does NOT
5875 # decode the multipart blocks
5876 sub CGIparseValue # (ValueName [, URL_encoded_QueryString [, \$QueryReturnReference]]) -> Decoded value
5878 my $ValueName = shift;
5879 my $QueryString = shift || $main::ENV{'QUERY_STRING
'};
5880 my $ReturnReference = shift || undef;
5883 if($QueryString =~ /(^|\&)$ValueName\=([^\&]*)(\&|$)/)
5885 $output = URLdecode($2);
5886 $$ReturnReference = $' if ref($ReturnReference);
5888 # Get multipart POST or PUT methods
5889 elsif($main::ENV
{'CONTENT_TYPE'} =~ m@
(multipart
/([\w\
-]+)\
;\s
+boundary\
=([\S
]+))@i)
5891 my $MultipartType = $2;
5892 my $BoundaryString = $3;
5893 # Remove the boundary-string
5894 my $temp = $QueryString;
5895 $temp =~ /^\Q--$BoundaryString\E/m;
5898 # Identify the newline character(s), this is the first character in $temp
5899 my $NewLine = "\r\n"; # Actually, this IS the correct one
5900 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
5902 # Is this correct??? I have to check.
5903 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
5904 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
5905 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
5906 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
5909 # search through all data blocks
5910 while($temp =~ /^\Q--$BoundaryString\E/m)
5914 # Get the empty line after the header
5915 $DataBlock =~ /$NewLine$NewLine/;
5921 # Remove newlines from the header
5922 $Header =~ s/$NewLine/ /g;
5924 # Look whether this block is the one you are looking for
5925 # Require the quotes!
5926 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
5929 for($i=length($NewLine); $i; --$i)
5936 # reinitialize the output
5939 $$ReturnReference = $temp if ref($ReturnReference);
5941 elsif($QueryString !~ /(^|\&)$ValueName\=/) # The value simply isn't there
5944 $$ReturnReference = undef if ref($ReturnReference);
5948 print "ERROR: $ValueName $main::ENV{'CONTENT_TYPE'}\n";
5954 # Get a list of values for the same ValueName. Uses CGIparseValue
5956 sub CGIparseValueList
# (ValueName [, URL_encoded_QueryString]) -> List of decoded values
5958 my $ValueName = shift;
5959 my $QueryString = shift || $main::ENV
{'QUERY_STRING'};
5961 my $RestQueryString;
5963 while($QueryString &&
5964 (($Value = CGIparseValue
($ValueName, $QueryString, \
$RestQueryString))
5965 || defined($Value)))
5967 push(@output, $Value);
5968 $QueryString = $RestQueryString; # QueryString is consumed!
5970 # ready, return list with values
5974 sub CGIparseValueHash
# (ValueName [, URL_encoded_QueryString]) -> Hash table of decoded values
5976 my $ValueName = shift;
5977 my $QueryString = shift || $main::ENV
{'QUERY_STRING'};
5978 my $RestQueryString;
5980 while($QueryString && $QueryString =~ /(^|\&)$ValueName([\w]*)\=/)
5983 my $Value = CGIparseValue
("$ValueName$Key", $QueryString, \
$RestQueryString);
5984 $output{$Key} = $Value;
5985 $QueryString = $RestQueryString; # QueryString is consumed!
5987 # ready, return list with values
5991 sub CGIparseForm
# ([URL_encoded_QueryString]) -> Decoded Form (NO multipart)
5993 my $QueryString = shift || $main::ENV
{'QUERY_STRING'};
5996 $QueryString =~ s/\&/\n/g;
5997 $output = URLdecode
($QueryString);
6002 # Extract the header of a multipart CGI variable from the POST input
6003 sub CGIparseHeader
# (ValueName [, URL_encoded_QueryString]) -> Decoded value
6005 my $ValueName = shift;
6006 my $QueryString = shift || $main::ENV
{'QUERY_STRING'};
6009 if($main::ENV
{'CONTENT_TYPE'} =~ m@
(multipart
/([\w\
-]+)\
;\s
+boundary\
=([\S
]+))@i)
6011 my $MultipartType = $2;
6012 my $BoundaryString = $3;
6013 # Remove the boundary-string
6014 my $temp = $QueryString;
6015 $temp =~ /^\Q--$BoundaryString\E/m;
6018 # Identify the newline character(s), this is the first character in $temp
6019 my $NewLine = "\r\n"; # Actually, this IS the correct one
6020 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
6022 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
6023 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
6024 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
6025 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
6028 # search through all data blocks
6029 while($temp =~ /^\Q--$BoundaryString\E/m)
6033 # Get the empty line after the header
6034 $DataBlock =~ /$NewLine$NewLine/;
6038 # Remove newlines from the header
6039 $Header =~ s/$NewLine/ /g;
6041 # Look whether this block is the one you are looking for
6042 # Require the quotes!
6043 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
6048 # reinitialize the output
6056 # Checking variables for security (e.g., file names and email addresses)
6057 # File names are tested against the $::FileAllowedChars and $::BlockPathAccess variables
6058 sub CGIsafeFileName
# FileName -> FileName or ""
6060 my $FileName = shift || "";
6061 return "" if $FileName =~ m?[^$::FileAllowedChars]?;
6062 return "" if $FileName =~ m!(^|/|\:)[\-\.]!;
6063 return "" if $FileName =~ m@\
.\
.\Q
$::DirectorySeparator\E@
; # Higher directory not allowed
6064 return "" if $FileName =~ m@\Q
$::DirectorySeparator\E\
.\
.@
; # Higher directory not allowed
6065 return "" if $::BlockPathAccess
&& $FileName =~ m@
$::BlockPathAccess@
; # Invisible (blocked) file
6070 sub CGIsafeEmailAddress
# email -> email or ""
6072 my $Email = shift || "";
6073 return "" unless $Email =~ m/^[\w\.\-]+[\@][\w\.\-\:]+$/;
6077 # Get a URL from the web. Needs main::GET_URL($URL) function
6078 # (i.e., curl, snarf, or wget)
6079 sub read_url
# ($URL) -> page/file
6081 my $URL = shift || return "";
6083 # Get the commands to read the URL, do NOT add a print command
6084 my $URL_command = main
::GET_URL
($URL, 1);
6085 # execute the commands, i.e., actually read it
6086 my $URLcontent = CGIexecute
->evaluate($URL_command);
6088 # Ready, return the content.
6092 ################################################>>>>>>>>>>Start Remove
6094 # BrowseAllDirs(Directory, indexfile)
6097 # <SCRIPT TYPE='text/ssperl'>
6098 # CGIscriptor::BrowseAllDirs('Sounds', 'index.html', '\.wav$')
6101 # Allows to browse all directories. Stops at '/'. If the directory contains
6102 # an indexfile, eg, index.html, that file will be used instead. Files must match
6103 # the $Pattern, if it is given. Default is
6104 # CGIscriptor::BrowseAllDirs('/', 'index.html', '')
6106 sub BrowseAllDirs
# (Directory, indexfile, $Pattern) -> Print HTML code
6108 my $Directory = shift || '/';
6109 my $indexfile = shift || 'index.html';
6110 my $Pattern = shift || '';
6111 $Directory =~ s!/$!!g;
6113 # If the index directory exists, use that one
6114 if(-s
"$::CGI_HOME$Directory/$indexfile")
6116 return main
::ProcessFile
("$::CGI_HOME$Directory/$indexfile");
6119 # No indexfile, continue
6120 my @DirectoryList = glob("$::CGI_HOME$Directory");
6121 $CurrentDirectory = shift(@DirectoryList);
6122 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
6123 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
6125 print "$CurrentDirectory" if $CurrentDirectory;
6128 opendir(BROWSE, "$::CGI_HOME$Directory") || main::dieHandler(31, "$::CGI_HOME$Directory $!");
6129 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
6133 print "<pre><ul TYPE='NONE
'>\n";
6134 foreach $file (@AllFiles)
6136 next unless -d "$::CGI_HOME$Directory/$file";
6137 # Check whether this file should be visible
6138 next if $::BlockPathAccess &&
6139 "$Directory/$file/" =~ m@$::BlockPathAccess@;
6140 print "<dt><a href='$Directory/$file'>$file</a></dt>\n";
6142 print "</ul></pre>\n";
6145 print "<pre><ul TYPE='CIRCLE
'>\n";
6147 foreach $file (@AllFiles)
6149 next if $file =~ /^\./;
6150 next if -d "$::CGI_HOME$Directory/$file";
6151 next if -l "$::CGI_HOME$Directory/$file";
6152 # Check whether this file should be visible
6153 next if $::BlockPathAccess &&
6154 "$Directory/$file" =~ m@$::BlockPathAccess@;
6156 if(!$Pattern || $file =~ m@$Pattern@)
6158 my $Date = localtime($^T - (-M "$::CGI_HOME$Directory/$file")*3600*24);
6159 my $Size = -s "$::CGI_HOME$Directory/$file";
6160 $Size = sprintf("%6.0F kB", $Size/1024);
6161 my $Type = `file $::CGI_HOME$Directory/$file`;
6162 $Type =~ s@\s*$::CGI_HOME$Directory/$file\s*\:\s*@@ig;
6166 print "<a href='$Directory/$file'>";
6167 printf("%-40s", "$file</a>");
6168 print "\t$Size\t$Date\t$Type";
6172 print "</ul></pre>";
6178 ################################################
6180 # BrowseDirs(RootDirectory [, Pattern, Start])
6183 # <SCRIPT TYPE='text
/ssperl
'>
6184 # CGIscriptor::BrowseDirs('Sounds
', '\
.aifc
$', 'Speech
', 'DIRECTORY
')
6187 # Allows to browse subdirectories. Start should be relative to the RootDirectory,
6188 # e.g., the full path of the directory 'Speech
' is '~/Sounds/Speech
'.
6189 # Only files which fit /$Pattern/ and directories are displayed.
6190 # Directories down or up the directory tree are supplied with a
6191 # GET request with the name of the CGI variable in the fourth argument (default
6192 # is 'BROWSEDIRS
'). So the correct call for a subdirectory could be:
6193 # CGIscriptor::BrowseDirs('Sounds
', '\
.aifc
$', $DIRECTORY, 'DIRECTORY
')
6195 sub BrowseDirs # (RootDirectory [, Pattern, Start, CGIvariable, HTTPserver]) -> Print HTML code
6197 my $RootDirectory = shift; # || return 0;
6198 my $Pattern = shift || '\S
';
6199 my $Start = shift || "";
6200 my $CGIvariable = shift || "BROWSEDIRS";
6201 my $HTTPserver = shift || '';
6203 $Start = CGIscriptor::URLdecode($Start); # Sometimes, too much has been encoded
6204 $Start =~ s@//+@/@g;
6205 $Start =~ s@[^/]+/\.\.@@ig;
6206 $Start =~ s@^\.\.@@ig;
6207 $Start =~ s@/\.$@@ig;
6209 $Start .= "/" if $Start;
6211 my @Directory = glob("$::CGI_HOME/$RootDirectory/$Start");
6212 $CurrentDirectory = shift(@Directory);
6213 $CurrentDirectory = $' if $CurrentDirectory =~ m@
(/\.\./)+@
;
6214 $CurrentDirectory =~ s@
^$::CGI_HOME@
@g;
6216 print "$CurrentDirectory" if $CurrentDirectory;
6218 opendir(BROWSE
, "$::CGI_HOME/$RootDirectory/$Start") || main
::dieHandler
(31, "$::CGI_HOME/$RootDirectory/$Start $!");
6219 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE
));
6223 print "<pre><ul TYPE='NONE'>\n";
6224 foreach $file (@AllFiles)
6226 next unless -d
"$::CGI_HOME/$RootDirectory/$Start$file";
6227 # Check whether this file should be visible
6228 next if $::BlockPathAccess
&&
6229 "/$RootDirectory/$Start$file/" =~ m@
$::BlockPathAccess@
;
6231 my $NewURL = $Start ?
"$Start$file" : $file;
6232 $NewURL = CGIscriptor
::URLencode
($NewURL);
6233 print "<dt><a href='";
6234 print "$ENV{SCRIPT_NAME}" if $ENV{SCRIPT_NAME
} !~ m@
[^\w
+\
-/]@
;
6235 print "$ENV{PATH_INFO}?$CGIvariable=$NewURL'>$file</a></dt>\n";
6237 print "</ul></pre>\n";
6240 print "<pre><ul TYPE='CIRCLE'>\n";
6242 foreach $file (@AllFiles)
6244 next if $file =~ /^\./;
6245 next if -d
"$::CGI_HOME/$RootDirectory/$Start$file";
6246 next if -l
"$::CGI_HOME/$RootDirectory/$Start$file";
6247 # Check whether this file should be visible
6248 next if $::BlockPathAccess
&&
6249 "$::CGI_HOME/$RootDirectory/$Start$file" =~ m@
$::BlockPathAccess@
;
6251 if($file =~ m@
$Pattern@
)
6253 my $Date = localtime($^T
- (-M
"$::CGI_HOME/$RootDirectory/$Start$file")*3600*24);
6254 my $Size = -s
"$::CGI_HOME/$RootDirectory/$Start$file";
6255 $Size = sprintf("%6.0F kB", $Size/1024);
6256 my $Type = `file $::CGI_HOME/$RootDirectory/$Start$file`;
6257 $Type =~ s@\s
*$::CGI_HOME
/$RootDirectory/$Start$file\s
*\
:\s
*@
@ig;
6261 if($HTTPserver =~ /^\s*[\.\~]\s*$/)
6263 print "<a href='$RootDirectory/$Start$file'>";
6267 print "<a href='$HTTPserver/$RootDirectory/$Start$file'>";
6269 printf("%-40s", "$file</a>") if $HTTPserver;
6270 printf("%-40s", "$file") unless $HTTPserver;
6271 print "\t$Size\t$Date\t$Type";
6275 print "</ul></pre>";
6281 # ListDocs(Pattern [,ListType])
6284 # <SCRIPT TYPE=text/ssperl>
6285 # CGIscriptor::ListDocs("/*", "dl");
6288 # This subroutine is very usefull to manage collections of independent
6289 # documents. The resulting list will display the tree-like directory
6290 # structure. If this routine is too slow for online use, you can
6291 # store the result and use a link to that stored file.
6293 # List HTML and Text files with title and first header (HTML)
6294 # or filename and first meaningfull line (general text files).
6295 # The listing starts at the ServerRoot directory. Directories are
6296 # listed recursively.
6298 # You can change the list type (default is dl).
6300 # <dt><a href=<file.html>>title</a>
6302 # <dt><a href=<file.txt>>file.txt</a>
6303 # <dd>First meaningfull line of text
6305 sub ListDocs
# ($Pattern [, prefix]) e.g., ("/Books/*", [, "dl"])
6307 my $Pattern = shift;
6309 my $ListType = shift || "dl";
6310 my $Prefix = lc($ListType) eq "dl" ?
"dt" : "li";
6311 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
6312 my @FileList = glob("$::CGI_HOME$Pattern");
6313 my ($FileName, $Path, $Link);
6315 # Print List markers
6316 print "<$ListType>\n";
6319 File
: foreach $FileName (@FileList)
6321 # Check whether this file should be visible
6322 next if $::BlockPathAccess
&& $FileName =~ m@
$::BlockPathAccess@
;
6324 # Recursively list files in all directories
6327 $FileName =~ m@
([^/]*)$@
;
6329 print "<$Prefix>$DirName\n";
6330 $Pattern =~ m@
([^/]*)$@
;
6331 &ListDocs
("$`$DirName/$1", $ListType);
6335 elsif(-T
"$FileName")
6337 open(TextFile
, $FileName) || next;
6339 # Ignore all other file types
6343 # Get file path for link
6344 $FileName =~ /$::CGI_HOME/;
6345 print "<$Prefix><a href=$URL_root$'>";
6346 # Initialize all variables
6351 # Read file and step through
6357 if($FileName =~ /\.ht[a-zA-Z]*$/i)
6362 if($Line =~ m@
<title
>([^<]*)</title
>@i)
6369 $Line .= <TextFile> || goto Print;
6373 # Catch First Header
6376 if($Line =~ m@</h1>@i)
6380 $Caption =~ m@
<h1
>@i;
6382 $Line = $`.$Caption.$Line;
6386 $Line .= <TextFile> || goto Print;
6394 # Title equals file name
6395 $FileName =~ /([^\/]+)$/;
6397 # Catch equals First Meaningfull line
6400 if($Line =~ /[A-Z]/ &&
6401 ($Line =~ /subject|title/i || $Line =~ /^[\w,\.\s\?\:]+$/)
6402 && $Line !~ /Newsgroup/ && $Line !~ /\:\s*$/)
6404 $Line =~ s/\<[^\>]+\>//g;
6409 $Line = <TextFile> || goto Print;
6413 Print: # Print title and subject
6414 print "$Title</a>\n";
6415 print "<dd>$Caption\n" if $ListType eq "dl";
6422 # Print Closing List Marker
6423 print "</$ListType>\n";
6424 ""; # Empty return value
6428 # HTMLdocTree(Pattern [,ListType])
6431 # <SCRIPT TYPE=text/ssperl>
6432 # CGIscriptor::HTMLdocTree("/Welcome.html", "dl");
6435 # The following subroutine is very usefull for checking large document
6436 # trees. Starting from the root (s), it reads all files and prints out
6437 # a nested list of links to all attached files. Non-existing or misplaced
6438 # files are flagged. This is quite a file-i/o intensive routine
6439 # so you would not like it to be accessible to everyone. If you want to
6440 # use the result, save the whole resulting page to disk and use a link
6443 # HTMLdocTree takes an HTML file or file pattern and constructs nested lists
6444 # with links to *local* files (i.e., only links to the local server are
6445 # followed). The list entries are the document titles.
6446 # If the list type is <dl>, the first <H1> header is used too.
6447 # For each file matching the pattern, a list is made recursively of all
6448 # HTML documents that are linked from it and are stored in the same directory
6449 # or a sub-directory. Warnings are given for missing files.
6450 # The listing starts for the ServerRoot directory.
6451 # You can change the default list type <dl> (<dl>, <ul>, <ol>).
6455 sub HTMLdocTree # ($Pattern [, listtype])
6456 # e.g., ("/Welcome.html", [, "ul"])
6458 my $Pattern = shift;
6459 my $ListType = shift || "dl";
6460 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
6461 my $URL_root = "http://$::ENV{'SERVER_NAME
'}\:$::ENV{'SERVER_PORT
'}";
6462 my ($Filename, $Path, $Link);
6463 my %LocalLinks = {};
6465 # Read files (glob them for expansion of wildcards)
6466 my @FileList = glob("$::CGI_HOME$Pattern");
6467 foreach $Path (@FileList)
6470 $Path =~ /$::CGI_HOME/;
6472 # Check whether this file should be visible
6473 next if $::BlockPathAccess
&& $URL_path =~ m@
$::BlockPathAccess@
;
6475 my $Title = $URL_path;
6477 # Current file should not be used again
6478 ++$LinkUsed{$URL_path};
6480 unless(open(TextFile
, $Path))
6482 print "<$Prefix>$Title <blink>(not found)</blink><br>\n";
6490 while($Line =~ m@
<title
>@i)
6492 if($Line =~ m@
<title
>([^<]*)</title
>@i)
6499 $Line .= <TextFile>;
6503 # Catch First Header
6504 while(!$Caption && $Line =~ m@<h1>@i)
6506 if($Line =~ m@</h[1-9]>@i)
6510 $Caption =~ m@
<h1
>@i;
6512 $Line = $`.$Caption.$Line;
6516 $Line .= <TextFile>;
6520 # Catch and print Links
6521 while($Line =~ m@<a href\=([^>]*)>@i)
6528 $Link =~ s/[\#\?].*$//g;
6530 if($Link =~ m@
(http
://|^)@i)
6533 # Only build tree for current server
6534 next unless $Link =~ m@$::ENV{'SERVER_NAME
'}|^/@;
6535 # Remove server name and port
6536 $Link =~ s@^[^\/]*@@g;
6538 # Store the current link
6539 next if $LinkUsed{$Link} || $Link eq $URL_path;
6541 ++$LocalLinks{$Link};
6547 print "<a href=http://";
6548 print "$::ENV{'SERVER_NAME
'}\:$::ENV{'SERVER_PORT
'}$URL_path>";
6549 print "$Title</a>\n";
6550 print "<br>$Caption\n"
6551 if $Caption && $Caption ne $Title && $ListType =~ /dl/i;
6552 print "<$ListType>\n";
6553 foreach $Link (keys(%LocalLinks))
6555 &HTMLdocTree($Link, $ListType);
6557 print "</$ListType>\n";
6561 ###########################<<<<<<<<<<End Remove
6563 # Make require happy
6572 A flexible HTML 4 compliant script/module for CGI-aware
6573 embeded Perl, shell-scripts, and other scripting languages,
6574 executed at the server side.
6578 Executes embeded Perl code in HTML pages with easy
6579 access to CGI variables. Also processes embeded shell
6580 scripts and scripts in any other language with an
6581 interactive interpreter (e.g., in-line Python, Tcl,
6582 Ruby, Awk, Lisp, Xlispstat, Prolog, M4, R, REBOL, Praat,
6583 sh, bash, csh, ksh).
6585 CGIscriptor is very flexible and hides all the specifics
6586 and idiosyncrasies of correct output and CGI coding and naming.
6587 CGIscriptor complies with the W3C HTML 4.0 recommendations.
6589 This Perl program will run on any WWW server that runs
6590 Perl scripts, just add a line like the following to your
6591 srm.conf file (Apache example):
6593 ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
6595 URL's that refer to http
://www
.your
.address
/SHTML/... will
6596 now be handled by CGIscriptor
.pl
, which can
use a private
6597 directory tree
(default is the DOCUMENT_ROOT directory tree
,
6598 but it can be anywhere
).
6600 =head1 PREREQUISITES
6608 Linux, *BSD, *nix, MS WinXP
6610 =pod SCRIPT CATEGORIES