Renamed $ENV{COOKIE_JAR} to $ENV{HTTP_COOKIE} in added support for CGI::Cookie, not...
[CGIscriptor.git] / CGIscriptor.pl
blob5bbeb46ce7fc647fe153f12cb8687b3fe6cb4e05
1 #! /usr/bin/perl
3 # (configure the first line to contain YOUR path to perl 5.000+)
5 # CGIscriptor.pl
6 # Version 2.4
7 # 10 July 2012
9 # YOU NEED:
11 # perl 5.0 or higher (see: "http://www.perl.org/")
13 # Notes:
15 if(grep(/\-\-help/i, @ARGV))
17 print << 'ENDOFPREHELPTEXT1';
18 # CGIscriptor.pl is a Perl program will run on any WWW server that
19 # runs Perl scripts, just add a line like the following to your
20 # httpd.conf file (Apache example):
22 # ScriptAlias /SHTML/ "/real-path/CGIscriptor.pl/"
24 # URL's that refer to http://www.your.address/SHTML/... will now be handled
25 # by CGIscriptor.pl, which can use a private directory tree (default is the
26 # DOCUMENT_ROOT directory tree, but it can be anywhere, see below).
27 # NOTE: if you cannot use a ScriptAlias, there is a way to use .htaccess
28 # instead. See below.
30 # This file contains all documentation as comments. These comments
31 # can be removed to speed up loading (e.g., `egrep -v '^#' CGIscriptor.pl` >
32 # leanScriptor.pl). A bare bones version of CGIscriptor.pl, lacking
33 # documentation, most comments, access control, example functions etc.
34 # (but still with the copyright notice and some minimal documentation)
35 # can be obtained by calling CGIscriptor.pl with the '-slim'
36 # command line argument, e.g.,
37 # >CGIscriptor.pl -slim >slimCGIscriptor.pl
39 # CGIscriptor.pl can be run from the command line as
40 # `CGIscriptor.pl <path> <query>`, inside a perl script with
41 # 'do CGIscriptor.pl' after setting $ENV{PATH_INFO} and $ENV{QUERY_STRING},
42 # or CGIscriptor.pl can be loaded with 'require "/real-path/CGIscriptor.pl"'.
43 # In the latter case, requests are processed by 'Handle_Request();'
44 # (again after setting $ENV{PATH_INFO} and $ENV{QUERY_STRING}).
46 # The --help command line switch will print the manual.
48 # Running demo's and more information can be found at
49 # http://www.fon.hum.uva.nl/rob/OSS/OSS.html
51 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site
52 # or CPAN that can use CGIscriptor.pl as the base of a µWWW server and
53 # demonstrates its use.
55 ENDOFPREHELPTEXT1
58 # Configuration, copyright notice, and user manual follow the next
59 # (Changes) section.
61 ############################################################################
63 # Changes (document ALL changes with date, name and email here):
64 # 05 Apr 2013 - Renamed COOKIE_JAR to HTTP_COOKIE, added support for
65 # CGI::Cookie in case $ENV{HTTP_COOKIE} is undefined (untested)
66 # 31 Mar 2013 - Added support for Digest::SHA
67 # 13 Mar 2013 - Changed password hash
68 # 10 Jul 2012 - Version 2.4
69 # 11 Jun 2012 - Securing CGIvariable setting. Made
70 # 'if($ENV{QUERY_STRING} =~ /$name/)' into elsif in
71 # defineCGIvariable/List/Hash to give precedence to ENV{$name}
72 # This was a very old security bug. Added ProtectCGIvariable($name).
73 # 06 Jun 2012 - Added IP only session types after login.
74 # 31 May 2012 - Session ticket system added for handling login sessions.
75 # 29 May 2012 - CGIsafeFileName does not accept filenames starting with '.'
76 # 29 May 2012 - Added CGIscriptor::BrowseAllDirs to handle browsing directories
77 # correctly.
78 # 22 May 2012 - Added Access control with Session Tickets linked to
79 # IP Address and PATH_INFO.
80 # 21 May 2012 - Corrected the links generated by CGIscriptor::BrowseDirs
81 # Will link to current base URL when the HTTP server is '.' or '~'
82 # 29 Oct 2009 - Adapted David A. Wheeler's suggestion about filenames:
83 # CGIsafeFileName does not accept filenames starting with '-'
84 # (http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html)
85 # 08 Oct 2009 - Some corrections in the README.txt file, eg, new email address
86 # 28 Jan 2005 - Added a file selector to performTranslation.
87 # Changed %TranslationTable to @TranslationTable
88 # and patterns to lists.
89 # 27 Jan 2005 - Added a %TranslationTable with associated
90 # performTranslation(\$text) function to allow
91 # run changes in the web pages. Say, to translate
92 # legacy pages with <%=...%> delimiters to the new
93 # <SCRIPT TYPE=..></SCRIPT> format.
94 # 27 Jan 2005 - Small bug of extra '\n' in output removed from the
95 # Other Languages Code.
96 # 10 May 2004 - Belated upload of latest version (2.3) to CPAN
97 # 07 Oct 2003 - Corrected error '\s' -> '\\s' in rebol scripting
98 # language call
99 # 07 Oct 2003 - Corrected omitted INS tags in <DIV><INS> handling
100 # 20 May 2003 - Added a --help switch to print the manual.
101 # 06 Mar 2003 - Adapted the blurb at the end of the file.
102 # 03 Mar 2003 - Added a user definable dieHandler function to catch all
103 # "die" calls. Also "enhanced" the STDERR printout.
104 # 10 Feb 2003 - Split off the reading of the POST part of a query
105 # from Initialize_output. This was suggested by Gerd Franke
106 # to allow for the catching of the file_path using a
107 # POST based lookup. That is, he needed the POST part
108 # to change the file_path.
109 # 03 Feb 2003 - %{$name}; => %{$name} = (); in defineCGIvariableHash.
110 # 03 Feb 2003 - \1 better written as $1 in
111 # $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
112 # 29 Jan 2003 - This makes "CLASS="ssperl" CSS-compatible Gerd Franke
113 # added:
114 # $ServerScriptContentClass = "ssperl";
115 # changed in ProcessFile():
116 # unless(($CurrentContentType =~
117 # 28 Jan 2003 - Added 'INS' Tag! Gerd Franke
118 # 20 Dec 2002 - Removed useless $Directoryseparator variable.
119 # Update comments and documentation.
120 # 18 Dec 2002 - Corrected bug in Accept/Reject processing.
121 # Files didn't work.
122 # 24 Jul 2002 - Added .htaccess documentation (from Gerd Franke)
123 # Also added a note that RawFilePattern can be a
124 # complete file name.
125 # 19 Mar 2002 - Added SRC pseudo-files PREFIX and POSTFIX. These
126 # switch to prepending or to appending the content
127 # of the SRC attribute. Default is prefixing. You
128 # can add as many of these switches as you like.
129 # 13 Mar 2002 - Do not search for tag content if a tag closes with
130 # />, i.e., <DIV ... /> will be handled the XML/XHTML way.
131 # 25 Jan 2002 - Added 'curl' and 'snarf' to SRC attribute URL handling
132 # (replaces wget).
133 # 25 Jan 2002 - Found a bug in SAFEqx, now executes qx() in a scalar context
134 # (i.o. a list context). This is necessary for binary results.
135 # 24 Jan 2002 - Disambiguated -T $SRCfile to -T "$SRCfile" (and -e) and
136 # changed the order of if/elsif to allow removing these
137 # conditions in systems with broken -T functions.
138 # (I also removed a spurious ')' bracket)
139 # 17 Jan 2002 - Changed DIV tag SRC from <SOURCE> to sysread(SOURCE,...)
140 # to support binary files.
141 # 17 Jan 2002 - Removed WhiteSpace from $FileAllowedCharacters.
142 # 17 Jan 2002 - Allow "file://" prefix in SRC attribute. It is simply
143 # stipped from the path.
144 # 15 Jan 2002 - Version 2.2
145 # 15 Jan 2002 - Debugged and completed URL support (including
146 # CGIscriptor::read_url() function)
147 # 07 Jan 2002 - Added automatic (magic) URL support to the SRC attribute
148 # with the main::GET_URL function. Uses wget -O underlying.
149 # 04 Jan 2002 - Added initialization of $NewDirective in InsertForeignScript
150 # (i.e., my $NewDirective = "";) to clear old output
151 # (this was a realy anoying bug).
152 # 03 Jan 2002 - Added a <DIV CLASS='text/ssperl' ID='varname'></DIV>
153 # tags that assign the body text as-is (literally)
154 # to $varname. Allows standard HTML-tools to handle
155 # Cascading Style Sheet templates. This implements a
156 # design by Gerd Franke (franke@roo.de).
157 # 03 Jan 2002 - I finaly gave in and allowed SRC files to expand ~/.
158 # 12 Oct 2001 - Normalized spelling of "CGIsafFileName" in documentation.
159 # 09 Oct 2001 - Added $ENV{'CGI_BINARY_FILE'} to log files to
160 # detect unwanted indexing of TAR files by webcrawlers.
161 # 10 Sep 2001 - Added $YOUR_SCRIPTS directory to @INC for 'require'.
162 # 22 Aug 2001 - Added .txt (Content-type: text/plain) as a default
163 # processed file type. Was processed via BinaryMapFile.
164 # 31 May 2001 - Changed =~ inside CGIsafeEmailAddress that was buggy.
165 # 29 May 2001 - Updated $CGI_HOME to point to $ENV{DOCUMENT_ROOT} io
166 # the root of PATH_TRANSLATED. DOCUMENT_ROOT can now
167 # be manipulated to achieve a "Sub Root".
168 # NOTE: you can have $YOUR_HTML_FILES != DOCUMENT_ROOT
169 # 28 May 2001 - Changed CGIscriptor::BrowsDirs function for security
170 # and debugging (it now works).
171 # 21 May 2001 - defineCGIvariableHash will ADD values to existing
172 # hashes,instead of replacing existing hashes.
173 # 17 May 2001 - Interjected a '&' when pasting POST to GET data
174 # 24 Apr 2001 - Blocked direct requests for BinaryMapFile.
175 # 16 Aug 2000 - Added hash table extraction for CGI parameters with
176 # CGIparseValueHash (used with structured parameters).
177 # Use: CGI='%<CGI-partial-name>' (fill in your name in <>)
178 # Will collect all <CGI-partial-name><key>=value pairs in
179 # $<CGI-partial-name>{<key>} = value;
180 # 16 Aug 2000 - Adapted SAFEqx to protect @PARAMETER values.
181 # 09 Aug 2000 - Added support for non-filesystem input by way of
182 # the CGI_FILE_CONTENTS and CGI_DATA_ACCESS_CODE
183 # environment variables.
184 # 26 Jul 2000 - On the command-line, file-path '-' indicates STDIN.
185 # This allows CGIscriptor to be used in pipes.
186 # Default, $BLOCK_STDIN_HTTP_REQUEST=1 will block this
187 # in an HTTP request (i.e., in a web server).
188 # 26 Jul 2000 - Blocked 'Content-type: text/html' if the SERVER_PROTOCOL
189 # is not HTTP or another protocol. Changed the default
190 # source directory to DOCUMENT_ROOT (i.o. the incorrect
191 # SERVER_ROOT).
192 # 24 Jul 2000 - -slim Command-line argument added to remove all
193 # comments, security, etc.. Updated documentation.
194 # 05 Jul 2000 - Added IF and UNLESS attributes to make the
195 # execution of all <META> and <SCRIPT> code
196 # conditional.
197 # 05 Jul 2000 - Rewrote and isolated the code for extracting
198 # quoted items from CGI and SRC attributes.
199 # Now all attributes expect the same set of
200 # quotes: '', "", ``, (), {}, [] and the same
201 # preceded by a \, e.g., "\((aap)\)" will be
202 # extracted as "(aap)".
203 # 17 Jun 2000 - Construct @ARGV list directly in CGIexecute
204 # name-space (i.o. by evaluation) from
205 # CGI attributes to prevent interference with
206 # the processing for non perl scripts.
207 # Changed CGIparseValueList to prevent runaway
208 # loops.
209 # 16 Jun 2000 - Added a direct (interpolated) display mode
210 # (text/ssdisplay) and a user log mode
211 # (text/sslogfile).
212 # 06 Jun 2000 - Replace "print $Result" with a syswrite loop to
213 # allow large string output.
214 # 02 Jun 2000 - Corrected shrubCGIparameter($CGI_VALUE) to realy
215 # remove all control characters. Changed Interpreter
216 # initialization to shrub interpolated CGI parameters.
217 # Added 'text/ssmailto' interpreter script.
218 # 22 May 2000 - Changed some of the comments
219 # 09 May 2000 - Added list extraction for CGI parameters with
220 # CGIparseValueList (used with multiple selections).
221 # Use: CGI='@<CGI-parameter>' (fill in your name in <>)
222 # 09 May 2000 - Added a 'Not Present' condition to CGIparseValue.
223 # 27 Apr 2000 - Updated documentation to reflect changes.
224 # 27 Apr 2000 - SRC attribute "cleaned". Supported for external
225 # interpreters.
226 # 27 Apr 2000 - CGI attribute can be used in <SCRIPT> tag.
227 # 27 Apr 2000 - Gprolog, M4 support added.
228 # 26 Apr 2000 - Lisp (rep) support added.
229 # 20 Apr 2000 - Use of external interpreters now functional.
230 # 20 Apr 2000 - Removed bug from extracting Content types (RegExp)
231 # 10 Mar 2000 - Qualified unconditional removal of '#' that preclude
232 # the use of $#foo, i.e., I changed
233 # s/[^\\]\#[^\n\f\r]*([\n\f\r])/\1/g
234 # to
235 # s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/\1/g
236 # 03 Mar 2000 - Added a '$BlockPathAccess' variable to "hide"
237 # things like, e.g., CVS information in CVS subtrees
238 # 10 Feb 2000 - URLencode/URLdecode have been made case-insensitive
239 # 10 Feb 2000 - Added a BrowseDirs function (CGIscriptor package)
240 # 01 Feb 2000 - A BinaryMapFile in the ~/ directory has precedence
241 # over a "burried" BinaryMapFile.
242 # 04 Oct 1999 - Added two functions to check file names and email addresses
243 # (CGIscriptor::CGIsafeFileName and
244 # CGIscriptor::CGIsafeEmailAddress)
245 # 28 Sept 1999 - Corrected bug in sysread call for reading POST method
246 # to allow LONG posts.
247 # 28 Sept 1999 - Changed CGIparseValue to handle multipart/form-data.
248 # 29 July 1999 - Refer to BinaryMapFile from CGIscriptor directory, if
249 # this directory exists.
250 # 07 June 1999 - Limit file-pattern matching to LAST extension
251 # 04 June 1999 - Default text/html content type is printed only once.
252 # 18 May 1999 - Bug in replacement of ~/ and ./ removed.
253 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
254 # 15 May 1999 - Changed the name of the execute package to CGIexecute.
255 # Changed the processing of the Accept and Reject file.
256 # Added a full expression evaluation to Access Control.
257 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
258 # 27 Apr 1999 - Brought CGIscriptor under the GNU GPL. Made CGIscriptor
259 # Version 1.1 a module that can be called with 'require "CGIscriptor.pl"'.
260 # Requests are serviced by "Handle_Request()". CGIscriptor
261 # can still be called as a isolated perl script and a shell
262 # command.
263 # Changed the "factory default setting" so that it will run
264 # from the DOCUMENT_ROOT directory.
265 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
266 # 29 Mar 1999 - Remove second debugging STDERR switch. Moved most code
267 # to subroutines to change CGIscriptor into a module.
268 # Added mapping to process unsupported file types (e.g., binary
269 # pictures). See $BinaryMapFile.
270 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
271 # 24 Sept 1998 - Changed text of license (Rob van Son, R.J.J.H.vanSon@gmail.com)
272 # Removed a double setting of filepatterns and maximum query
273 # size. Changed email address. Removed some typos from the
274 # comments.
275 # 02 June 1998 - Bug fixed in URLdecode. Changing the foreach loop variable
276 # caused quiting CGIscriptor.(Rob van Son, R.J.J.H.vanSon@gmail.com)
277 # 02 June 1998 - $SS_PUB and $SS_SCRIPT inserted an extra /, removed.
278 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
281 # Known Bugs:
283 # 23 Mar 2000
284 # It is not possible to use operators or variables to construct variable names,
285 # e.g., $bar = \@{$foo}; won't work. However, eval('$bar = \@{'.$foo.'};');
286 # will indeed work. If someone could tell me why, I would be obliged.
289 ############################################################################
291 # OBLIGATORY USER CONFIGURATION
293 # Configure the directories where all user files can be found (this
294 # is the equivalent of the server root directory of a WWW-server).
295 # These directories can be located ANYWHERE. For security reasons, it is
296 # better to locate them outside the WWW-tree of your HTTP server, unless
297 # CGIscripter handles ALL requests.
299 # For convenience, the defaults are set to the root of the WWW server.
300 # However, this might not be safe!
302 # ~/ text files
303 # $YOUR_HTML_FILES = "/usr/pub/WWW/SHTML"; # or SS_PUB as environment var
304 # (patch to use the parent directory of CGIscriptor as document root, should be removed)
305 if($ENV{'SCRIPT_FILENAME'}) # && $ENV{'SCRIPT_FILENAME'} !~ /\Q$ENV{'DOCUMENT_ROOT'}\E/)
307 $ENV{'DOCUMENT_ROOT'} = $ENV{'SCRIPT_FILENAME'};
308 $ENV{'DOCUMENT_ROOT'} =~ s@/CGIscriptor.*$@@ig;
311 # Just enter your own directory path here
312 $YOUR_HTML_FILES = $ENV{'DOCUMENT_ROOT'}; # default is the DOCUMENT_ROOT
314 # ./ script files (recommended to be different from the previous)
315 # $YOUR_SCRIPTS = "/usr/pub/WWW/scripts"; # or SS_SCRIPT as environment var
316 $YOUR_SCRIPTS = $YOUR_HTML_FILES; # This might be a SECURITY RISK
318 # End of obligatory user configuration
319 # (note: there is more non-essential user configuration below)
321 ############################################################################
323 # OPTIONAL USER CONFIGURATION (all values are used CASE INSENSITIVE)
325 # Script content-types: TYPE="Content-type" (user defined mime-type)
326 $ServerScriptContentType = "text/ssperl"; # Server Side Perl scripts
327 # CSS require a simple class
328 $ServerScriptContentClass = $ServerScriptContentType =~ m!/! ?
329 $' : "ssperl"; # Server Side Perl CSS classes
331 $ShellScriptContentType = "text/osshell"; # OS shell scripts
332 # # (Server Side perl ``-execution)
334 # Accessible file patterns, block any request that doesn't match.
335 # Matches any file with the extension .(s)htm(l), .txt, or .xmr
336 # (\. is used in regexp)
337 # Note: die unless $PATH_INFO =~ m@($FilePattern)$@is;
338 $FilePattern = ".shtml|.htm|.html|.xml|.xmr|.txt|.js|.css";
340 # The table with the content type MIME types
341 # (allows to differentiate MIME types, if needed)
342 %ContentTypeTable =
344 '.html' => 'text/html',
345 '.shtml' => 'text/html',
346 '.htm' => 'text/html',
347 '.xml' => 'text/xml',
348 '.txt' => 'text/plain',
349 '.js' => 'text/plain',
350 '.css' => 'text/plain'
354 # File pattern post-processing
355 $FilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
357 # SHAsum command needed for Authorization and Login
358 # (note, these have to be accessible in the HTML pages, ie, the CGIexecute environment)
359 my $shasum = "shasum -a 256";
360 if(qx{uname} =~ /Darwin/)
362 $shasum = "shasum-5.12 -a 256" unless `which shasum`;
364 my $SHASUMCMD = $shasum.' |cut -f 1 -d" "';
365 $ENV{"SHASUMCMD"} = $SHASUMCMD;
366 my $RANDOMHASHCMD = 'dd bs=1 count=64 if=/dev/urandom 2>/dev/null | '.$shasum.' -b |cut -f 1 -d" "';
367 $ENV{"RANDOMHASHCMD"} = $RANDOMHASHCMD;
369 # Hash a string, return hex of hash
370 sub hash_string_cmd # ($string) -> hex_hash
372 my $string = shift || "";
373 # Catch nasty \'-quotes, embed them in '..'"'"'..'
374 $string =~ s/\'/\'\"\'\"\'/isg;
375 my $hash = `printf '%s' '$string'| $ENV{"SHASUMCMD"}`;
376 chomp($hash);
377 return $hash;
380 # Note that you CANNOT replace $RANDOMHASHCMD with a call using hash_string_cmd
381 # as the output of /dev/urandom breaks string handling in Perl.
382 # Generate random hex hash
383 sub get_random_hex_cmd # () -> hex
385 # Create Random Hash Salt
386 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $RANDOMHASHCMD | $!\n";
387 my $RANDOMSALT= <URANDOM>;
388 close(URANDOM);
389 chomp($RANDOMSALT);
391 return $RANDOMSALT;
395 # You can use Digest::SHA (SHA.pm), you need sha256_hex
396 # See http://search.cpan.org/~mshelor/Digest-SHA-5.84/lib/Digest/SHA.pm
397 # > sudo CPAN -i Digest
399 # The following code will check whether Digest::SHA is available and then
400 # use the appropriate function calls.
402 $shaDigestLoaded = (eval("require Digest::SHA;1;") eq "1") ? 1 : 0;
404 sub hash_string_Digest # ($string) -> hex_hash
406 my $string = shift || "";
407 my $digest = Digest::SHA::sha256_hex($string);
408 $string = $digest;
409 return $digest;
412 sub get_random_hex_Digest # () -> hex
414 my $randomstring = "";
415 # Create Random Hash Salt
416 open(URANDOM, "</dev/urandom") || die "/dev/urandom: $!\n";
417 read URANDOM, $randomstring, 64 || die "No random bytes read: $!\n";
418 close(URANDOM);
419 my $RANDOMSALT= hash_string_Digest($randomstring);
421 return $RANDOMSALT;
424 # The final functions
425 sub hash_string # ($string) -> hex_hash
427 if($shaDigestLoaded)
428 { return hash_string_Digest (@_) }
429 else
430 { return hash_string_cmd(@_);};
433 sub get_random_hex # () -> hex
435 if($shaDigestLoaded)
436 { return get_random_hex_Digest () }
437 else
438 { return get_random_hex_cmd();};
441 ######################################################################
443 # File patterns of files which are handled by session tickets.
444 %TicketRequiredPatterns = (
445 '^/Private(/|$)' => "Private/.Sessions\tPrivate/.Passwords\t/Private/Login.html\t+36000"
447 # Used to set cookies, only session cookies supported
448 my %SETCOOKIELIST = ();
449 my %CGI_Cookies = ();
450 # Parse the cookies if $ENV{'HTTP_COOKIE'} is defined, else use CGI::Cookie
451 # if it is available
452 sub Get_All_Cookies
454 if(defined($ENV{'HTTP_COOKIE'}))
456 my @CookieList = split(/[\;\s]+/, $ENV{'HTTP_COOKIE'});
457 foreach my $CookieEntry (@CookieList)
459 my ($k, $v) = split(/\=/, $CookieEntry);
460 # Add new cookie only if it does not already exist
461 $CGI_Cookies{$k} = $v unless exists($CGI_Cookies{$k}) && ($v eq "" || $v eq "-");
462 ($k, $v, $CookieEntry) = (0, 0, 0);
464 @CookieList = ();
466 else
468 my $cookiesLoaded = (eval("require CGI::Cookie;1;") eq "1") ? 1 : 0;
469 if($cookiesLoaded)
471 %CGI_Cookies = fetch CGI::Cookie;
477 # Session Ticket Directory: Private/.Sessions
478 # Password Directory: Private/.Passwords
479 # Login page (url path): /Private/Login.html
480 # Expiration time (s): +3600
481 # +<seconds> = relative time <seconds> is absolute date-time
483 # Manage login
484 # Set up a valid ticket from a given text file
485 # Use from command line. DO NOT USE ONLINE
486 # Watch out for passwords that get stored in the history file
488 # perl CGIscriptor.pl --managelogin [options] [files]
489 # Options:
490 # salt={file or saltvalue}
491 # masterkey={file or plaintext}
492 # newmasterkey={file or plaintext}
493 # password={file or palintext}
495 # Followed by one or more file names.
496 # Options can be interspersed between filenames,
497 # e.g., password='plaintext'
498 # Note that passwords are only used once!
500 if($ARGV[0] =~ /^\-\-managelogin/i)
502 my @arguments = @ARGV;
503 shift(@arguments);
504 setup_ticket_file(@arguments);
505 # Should be run on the command line
506 exit;
511 # Raw files must contain their own Content-type (xmr <- x-multipart-replace).
512 # THIS IS A SUBSET OF THE FILES DEFINED IN $FilePattern
513 $RawFilePattern = ".xmr";
514 # (In principle, this could contain a full file specification, e.g.,
515 # ".xmr|relocated.html")
517 # Raw File pattern post-processing
518 $RawFilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
520 # Server protocols for which "Content-type: text/html\n\n" should be printed
521 # (you should not bother with these, except for HTTP, they are mostly imaginary)
522 $ContentTypeServerProtocols = 'HTTP|MAIL|MIME';
524 # Block access to all (sub-) paths and directories that match the
525 # following (URL) path (is used as:
526 # 'die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;' )
527 $BlockPathAccess = '/(CVS|\.git)/'; # Protect CVS and .git information
529 # All (blocked) other file-types can be mapped to a single "binary-file"
530 # processor (a kind of pseudo-file path). This can either be an error
531 # message (e.g., "illegal file") or contain a script that serves binary
532 # files.
533 # Note: the real file path wil be stored in $ENV{CGI_BINARY_FILE}.
534 $BinaryMapFile = "/BinaryMapFile.xmr";
535 # Allow for the addition of a CGIscriptor directory
536 # Note that a BinaryMapFile in the root "~/" directory has precedence
537 $BinaryMapFile = "/CGIscriptor".$BinaryMapFile
538 if ! -e "$YOUR_HTML_FILES".$BinaryMapFile
539 && -e "$YOUR_HTML_FILES/CGIscriptor".$BinaryMapFile;
542 # List of all characters that are allowed in file names and paths.
543 # All requests containing illegal characters are blocked. This
544 # blocks most tricks (e.g., adding "\000", "\n", or other control
545 # characters, also blocks URI's using %FF)
546 # THIS IS A SECURITY FEATURE
547 # (this is also used to parse filenames in SRC= features, note the
548 # '-quotes, they are essential)
549 $FileAllowedChars = '\w\.\~\/\:\*\?\-'; # Covers Unix and Mac, but NO spaces
551 # Maximum size of the Query (number of characters clients can send
552 # covers both GET & POST combined)
553 $MaximumQuerySize = 2**20 - 1; # = 2**14 - 1
556 # Embeded URL get function used in SRC attributes and CGIscriptor::read_url
557 # (returns a string with the PERL code to transfer the URL contents, e.g.,
558 # "SAFEqx(\'curl \"http://www.fon.hum.uva.nl\"\')")
559 # "SAFEqx(\'wget --quiet --output-document=- \"http://www.fon.hum.uva.nl\"\')")
560 # Be sure to handle <BASE HREF='URL'> and allow BOTH
561 # direct printing GET_URL($URL [, 0]) and extracting the content of
562 # the $URL for post-processing GET_URL($URL, 1).
563 # You get the WHOLE file, including HTML header.
564 # The shell command Use $URL where the URL should go
565 # ('wget', 'snarf' or 'curl', uncomment the one you would like to use)
566 my $GET_URL_shell_command = 'wget --quiet --output-document=- $URL';
567 #my $GET_URL_shell_command = 'snarf $URL -';
568 #my $GET_URL_shell_command = 'curl $URL';
570 sub GET_URL # ($URL, $ValueNotPrint) -> content_of_url
572 my $URL = shift || return;
573 my $ValueNotPrint = shift || 0;
575 # Check URL for illegal characters
576 return "print '<h1>Illegal URL<h1>'\"\n\";" if $URL =~ /[^$FileAllowedChars\%]/;
578 # Include URL in final command
579 my $CurrentCommand = $GET_URL_shell_command;
580 $CurrentCommand =~ s/\$URL/$URL/g;
582 # Print to STDOUT or return a value
583 my $BlockPrint = "print STDOUT ";
584 $BlockPrint = "" if $ValueNotPrint;
586 my $Commands = <<"GETURLCODE";
587 # Get URL
589 my \$Page = "";
591 # Simple, using shell command
592 \$Page = SAFEqx('$CurrentCommand');
594 # Add a BASE tage to the header
595 \$Page =~ s!\\</head!\\<base href='$URL'\\>\\</head!ig unless \$Page =~ m!\\<base!;
597 # Print the URL value, or return it as a value
598 $BlockPrint\$Page;
600 GETURLCODE
601 return $Commands;
604 # As files can get rather large (and binary), you might want to use
605 # some more intelligent reading procedure, e.g.,
606 # Direct Perl
607 # # open(URLHANDLE, '/usr/bin/wget --quiet --output-document=- "$URL"|') || die "wget: \$!";
608 # #open(URLHANDLE, '/usr/bin/snarf "$URL" -|') || die "snarf: \$!";
609 # open(URLHANDLE, '/usr/bin/curl "$URL"|') || die "curl: \$!";
610 # my \$text = "";
611 # while(sysread(URLHANDLE,\$text, 1024) > 0)
613 # \$Page .= \$text;
614 # };
615 # close(URLHANDLE) || die "\$!";
616 # However, this doesn't work with the CGIexecute->evaluate() function.
617 # You get an error: 'No child processes at (eval 16) line 15, <file0> line 8.'
619 # You can forget the next two variables, they are only needed when
620 # you don't want to use a regular file system (i.e., with open)
621 # but use some kind of database/RAM image for accessing (generating)
622 # the data.
624 # Name of the environment variable that contains the file contents
625 # when reading directly from Database/RAM. When this environment variable,
626 # $ENV{$CGI_FILE_CONTENTS}, is not false, no real file will be read.
627 $CGI_FILE_CONTENTS = 'CGI_FILE_CONTENTS';
628 # Uncomment the following if you want to force the use of the data access code
629 # $ENV{$CGI_FILE_CONTENTS} = '-'; # Force use of $ENV{$CGI_DATA_ACCESS_CODE}
631 # Name of the environment variable that contains the RAM access perl
632 # code needed to read additional "files", i.e.,
633 # $ENV{$CGI_FILE_CONTENTS} = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
634 # When $ENV{$CGI_FILE_CONTENTS} eq '-', this code is executed to generate the data.
635 $CGI_DATA_ACCESS_CODE = 'CGI_DATA_ACCESS_CODE';
637 # You can, of course, fill this yourself, e.g.,
638 # $ENV{$CGI_DATA_ACCESS_CODE} =
639 # 'open(INPUT, "<$_[0]"); while(<INPUT>){print;};close(INPUT);'
642 # DEBUGGING
644 # Suppress error messages, this can be changed for debugging or error-logging
645 #open(STDERR, "/dev/null"); # (comment out for use in debugging)
647 # SPECIAL: Remove Comments, security, etc. if the command line is
648 # '>CGIscriptor.pl -slim >slimCGIscriptor.pl'
649 $TrimDownCGIscriptor = 1 if $ARGV[0] =~ /^\-slim/i;
651 # If CGIscriptor is used from the command line, the command line
652 # arguments are interpreted as the file (1st) and the Query String (rest).
653 # Get the arguments
654 $ENV{'PATH_INFO'} = shift(@ARGV) unless exists($ENV{'PATH_INFO'}) || grep(/\-\-help/i, @ARGV);
655 $ENV{'QUERY_STRING'} = join("&", @ARGV) unless exists($ENV{'QUERY_STRING'});
658 # Handle bail-outs in a user definable way.
659 # Catch Die and replace it with your own function.
660 # Ends with a call to "die $_[0];"
662 sub dieHandler # ($ErrorCode, "Message", @_) -> DEAD
664 my $ErrorCode = shift;
665 my $ErrorMessage = shift;
667 # Place your own reporting functions here
669 # Now, kill everything (default)
670 print STDERR "$ErrorCode: $ErrorMessage\n";
671 die $ErrorMessage;
675 # End of optional user configuration
676 # (note: there is more non-essential user configuration below)
678 if(grep(/\-\-help/i, @ARGV))
680 print << 'ENDOFPREHELPTEXT2';
682 ###############################################################################
684 # Author and Copyright (c):
685 # Rob van Son, © 1995,1996,1997,1998,1999,2000,2001,2002-2012
686 # NKI-AVL Amsterdam
687 # r.v.son@nki.nl
688 # Institute of Phonetic Sciences & IFOTT/ACLS
689 # University of Amsterdam
690 # Email: R.J.J.H.vanSon@gmail.com
691 # Email: R.J.J.H.vanSon@gmail.com
692 # WWW : http://www.fon.hum.uva.nl/rob/
694 # License for use and disclaimers
696 # CGIscriptor merges plain ASCII HTML files transparantly
697 # with CGI variables, in-line PERL code, shell commands,
698 # and executable scripts in other scripting languages.
700 # This program is free software; you can redistribute it and/or
701 # modify it under the terms of the GNU General Public License
702 # as published by the Free Software Foundation; either version 2
703 # of the License, or (at your option) any later version.
705 # This program is distributed in the hope that it will be useful,
706 # but WITHOUT ANY WARRANTY; without even the implied warranty of
707 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
708 # GNU General Public License for more details.
710 # You should have received a copy of the GNU General Public License
711 # along with this program; if not, write to the Free Software
712 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
715 # Contributors:
716 # Rob van Son (R.J.J.H.vanSon@gmail.com)
717 # Gerd Franke franke@roo.de (designed the <DIV> behaviour)
719 #######################################################
720 ENDOFPREHELPTEXT2
722 #######################################################>>>>>>>>>>Start Remove
724 # You can skip the following code, it is an auto-splice
725 # procedure.
727 # Construct a slimmed down version of CGIscriptor
728 # (i.e., CGIscriptor.pl -slim > slimCGIscriptor.pl)
730 if($TrimDownCGIscriptor)
732 open(CGISCRIPTOR, "<CGIscriptor.pl")
733 || dieHandler(1, "<CGIscriptor.pl not slimmed down: $!\n");
734 my $SKIPtext = 0;
735 my $SKIPComments = 0;
737 while(<CGISCRIPTOR>)
739 my $SKIPline = 0;
741 ++$LineCount;
743 # Start of SKIP text
744 $SKIPtext = 1 if /[\>]{10}Start Remove/;
745 $SKIPComments = 1 if $SKIPtext == 1;
747 # Skip this line?
748 $SKIPline = 1 if $SKIPtext || ($SKIPComments && /^\s*\#/);
750 ++$PrintCount unless $SKIPline;
752 print STDOUT $_ unless $SKIPline;
754 # End of SKIP text ?
755 $SKIPtext = 0 if /[\<]{10}End Remove/;
757 # Ready!
758 print STDERR "\# Printed $PrintCount out of $LineCount lines\n";
759 exit;
762 #######################################################
764 if(grep(/\-\-help/i, @ARGV))
766 print << 'ENDOFHELPTEXT';
768 # HYPE
770 # CGIscriptor merges plain ASCII HTML files transparantly and safely
771 # with CGI variables, in-line PERL code, shell commands, and executable
772 # scripts in many languages (on-line and real-time). It combines the
773 # "ease of use" of HTML files with the versatillity of specialized
774 # scripts and PERL programs. It hides all the specifics and
775 # idiosyncrasies of correct output and CGI coding and naming. Scripts
776 # do not have to be aware of HTML, HTTP, or CGI conventions just as HTML
777 # files can be ignorant of scripts and the associated values. CGIscriptor
778 # complies with the W3C HTML 4.0 recommendations.
779 # In addition to its use as a WWW embeded CGI processor, it can
780 # be used as a command-line document preprocessor (text-filter).
782 # THIS IS HOW IT WORKS
784 # The aim of CGIscriptor is to execute "plain" scripts inside a text file
785 # using any required CGIparameters and environment variables. It
786 # is optimized to transparantly process HTML files inside a WWW server.
787 # The native language is Perl, but many other scripting languages
788 # can be used.
790 # CGIscriptor reads text files from the requested input file (i.e., from
791 # $YOUR_HTML_FILES$PATH_INFO) and writes them to <STDOUT> (i.e., the
792 # client requesting the service) preceded by the obligatory
793 # "Content-type: text/html\n\n" or "Content-type: text/plain\n\n" string
794 # (except for "raw" files which supply their own Content-type message
795 # and only if the SERVER_PROTOCOL supports HTTP, MAIL, or MIME).
797 # When CGIscriptor encounters an embedded script, indicated by an HTML4 tag
799 # <SCRIPT TYPE="text/ssperl" [CGI="$VAR='default value'"] [SRC="ScriptSource"]>
800 # PERL script
801 # </SCRIPT>
803 # or
805 # <SCRIPT TYPE="text/osshell" [CGI="$name='default value'"] [SRC="ScriptSource"]>
806 # OS Shell script
807 # </SCRIPT>
809 # construct (anything between []-brackets is optional, other MIME-types
810 # and scripting languages are supported), the embedded script is removed
811 # and both the contents of the source file (i.e., "do 'ScriptSource'")
812 # AND the script are evaluated as a PERL program (i.e., by eval()),
813 # shell script (i.e., by a "safe" version of `Command`, qx) or an external
814 # interpreter. The output of the eval() function takes the place of the
815 # original <SCRIPT></SCRIPT> construct in the output string. Any CGI
816 # parameters declared by the CGI attribute are available as simple perl
817 # variables, and can subsequently be made available as variables to other
818 # scripting languages (e.g., bash, python, or lisp).
820 # Example: printing "Hello World"
821 # <HTML><HEAD><TITLE>Hello World</TITLE>
822 # <BODY>
823 # <H1><SCRIPT TYPE="text/ssperl">"Hello World"</SCRIPT></H1>
824 # </BODY></HTML>
826 # Save this in a file, hello.html, in the directory you indicated with
827 # $YOUR_HTML_FILES and access http://your_server/SHTML/hello.html
828 # (or to whatever name you use as an alias for CGIscriptor.pl).
829 # This is realy ALL you need to do to get going.
831 # You can use any values that are delivered in CGI-compliant form (i.e.,
832 # the "?name=value" type URL additions) transparently as "$name" variables
833 # in your scripts IFF you have declared them in the CGI attribute of
834 # a META or SCRIPT tag before e.g.:
835 # <META CONTENT="text/ssperl; CGI='$name = `default value`'
836 # [SRC='ScriptSource']">
837 # or
838 # <SCRIPT TYPE="text/ssperl" CGI="$name = 'default value'"
839 # [SRC='ScriptSource']>
840 # After such a 'CGI' attribute, you can use $name as an ordinary PERL variable
841 # (the ScriptSource file is immediately evaluated with "do 'ScriptSource'").
842 # The CGIscriptor script allows you to write ordinary HTML files which will
843 # include dynamic CGI aware (run time) features, such as on-line answers
844 # to specific CGI requests, queries, or the results of calculations.
846 # For example, if you wanted to answer questions of clients, you could write
847 # a Perl program called "Answer.pl" with a function "AnswerQuestion()"
848 # that prints out the answer to requests given as arguments. You then write
849 # an HTML page "Respond.html" containing the following fragment:
851 # <center>
852 # The Answer to your question
853 # <META CONTENT="text/ssperl; CGI='$Question'">
854 # <h3><SCRIPT TYPE="text/ssperl">$Question</SCRIPT></h3>
855 # is
856 # <h3><SCRIPT TYPE="text/ssperl" SRC="./PATH/Answer.pl">
857 # AnswerQuestion($Question);
858 # </SCRIPT></h3>
859 # </center>
860 # <FORM ACTION=Respond.html METHOD=GET>
861 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
862 # <INPUT TYPE=SUBMIT VALUE="Ask">
863 # </FORM>
865 # The output could look like the following (in HTML-speak):
867 # <CENTER>
868 # The Answer to your question
869 # <h3>What is the capital of the Netherlands?</h3>
870 # is
871 # <h3>Amsterdam</h3>
872 # </CENTER>
873 # <FORM ACTION=Respond.html METHOD=GET>
874 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
875 # <INPUT TYPE=SUBMIT VALUE="Ask">
877 # Note that the function "Answer.pl" does know nothing about CGI or HTML,
878 # it just prints out answers to arguments. Likewise, the text has no
879 # provisions for scripts or CGI like constructs. Also, it is completely
880 # trivial to extend this "program" to use the "Answer" later in the page
881 # to call up other information or pictures/sounds. The final text never
882 # shows any cue as to what the original "source" looked like, i.e.,
883 # where you store your scripts and how they are called.
885 # There are some extra's. The argument of the files called in a SRC= tag
886 # can access the CGI variables declared in the preceding META tag from
887 # the @ARGV array. Executable files are called as:
888 # `file '$ARGV[0]' ... ` (e.g., `Answer.pl \'$Question\'`;)
889 # The files called from SRC can even be (CGIscriptor) html files which are
890 # processed in-line. Furthermore, the SRC= tag can contain a perl block
891 # that is evaluated. That is,
892 # <META CONTENT="text/ssperl; CGI='$Question' SRC='{$Question}'">
893 # will result in the evaluation of "print do {$Question};" and the VALUE
894 # of $Question will be printed. Note that these "SRC-blocks" can be
895 # preceded and followed by other file names, but only a single block is
896 # allowed in a SRC= tag.
898 # One of the major hassles of dynamic WWW pages is the fact that several
899 # mutually incompatible browsers and platforms must be supported. For example,
900 # the way sound is played automatically is different for Netscape and
901 # Internet Explorer, and for each browser it is different again on
902 # Unix, MacOS, and Windows. Realy dangerous is processing user-supplied
903 # (form-) values to construct email addresses, file names, or database
904 # queries. All Apache WWW-server exploits reported in the media are
905 # based on faulty CGI-scripts that didn't check their user-data properly.
907 # There is no panacee for these problems, but a lot of work and problems
908 # can be saved by allowing easy and transparent control over which
909 # <SCRIPT></SCRIPT> blocks are executed on what CGI-data. CGIscriptor
910 # supplies such a method in the form of a pair of attributes:
911 # IF='...condition..' and UNLESS='...condition...'. When added to a
912 # script tag, the whole block (including the SRC attribute) will be
913 # ignored if the condition is false (IF) or true (UNLESS).
914 # For example, the following block will NOT be evaluated if the value
915 # of the CGI variable FILENAME is NOT a valid filename:
917 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
918 # IF='CGIscriptor::CGIsafeFileName($FILENAME)'>
919 # .....
920 # </SCRIPT>
922 # (the function CGIsafeFileName(String) returns an empty string ("")
923 # if the String argument is not a valid filename).
924 # The UNLESS attribute is the mirror image of IF.
926 # A user manual follows the HTML 4 and security paragraphs below.
928 ##########################################################################
930 # HTML 4 compliance
932 # In general, CGIscriptor.pl complies with the HTML 4 recommendations of
933 # the W3C. This means that any software to manage Web sites will be able
934 # to handle CGIscriptor files, as will web agents.
936 # All script code should be placed between <SCRIPT></SCRIPT> tags, the
937 # script type is indicated with TYPE="mime-type", the LANGUAGE
938 # feature is ignored, and a SRC feature is implemented. All CGI specific
939 # features are delegated to the CGI attribute.
941 # However, the behavior deviates from the W3C recommendations at some
942 # points. Most notably:
943 # 0- The scripts are executed at the server side, invissible to the
944 # client (i.e., the browser)
945 # 1- The mime-types are personal and idiosyncratic, but can be adapted.
946 # 2- Code in the body of a <SCRIPT></SCRIPT> tag-pair is still evaluated
947 # when a SRC feature is present.
948 # 3- The SRC attribute reads a list of files.
949 # 4- The files in a SRC attribute are processed according to file type.
950 # 5- The SRC attribute evaluates inline Perl code.
951 # 6- Processed META, DIV, INS tags are removed from the output
952 # document.
953 # 7- All attributes of the processed META tags, except CONTENT, are ignored
954 # (i.e., deleted from the output).
955 # 8- META tags can be placed ANYWHERE in the document.
956 # 9- Through the SRC feature, META tags can have visible output in the
957 # document.
958 # 10- The CGI attribute that declares CGI parameters, can be used
959 # inside the <SCRIPT> tag.
960 # 11- Use of an extended quote set, i.e., '', "", ``, (), {}, []
961 # and their \-slashed combinations: \'\', \"\", \`\`, \(\),
962 # \{\}, \[\].
963 # 12- IF and UNLESS attributes to <SCRIPT>, <META>, <DIV>, <INS> tags.
964 # 13- <DIV> tags cannot be nested, DIV tags are not
965 # rendered with new-lines.
966 # 14- The XML style <TAG .... /> is recognized and handled correctly.
967 # (i.e., no content is processed)
969 # The reasons for these choices are:
970 # You can still write completely HTML4 compliant documents. CGIscriptor
971 # will not force you to write "deviant" code. However, it allows you to
972 # do so (which is, in fact, just as bad). The prime design principle
973 # was to allow users to include plain Perl code. The code itself should
974 # be "enhancement free". Therefore, extra features were needed to
975 # supply easy access to CGI and Web site components. For security
976 # reasons these have to be declared explicitly. The SRC feature
977 # transparently manages access to external files, especially the safe
978 # use of executable files.
979 # The CGI attribute handles the declarations of external (CGI) variables
980 # in the SCRIPT and META tag's.
981 # EVERYTHING THE CGI ATTRIBUTE AND THE META TAG DO CAN BE DONE INSIDE
982 # A <SCRIPT></SCRIPT> TAG CONSTRUCT.
984 # The reason for the IF, UNLESS, and SRC attributes (and their Perl code
985 # evaluation) were build into the META and SCRIPT tags is part laziness,
986 # part security. The SRC blocks allows more compact documents and easier
987 # debugging. The values of the CGI variables can be immediately screened
988 # for security by IF or UNLESS conditions, and even SRC attributes (e.g.,
989 # email addresses and file names), and a few commands can be called
990 # without having to add another Perl TAG pair. This is especially important
991 # for documents that require the use of other (more restricted) "scripting"
992 # languages and facilities that lag transparent control structures.
994 ##########################################################################
996 # SECURITY
998 # Your WWW site is a few keystrokes away from a few hundred million internet
999 # users. A fair percentage of these users knows more about your computer
1000 # than you do. And some of these just might have bad intentions.
1002 # To ensure uncompromized operation of your server and platform, several
1003 # features are incorporated in CGIscriptor.pl to enhance security.
1004 # First of all, you should check the source of this program. No security
1005 # measures will help you when you download programs from anonymous sources.
1006 # If you want to use THIS file, please make sure that it is uncompromized.
1007 # The best way to do this is to contact the source and try to determine
1008 # whether s/he is reliable (and accountable).
1010 # BE AWARE THAT ANY PROGRAMMER CAN CHANGE THIS PROGRAM IN SUCH A WAY THAT
1011 # IT WILL SET THE DOORS TO YOUR SYSTEM WIDE OPEN
1013 # I would like to ask any user who finds bugs that could compromise
1014 # security to report them to me (and any other bug too,
1015 # Email: R.J.J.H.vanSon@gmail.com or ifa@hum.uva.nl).
1017 # Security features
1019 # 1 Invisibility
1020 # The inner workings of the HTML source files are completely hidden
1021 # from the client. Only the HTTP header and the ever changing content
1022 # of the output distinguish it from the output of a plain, fixed HTML
1023 # file. Names, structures, and arguments of the "embedded" scripts
1024 # are invisible to the client. Error output is suppressed except
1025 # during debugging (user configurable).
1027 # 2 Separate directory trees
1028 # Directories containing Inline text and script files can reside on
1029 # separate trees, distinct from those of the HTTP server. This means
1030 # that NEITHER the text files, NOR the script files can be read by
1031 # clients other than through CGIscriptor.pl, UNLESS they are
1032 # EXPLICITELY made available.
1034 # 3 Requests are NEVER "evaluated"
1035 # All client supplied values are used as literal values (''-quoted).
1036 # Client supplied ''-quotes are ALWAYS removed. Therefore, as long as the
1037 # embedded scripts do NOT themselves evaluate these values, clients CANNOT
1038 # supply executable commands. Be sure to AVOID scripts like:
1040 # <META CONTENT="text/ssperl; CGI='$UserValue'">
1041 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 $UserValue`;</SCRIPT>
1043 # These are a recipe for disaster. However, the following quoted
1044 # form should be save (but is still not adviced):
1046 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 \'$UserValue\'`;</SCRIPT>
1048 # A special function, SAFEqx(), will automatically do exactly this,
1049 # e.g., SAFEqx('ls -1 $UserValue') will execute `ls -1 \'$UserValue\'`
1050 # with $UserValue interpolated. I recommend to use SAFEqx() instead
1051 # of backticks whenever you can. The OS shell scripts inside
1053 # <SCRIPT TYPE="text/osshell">ls -1 $UserValue</SCRIPT>
1055 # are handeld by SAFEqx and automatically ''-quoted.
1057 # 4 Logging of requests
1058 # All requests can be logged separate from the Host server. The level of
1059 # detail is user configurable: Including or excluding the actual queries.
1060 # This allows for the inspection of (im-) proper use.
1062 # 5 Access control: Clients
1063 # The Remote addresses can be checked against a list of authorized
1064 # (i.e., accepted) or non-authorized (i.e., rejected) clients. Both
1065 # REMOTE_HOST and REMOTE_ADDR are tested so clients without a proper
1066 # HOST name can be (in-) excluded by their IP-address. Client patterns
1067 # containing all numbers and dots are considered IP-addresses, all others
1068 # domain names. No wild-cards or regexp's are allowed, only partial
1069 # addresses.
1070 # Matching of names is done from the back to the front (domain first,
1071 # i.e., $REMOTE_HOST =~ /\Q$pattern\E$/is), so including ".edu" will
1072 # accept or reject all clients from the domain EDU. Matching of
1073 # IP-addresses is done from the front to the back (domain first, i.e.,
1074 # $REMOTE_ADDR =~ /^\Q$pattern\E/is), so including "128." will (in-)
1075 # exclude all clients whose IP-address starts with 128.
1076 # There are two special symbols: "-" matches HOSTs with no name and "*"
1077 # matches ALL HOSTS/clients.
1078 # For those needing more expressional power, lines starting with
1079 # "-e" are evaluated by the perl eval() function. E.g.,
1080 # '-e $REMOTE_HOST =~ /\.edu$/is;' will accept/reject clients from the
1081 # domain '.edu'.
1083 # 6 Access control: Files
1084 # In principle, CGIscriptor could read ANY file in the directory
1085 # tree as discussed in 1. However, for security reasons this is
1086 # restricted to text files. It can be made more restricted by entering
1087 # a global file pattern (e.g., ".html"). This is done by default.
1088 # For each client requesting access, the file pattern(s) can be made
1089 # more restrictive than the global pattern by entering client specific
1090 # file patterns in the Access Control files (see 5).
1091 # For example: if the ACCEPT file contained the lines
1092 # * DEMO
1093 # .hum.uva.nl LET
1094 # 145.18.230.
1095 # Then all clients could request paths containing "DEMO" or "demo", e.g.
1096 # "/my/demo/file.html" ($PATH_INFO =~ /\Q$pattern\E/), Clients from
1097 # *.hum.uva.nl could also request paths containing "LET or "let", e.g.
1098 # "/my/let/file.html", and clients from the local cluster
1099 # 145.18.230.[0-9]+ could access ALL files.
1100 # Again, for those needing more expressional power, lines starting with
1101 # "-e" are evaluated. For instance:
1102 # '-e $REMOTE_HOST =~ /\.edu$/is && $PATH_INFO =~ m@/DEMO/@is;'
1103 # will accept/reject requests for files from the directory "/demo/" from
1104 # clients from the domain '.edu'.
1106 # 7 Access control: Server side session tickets
1107 # Specific paths can be controlled by Session Tickets which must be
1108 # present as a SESSIONTICKET=<value> CGI variable in the request. These paths
1109 # are defined in %TicketRequiredPatterns as pairs of:
1110 # ('regexp' => 'SessionPath\tPasswordPath\tLogin.html\tExpiration').
1111 # Session Tickets are stored in a separate directory (SessionPath, e.g.,
1112 # "Private/.Session") as files with the exact same name of the SESSIONTICKET
1113 # CGI. The following is an example:
1114 # Type: SESSION
1115 # IPaddress: 127.0.0.1
1116 # AllowedPaths: ^/Private/Name/
1117 # Expires: 3600
1118 # Username: test
1119 # ...
1120 # Other content can follow.
1122 # It is adviced that Session Tickets should be deleted
1123 # after some (idle) time. The IP address should be the IP number at login, and
1124 # the SESSIONTICKET will be rejected if it is presented from another IP address.
1125 # AllowedPaths and DeniedPaths are perl regexps. Be careful how they match. Make sure to delimit
1126 # the names to prevent access to overlapping names, eg, "^/Private/Rob" will also
1127 # match "^/Private/Robert", however, "^/Private/Rob/" will not. Expires is the
1128 # time the ticket will remain valid after creation (file ctime). Time can be given
1129 # in s[econds] (default), m[inutes], h[hours], or d[ays], eg, "24h" means 24 hours.
1130 # None of these need be present, but the Ticket must have a non-zero size.
1132 # Next to Session Tickets, there are two other type of ticket files:
1133 # - LOGIN tickets store information about a current login request
1134 # - PASSWORD ticket store account information to authorize login requests
1136 # 8 Query length limiting
1137 # The length of the Query string can be limited. If CONTENT_LENGTH is larger
1138 # than this limit, the request is rejected. The combined length of the
1139 # Query string and the POST input is checked before any processing is done.
1140 # This will prevent clients from overloading the scripts.
1141 # The actual, combined, Query Size is accessible as a variable through
1142 # $CGI_Content_Length.
1144 # 9 Illegal filenames, paths, and protected directories
1145 # One of the primary security concerns in handling CGI-scripts is the
1146 # use of "funny" characters in the requests that con scripts in executing
1147 # malicious commands. Examples are inserting ';', null bytes, or <newline>
1148 # characters in URL's and filenames, followed by executable commands. A
1149 # special variable $FileAllowedChars stores a string of all allowed
1150 # characters. Any request that translates to a filename with a character
1151 # OUTSIDE this set will be rejected.
1152 # In general, all (readable files) in the DocumentRoot tree are accessible.
1153 # This might not be what you want. For instance, your DocumentRoot directory
1154 # might be the working directory of a CVS project and contain sensitive
1155 # information (e.g., the password to get to the repository). You can block
1156 # access to these subdirectories by adding the corresponding patterns to
1157 # the $BlockPathAccess variable. For instance, $BlockPathAccess = '/CVS/'
1158 # will block any request that contains '/CVS/' or:
1159 # die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;
1161 #10 The execution of code blocks can be controlled in a transparent way
1162 # by adding IF or UNLESS conditions in the tags themselves. That is,
1163 # a simple check of the validity of filenames or email addresses can
1164 # be done before any code is executed.
1166 ###############################################################################
1168 # USER MANUAL (sort of)
1170 # CGIscriptor removes embedded scripts, indicated by an HTML 4 type
1171 # <SCRIPT TYPE='text/ssperl'> </SCRIPT> or <SCRIPT TYPE='text/osshell'>
1172 # </SCRIPT> constructs. CGIscriptor also recognizes XML-type
1173 # <SCRIPT TYPE='text/ssperl'/> constructs. These are usefull when
1174 # the necessary code is already available in the TAG itself (e.g.,
1175 # using external files). The contents of the directive are executed by
1176 # the PERL eval() and `` functions (in a separate name space). The
1177 # result of the eval() function replaces the <SCRIPT> </SCRIPT> construct
1178 # in the output file. You can use the values that are delivered in
1179 # CGI-compliant form (i.e., the "?name=value&.." type URL additions)
1180 # transparently as "$name" variables in your directives after they are
1181 # defined in a <META> or <SCRIPT> tag.
1182 # If you define the variable "$CGIscriptorResults" in a CGI attribute, all
1183 # subsequent <SCRIPT> and <META> results (including the defining
1184 # tag) will also be pushed onto a stack: @CGIscriptorResults. This list
1185 # behaves like any other, ordinary list and can be manipulated.
1187 # Both GET and POST requests are accepted. These two methods are treated
1188 # equal. Variables, i.e., those values that are determined when a file is
1189 # processed, are indicated in the CGI attribute by $<name> or $<name>=<default>
1190 # in which <name> is the name of the variable and <default> is the value
1191 # used when there is NO current CGI value for <name> (you can use
1192 # white-spaces in $<name>=<default> but really DO make sure that the
1193 # default value is followed by white space or is quoted). Names can contain
1194 # any alphanumeric characters and _ (i.e., names match /[\w]+/).
1195 # If the Content-type: is 'multipart/*', the input is treated as a
1196 # MIME multipart message and automatically delimited. CGI variables get
1197 # the "raw" (i.e., undecoded) body of the corresponding message part.
1199 # Variables can be CGI variables, i.e., those from the QUERY_STRING,
1200 # environment variables, e.g., REMOTE_USER, REMOTE_HOST, or REMOTE_ADDR,
1201 # or predefined values, e.g., CGI_Decoded_QS (The complete, decoded,
1202 # query string), CGI_Content_Length (the length of the decoded query
1203 # string), CGI_Year, CGI_Month, CGI_Time, and CGI_Hour (the current
1204 # date and time).
1206 # All these are available when defined in a CGI attribute. All environment
1207 # variables are accessible as $ENV{'name'}. So, to access the REMOTE_HOST
1208 # and the REMOTE_USER, use, e.g.:
1210 # <SCRIPT TYPE='text/ssperl'>
1211 # ($ENV{'REMOTE_HOST'}||"-")." $ENV{'REMOTE_USER'}"
1212 # </SCRIPT>
1214 # (This will print a "-" if REMOTE_HOST is not known)
1215 # Another way to do this is:
1217 # <META CONTENT="text/ssperl; CGI='$REMOTE_HOST = - $REMOTE_USER'">
1218 # <SCRIPT TYPE='text/ssperl'>"$REMOTE_HOST $REMOTE_USER"</SCRIPT>
1219 # or
1220 # <META CONTENT='text/ssperl; CGI="$REMOTE_HOST = - $REMOTE_USER"
1221 # SRC={"$REMOTE_HOST $REMOTE_USER\n"}'>
1223 # This is possible because ALL environment variables are available as
1224 # CGI variables. The environment variables take precedence over CGI
1225 # names in case of a "name clash". For instance:
1226 # <META CONTENT="text/ssperl; CGI='$HOME' SRC={$HOME}">
1227 # Will print the current HOME directory (environment) irrespective whether
1228 # there is a CGI variable from the query
1229 # (e.g., Where do you live? <INPUT TYPE="TEXT" NAME="HOME">)
1230 # THIS IS A SECURITY FEATURE. It prevents clients from changing
1231 # the values of defined environment variables (e.g., by supplying
1232 # a bogus $REMOTE_ADDR). Although $ENV{} is not changed by the META tags,
1233 # it would make the use of declared variables insecure. You can still
1234 # access CGI variables after a name clash with
1235 # CGIscriptor::CGIparseValue(<name>).
1237 # Some CGI variables are present several times in the query string
1238 # (e.g., from multiple selections). These should be defined as
1239 # @VARIABLENAME=default in the CGI attribute. The list @VARIABLENAME
1240 # will contain ALL VARIABLENAME values from the query, or a single
1241 # default value. If there is an ENVIRONMENT variable of the
1242 # same name, it will be used instead of the default AND the query
1243 # values. The corresponding function is
1244 # CGIscriptor::CGIparseValueList(<name>)
1246 # CGI variables collected in a @VARIABLENAME list are unordered.
1247 # When more structured variables are needed, a hash table can be used.
1248 # A variable defined as %VARIABLE=default will collect all
1249 # CGI-parameters whose name start with 'VARIABLE' in a hash table with
1250 # the remainder of the name as a key. For instance, %PERSON will
1251 # collect PERSONname='John Doe', PERSONbirthdate='01 Jan 00', and
1252 # PERSONspouse='Alice' into a hash table %PERSON such that $PERSON{'spouse'}
1253 # equals 'Alice'. Any default value or environment value will be stored
1254 # under the "" key. If there is an ENVIRONMENT variable of the same name,
1255 # it will be used instead of the default AND the query values. The
1256 # corresponding function is CGIscriptor::CGIparseValueHash(<name>)
1258 # This method of first declaring your environment and CGI variables
1259 # before being able to use them in the scripts might seem somewhat
1260 # clumsy, but it protects you from inadvertedly printing out the values of
1261 # system environment variables when their names coincide with those used
1262 # in the CGI forms. It also prevents "clients" from supplying CGI
1263 # parameter values for your private variables.
1264 # THIS IS A SECURITY FEATURE!
1267 # NON-HTML CONTENT TYPES
1269 # Normally, CGIscriptor prints the standard "Content-type: text/html\n\n"
1270 # message before anything is printed. This has been extended to include
1271 # plain text (.txt) files, for which the Content-type (MIME type)
1272 # 'text/plain' is printed. In all other respects, text files are treated
1273 # as HTML files (this can be switched off by removing '.txt' from the
1274 # $FilePattern variable) . When the content type should be something else,
1275 # e.g., with multipart files, use the $RawFilePattern (.xmr, see also next
1276 # item). CGIscriptor will not print a Content-type message for this file
1277 # type (which must supply its OWN Content-type message). Raw files must
1278 # still conform to the <SCRIPT></SCRIPT> and <META> tag specifications.
1281 # NON-HTML FILES
1283 # CGIscriptor is intended to process HTML and text files only. You can
1284 # create documents of any mime-type on-the-fly using "raw" text files,
1285 # e.g., with the .xmr extension. However, CGIscriptor will not process
1286 # binary files of any type, e.g., pictures or sounds. Given the sheer
1287 # number of formats, I do not have any intention to do so. However,
1288 # an escape route has been provided. You can construct a genuine raw
1289 # (.xmr) text file that contains the perl code to service any file type
1290 # you want. If the global $BinaryMapFile variable contains the path to
1291 # this file (e.g., /BinaryMapFile.xmr), this file will be called
1292 # whenever an unsupported (non-HTML) file type is requested. The path
1293 # to the requested binary file is stored in $ENV('CGI_BINARY_FILE')
1294 # and can be used like any other CGI-variable. Servicing binary files
1295 # then becomes supplying the correct Content-type (e.g., print
1296 # "Content-type: image/jpeg\n\n";) and reading the file and writing it
1297 # to STDOUT (e.g., using sysread() and syswrite()).
1300 # THE META TAG
1302 # All attributes of a META tag are ignored, except the
1303 # CONTENT='text/ssperl; CGI=" ... " [SRC=" ... "]' attribute. The string
1304 # inside the quotes following the CONTENT= indication (white-space is
1305 # ignored, "" '' `` (){}[]-quote pairs are allowed, plus their \ versions)
1306 # MUST start with any of the CGIscriptor mime-types (e.g.: text/ssperl or
1307 # text/osshell) and a comma or semicolon.
1308 # The quoted string following CGI= contains a white-space separated list
1309 # of declarations of the CGI (and Environment) values and default values
1310 # used when no CGI values are supplied by the query string.
1312 # If the default value is a longer string containing special characters,
1313 # possibly spanning several lines, the string must be enclosed in quotes.
1314 # You may use any pair of quotes or brackets from the list '', "", ``, (),
1315 # [], or {} to distinguish default values (or preceded by \, e.g., \(...\)
1316 # is different from (...)). The outermost pair will always be used and any
1317 # other quotes inside the string are considered to be part of the string
1318 # value, e.g.,
1320 # $Value = {['this'
1321 # "and" (this)]}
1322 # will result in $Value getting the default value: ['this'
1323 # "and" (this)]
1324 # (NOTE that the newline is part of the default value!).
1326 # Internally, for defining and initializing CGI (ENV) values, the META
1327 # and SCRIPT tags use the functions "defineCGIvariable($name, $default)"
1328 # (scalars) and "defineCGIvariableList($name, $default)" (lists).
1329 # These functions can be used inside scripts as
1330 # "CGIscriptor::defineCGIvariable($name, $default)" and
1331 # "CGIscriptor::defineCGIvariableList($name, $default)".
1332 # "CGIscriptor::defineCGIvariableHash($name, $default)".
1334 # The CGI attribute will be processed exactly identical when used inside
1335 # the <SCRIPT> tag. However, this use is not according to the
1336 # HTML 4.0 specifications of the W3C.
1339 # THE DIV/INS TAGS
1341 # There is a problem when constructing html files containing
1342 # server-side perl scripts with standard HTML tools. These
1343 # tools will refuse to process any text between <SCRIPT></SCRIPT>
1344 # tags. This is quite annoying when you want to use large
1345 # HTML templates where you will fill in values.
1347 # For this purpose, CGIscriptor will read the neutral
1348 # <DIV CLASS="ssperl" ID="varname"></DIV> or
1349 # <INS CLASS="ssperl" ID="varname"></INS>
1350 # tag (in Cascading Style Sheet manner) Note that
1351 # "varname" has NO '$' before it, it is a bare name.
1352 # Any text between these <DIV ...></DIV> or
1353 # <INS ...></INS>tags will be assigned to '$varname'
1354 # as is (e.g., as a literal).
1355 # No processing or interpolation will be performed.
1356 # There is also NO nesting possible. Do NOT nest a
1357 # </DIV> inside a <DIV></DIV>! Moreover, neither INS nor
1358 # DIV tags do ensure a block structure in the final
1359 # rendering (i.e., no empty lines).
1361 # Note that <DIV CLASS="ssperl" ID="varname"/>
1362 # is handled the XML way. No content is processed,
1363 # but varname is defined, and any SRC directives are
1364 # processed.
1366 # You can use $varname like any other variable name.
1367 # However, $varname is NOT a CGI variable and will be
1368 # completely internal to your script. There is NO
1369 # interaction between $varname and the outside world.
1371 # To interpolate a DIV derived text, you can use:
1372 # $varname =~ s/([\]])/\\\1/g; # Mark ']'-quotes
1373 # $varname = eval("qq[$varname]"); # Interpolate all values
1375 # The DIV tags will process IF, UNLESS, CGI and
1376 # SRC attributes. The SRC files will be pre-pended to the
1377 # body text of the tag. SRC blocks are NOT executed.
1379 # CONDITIONAL PROCESSING: THE 'IF' AND 'UNLESS' ATTRIBUTES
1381 # It is often necessary to include code-blocks that should be executed
1382 # conditionally, e.g., only for certain browsers or operating system.
1383 # Furthermore, quite often sanity and security checks are necessary
1384 # before user (form) data can be processed, e.g., with respect to
1385 # email addresses and filenames.
1387 # Checks added to the code are often difficult to find, interpret or
1388 # maintain and in general mess up the code flow. This kind of confussion
1389 # is dangerous.
1390 # Also, for many of the supported "foreign" scripting languages, adding
1391 # these checks is cumbersome or even impossible.
1393 # As a uniform method for asserting the correctness of "context", two
1394 # attributes are added to all supported tags: IF and UNLESS.
1395 # They both evaluate their value and block execution when the
1396 # result is <FALSE> (IF) or <TRUE> (UNLESS) in Perl, e.g.,
1397 # UNLESS='$NUMBER \> 100;' blocks execution if $NUMBER <= 100. Note that
1398 # the backslash in the '\>' is removed and only used to differentiate
1399 # this conditional '>' from the tag-closing '>'. For symmetry, the
1400 # backslash in '\<' is also removed. Inside these conditionals,
1401 # ~/ and ./ are expanded to their respective directory root paths.
1403 # For example, the following tag will be ignored when the filename is
1404 # invalid:
1406 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
1407 # IF='CGIscriptor::CGIsafeFileName($FILENAME);'>
1408 # ...
1409 # </SCRIPT>
1411 # The IF and UNLESS values must be quoted. The same quotes are supported
1412 # as with the other attributes. The SRC attribute is ignored when IF and
1413 # UNLESS block execution.
1415 # NOTE: 'IF' and 'UNLESS' always evaluate perl code.
1418 # THE MAGIC SOURCE ATTRIBUTE (SRC=)
1420 # The SRC attribute inside tags accepts a list of filenames and URL's
1421 # separated by "," comma's (or ";" semicolons).
1422 # ALL the variable values defined in the CGI attribute are available
1423 # in @ARGV as if the file or block was executed from the command line,
1424 # in the exact order in which they were declared in the preceding CGI
1425 # attribute.
1427 # First, a SRC={}-block will be evaluated as if the code inside the
1428 # block was part of a <SCRIPT></SCRIPT> construct, i.e.,
1429 # "print do { code };'';" or `code` (i.e., SAFEqx('code)).
1430 # Only a single block is evaluated. Note that this is processed less
1431 # efficiently than <SCRIPT> </SCRIPT> blocks. Type of evaluation
1432 # depends on the content-type: Perl for text/ssperl and OS shell for
1433 # text/osshell. For other mime types (scripting languages), anything in
1434 # the source block is put in front of the code block "inside" the tag.
1436 # Second, executable files (i.e., -x filename != 0) are evaluated as:
1437 # print `filename \'$ARGV[0]\' \'$ARGV[1]\' ...`
1438 # That is, you can actually call executables savely from the SRC tag.
1440 # Third, text files that match the file pattern, used by CGIscriptor to
1441 # check whether files should be processed ($FilePattern), are
1442 # processed in-line (i.e., recursively) by CGIscriptor as if the code
1443 # was inserted in the original source file. Recursions, i.e., calling
1444 # a file inside itself, are blocked. If you need them, you have to code
1445 # them explicitely using "main::ProcessFile($file_path)".
1447 # Fourth, Perl text files (i.e., -T filename != 0) are evaluated as:
1448 # "do FileName;'';".
1450 # Last, URL's (i.e., starting with 'HTTP://', 'FTP://', 'GOPHER://',
1451 # 'TELNET://', 'WHOIS://' etc.) are loaded
1452 # and printed. The loading and handling of <BASE> and document header
1453 # is done by a command generated by main::GET_URL($URL [, 0]). You can enter your
1454 # own code (default is curl, wget, or snarf and some post-processing to add a <BASE> tag).
1456 # There are two pseudo-file names: PREFIX and POSTFIX. These implement
1457 # a switch from prefixing the SRC code/files (PREFIX, default) before the
1458 # content of the tag to appending the code after the content of the tag
1459 # (POSTFIX). The switches are done in the order in which the PREFIX and
1460 # POSTFIX labels are encountered. You can mix PREFIX and POSTFIX labels
1461 # in any order with the SRC files. Note that the ORDER of file execution
1462 # is determined for prefixed and postfixed files seperately.
1464 # File paths can be preceded by the URL protocol prefix "file://". This
1465 # is simply STRIPPED from the name.
1467 # Example:
1468 # The request
1469 # "http://cgi-bin/Action_Forms.pl/Statistics/Sign_Test.html?positive=8&negative=22
1470 # will result in printing "${SS_PUB}/Statistics/Sign_Test.html"
1471 # With QUERY_STRING = "positive=8&negative=22"
1473 # on encountering the lines:
1474 # <META CONTENT="text/osshell; CGI='$positive=11 $negative=3'">
1475 # <b><SCRIPT LANGUAGE=PERL TYPE="text/ssperl" SRC="./Statistics/SignTest.pl">
1476 # </SCRIPT></b><p>"
1478 # This line will be processed as:
1479 # "<b>`${SS_SCRIPT}/Statistics/SignTest.pl '8' '22'`</b><p>"
1481 # In which "${SS_SCRIPT}/Statistics/SignTest.pl" is an executable script,
1482 # This line will end up printed as:
1483 # "<b>p <= 0.0161</b><p>"
1485 # Note that the META tag itself will never be printed, and is invisible to
1486 # the outside world.
1488 # The SRC files in a DIV or INS tag will be added (pre-pended) to the body
1489 # of the <DIV></DIV> tag. Blocks are NOT executed! If you do not
1490 # need any content, you can use the <DIV...../> format.
1493 # THE CGISCRIPTOR ROOT DIRECTORIES ~/ AND ./
1495 # Inside <SCRIPT></SCRIPT> tags, filepaths starting
1496 # with "~/" are replaced by "$YOUR_HTML_FILES/", this way files in the
1497 # public directories can be accessed without direct reference to the
1498 # actual paths. Filepaths starting with "./" are replaced by
1499 # "$YOUR_SCRIPTS/" and this should only be used for scripts.
1501 # Note: this replacement can seriously affect Perl scripts. Watch
1502 # out for constructs like $a =~ s/aap\./noot./g, use
1503 # $a =~ s@aap\.@noot.@g instead.
1505 # CGIscriptor.pl will assign the values of $SS_PUB and $SS_SCRIPT
1506 # (i.e., $YOUR_HTML_FILES and $YOUR_SCRIPTS) to the environment variables
1507 # $SS_PUB and $SS_SCRIPT. These can be accessed by the scripts that are
1508 # executed.
1509 # Values not preceded by $, ~/, or ./ are used as literals
1512 # OS SHELL SCRIPT EVALUATION (CONTENT-TYPE=TEXT/OSSHELL)
1514 # OS scripts are executed by a "safe" version of the `` operator (i.e.,
1515 # SAFEqx(), see also below) and any output is printed. CGIscriptor will
1516 # interpolate the script and replace all user-supplied CGI-variables by
1517 # their ''-quoted values (actually, all variables defined in CGI attributes
1518 # are quoted). Other Perl variables are interpolated in a simple fasion,
1519 # i.e., $scalar by their value, @list by join(' ', @list), and %hash by
1520 # their name=value pairs. Complex references, e.g., @$variable, are all
1521 # evaluated in a scalar context. Quotes should be used with care.
1522 # NOTE: the results of the shell script evaluation will appear in the
1523 # @CGIscriptorResults stack just as any other result.
1524 # All occurrences of $@% that should NOT be interpolated must be
1525 # preceeded by a "\". Interpolation can be switched off completely by
1526 # setting $CGIscriptor::NoShellScriptInterpolation = 1
1527 # (set to 0 or undef to switch interpolation on again)
1528 # i.e.,
1529 # <SCRIPT TYPE="text/ssperl">
1530 # $CGIscriptor::NoShellScriptInterpolation = 1;
1531 # </SCRIPT>
1534 # RUN TIME TRANSLATION OF INPUT FILES
1536 # Allows general and global conversions of files using Regular Expressions.
1537 # Very handy (but costly) to rewrite legacy pages to a new format.
1538 # Select files to use it on with
1539 # my $TranslationPaths = 'filepattern';
1540 # This is costly. For efficiency, define:
1541 # $TranslationPaths = ''; when not using translations.
1542 # Accepts general regular expressions: [$pattern, $replacement]
1544 # Define:
1545 # my $TranslationPaths = 'filepattern'; # Pattern matching PATH_INFO
1547 # push(@TranslationTable, ['pattern', 'replacement']);
1548 # e.g. (for Ruby Rails):
1549 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
1550 # push(@TranslationTable, ['%>', '</SCRIPT>']);
1552 # Runs:
1553 # my $currentRegExp;
1554 # foreach $currentRegExp (@TranslationTable)
1556 # my ($pattern, $replacement) = @$currentRegExp;
1557 # $$text =~ s!$pattern!$replacement!msg;
1558 # };
1561 # EVALUATION OF OTHER SCRIPTING LANGUAGES
1563 # Adding a MIME-type and an interpreter command to
1564 # %ScriptingLanguages automatically will catch any other
1565 # scripting language in the standard
1566 # <SCRIPT TYPE="[mime]"></SCRIPT> manner.
1567 # E.g., adding: $ScriptingLanguages{'text/sspython'} = 'python';
1568 # will actually execute the folowing code in an HTML page
1569 # (ignore 'REMOTE_HOST' for the moment):
1570 # <SCRIPT TYPE="text/sspython">
1571 # # A Python script
1572 # x = ["A","real","python","script","Hello","World","and", REMOTE_HOST]
1573 # print x[4:8] # Prints the list ["Hello","World","and", REMOTE_HOST]
1574 # </SCRIPT>
1576 # The script code is NOT interpolated by perl, EXCEPT for those
1577 # interpreters that cannot handle variables themselves.
1578 # Currently, several interpreters are pre-installed:
1580 # Perl test - "text/testperl" => 'perl',
1581 # Python - "text/sspython" => 'python',
1582 # Ruby - "text/ssruby" => 'ruby',
1583 # Tcl - "text/sstcl" => 'tcl',
1584 # Awk - "text/ssawk" => 'awk -f-',
1585 # Gnu Lisp - "text/sslisp" => 'rep | tail +5 '.
1586 # "| egrep -v '> |^rep. |^nil\\\$'",
1587 # XLispstat - "text/xlispstat" => 'xlispstat | tail +7 '.
1588 # "| egrep -v '> \\\$|^NIL'",
1589 # Gnu Prolog- "text/ssprolog" => 'gprolog',
1590 # M4 macro's- "text/ssm4" => 'm4',
1591 # Born shell- "text/sh" => 'sh',
1592 # Bash - "text/bash" => 'bash',
1593 # C-shell - "text/csh" => 'csh',
1594 # Korn shell- "text/ksh" => 'ksh',
1595 # Praat - "text/sspraat" => "praat - | sed 's/Praat > //g'",
1596 # R - "text/ssr" => "R --vanilla --slave | sed 's/^[\[0-9\]*] //g'",
1597 # REBOL - "text/ssrebol" =>
1598 # "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\s*\[> \]* //g'",
1599 # PostgreSQL- "text/postgresql" => 'psql 2>/dev/null',
1600 # (psql)
1602 # Note that the "value" of $ScriptingLanguages{mime} must be a command
1603 # that reads Standard Input and writes to standard output. Any extra
1604 # output of interactive interpreters (banners, echo's, prompts)
1605 # should be removed by piping the output through 'tail', 'grep',
1606 # 'sed', or even 'awk' or 'perl'.
1608 # For access to CGI variables there is a special hashtable:
1609 # %ScriptingCGIvariables.
1610 # CGI variables can be accessed in three ways.
1611 # 1. If the mime type is not present in %ScriptingCGIvariables,
1612 # nothing is done and the script itself should parse the relevant
1613 # environment variables.
1614 # 2. If the mime type IS present in %ScriptingCGIvariables, but it's
1615 # value is empty, e.g., $ScriptingCGIvariables{"text/sspraat"} = '';,
1616 # the script text is interpolated by perl. That is, all $var, @array,
1617 # %hash, and \-slashes are replaced by their respective values.
1618 # 3. In all other cases, the CGI and environment variables are added
1619 # in front of the script according to the format stored in
1620 # %ScriptingCGIvariables. That is, the following (pseudo-)code is
1621 # executed for each CGI- or Environment variable defined in the CGI-tag:
1622 # printf(INTERPRETER, $ScriptingCGIvariables{$mime}, $CGI_NAME, $CGI_VALUE);
1624 # For instance, "text/testperl" => '$%s = "%s";' defines variable
1625 # definitions for Perl, and "text/sspython" => '%s = "%s"' for Python
1626 # (note that these definitions are not save, the real ones contain '-quotes).
1628 # THIS WILL NOT WORK FOR @VARIABLES, the (empty) $VARIABLES will be used
1629 # instead.
1631 # The $CGI_VALUE parameters are "shrubed" of all control characters
1632 # and quotes (by &shrubCGIparameter($CGI_VALUE)) for the options 2 and 3.
1633 # Control characters are replaced by \0<octal ascii value> (the exception
1634 # is \015, the newline, which is replaced by \n) and quotes
1635 # and backslashes by their HTML character
1636 # value (' -> &#39; ` -> &#96; " -> &quot; \ -> &#92; & -> &amper;).
1637 # For example:
1638 # if a client would supply the string value (in standard perl, e.g.,
1639 # \n means <newline>)
1640 # "/dev/null';\nrm -rf *;\necho '"
1641 # it would be processed as
1642 # '/dev/null&#39;;\nrm -rf *;\necho &#39;'
1643 # (e.g., sh or bash would process the latter more according to your
1644 # intentions).
1645 # If your intepreter requires different protection measures, you will
1646 # have to supply these in %main::SHRUBcharacterTR (string => translation),
1647 # e.g., $SHRUBcharacterTR{"\'"} = "&#39;";
1649 # Currently, the following definitions are used:
1650 # %ScriptingCGIvariables = (
1651 # "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value' (for testing)
1652 # "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
1653 # "text/ssruby" => '@%s = "%s"', # Ruby @VAR = "value"
1654 # "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
1655 # "text/ssawk" => '%s = "%s";', # Awk VAR = "value";
1656 # "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
1657 # "text/xlispstat" => '(setq %s "%s")', # Xlispstat (setq VAR "value")
1658 # "text/ssprolog" => '', # Gnu prolog (interpolated)
1659 # "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
1660 # "text/sh" => "\%s='\%s';", # Born shell VAR='value';
1661 # "text/bash" => "\%s='\%s';", # Born again shell VAR='value';
1662 # "text/csh" => "\$\%s = '\%s';", # C shell $VAR = 'value';
1663 # "text/ksh" => "\$\%s = '\%s';", # Korn shell $VAR = 'value';
1664 # "text/sspraat" => '', # Praat (interpolation)
1665 # "text/ssr" => '%s <- "%s";', # R VAR <- "value";
1666 # "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
1667 # "text/postgresql" => '', # PostgreSQL (interpolation)
1668 # "" => ""
1669 # );
1671 # Four tables allow fine-tuning of interpreter with code that should be
1672 # added before and after each code block:
1674 # Code added before each script block
1675 # %ScriptingPrefix = (
1676 # "text/testperl" => "\# Prefix Code;", # Perl script testing
1677 # "text/ssm4" => 'divert(0)' # M4 macro's (open STDOUT)
1678 # );
1679 # Code added at the end of each script block
1680 # %ScriptingPostfix = (
1681 # "text/testperl" => "\# Postfix Code;", # Perl script testing
1682 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1683 # );
1684 # Initialization code, inserted directly after opening (NEVER interpolated)
1685 # %ScriptingInitialization = (
1686 # "text/testperl" => "\# Initialization Code;", # Perl script testing
1687 # "text/ssawk" => 'BEGIN {', # Server Side awk scripts
1688 # "text/sslisp" => '(prog1 nil ', # Lisp (rep)
1689 # "text/xlispstat" => '(prog1 nil ', # xlispstat
1690 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1691 # );
1692 # Cleanup code, inserted before closing (NEVER interpolated)
1693 # %ScriptingCleanup = (
1694 # "text/testperl" => "\# Cleanup Code;", # Perl script testing
1695 # "text/sspraat" => 'Quit',
1696 # "text/ssawk" => '};', # Server Side awk scripts
1697 # "text/sslisp" => '(princ "\n" standard-output)).' # Closing print to rep
1698 # "text/xlispstat" => '(print "" *standard-output*)).' # Closing print to xlispstat
1699 # "text/postgresql" => '\q',
1700 # );
1703 # The SRC attribute is NOT magical for these interpreters. In short,
1704 # all code inside a source file or {} block is written verbattim
1705 # to the interpreter. No (pre-)processing or executional magic is done.
1707 # A serious shortcomming of the described mechanism for handling other
1708 # (scripting) languages, with respect to standard perl scripts
1709 # (i.e., 'text/ssperl'), is that the code is only executed when
1710 # the pipe to the interpreter is closed. So the pipe has to be
1711 # closed at the end of each block. This means that the state of the
1712 # interpreter (e.g., all variable values) is lost after the closing of
1713 # the next </SCRIPT> tag. The standard 'text/ssperl' scripts retain
1714 # all values and definitions.
1716 # APPLICATION MIME TYPES
1718 # To ease some important auxilliary functions from within the
1719 # html pages I have added them as MIME types. This uses
1720 # the mechanism that is also used for the evaluation of
1721 # other scripting languages, with interpolation of CGI
1722 # parameters (and perl-variables). Actually, these are
1723 # defined exactly like any other "scripting language".
1725 # text/ssdisplay: display some (HTML) text with interpolated
1726 # variables (uses `cat`).
1727 # text/sslogfile: write (append) the interpolated block to the file
1728 # mentioned on the first, non-empty line
1729 # (the filename can be preceded by 'File: ',
1730 # note the space after the ':',
1731 # uses `awk .... >> <filename>`).
1732 # text/ssmailto: send email directly from within the script block.
1733 # The first line of the body must contain
1734 # To:Name@Valid.Email.Address
1735 # (note: NO space between 'To:' and the email adres)
1736 # For other options see the mailto man pages.
1737 # It works by directly sending the (interpolated)
1738 # content of the text block to a pipe into the
1739 # Linux program 'mailto'.
1741 # In these script blocks, all Perl variables will be
1742 # replaced by their values. All CGI variables are cleaned before
1743 # they are used. These CGI variables must be redefined with a
1744 # CGI attribute to restore their original values.
1745 # In general, this will be more secure than constructing
1746 # e.g., your own email command lines. For instance, Mailto will
1747 # not execute any odd (forged) email addres, but just stops
1748 # when the email address is invalid and awk will construct
1749 # any filename you give it (e.g. '<File;rm\\\040-f' would end up
1750 # as a "valid" UNIX filename). Note that it will also gladly
1751 # store this file anywhere (/../../../etc/passwd will work!).
1752 # Use the CGIscriptor::CGIsafeFileName() function to clean the
1753 # filename.
1755 # SHELL SCRIPT PIPING
1757 # If a shell script starts with the UNIX style "#! <shell command> \n"
1758 # line, the rest of the shell script is piped into the indicated command,
1759 # i.e.,
1760 # open(COMMAND, "| command");print COMMAND $RestOfScript;
1762 # In many ways this is equivalent to the MIME-type profiling for
1763 # evaluating other scripting languages as discussed above. The
1764 # difference breaks down to convenience. Shell script piping is a
1765 # "raw" implementation. It allows you to control all aspects of
1766 # execution. Using the MIME-type profiling is easier, but has a
1767 # lot of defaults built in that might get in the way. Another
1768 # difference is that shell script piping uses the SAFEqx() function,
1769 # and MIME-type profiling does not.
1771 # Execution of shell scripts is under the control of the Perl Script blocks
1772 # in the document. The MIME-type triggered execution of <SCRIPT></SCRIPT>
1773 # blocks can be simulated easily. You can switch to a different shell,
1774 # e.g. tcl, completely by executing the following Perl commands inside
1775 # your document:
1777 # <SCRIPT TYPE="text/ssperl">
1778 # $main::ShellScriptContentType = "text/ssTcl"; # Yes, you can do this
1779 # CGIscriptor::RedirectShellScript('/usr/bin/tcl'); # Pipe to Tcl
1780 # $CGIscriptor::NoShellScriptInterpolation = 1;
1781 # </SCRIPT>
1783 # After this script is executed, CGIscriptor will parse scripts of
1784 # TYPE="text/ssTcl" and pipe their contents into '|/usr/bin/tcl'
1785 # WITHOUT interpolation (i.e., NO substitution of Perl variables).
1786 # The crucial function is :
1787 # CGIscriptor::RedirectShellScript('/usr/bin/tcl')
1788 # After executing this function, all shell scripts AND all
1789 # calls to SAFEqx()) are piped into '|/usr/bin/tcl'. If the argument
1790 # of RedirectShellScript is empty, e.g., '', the original (default)
1791 # value is reset.
1793 # The standard output, STDOUT, of any pipe is send to the client.
1794 # Currently, you should be carefull with quotes in such a piped script.
1795 # The results of a pipe is NOT put on the @CGIscriptorResults stack.
1796 # As a result, you do not have access to the output of any piped (#!)
1797 # process! If you want such access, execute
1798 # <SCRIPT TYPE="text/osshell">echo "script"|command</SCRIPT>
1799 # or
1800 # <SCRIPT TYPE="text/ssperl">
1801 # $resultvar = SAFEqx('echo "script"|command');
1802 # </SCRIPT>.
1804 # Safety is never complete. Although SAFEqx() prevents some of the
1805 # most obvious forms of attacks and security slips, it cannot prevent
1806 # them all. Especially, complex combinations of quotes and intricate
1807 # variable references cannot be handled safely by SAFEqx. So be on
1808 # guard.
1811 # PERL CODE EVALUATION (CONTENT-TYPE=TEXT/SSPERL)
1813 # All PERL scripts are evaluated inside a PERL package. This package
1814 # has a separate name space. This isolated name space protects the
1815 # CGIscriptor.pl program against interference from user code. However,
1816 # some variables, e.g., $_, are global and cannot be protected. You are
1817 # advised NOT to use such global variable names. You CAN write
1818 # directives that directly access the variables in the main program.
1819 # You do so at your own risk (there is definitely enough rope available
1820 # to hang yourself). The behavior of CGIscriptor becomes undefined if
1821 # you change its private variables during run time. The PERL code
1822 # directives are used as in:
1823 # $Result = eval($directive); print $Result;'';
1824 # ($directive contains all text between <SCRIPT></SCRIPT>).
1825 # That is, the <directive> is treated as ''-quoted string and
1826 # the result is treated as a scalar. To prevent the VALUE of the code
1827 # block from appearing on the client's screen, end the directive with
1828 # ';""</SCRIPT>'. Evaluated directives return the last value, just as
1829 # eval(), blocks, and subroutines, but only as a scalar.
1831 # IMPORTANT: All PERL variables defined are persistent. Each <SCRIPT>
1832 # </SCRIPT> construct is evaluated as a {}-block with associated scope
1833 # (e.g., for "my $var;" declarations). This means that values assigned
1834 # to a PERL variable can be used throughout the document unless they
1835 # were declared with "my". The following will actually work as intended
1836 # (note that the ``-quotes in this example are NOT evaluated, but used
1837 # as simple quotes):
1839 # <META CONTENT="text/ssperl; CGI=`$String='abcdefg'`">
1840 # anything ...
1841 # <SCRIPT TYPE=text/ssperl>@List = split('', $String);</SCRIPT>
1842 # anything ...
1843 # <SCRIPT TYPE=text/ssperl>join(", ", @List[1..$#List]);</SCRIPT>
1845 # The first <SCRIPT TYPE=text/ssperl></SCRIPT> construct will return the
1846 # value scalar(@List), the second <SCRIPT TYPE=text/ssperl></SCRIPT>
1847 # construct will print the elements of $String separated by commas, leaving
1848 # out the first element, i.e., $List[0].
1850 # Another warning: './' and '~/' are ALWAYS replaced by the values of
1851 # $YOUR_SCRIPTS and $YOUR_HTML_FILES, respectively . This can interfere
1852 # with pattern matching, e.g., $a =~ s/aap\./noot\./g will result in the
1853 # evaluations of $a =~ s/aap\\${YOUR_SCRIPTS}noot\\${YOUR_SCRIPTS}g. Use
1854 # s@<regexp>.@<replacement>.@g instead.
1857 # SERVER SIDE SESSIONS AND ACCESS CONTROL (LOGIN)
1859 # An infrastructure for user acount authorization and file access control
1860 # is available. Each request is matched against a list of URL path patterns.
1861 # If the request matches, a Session Ticket is required to access the URL.
1862 # This Session Ticket should be present as a CGI parameter or Cookie, eg:
1864 # CGI: SESSIONTICKET=&lt;value&gt;
1865 # Cookie: CGIscriptorSESSION=&lt;value&gt;
1867 # The example implementation stores Session Tickets as files in a local
1868 # directory. To create Session Tickets, a Login request must be given
1869 # with a LOGIN=&lt;value&gt; CGI parameter, a user name and a (doubly hashed)
1870 # password. The user name and (singly hashed) password are stored in a
1871 # PASSWORD ticket with the same name as the user account (name cleaned up
1872 # for security).
1874 # The example session model implements 4 functions:
1875 # - Login
1876 # The password is hashed with the user name and server side salt, and then
1877 # hashed with a random salt. Client and Server both perform these actions
1878 # and the Server only grants access if restults are the same. The server
1879 # side only stores the password hashed with the user name and
1880 # server side salt. Neither the plain password, nor the hashed password is
1881 # ever exchanged. Only values hashed with the one-time salt are exchanged.
1882 # - Session
1883 # For every access to a restricted URL, the Session Ticket is checked before
1884 # access is granted. There are three session modes. The first uses a fixed
1885 # Session Ticket that is stored as a cookie value in the browser (actually,
1886 # as a sessionStorage value). The second uses only the IP address at login
1887 # to authenticate requests. The third
1888 # is a Challenge mode, where the client has to calculate the value of the
1889 # next one-time Session Ticket from a value derived from the password and
1890 # a random string.
1891 # - Password Change
1892 # A new password is hashed with the user name and server side salt, and
1893 # then encrypted (XORed)
1894 # with the old password hashed with the user name and salt. That value is
1895 # exchanged and XORed with the stored old hashed(password+username+salt).
1896 # Again, the stored password value is never exchanged unencrypted.
1897 # - New Account
1898 # The text of a new account (Type: PASSWORD) file is constructed from
1899 # the new username (CGI: NEWUSERNAME, converted to lowercase) and
1900 # hashed new password (CGI: NEWPASSWORD). The same process is used to encrypt
1901 # the new password as is used for the Password Change function.
1902 # Again, the stored password value is never exchanged unencrypted.
1903 # Some default setting are encoded. For display in the browser, the new password
1904 # is reencrypted (XORed) with a special key, the old password hash
1905 # hashed with a session specific random hex value sent initially with the
1906 # session login ticket ($RANDOMSALT).
1907 # For example for user "NewUser" and password "NewPassword" with filename
1908 # "newuser":
1910 # Type: PASSWORD
1911 # Username: newuser
1912 # Password: 19afeadfba8d5dcd252e157fafd3010859f8762b87682b6b6cdb3e565194fa91
1913 # IPaddress: 127\.0\.0\.1
1914 # AllowedPaths: ^/Private/[\w\-]+\.html?
1915 # AllowedPaths: ^/Private/newuser/
1916 # Salt: e93cf858a1d5626bf095ea5c25df990dfa969ff5a5dc908b22c9a5229b525f65
1917 # Session: SESSION
1918 # Date: Fri Jun 29 12:46:22 2012
1919 # Time: 1340973982
1920 # Signature: 676c35d3aa63540293ea5442f12872bfb0a22665b504f58f804582493b6ef04e
1922 # The password is created with the commands:
1924 # printf '%s' 'NewPasswordnewuser970e68017413fb0ea84d7fe3c463077636dd6d53486910d4a53c693dd4109b1a'|shasum -a 256
1926 # If the CPAN mudule Digest is installed, it is used instead of the commands.
1927 # However, the password account files are protected against unauthorized change.
1928 # To obtain a valid Password account, the following command should be given:
1930 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
1931 # masterkey='Sherlock investigates oleander curry in Bath' \
1932 # password='NewPassword' \
1933 # Private/.Passwords/newuser
1936 # Implementation
1938 # The session authentication mechanism is based on the exchange of ticket
1939 # identifiers. A ticket identifier is just a string of characters, a name
1940 # or a random 64 character hexadecimal string. Authentication is based
1941 # on a (password derived) shared secret and the ability to calculate ticket
1942 # identifiers from this shared secret. Ticket identifiers should be
1943 # "safe" filenames (except user names). There are four types of tickets:
1944 # PASSWORD: User account descriptors, including a user name and password
1945 # LOGIN: Temporary anonymous tickets used during login
1946 # IPADDRESS: Authentication tokens that allow access based on the IP address of the request
1947 # SESSION: Reusable authentication tokens
1948 # CHALLENGE: One-time authentication tokens
1949 # All tickets can have an expiration date in the form of a time duration
1950 # from creation, in seconds, minutes, hours, or days (+duration[smhd]).
1951 # An absolute time can be given in seconds since the epoch of the server host.
1952 # Note that expiration times of CHALLENGE authentication tokens are calculated
1953 # from the last access time. Accounts can include a maximal lifetime
1954 # for session tickets (MaxLifetime).
1956 # A Login page should create a LOGIN ticket file locally and send a
1957 # server specific salt, a Random salt, and a LOGIN ticket
1958 # identifier. The server side compares the username and hashed password,
1959 # actually hashed(hashed(password+serversalt)+Random salt) from the client with
1960 # the values it calculates from the stored Random salt from the LOGIN
1961 # ticket and the hashed(password+serversalt) from the PASSWORD ticket. If
1962 # successful, a new SESSION ticket is generated as a (double) hash sum of the stored
1963 # password and the LOGIN ticket. This SESSION ticket should also be
1964 # generated by the client and stored as sessionStorage and cookie values
1965 # as needed. The Username, IP address and Path are available as
1966 # $LoginUsername, $LoginIPaddress, and $LoginPath, respectively.
1968 # The CHALLENGE protocol stores the single hashed version of the SESSION tickets.
1969 # However, this value is not exchanged, but kept secret in the JavaScript
1970 # sessionStorage object. Instead, every page returned from the
1971 # server will contain a one-time Challenge value ($CHALLENGETICKET) which
1972 # has to be hashed with the stored value to return the current ticket
1973 # id string.
1975 # In the current example implementation, all random values are created as
1976 # full, 256 bit SHA256 hash values (Hex strings) of 64 bytes read from
1977 # /dev/urandom.
1980 # Authorization
1982 # A limited level of authorization tuning is build into the login system.
1983 # Each account file (PASSWORD ticket file) can contain a number of
1984 # Capabilities lines. These control special priveliges. The
1985 # Capabilities can be checked inside the HTML pages as part of the
1986 # ticket information. Two privileges are handled internally:
1987 # CreateUser and VariableREMOTE_ADDR.
1988 # CreateUser allows the logged in user to create a new user account.
1989 # With VariableREMOTE_ADDR, the session of the logged in user is
1990 # not limited to the Remote IP address from which the inital log-in took
1991 # place. Sessions can hop from one apparant (proxy) IP address to another,
1992 # e.g., when using Tor. Any IPaddress patterns given in the PASSWORD
1993 # ticket file remain in effect during the session. For security reasons,
1994 # the VariableREMOTE_ADDR capability is only effective if the session
1995 # type is CHALLENGE.
1998 # Security considerations with Session tickets
2000 # For strong security, please use end-to-end encryption. This can be
2001 # achieved using a VPN (Virtual Private Network), SSH tunnel, or a HTTPS
2002 # capable server with OpenSSL. The session ticket system of CGIscriptor.pl
2003 # is intended to be used as a simple authentication mechanism WITHOUT
2004 # END-TO-END ENCRYPTION. The authenticating mechanism tries to use some
2005 # simple means to protect the authentication process from eavesdropping.
2006 # For this it uses a secure hash function, SHA256. For all practial purposes,
2007 # it is impossible to "decrypt" a SHA256 sum. But this login scheme is
2008 # only as secure as your browser. Which, in general, is not very secure.
2010 # One fundamental weakness of the implemented procedure is that the Client
2011 # obtains the code to encrypt the passwords from the server. It is the JavaScript
2012 # code in the HTML pages. An attacker who could place himself between Server
2013 # and Client, a man in the middle attack (MITM), could change the code to
2014 # reveal the plaintext password and other information. There is no
2015 # real protection against this attack without end-to-end encryption and
2016 # authentication. A simple, but rather cumbersome, way to check for such
2017 # attacks would be to store known good copys of the pages (downloaded
2018 # with a browser or automatically with curl or wget) and
2019 # then use other tools to download new pages at random intervals and compare
2020 # them to the old pages. For instance, the following line would remove
2021 # the variable ticket codes and give a fixed SHA256 sum for the original
2022 # Login.html page+code:
2023 # curl http://localhost:8080/Private/index.html | \
2024 # sed 's/=\"[a-z0-9]\{64\}\"/=""/g' | shasum -a 256
2025 # A simple diff command between old and new files should give only
2026 # differences in half a dozen lines, where only hexadecimal salt values
2027 # will actually differ.
2029 # A sort of solution for the MITM attack problem that might protect at
2030 # least the plaintext password would be to run a trusted web
2031 # page from local storage to handle password input. The solution would be
2032 # to add a hidden iFrame tag loading the untrusted page from the URL and
2033 # extract the needed ticket and salt values. Then run the stored, trusted,
2034 # code with these values. It is not (yet) possible to set the
2035 # required session storage inside the browser, so this method only works
2036 # for IPADDRESS sessions and plain SESSION tickets. There are many
2037 # security problems with this "solution".
2039 # Humans tend to reuse passwords. A compromise of a site running
2040 # CGIscriptor.pl could therefore lead to a compromise of user accounts at
2041 # other sites. Therefore, plain text passwords are never stored, used, or
2042 # exchanged. Instead, the plain password and user name are "encrypted" with
2043 # a server site salt value. Actually, all are concatenated and hashed
2044 # with a one-way secure hash function (SHA256) into a single string.
2045 # Whenever the word "password" is used, this hash sum is meant. Note that
2046 # the salts are generated from /dev/urandom. You should check whether the
2047 # implementation of /dev/urandom on your platform is secure before
2048 # relying on it. This might be a problem when running CGIscriptor under
2049 # Cygwin on MS Windows.
2050 # Note: no attempt is made to slow down the password hash, so bad
2051 # passwords can be cracked by brute force
2053 # As the (hashed) passwords are all that is needed to identify at the site,
2054 # these should not be stored in this form. A site specific passphrase
2055 # can be entered as an environment variable ($ENV{'CGIMasterKey'}). This
2056 # phrase is hashed with the server site salt and the result is hashed with
2057 # the user name and then XORed with the password when it is stored. Also, to
2058 # detect changes to the account (PASSWORD) and session tickets, a
2059 # (HMAC) hash of some of the contents of the ticket with the server salt and
2060 # CGIMasterKey is stored in each ticket.
2062 # Creating a valid (hashed) password, encrypt it with the CGIMasterKey and
2063 # construct a signature of the ticket are non-trivial. This has to be redone
2064 # with every change of the ticket file or CGIMasterKey change. CGIscriptor
2065 # can do this from the command line with the command:
2067 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
2068 # masterkey='Sherlock investigates oleander curry in Bath' \
2069 # password='There is no password like more password' \
2070 # admin
2072 # CGIscriptor will exit after this command with the first option being
2073 # --managelogin. Options have the form:
2075 # salt=[file or string]
2076 # Server salt value to use io the value
2077 # stored in the ticket file. Will replace the stored value if a new
2078 # password is given. If you change the server salt, you have to
2079 # reset all the passwords. There is absolutely no procedure known
2080 # to recover plaintext passwords, except asking the account holders.
2081 # You are strongly adviced to make a backup before you apply such a change
2082 # masterkey=[file or string]
2083 # CGIMasterKey used to read and decrypt the ticket
2084 # newmasterkey=[file or string]
2085 # CGIMasterKey used to encrypt, sign,
2086 # and write the ticket. Defaults to the masterkey. If you change
2087 # the masterkey, you will have to reset all the accounts. You are strongly
2088 # adviced to make a backup before you apply such a change
2089 # password=[file or string]
2090 # New plaintext password
2092 # When the value of an option is a existing file path, the first line of
2093 # that file is used. Options are followed by one or more paths plus names
2094 # of existing ticket files. Each password option is only used for a single
2095 # ticket file. It is most definitely a bad idea to use a password that is
2096 # identical to an existing filepath, as the file will be read instead. Be
2097 # aware that the name of the file should be a cleaned up version of the
2098 # Username. This will not be checked.
2100 # For the authentication and a change of password, the (old) password
2101 # is used to "encrypt" a random one-time token or the new password,
2102 # respectively. For authentication, decryption is not needed, so a secure
2103 # hash function (SHA256) is used to create a one-way hash sum "encryption".
2104 # A new password must be decrypted. New passwords are encryped by XORing
2105 # them with the old password.
2107 # Strong Passwords: It is so easy
2108 # If you only could see what you are typing
2110 # Your password might be vulnerable to brute force guessing
2111 # (https://en.wikipedia.org/wiki/Brute_force_attack).
2112 # Protections against such attacks are costly in terms of code
2113 # complexity, bugs, and execution time. However, there is a very
2114 # simple and secure counter measure. See the XKCD comic
2115 # (http://xkcd.com/936/). The phrase, "There is no password like more
2116 # password" would be both much easier to remember, and still stronger
2117 # than "h4]D%@m:49", at least before this phrase was pasted as an
2118 # example on the Internet.
2120 # For the procedures used at this site, a basic computer setup can
2121 # check in the order of a billion passwords per second. You need a
2122 # password (or phrase) strength in the order of 56 bits to be a
2123 # little secure (one year on a single computer). Please be so kind
2124 # and add the name of your favorite flower, dish, fictional
2125 # character, or small town to your password. Say, Oleander, Curry,
2126 # Sherlock, or Bath, UK (each adds ~12 bits) or even the phrase "Sherlock
2127 # investigates oleander curry in Bath" (adds > 56 bits, note that
2128 # oleander is poisonous, so do not try this curry at home). That
2129 # would be more effective than adding a thousand rounds of encryption.
2130 # Typing long passwords without seeing what you are typing is
2131 # problematic. So a button should be included to make password
2132 # visible.
2135 # Technical matters
2137 # Client side JavaScript code definitions. Variable names starting with '$'
2138 # are CGIscriptor CGI variables. Some of the hashes could be strengthened
2139 # by switching to HMAC signatures. However, the security issues of
2140 # maintaining parallel functions for HMAC in both Perl and Javascript seem
2141 # to be more serious than the attack vectors against the hashes. But HMAC
2142 # is indeed used for the ticket signatures.
2144 # // On Login
2145 # HashPlaintextPassword() {
2146 # var plaintextpassword = document.getElementById('PASSWORD');
2147 # var serversalt = document.getElementById('SERVERSALT');
2148 # var username = document.getElementById('CGIUSERNAME');
2149 # return hex_sha256(plaintextpassword.value+username.value.toLowerCase()+serversalt.value);
2151 # var randomsalt = $RANDOMSALT; // From CGIscriptor
2152 # var loginticket = $LOGINTICKET; // From CGIscriptor
2153 # // Hash plaintext password
2154 # var password = HashPlaintextPassword();
2155 # // Authorize login
2156 # var hashedpassword = hex_sha256(randomsalt+password);
2157 # // Sessionticket
2158 # var sessionticket = hex_sha256(loginticket+password);
2159 # sessionStorage.setItem("CGIscriptorPRIVATE", sessionticket);
2160 # // Secretkey for encrypting new passwords, acts like a one-time pad
2161 # // Is set anew with every login, ie, also whith password changes
2162 # // and for each create new user request
2163 # var secretkey = hex_sha256(password+loginticket+randomsalt);
2164 # sessionStorage.setItem("CGIscriptorSECRET", secretkey);
2166 # // For a SESSION type request
2167 # sessionticket = hex_sha256(sessionStorage.getItem("CGIscriptorPRIVATE"));
2168 # createCookie("CGIscriptorSESSION",sessionticket, 0, "");
2170 // For a CHALLENGE type request
2171 # var sessionset = "$CHALLENGETICKET"; // From CGIscriptor
2172 # var sessionkey = sessionStorage.getItem("CGIscriptorPRIVATE");
2173 # sessionticket = hex_sha256(sessionset+sessionkey);
2174 # createCookie("CGIscriptorCHALLENGE",sessionticket, 0, "");
2176 # // For transmitting a new password
2177 # HashPlaintextNewPassword() {
2178 # var plaintextpassword = document.getElementById('NEWPASSWORD');
2179 # var serversalt = document.getElementById('SERVERSALT');
2180 # var username = document.getElementById('NEWUSERNAME');
2181 # return hex_sha256(plaintextpassword.value+username.value.toLowerCase()+serversalt.value);
2184 # var newpassword = document.getElementById('NEWPASSWORD');
2185 # var newpasswordrep = document.getElementById('NEWPASSWORDREP');
2186 # // Hash plaintext password
2187 # newpassword.value = HashPlaintextNewPassword();
2188 # var secretkey = sessionStorage.getItem("CGIscriptorSECRET");
2190 # var encrypted = XOR_hex_strings(secretkey, newpassword.value);
2191 # newpassword.value = encrypted;
2192 # newpasswordrep.value = encrypted;
2194 # // XOR of hexadecimal strings of equal length
2195 # function XOR_hex_strings(hex1, hex2) {
2196 # var resultHex = "";
2197 # var maxlength = Math.max(hex1.length, hex2.length);
2199 # for(var i=0; i &lt; maxlength; ++i) {
2200 # var h1 = hex1.charAt(i);
2201 # if(! h1) h1='0';
2202 # var h2 = hex2.charAt(i);
2203 # if(! h2) h2 ='0';
2204 # var d1 = parseInt(h1,16);
2205 # var d2 = parseInt(h2,16);
2206 # var resultD = d1^d2;
2207 # resultHex = resultHex+resultD.toString(16);
2208 # };
2209 # return resultHex;
2210 # };
2212 # Password encryption based on $ENV{'CGIMasterKey'}.
2213 # Server side Perl code:
2215 # # Password encryption
2216 # my $masterkey = $ENV{'CGIMasterKey'}
2217 # my $hash1 = hash_string($masterkey.$serversalt);
2218 # my $CryptKey = hash_string($username.$hash1);
2219 # $password = XOR_hex_strings($CryptKey,$password);
2221 # # Key for HMAC signing
2222 # my $hash1 = hash_string($masterkey.$serversalt);
2223 # my $HMACKey = hash_string($username.$hash1);
2227 # USER EXTENSIONS
2229 # A CGIscriptor package is attached to the bottom of this file. With
2230 # this package you can personalize your version of CGIscriptor by
2231 # including often used perl routines. These subroutines can be
2232 # accessed by prefixing their names with CGIscriptor::, e.g.,
2233 # <SCRIPT LANGUAGE=PERL TYPE=text/ssperl>
2234 # CGIscriptor::ListDocs("/Books/*") # List all documents in /Books
2235 # </SCRIPT>
2236 # It already contains some useful subroutines for Document Management.
2237 # As it is a separate package, it has its own namespace, isolated from
2238 # both the evaluator and the main program. To access variables from
2239 # the document <SCRIPT></SCRIPT> blocks, use $CGIexecute::<var>.
2241 # Currently, the following functions are implemented
2242 # (precede them with CGIscriptor::, see below for more information)
2243 # - SAFEqx ('String') -> result of qx/"String"/ # Safe application of ``-quotes
2244 # Is used by text/osshell Shell scripts. Protects all CGI
2245 # (client-supplied) values with single quotes before executing the
2246 # commands (one of the few functions that also works WITHOUT CGIscriptor::
2247 # in front)
2248 # - defineCGIvariable ($name[, $default) -> 0/1 (i.e., failure/success)
2249 # Is used by the META tag to define and initialize CGI and ENV
2250 # name/value pairs. Tries to obtain an initializing value from (in order):
2251 # $ENV{$name}
2252 # The Query string
2253 # The default value given (if any)
2254 # (one of the few functions that also works WITHOUT CGIscriptor::
2255 # in front)
2256 # - CGIsafeFileName (FileName) -> FileName or ""
2257 # Check a string against the Allowed File Characters (and ../ /..).
2258 # Returns an empty string for unsafe filenames.
2259 # - CGIsafeEmailAddress (Email) -> Email or ""
2260 # Check a string against correct email address pattern.
2261 # Returns an empty string for unsafe addresses.
2262 # - RedirectShellScript ('CommandString') -> FILEHANDLER or undef
2263 # Open a named PIPE for SAFEqx to receive ALL shell scripts
2264 # - URLdecode (URL encoded string) -> plain string # Decode URL encoded argument
2265 # - URLencode (plain string) -> URL encoded string # Encode argument as URL code
2266 # - CGIparseValue (ValueName [, URL_encoded_QueryString]) -> Decoded value
2267 # Extract the value of a CGI variable from the global or a private
2268 # URL-encoded query (multipart POST raw, NOT decoded)
2269 # - CGIparseValueList (ValueName [, URL_encoded_QueryString])
2270 # -> List of decoded values
2271 # As CGIparseValue, but now assembles ALL values of ValueName into a list.
2272 # - CGIparseHeader (ValueName [, URL_encoded_QueryString]) -> Header
2273 # Extract the header of a multipart CGI variable from the global or a private
2274 # URL-encoded query ("" when not a multipart variable or absent)
2275 # - CGIparseForm ([URL_encoded_QueryString]) -> Decoded Form
2276 # Decode the complete global URL-encoded query or a private
2277 # URL-encoded query
2278 # - read_url(URL) # Returns the page from URL (with added base tag, both FTP and HTTP)
2279 # Uses main::GET_URL(URL, 1) to get at the command to read the URL.
2280 # - BrowseDirs(RootDirectory [, Pattern, Startdir, CGIname]) # print browsable directories
2281 # - ListDocs(Pattern [,ListType]) # Prints a nested HTML directory listing of
2282 # all documents, e.g., ListDocs("/*", "dl");.
2283 # - HTMLdocTree(Pattern [,ListType]) # Prints a nested HTML listing of all
2284 # local links starting from a given document, e.g.,
2285 # HTMLdocTree("/Welcome.html", "dl");
2288 # THE RESULTS STACK: @CGISCRIPTORRESULTS
2290 # If the pseudo-variable "$CGIscriptorResults" has been defined in a
2291 # META tag, all subsequent SCRIPT and META results are pushed
2292 # on the @CGIscriptorResults stack. This list is just another
2293 # Perl variable and can be used and manipulated like any other list.
2294 # $CGIscriptorResults[-1] is always the last result.
2295 # This is only of limited use, e.g., to use the results of an OS shell
2296 # script inside a Perl script. Will NOT contain the results of Pipes
2297 # or code from MIME-profiling.
2300 # USEFULL CGI PREDEFINED VARIABLES (DO NOT ASSIGN TO THESE)
2302 # $CGI_HOME - The DocumentRoot directory
2303 # $CGI_Decoded_QS - The complete decoded Query String
2304 # $CGI_Content_Length - The ACTUAL length of the Query String
2305 # $CGI_Date - Current date and time
2306 # $CGI_Year $CGI_Month $CGI_Day $CGI_WeekDay - Current Date
2307 # $CGI_Time - Current Time
2308 # $CGI_Hour $CGI_Minutes $CGI_Seconds - Current Time, split
2309 # GMT Date/Time:
2310 # $CGI_GMTYear $CGI_GMTMonth $CGI_GMTDay $CGI_GMTWeekDay $CGI_GMTYearDay
2311 # $CGI_GMTHour $CGI_GMTMinutes $CGI_GMTSeconds $CGI_GMTisdst
2314 # USEFULL CGI ENVIRONMENT VARIABLES
2316 # Variables accessible (in APACHE) as $ENV{<name>}
2317 # (see: "http://hoohoo.ncsa.uiuc.edu/cgi/env.html"):
2319 # QUERY_STRING - The query part of URL, that is, everything that follows the
2320 # question mark.
2321 # PATH_INFO - Extra path information given after the script name
2322 # PATH_TRANSLATED - Extra pathinfo translated through the rule system.
2323 # (This doesn't always make sense.)
2324 # REMOTE_USER - If the server supports user authentication, and the script is
2325 # protected, this is the username they have authenticated as.
2326 # REMOTE_HOST - The hostname making the request. If the server does not have
2327 # this information, it should set REMOTE_ADDR and leave this unset
2328 # REMOTE_ADDR - The IP address of the remote host making the request.
2329 # REMOTE_IDENT - If the HTTP server supports RFC 931 identification, then this
2330 # variable will be set to the remote user name retrieved from
2331 # the server. Usage of this variable should be limited to logging
2332 # only.
2333 # AUTH_TYPE - If the server supports user authentication, and the script
2334 # is protected, this is the protocol-specific authentication
2335 # method used to validate the user.
2336 # CONTENT_TYPE - For queries which have attached information, such as HTTP
2337 # POST and PUT, this is the content type of the data.
2338 # CONTENT_LENGTH - The length of the said content as given by the client.
2339 # SERVER_SOFTWARE - The name and version of the information server software
2340 # answering the request (and running the gateway).
2341 # Format: name/version
2342 # SERVER_NAME - The server's hostname, DNS alias, or IP address as it
2343 # would appear in self-referencing URLs
2344 # GATEWAY_INTERFACE - The revision of the CGI specification to which this
2345 # server complies. Format: CGI/revision
2346 # SERVER_PROTOCOL - The name and revision of the information protocol this
2347 # request came in with. Format: protocol/revision
2348 # SERVER_PORT - The port number to which the request was sent.
2349 # REQUEST_METHOD - The method with which the request was made. For HTTP,
2350 # this is "GET", "HEAD", "POST", etc.
2351 # SCRIPT_NAME - A virtual path to the script being executed, used for
2352 # self-referencing URLs.
2353 # HTTP_ACCEPT - The MIME types which the client will accept, as given by
2354 # HTTP headers. Other protocols may need to get this
2355 # information from elsewhere. Each item in this list should
2356 # be separated by commas as per the HTTP spec.
2357 # Format: type/subtype, type/subtype
2358 # HTTP_USER_AGENT - The browser the client is using to send the request.
2359 # General format: software/version library/version.
2362 # INSTRUCTIONS FOR RUNNING CGIscriptor ON UNIX
2364 # CGIscriptor.pl will run on any WWW server that runs Perl scripts, just add
2365 # a line like the following to your srm.conf file (Apache example):
2367 # ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
2369 # URL's that refer to http://www.your.address/SHTML/... will now be handled
2370 # by CGIscriptor.pl, which can use a private directory tree (default is the
2371 # DOCUMENT_ROOT directory tree, but it can be anywhere, see manual).
2373 # If your hosting ISP won't let you add ScriptAlias lines you can use
2374 # the following "rewrite"-based "scriptalias" in .htaccess
2375 # (from Gerd Franke)
2377 # RewriteEngine On
2378 # RewriteBase /
2379 # RewriteCond %{REQUEST_FILENAME} .html$
2380 # RewriteCond %{SCRIPT_FILENAME} !cgiscriptor.pl$
2381 # RewriteCond %{REQUEST_FILENAME} -f
2382 # RewriteRule ^(.*)$ /cgi-bin/cgiscriptor.pl/$1?&%{QUERY_STRING}
2384 # Everthing with the extension ".html" and not including "cgiscriptor.pl"
2385 # in the url and where the file "path/filename.html" exists is redirected
2386 # to "/cgi.bin/cgiscriptor.pl/path/filename.html?query".
2387 # The user configuration should get the same path-level as the
2388 # .htaccess-file:
2390 # # Just enter your own directory path here
2391 # $YOUR_HTML_FILES = "$ENV{'DOCUMENT_ROOT'}";
2392 # # use DOCUMENT_ROOT only, if .htaccess lies in the root-directory.
2394 # If this .htaccess goes in a specific directory, the path to this
2395 # directory must be added to $ENV{'DOCUMENT_ROOT'}.
2397 # The CGIscriptor file contains all documentation as comments. These
2398 # comments can be removed to speed up loading (e.g., `egrep -v '^#'
2399 # CGIscriptor.pl` > leanScriptor.pl). A bare bones version of
2400 # CGIscriptor.pl, lacking documentation, most comments, access control,
2401 # example functions etc. (but still with the copyright notice and some
2402 # minimal documentation) can be obtained by calling CGIscriptor.pl on the
2403 # command line with the '-slim' command line argument, e.g.,
2405 # >CGIscriptor.pl -slim > slimCGIscriptor.pl
2407 # CGIscriptor.pl can be run from the command line with <path> and <query> as
2408 # arguments, as `CGIscriptor.pl <path> <query>`, inside a perl script
2409 # with 'do CGIscriptor.pl' after setting $ENV{PATH_INFO}
2410 # and $ENV{QUERY_STRING}, or CGIscriptor.pl can be loaded with 'require
2411 # "/real-path/CGIscriptor.pl"'. In the latter case, requests are processed
2412 # by 'Handle_Request();' (again after setting $ENV{PATH_INFO} and
2413 # $ENV{QUERY_STRING}).
2415 # Using the command line execution option, CGIscriptor.pl can be used as a
2416 # document (meta-)preprocessor. If the first argument is '-', STDIN will be read.
2417 # For example:
2419 # > cat MyDynamicDocument.html | CGIscriptor.pl - '[QueryString]' > MyStaticFile.html
2421 # This command line will produce a STATIC file with the DYNAMIC content of
2422 # MyDocument.html "interpolated".
2424 # This option would be very dangerous when available over the internet.
2425 # If someone could sneak a 'http://www.your.domain/-' URL past your
2426 # server, CGIscriptor could EXECUTE any POSTED contend.
2427 # Therefore, for security reasons, STDIN will NOT be read
2428 # if ANY of the HTTP server environment variables is set (e.g.,
2429 # SERVER_PORT, SERVER_PROTOCOL, SERVER_NAME, SERVER_SOFTWARE,
2430 # HTTP_USER_AGENT, REMOTE_ADDR).
2431 # This block on processing STDIN on HTTP requests can be lifted by setting
2432 # $BLOCK_STDIN_HTTP_REQUEST = 0;
2433 # In the security configuration. Butbe carefull when doing this.
2434 # It can be very dangerous.
2436 # Running demo's and more information can be found at
2437 # http://www.fon.hum.uva.nl/~rob/OSS/OSS.html
2439 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site or
2440 # CPAN that can use CGIscriptor.pl as the base of a µWWW server and
2441 # demonstrates its use.
2444 # PROCESSING NON-FILESYSTEM DATA
2446 # Normally, HTTP (WWW) requests map onto file that can be accessed
2447 # using the perl open() function. That is, the web server runs on top of
2448 # some directory structure. However, we can envission (and put to good
2449 # use) other systems that do not use a normal file system. The whole CGI
2450 # was developed to make dynamic document generation possible.
2452 # A special case is where we want to have it both: A normal web server
2453 # with normal "file data", but not a normal files system. For instance,
2454 # we want or normal Web Site to run directly from a RAM hash table or
2455 # other database, instead of from disk. But we do NOT want to code the
2456 # whole site structure in CGI.
2458 # CGIscriptor can do this. If the web server fills an environment variable
2459 # $ENV{'CGI_FILE_CONTENT'} with the content of the "file", then the content
2460 # of this variable is processed instead of opening a file. If this environment
2461 # variable has the value '-', the content of another environment variable,
2462 # $ENV{'CGI_DATA_ACCESS_CODE'} is executed as:
2463 # eval("\@_ = ($file_path); do {$ENV{'CGI_DATA_ACCESS_CODE'}};")
2464 # and the result is processed as if it was the content of the requested
2465 # file.
2466 # (actually, the names of the environment variables are user configurable,
2467 # they are stored in the local variables $CGI_FILE_CONTENT and
2468 # $CGI_DATA_ACCESS_CODE)
2470 # When using this mechanism, the SRC attribute mechanism will only partially work.
2471 # Only the "recursive" calls to CGIscriptor (the ProcessFile() function)
2472 # will work, the automagical execution of SRC files won't. (In this case,
2473 # the SRC attribute won't work either for other scripting languages)
2476 # NON-UNIX PLATFORMS
2478 # CGIscriptor.pl was mainly developed and tested on UNIX. However, as I
2479 # coded part of the time on an Apple Macintosh under MacPerl, I made sure
2480 # CGIscriptor did run under MacPerl (with command line options). But only
2481 # as an independend script, not as part of a HTTP server. I have used it
2482 # under Apache in Windows XP.
2484 ENDOFHELPTEXT
2485 exit;
2487 ###############################################################################
2489 # SECURITY CONFIGURATION
2491 # Special configurations related to SECURITY
2492 # (i.e., optional, see also environment variables below)
2494 # LOGGING
2495 # Log Clients and the requested paths (Redundant when loging Queries)
2497 $ClientLog = "./Client.log"; # (uncomment for use)
2499 # Format: Localtime | REMOTE_USER REMOTE_IDENT REMOTE_HOST REMOTE_ADDRESS \
2500 # PATH_INFO CONTENT_LENGTH (actually, the real query+post length)
2502 # Log Clients and the queries, the CGIQUERYDECODE is required if you want
2503 # to log queries. If you log Queries, the loging of Clients is redundant
2504 # (note that queries can be quite long, so this might not be a good idea)
2506 #$QueryLog = "./Query.log"; # (uncomment for use)
2508 # ACCESS CONTROL
2509 # the Access files should contain Hostnames or IP addresses,
2510 # i.e. REMOTE_HOST or REMOTE_ADDR, each on a separate line
2511 # optionally followed by one ore more file patterns, e.g., "edu /DEMO".
2512 # Matching is done "domain first". For example ".edu" matches all
2513 # clients whose "name" ends in ".edu" or ".EDU". The file pattern
2514 # "/DEMO" matches all paths that contain the strings "/DEMO" or "/demo"
2515 # (both matchings are done case-insensitive).
2516 # The name special symbol "-" matches ALL clients who do not supply a
2517 # REMOTE_HOST name, "*" matches all clients.
2518 # Lines starting with '-e' are evaluated. A non-zero return value indicates
2519 # a match. You can use $REMOTE_HOST, $REMOTE_ADDR, and $PATH_INFO. These
2520 # lines are evaluated in the program's own name-space. So DO NOT assign to
2521 # variables.
2523 # Accept the following users (remove comment # and adapt filename)
2524 $CGI_Accept = -s "$YOUR_SCRIPTS/ACCEPT.lis" ? "$YOUR_SCRIPTS/ACCEPT.lis" : ''; # (uncomment for use)
2526 # Reject requests from the following users (remove comment # and
2527 # adapt filename, this is only of limited use)
2528 $CGI_Reject = -s "$YOUR_SCRIPTS/REJECT.lis" ? "$YOUR_SCRIPTS/REJECT.lis" : ''; # (uncomment for use)
2530 # Empty lines or comment lines starting with '#' are ignored in both
2531 # $CGI_Accept and $CGI_Reject.
2533 # Block STDIN (i.e., '-') requests when servicing an HTTP request
2534 # Comment this out if you realy want to use STDIN in an on-line web server
2535 $BLOCK_STDIN_HTTP_REQUEST = 1;
2538 # End of security configuration
2540 ##################################################<<<<<<<<<<End Remove
2542 # PARSING CGI VALUES FROM THE QUERY STRING (USER CONFIGURABLE)
2544 # The CGI parse commands. These commands extract the values of the
2545 # CGI variables from the URL encoded Query String.
2546 # If you want to use your own CGI decoders, you can call them here
2547 # instead, using your own PATH and commenting/uncommenting the
2548 # appropriate lines
2550 # CGI parse command for individual values
2551 # (if $List > 0, returns a list value, if $List < 0, a hash table, this is optional)
2552 sub YOUR_CGIPARSE # ($Name [, $List]) -> Decoded value
2554 my $Name = shift;
2555 my $List = shift || 0;
2556 # Use one of the following by uncommenting
2557 if(!$List) # Simple value
2559 return CGIscriptor::CGIparseValue($Name) ;
2561 elsif($List < 0) # Hash tables
2563 return CGIscriptor::CGIparseValueHash($Name); # Defined in CGIscriptor below
2565 else # Lists
2567 return CGIscriptor::CGIparseValueList($Name); # Defined in CGIscriptor below
2570 # return `/PATH/cgiparse -value $Name`; # Shell commands
2571 # require "/PATH/cgiparse.pl"; return cgivalue($Name); # Library
2573 # Complete queries
2574 sub YOUR_CGIQUERYDECODE
2576 # Use one of the following by uncommenting
2577 return CGIscriptor::CGIparseForm(); # Defined in CGIscriptor below
2578 # return `/PATH/cgiparse -form`; # Shell commands
2579 # require "/PATH/cgiparse.pl"; return cgiform(); # Library
2582 # End of configuration
2584 #######################################################################
2586 # Translating input files.
2587 # Allows general and global conversions of files using Regular Expressions
2588 # Translations are applied in the order of definition.
2590 # Define:
2591 # my $TranslationPaths = 'pattern'; # Pattern matching PATH_INFO
2593 # push(@TranslationTable, ['pattern', 'replacement']);
2594 # e.g. (for Ruby Rails):
2595 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2596 # push(@TranslationTable, ['%>', '</SCRIPT>']);
2598 # Runs:
2599 # my $currentRegExp;
2600 # foreach $currentRegExp (keys(%TranslationTable))
2602 # my $currentRegExp;
2603 # foreach $currentRegExp (@TranslationTable)
2605 # my ($pattern, $replacement) = @$currentRegExp;
2606 # $$text =~ s!$pattern!$replacement!msg;
2607 # };
2608 # };
2610 # Configuration section
2612 #######################################################################
2614 # The file paths on which to apply the translation
2615 my $TranslationPaths = ''; # NO files
2616 #$TranslationPaths = '.'; # ANY file
2617 # $TranslationPaths = '\.html'; # HTML files
2619 my @TranslationTable = ();
2620 # Some legacy code
2621 push(@TranslationTable, ['\<\s*CGI\s+([^\>])*\>', '\<SCRIPT TYPE=\"text/ssperl\"\>$1\<\/SCRIPT>']);
2622 # Ruby Rails?
2623 push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2624 push(@TranslationTable, ['%>', '</SCRIPT>']);
2626 sub performTranslation # (\$text)
2628 my $text = shift || return;
2629 if(@TranslationTable && $TranslationPaths && $ENV{'PATH_INFO'} =~ m!$TranslationPaths!)
2631 my $currentRegExp;
2632 foreach $currentRegExp (@TranslationTable)
2634 my ($pattern, $replacement) = @$currentRegExp;
2635 $$text =~ s!$pattern!$replacement!msg;
2640 #######################################################################
2642 # Seamless access to other (Scripting) Languages
2643 # TYPE='text/ss<interpreter>'
2645 # Configuration section
2647 #######################################################################
2649 # OTHER SCRIPTING LANGUAGES AT THE SERVER SIDE (MIME => OScommand)
2650 # Yes, it realy is this simple! (unbelievable, isn't it)
2651 # NOTE: Some interpreters require some filtering to obtain "clean" output
2653 %ScriptingLanguages = (
2654 "text/testperl" => 'perl', # Perl for testing
2655 "text/sspython" => 'python', # Python
2656 "text/ssruby" => 'ruby', # Ruby
2657 "text/sstcl" => 'tcl', # TCL
2658 "text/ssawk" => 'awk -f-', # Awk
2659 "text/sslisp" => # lisp (rep, GNU)
2660 'rep | tail +4 '."| egrep -v '> |^rep. |^nil\\\$'",
2661 "text/xlispstat" => # xlispstat
2662 'xlispstat | tail +7 ' ."| egrep -v '> \\\$|^NIL'",
2663 "text/ssprolog" => # Prolog (GNU)
2664 "gprolog | tail +4 | sed 's/^| ?- //'",
2665 "text/ssm4" => 'm4', # M4 macro's
2666 "text/sh" => 'sh', # Born shell
2667 "text/bash" => 'bash', # Born again shell
2668 "text/csh" => 'csh', # C shell
2669 "text/ksh" => 'ksh', # Korn shell
2670 "text/sspraat" => # Praat (sound/speech analysis)
2671 "praat - | sed 's/Praat > //g'",
2672 "text/ssr" => # R
2673 "R --vanilla --slave | sed 's/^[\[0-9\]*] //'",
2674 "text/ssrebol" => # REBOL
2675 "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\\s*\[> \]* //'",
2676 "text/postgresql" => 'psql 2>/dev/null',
2678 # Not real scripting, but the use of other applications
2679 "text/ssmailto" => "awk 'NF||F{F=1;print \\\$0;}'|mailto >/dev/null", # Send mail from server
2680 "text/ssdisplay" => 'cat', # Display, (interpolation)
2681 "text/sslogfile" => # Log to file, (interpolation)
2682 "awk 'NF||L {if(!L){L=tolower(\\\$1)~/^file:\\\$/ ? \\\$2 : \\\$1;}else{print \\\$0 >> L;};}'",
2684 "" => ""
2687 # To be able to access the CGI variables in your script, they
2688 # should be passed to the scripting language in a readable form
2689 # Here you can enter how they should be printed (the first %s
2690 # is replaced by the NAME of the CGI variable as it apears in the
2691 # META tag, the second by its VALUE).
2692 # For Perl this would be:
2693 # "text/testperl" => '$%s = "%s";',
2694 # which would be executed as
2695 # printf('$%s = "%s";', $CGI_NAME, $CGI_VALUE);
2697 # If the hash table value doesn't exist, nothing is done
2698 # (you have to parse the Environment variables yourself).
2699 # If it DOES exist but is empty (e.g., "text/sspraat" => '',)
2700 # Perl string interpolation of variables (i.e., $var, @array,
2701 # %hash) is performed. This means that $@%\ must be protected
2702 # with a \.
2704 %ScriptingCGIvariables = (
2705 "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value'; (for testing)
2706 "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
2707 "text/ssruby" => '@%s = "%s"', # Ruby @VAR = 'value'
2708 "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
2709 "text/ssawk" => '%s = "%s";', # Awk VAR = 'value';
2710 "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
2711 "text/xlispstat" => '(setq %s "%s")', # xlispstat (setq VAR "value")
2712 "text/ssprolog" => '', # Gnu prolog (interpolated)
2713 "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
2714 "text/sh" => "\%s='\%s'", # Born shell VAR='value'
2715 "text/bash" => "\%s='\%s'", # Born again shell VAR='value'
2716 "text/csh" => "\$\%s='\%s';", # C shell $VAR = 'value';
2717 "text/ksh" => "\$\%s='\%s';", # Korn shell $VAR = 'value';
2719 "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
2720 "text/sspraat" => '', # Praat (interpolation)
2721 "text/ssr" => '%s <- "%s";', # R VAR <- "value";
2722 "text/postgresql" => '', # PostgreSQL (interpolation)
2724 # Not real scripting, but the use of other applications
2725 "text/ssmailto" => '', # MAILTO, (interpolation)
2726 "text/ssdisplay" => '', # Display, (interpolation)
2727 "text/sslogfile" => '', # Log to file, (interpolation)
2729 "" => ""
2732 # If you want something added in front or at the back of each script
2733 # block as send to the interpreter add it here.
2734 # mime => "string", e.g., "text/sspython" => "python commands"
2735 %ScriptingPrefix = (
2736 "text/testperl" => "\# Prefix Code;", # Perl script testing
2737 "text/ssm4" => 'divert(0)', # M4 macro's (open STDOUT)
2739 "" => ""
2741 # If you want something added at the end of each script block
2742 %ScriptingPostfix = (
2743 "text/testperl" => "\# Postfix Code;", # Perl script testing
2744 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2746 "" => ""
2748 # If you need initialization code, directly after opening
2749 %ScriptingInitialization = (
2750 "text/testperl" => "\# Initialization Code;", # Perl script testing
2751 "text/ssawk" => 'BEGIN {', # Server Side awk scripts (VAR = "value")
2752 "text/sslisp" => '(prog1 nil ', # Lisp (rep)
2753 "text/xlispstat" => '(prog1 nil ', # xlispstat
2754 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2756 "" => ""
2758 # If you need cleanup code before closing
2759 %ScriptingCleanup = (
2760 "text/testperl" => "\# Cleanup Code;", # Perl script testing
2761 "text/sspraat" => 'Quit',
2762 "text/ssawk" => '};', # Server Side awk scripts (VAR = "value")
2763 "text/sslisp" => '(princ "\n" standard-output)).', # Closing print to rep
2764 "text/xlispstat" => '(print ""))', # Closing print to xlispstat
2765 "text/postgresql" => '\q', # quit psql
2766 "text/ssdisplay" => "", # close cat
2768 "" => ""
2771 # End of configuration for foreign scripting languages
2773 ###############################################################################
2775 # Initialization Code
2778 sub Initialize_Request
2780 ###############################################################################
2782 # ENVIRONMENT VARIABLES
2784 # Use environment variables to configure CGIscriptor on a temporary basis.
2785 # If you define any of the configurable variables as environment variables,
2786 # these are used instead of the "hard coded" values above.
2788 $SS_PUB = $ENV{'SS_PUB'} || $YOUR_HTML_FILES;
2789 $SS_SCRIPT = $ENV{'SS_SCRIPT'} || $YOUR_SCRIPTS;
2792 # Substitution strings, these are used internally to handle the
2793 # directory separator strings, e.g., '~/' -> 'SS_PUB:' (Mac)
2794 $HOME_SUB = $SS_PUB;
2795 $SCRIPT_SUB = $SS_SCRIPT;
2798 # Make sure all script are reliably loaded
2799 push(@INC, $SS_SCRIPT);
2802 # Add the directory separator to the "home" directories.
2803 # (This is required for ~/ and ./ substitution)
2804 $HOME_SUB .= '/' if $HOME_SUB;
2805 $SCRIPT_SUB .= '/' if $SCRIPT_SUB;
2807 $CGI_HOME = $ENV{'DOCUMENT_ROOT'};
2808 $ENV{'PATH_TRANSLATED'} =~ /$ENV{'PATH_INFO'}/is;
2809 $CGI_HOME = $` unless $ENV{'DOCUMENT_ROOT'}; # Get the DOCUMENT_ROOT directory
2810 $default_values{'CGI_HOME'} = $CGI_HOME;
2811 $ENV{'HOME'} = $CGI_HOME;
2812 # Set SS_PUB and SS_SCRIPT as Environment variables (make them available
2813 # to the scripts)
2814 $ENV{'SS_PUB'} = $SS_PUB unless $ENV{'SS_PUB'};
2815 $ENV{'SS_SCRIPT'} = $SS_SCRIPT unless $ENV{'SS_SCRIPT'};
2817 $FilePattern = $ENV{'FilePattern'} || $FilePattern;
2818 $MaximumQuerySize = $ENV{'MaximumQuerySize'} || $MaximumQuerySize;
2819 $ClientLog = $ENV{'ClientLog'} || $ClientLog;
2820 $QueryLog = $ENV{'QueryLog'} || $QueryLog;
2821 $CGI_Accept = $ENV{'CGI_Accept'} || $CGI_Accept;
2822 $CGI_Reject = $ENV{'CGI_Reject'} || $CGI_Reject;
2824 # Parse file names
2825 $CGI_Accept =~ s@^\~/@$HOME_SUB@g if $CGI_Accept;
2826 $CGI_Reject =~ s@^\~/@$HOME_SUB@g if $CGI_Reject;
2827 $ClientLog =~ s@^\~/@$HOME_SUB@g if $ClientLog;
2828 $QueryLog =~ s@^\~/@$HOME_SUB@g if $QueryLog;
2830 $CGI_Accept =~ s@^\./@$SCRIPT_SUB@g if $CGI_Accept;
2831 $CGI_Reject =~ s@^\./@$SCRIPT_SUB@g if $CGI_Reject;
2832 $ClientLog =~ s@^\./@$SCRIPT_SUB@g if $ClientLog;
2833 $QueryLog =~ s@^\./@$SCRIPT_SUB@g if $QueryLog;
2835 @CGIscriptorResults = (); # A stack of results
2837 # end of Environment variables
2839 #############################################################################
2841 # Define and Store "standard" values
2843 # BEFORE doing ANYTHING check the size of Query String
2844 length($ENV{'QUERY_STRING'}) <= $MaximumQuerySize || dieHandler(2, "QUERY TOO LONG\n");
2846 # The Translated Query String and the Actual length of the (decoded)
2847 # Query String
2848 if($ENV{'QUERY_STRING'})
2850 # If this can contain '`"-quotes, be carefull to use it QUOTED
2851 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2852 $default_values{CGI_Content_Length} = length($default_values{CGI_Decoded_QS});
2855 # Get the current Date and time and store them as default variables
2857 # Get Local Time
2858 $LocalTime = localtime;
2860 # CGI_Year CGI_Month CGI_Day CGI_WeekDay CGI_Time
2861 # CGI_Hour CGI_Minutes CGI_Seconds
2863 $default_values{CGI_Date} = $LocalTime;
2864 ($default_values{CGI_WeekDay},
2865 $default_values{CGI_Month},
2866 $default_values{CGI_Day},
2867 $default_values{CGI_Time},
2868 $default_values{CGI_Year}) = split(' ', $LocalTime);
2869 ($default_values{CGI_Hour},
2870 $default_values{CGI_Minutes},
2871 $default_values{CGI_Seconds}) = split(':', $default_values{CGI_Time});
2873 # GMT:
2874 # CGI_GMTYear CGI_GMTMonth CGI_GMTDay CGI_GMTWeekDay CGI_GMTYearDay
2875 # CGI_GMTHour CGI_GMTMinutes CGI_GMTSeconds CGI_GMTisdst
2877 ($default_values{CGI_GMTSeconds},
2878 $default_values{CGI_GMTMinutes},
2879 $default_values{CGI_GMTHour},
2880 $default_values{CGI_GMTDay},
2881 $default_values{CGI_GMTMonth},
2882 $default_values{CGI_GMTYear},
2883 $default_values{CGI_GMTWeekDay},
2884 $default_values{CGI_GMTYearDay},
2885 $default_values{CGI_GMTisdst}) = gmtime;
2889 # End of Initialize Request
2891 ###################################################################
2893 # SECURITY: ACCESS CONTROL
2895 # Check the credentials of each client (use pattern matching, domain first).
2896 # This subroutine will kill-off (die) the current process whenever access
2897 # is denied.
2899 sub Access_Control
2901 # >>>>>>>>>>Start Remove
2903 # ACCEPTED CLIENTS
2905 # Only accept clients which are authorized, reject all unnamed clients
2906 # if REMOTE_HOST is given.
2907 # If file patterns are given, check whether the user is authorized for
2908 # THIS file.
2909 if($CGI_Accept)
2911 # Use local variables, REMOTE_HOST becomes '-' if undefined
2912 my $REMOTE_HOST = $ENV{REMOTE_HOST} || '-';
2913 my $REMOTE_ADDR = $ENV{REMOTE_ADDR};
2914 my $PATH_INFO = $ENV{'PATH_INFO'};
2916 open(CGI_Accept, "<$CGI_Accept") || dieHandler(3, "$CGI_Accept: $!\n");
2917 $NoAccess = 1;
2918 while(<CGI_Accept>)
2920 next unless /\S/; # Skip empty lines
2921 next if /^\s*\#/; # Skip comments
2923 # Full expressions
2924 if(/^\s*-e\s/is)
2926 my $Accept = $'; # Get the expression
2927 $NoAccess &&= eval($Accept); # evaluate the expresion
2929 else
2931 my ($Accept, @FilePatternList) = split;
2932 if($Accept eq '*' # Always match
2933 ||$REMOTE_HOST =~ /\Q$Accept\E$/is # REMOTE_HOST matches
2934 || (
2935 $Accept =~ /^[0-9\.]+$/
2936 && $REMOTE_ADDR =~ /^\Q$Accept\E/ # IP address matches
2940 if($FilePatternList[0])
2942 foreach $Pattern (@FilePatternList)
2944 # Check whether this patterns is accepted
2945 $NoAccess &&= ($PATH_INFO !~ m@\Q$Pattern\E@is);
2948 else
2950 $NoAccess = 0; # No file patterns -> Accepted
2954 # Blocked
2955 last unless $NoAccess;
2957 close(CGI_Accept);
2958 if($NoAccess){ dieHandler(4, "No Access: $PATH_INFO\n");};
2962 # REJECTED CLIENTS
2964 # Reject named clients, accept all unnamed clients
2965 if($CGI_Reject)
2967 # Use local variables, REMOTE_HOST becomes '-' if undefined
2968 my $REMOTE_HOST = $ENV{'REMOTE_HOST'} || '-';
2969 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
2970 my $PATH_INFO = $ENV{'PATH_INFO'};
2972 open(CGI_Reject, "<$CGI_Reject") || dieHandler(5, "$CGI_Reject: $!\n");
2973 $NoAccess = 0;
2974 while(<CGI_Reject>)
2976 next unless /\S/; # Skip empty lines
2977 next if /^\s*\#/; # Skip comments
2979 # Full expressions
2980 if(/^-e\s/is)
2982 my $Reject = $'; # Get the expression
2983 $NoAccess ||= eval($Reject); # evaluate the expresion
2985 else
2987 my ($Reject, @FilePatternList) = split;
2988 if($Reject eq '*' # Always match
2989 ||$REMOTE_HOST =~ /\Q$Reject\E$/is # REMOTE_HOST matches
2990 ||($Reject =~ /^[0-9\.]+$/
2991 && $REMOTE_ADDR =~ /^\Q$Reject\E/is # IP address matches
2995 if($FilePatternList[0])
2997 foreach $Pattern (@FilePatternList)
2999 $NoAccess ||= ($PATH_INFO =~ m@\Q$Pattern\E@is);
3002 else
3004 $NoAccess = 1; # No file patterns -> Rejected
3008 last if $NoAccess;
3010 close(CGI_Reject);
3011 if($NoAccess){ dieHandler(6, "Request rejected: $PATH_INFO\n");};
3014 ##########################################################<<<<<<<<<<End Remove
3017 # Get the filename
3019 # Does the filename contain any illegal characters (e.g., |, >, or <)
3020 dieHandler(7, "Illegal request: $ENV{'PATH_INFO'}\n") if $ENV{'PATH_INFO'} =~ /[^$FileAllowedChars]/;
3021 # Does the pathname contain an illegal (blocked) "directory"
3022 dieHandler(8, "Illegal request: $ENV{'PATH_INFO'}\n") if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@; # Access is blocked
3023 # Does the pathname contain a direct referencer to BinaryMapFile
3024 dieHandler(9, "Illegal request: $ENV{'PATH_INFO'}\n") if $BinaryMapFile && $ENV{'PATH_INFO'} =~ m@\Q$BinaryMapFile\E@; # Access is blocked
3026 # SECURITY: Is PATH_INFO allowed?
3027 if($FilePattern && $ENV{'PATH_INFO'} && $ENV{'PATH_INFO'} ne '-' &&
3028 ($ENV{'PATH_INFO'} !~ m@($FilePattern)$@is))
3030 # Unsupported file types can be processed by a special raw-file
3031 if($BinaryMapFile)
3033 $ENV{'CGI_BINARY_FILE'} = $ENV{'PATH_INFO'};
3034 $ENV{'PATH_INFO'} = $BinaryMapFile;
3036 else
3038 dieHandler(10, "Illegal file\n");
3044 # End of Security Access Control
3047 ############################################################################
3049 # Get the POST part of the query and add it to the QUERY_STRING.
3052 sub Get_POST_part_of_query
3055 # If POST, Read data from stdin to QUERY_STRING
3056 if($ENV{'REQUEST_METHOD'} =~ /POST/is)
3058 # SECURITY: Check size of Query String
3059 $ENV{'CONTENT_LENGTH'} <= $MaximumQuerySize || dieHandler(11, "Query too long: $ENV{'CONTENT_LENGTH'}\n"); # Query too long
3060 my $QueryRead = 0;
3061 my $SystemRead = $ENV{'CONTENT_LENGTH'};
3062 $ENV{'QUERY_STRING'} .= '&' if length($ENV{'QUERY_STRING'}) > 0;
3063 while($SystemRead > 0)
3065 $QueryRead = sysread(STDIN, $Post, $SystemRead); # Limit length
3066 $ENV{'QUERY_STRING'} .= $Post;
3067 $SystemRead -= $QueryRead;
3069 # Update decoded Query String
3070 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
3071 $default_values{CGI_Content_Length} =
3072 length($default_values{CGI_Decoded_QS});
3076 # End of getting POST part of query
3079 ############################################################################
3081 # Start (HTML) output and logging
3082 # (if there are irregularities, it can kill the current process)
3085 sub Initialize_output
3087 # Construct the REAL file path (except for STDIN on the command line)
3088 my $file_path = $ENV{'PATH_INFO'} ne '-' ? $SS_PUB . $ENV{'PATH_INFO'} : '-';
3089 $file_path =~ s/\?.*$//; # Remove query
3090 # This is only necessary if your server does not catch ../ directives
3091 $file_path !~ m@\.\./@ || dieHandler(12, "Illegal ../ Construct\n"); # SECURITY: Do not allow ../ constructs
3093 # Block STDIN use (-) if CGIscriptor is servicing a HTTP request
3094 if($file_path eq '-')
3096 dieHandler(13, "STDIN request in On Line system\n") if $BLOCK_STDIN_HTTP_REQUEST
3097 && ($ENV{'SERVER_SOFTWARE'}
3098 || $ENV{'SERVER_NAME'}
3099 || $ENV{'GATEWAY_INTERFACE'}
3100 || $ENV{'SERVER_PROTOCOL'}
3101 || $ENV{'SERVER_PORT'}
3102 || $ENV{'REMOTE_ADDR'}
3103 || $ENV{'HTTP_USER_AGENT'});
3108 if($ClientLog)
3110 open(ClientLog, ">>$ClientLog");
3111 print ClientLog "$LocalTime | ",
3112 ($ENV{REMOTE_USER} || "-"), " ",
3113 ($ENV{REMOTE_IDENT} || "-"), " ",
3114 ($ENV{REMOTE_HOST} || "-"), " ",
3115 $ENV{REMOTE_ADDR}, " ",
3116 $ENV{PATH_INFO}, " ",
3117 $ENV{'CGI_BINARY_FILE'}, " ",
3118 ($default_values{CGI_Content_Length} || "-"),
3119 "\n";
3120 close(ClientLog);
3122 if($QueryLog)
3124 open(QueryLog, ">>$QueryLog");
3125 print QueryLog "$LocalTime\n",
3126 ($ENV{REMOTE_USER} || "-"), " ",
3127 ($ENV{REMOTE_IDENT} || "-"), " ",
3128 ($ENV{REMOTE_HOST} || "-"), " ",
3129 $ENV{REMOTE_ADDR}, ": ",
3130 $ENV{PATH_INFO}, " ",
3131 $ENV{'CGI_BINARY_FILE'}, "\n";
3133 # Write Query to Log file
3134 print QueryLog $default_values{CGI_Decoded_QS}, "\n\n";
3135 close(QueryLog);
3138 # Return the file path
3139 return $file_path;
3142 # End of Initialize output
3145 ############################################################################
3147 # Handle login access
3149 # Access is based on a valid session ticket.
3150 # Session tickets should be dependend on user name
3151 # and IP address. The patterns of URLs for which a
3152 # session ticket is needed and the login URL are stored in
3153 # %TicketRequiredPatterns as:
3154 # 'RegEx pattern' -> 'SessionPath\tPasswordPath\tLogin URL\tExpiration'
3157 sub Log_In_Access # () -> 0 = Access Allowed, Login page if access is not allowed
3159 # No patterns, no login
3160 goto Return unless %TicketRequiredPatterns;
3162 # Get and initialize values (watch out for stuff processed by BinaryMap files)
3163 my ($SessionPath, $PasswordsPath, $Login, $valid_duration) = ("", "", "", 0);
3164 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3165 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
3166 goto Return if $REMOTE_ADDR =~ /[^0-9\.]/;
3167 # Extract TICKETs, starting with returned cookies
3168 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3169 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3170 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3171 Get_All_Cookies();
3172 if(length(keys(%CGI_Cookies)) > 0)
3174 ${"CGIexecute::LOGINTICKET"} = $CGI_Cookies{'CGIscriptorLOGIN'}
3175 if $CGI_Cookies{'CGIscriptorLOGIN'} && $CGI_Cookies{'CGIscriptorLOGIN'} ne "-";
3176 $CGI_Cookies{'CGIscriptorLOGIN'} = "-";
3177 ${"CGIexecute::CHALLENGETICKET"} = $CGI_Cookies{'CGIscriptorCHALLENGE'}
3178 if $CGI_Cookies{'CGIscriptorCHALLENGE'} && $CGI_Cookies{'CGIscriptorCHALLENGE'} ne "-";
3179 $CGI_Cookies{'CGIscriptorCHALLENGE'} = "-";
3180 ${"CGIexecute::SESSIONTICKET"} = $CGI_Cookies{'CGIscriptorSESSION'}
3181 if $CGI_Cookies{'CGIscriptorSESSION'} && $CGI_Cookies{'CGIscriptorSESSION'} ne "-";
3182 $CGI_Cookies{'CGIscriptorSESSION'} = "-";
3184 # Get and check the tickets. Tickets are restricted to word-characters (alphanumeric+_+.)
3185 my $LOGINTICKET = ${"CGIexecute::LOGINTICKET"};
3186 goto Return if ($LOGINTICKET && $LOGINTICKET =~ /[^\w\.]/isg);
3187 my $SESSIONTICKET = ${"CGIexecute::SESSIONTICKET"};
3188 goto Return if ($SESSIONTICKET && $SESSIONTICKET =~ /[^\w\.]/isg);
3189 my $CHALLENGETICKET = ${"CGIexecute::CHALLENGETICKET"};
3190 goto Return if ($CHALLENGETICKET && $CHALLENGETICKET =~ /[^\w\.]/isg);
3191 # Look for a LOGOUT message
3192 my $LOGOUT = $ENV{QUERY_STRING} =~ /(^|\&)LOGOUT([\=\&]|$)/;
3193 # Username and password
3194 CGIexecute::defineCGIvariable('CGIUSERNAME', "");
3195 my $username = lc(${"CGIexecute::CGIUSERNAME"});
3196 goto Return if $username =~ m!^[^\w]!isg || $username =~ m![^\w \-]!isg;
3197 my $userfile = lc($username);
3198 $userfile =~ s/[^\w]/_/isg;
3199 CGIexecute::defineCGIvariable('PASSWORD', "");
3200 my $password = ${"CGIexecute::PASSWORD"};
3201 CGIexecute::defineCGIvariable('NEWUSERNAME', "");
3202 my $newuser = lc(${"CGIexecute::NEWUSERNAME"});
3203 CGIexecute::defineCGIvariable('NEWPASSWORD', "");
3204 my $newpassword = ${"CGIexecute::NEWPASSWORD"};
3206 foreach my $pattern (keys(%TicketRequiredPatterns))
3208 # Check BOTH the real PATH_INFO and the CGI_BINARY_FILE variable
3209 if($ENV{'PATH_INFO'} =~ m#$pattern# || $ENV{'CGI_BINARY_FILE'} =~ m#$pattern#)
3211 # Fall through a sieve of requirements
3212 ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3213 # If a LOGOUT is present, remove everything
3214 if($LOGOUT && !$LOGINTICKET)
3216 unlink "$SessionPath/$LOGINTICKET" if $LOGINTICKET && (-s "$SessionPath/$LOGINTICKET");
3217 $LOGINTICKET = "";
3218 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
3219 $SESSIONTICKET = "";
3220 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
3221 $CHALLENGETICKET = "";
3222 unlink "$SessionPath/$REMOTE_ADDR" if (-s "$SessionPath/$REMOTE_ADDR");
3223 $CHALLENGETICKET = "";
3224 goto Login;
3226 # Is there a change password request?
3227 if($newuser && $LOGINTICKET && $username)
3229 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3230 goto Login unless (-s "$PasswordsPath/$userfile");
3231 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3232 goto Login unless $ticket_valid;
3233 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".", 1);
3234 goto Login unless $ticket_valid;
3236 my ($sessiontype, $currentticket) = ("", "");
3237 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
3238 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
3239 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
3241 if($sessiontype)
3243 goto Login unless (-s "$SessionPath/$currentticket");
3244 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3245 goto Login unless $ticket_valid;
3247 # Authorize
3248 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath);
3249 goto Login unless $TMPTICKET;
3251 # Create a new user account
3252 CGIexecute::defineCGIvariable('NEWSESSION', "");
3253 my $newsession = ${"CGIexecute::NEWSESSION"};
3254 my $newaccount = create_newuser("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket",
3255 "$PasswordsPath/$userfile", $password, $newuser, $newpassword, $newsession);
3256 CGIexecute::defineCGIvariable('NEWACCOUNTTEXT', $newaccount);
3257 ${CGIexecute::NEWACCOUNTTEXT} = $newaccount;
3258 # NEWACCOUNTTEXT is NOT to be set by the query
3259 CGIexecute::ProtectCGIvariable('NEWACCOUNTTEXT');
3262 # Ready
3263 goto Return;
3265 # Is there a change password request?
3266 elsif($newpassword && $LOGINTICKET && $username)
3268 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3269 goto Login unless (-s "$PasswordsPath/$userfile");
3270 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3271 goto Login unless $ticket_valid;
3272 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".", 1);
3273 goto Login unless $ticket_valid;
3275 my ($sessiontype, $currentticket) = ("", "");
3276 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
3277 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
3278 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
3280 if($sessiontype)
3282 goto Login unless (-s "$SessionPath/$currentticket");
3283 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3284 goto Login unless $ticket_valid;
3286 # Authorize
3287 change_password("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket", "$PasswordsPath/$userfile", $password, $newpassword);
3288 # After a change of password, you have to login again for a CHALLENGE
3289 if($CHALLENGETICKET){$CHALLENGETICKET = "";};
3290 # Ready
3291 goto Return;
3293 # Is there a login ticket of this name?
3294 elsif($LOGINTICKET)
3296 my $tickets_removed = remove_expired_tickets($SessionPath);
3297 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3298 goto Login unless (-s "$PasswordsPath/$userfile");
3299 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3300 goto Login unless $ticket_valid;
3301 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".");
3302 goto Login unless $ticket_valid;
3304 # Remove any lingering tickets
3305 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
3306 $SESSIONTICKET = "";
3307 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
3308 $CHALLENGETICKET = "";
3311 # Authorize
3312 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath);
3313 if($TMPTICKET)
3315 my $authorization = read_ticket("$PasswordsPath/$userfile");
3316 goto Login unless $authorization;
3317 # Session type is read from the userfile
3318 if($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "CHALLENGE")
3320 # Create New Random CHALLENGETICKET
3321 $CHALLENGETICKET = $TMPTICKET;
3322 create_session_file("$SessionPath/$CHALLENGETICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3324 elsif($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "IPADDRESS")
3326 create_session_file("$SessionPath/$REMOTE_ADDR", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3328 else
3330 # Extra hash to protect CHALLENGETICKET use
3331 $SESSIONTICKET = hash_string($TMPTICKET);
3332 create_session_file("$SessionPath/$SESSIONTICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3333 $SETCOOKIELIST{"CGIscriptorSESSION"} = "-";
3334 $TMPTICKET = $SESSIONTICKET;
3337 # Login ticket file has been used, remove it
3338 unlink($loginfile);
3340 # Is there a session ticket of this name?
3341 # CHALLENGE
3342 if($CHALLENGETICKET)
3344 # Do not log into a CHALLENGE account if the SESSION cookie is present
3345 # Uncomment when $SESSIONTICKET does not receive an extra hash
3346 #goto Login if $SESSIONTICKET =~ /\S/;
3347 goto Login unless (-s "$SessionPath/$CHALLENGETICKET");
3348 my $ticket_valid = check_ticket_validity("CHALLENGE", "$SessionPath/$CHALLENGETICKET", $REMOTE_ADDR, $PATH_INFO);
3349 goto Login unless $ticket_valid;
3351 my $oldchallenge = read_ticket("$SessionPath/$CHALLENGETICKET");
3352 goto Login unless $oldchallenge;
3353 # Check whether the login still exists
3354 my $userfile = lc($oldchallenge->{"Username"}->[0]);
3355 $userfile =~ s/[^\w]/_/isg;
3356 goto Login unless (-s "$PasswordsPath/$userfile");
3358 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3359 goto Login unless $ticket_valid;
3361 my $NEWCHALLENGETICKET = "";
3362 $NEWCHALLENGETICKET = copy_challenge_file("$SessionPath/$CHALLENGETICKET", "$PasswordsPath/$userfile", $SessionPath);
3363 # Sessionticket is available to scripts, do NOT set the cookie
3364 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3365 goto Return;
3367 # IPADDRESS
3368 elsif(-s "$SessionPath/$REMOTE_ADDR")
3370 my $ticket_valid = check_ticket_validity("IPADDRESS", "$SessionPath/$REMOTE_ADDR", $REMOTE_ADDR, $PATH_INFO);
3371 goto Login unless $ticket_valid;
3372 # Check whether the login still exists
3373 my $currentsessionticket = read_ticket("$SessionPath/$REMOTE_ADDR");
3374 my $userfile = lc($currentsessionticket->{"Username"}->[0]);
3375 $userfile =~ s/[^\w]/_/isg;
3376 goto Login unless (-s "$PasswordsPath/$userfile");
3378 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3379 goto Login unless $ticket_valid;
3381 goto Return;
3383 # SESSION
3384 elsif($SESSIONTICKET)
3386 goto Login unless (-s "$SessionPath/$SESSIONTICKET");
3387 my $ticket_valid = check_ticket_validity("SESSION", "$SessionPath/$SESSIONTICKET", $REMOTE_ADDR, $PATH_INFO);
3388 goto Login unless $ticket_valid;
3390 # Check whether the login still exists
3391 my $currentsessionticket = read_ticket("$SessionPath/$SESSIONTICKET");
3392 my $userfile = lc($currentsessionticket->{"Username"}->[0]);
3393 $userfile =~ s/[^\w]/_/isg;
3394 goto Login unless (-s "$PasswordsPath/$userfile");
3396 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3397 goto Login unless $ticket_valid;
3399 # Sessionticket is available to scripts
3400 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3401 goto Return;
3404 goto Login;
3405 goto Return;
3408 Return:
3409 # The Masterkey should NOT be accessible by the parsed files
3410 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3411 return 0;
3413 Login:
3414 create_login_file($PasswordsPath, $SessionPath, $REMOTE_ADDR);
3415 # Note, cookies are set only ONCE
3416 $SETCOOKIELIST{"CGIscriptorLOGIN"} = "-";
3417 # The Masterkey should NOT be accessible by the parsed files
3418 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3419 return "$YOUR_HTML_FILES/$Login";
3422 sub authorize_login # ($loginfile, $authorizationfile, $password, $SessionPath) => SESSIONTICKET First two arguments are file paths
3424 my $loginfile = shift || "";
3425 my $authorizationfile = shift || "";
3426 my $password = shift || "";
3427 my $SessionPath = shift || "";
3429 # Get Login session ticket
3430 my $loginticket = read_ticket($loginfile);
3431 return 0 unless $loginticket;
3432 # Get User credentials for authorization
3433 my $authorization = read_ticket($authorizationfile);
3434 return 0 unless $authorization;
3436 # Get Randomsalt
3437 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3438 return "" unless $Randomsalt;
3440 my $storedpassword = $authorization->{'Password'}->[0];
3441 return "" unless $storedpassword;
3442 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3443 return "" unless $password eq $Hashedpassword;
3445 # Extract Session Ticket
3446 my $loginsession = $loginticket->{'Session'}->[0];
3447 my $sessionticket = hash_string($storedpassword.$loginsession);
3448 chomp($sessionticket);
3449 $sessionticket = "" if -x "$SessionPath/$sessionticket";
3451 # No lingering password variables
3452 $Hashedpassword = $Randomsalt;
3453 $password = $Randomsalt;
3454 $authorization->{'Password'}->[0] = $Randomsalt;
3456 return $sessionticket;
3459 sub change_password # ($loginfile, $sessionfile, $authorizationfile, $password, $newpassword) First three arguments are file paths
3461 my $loginfile = shift || "";
3462 my $sessionfile = shift || "";
3463 my $authorizationfile = shift || "";
3464 my $password = shift || "";
3465 my $newpassword = shift || "";
3466 # Get Login session ticket
3467 my $loginticket = read_ticket($loginfile);
3468 return "" unless $loginticket;
3469 # Login ticket file has been used, remove it
3470 unlink($loginfile);
3471 # Get Randomsalt
3472 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3473 return "" unless $Randomsalt;
3474 my $LoginID = $loginticket->{'Session'}->[0];
3475 return "" unless $LoginID;
3477 # Get session ticket
3478 my $sessionticket = read_ticket($sessionfile);
3479 return "" unless $sessionticket;
3481 # Get User credentials for authorization
3482 my $authorization = read_ticket($authorizationfile);
3483 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3485 my $storedpassword = $authorization->{'Password'}->[0];
3486 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3487 return "" unless $password eq $Hashedpassword;
3488 my $secretkey = hash_string($storedpassword.$LoginID.$Randomsalt);
3490 # Decrypt the $newpassword
3491 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3492 return "" unless $decryptedPassword;
3493 # Authorization succeeded, change password
3494 $authorization->{'Password'}->[0] = $decryptedPassword;
3495 # Write out
3496 write_ticket($authorizationfile, $authorization, $authorization->{'Salt'}->[0]);
3498 # No lingering password variables
3499 $decryptedPassword = $Randomsalt;
3500 $secretkey = $Randomsalt;
3501 $storedpassword = $Randomsalt;
3502 $Hashedpassword = $Randomsalt;
3503 $authorization->{'Password'}->[0] = $Randomsalt;
3505 return $newpassword;
3507 # First three arguments are file paths
3508 sub create_newuser # ($loginfile, $sessionfile, $authorizationfile, $password, $newuser, $newpassword, $newsession) -> account text
3510 my $loginfile = shift || "";
3511 my $sessionfile = shift || "";
3512 my $authorizationfile = shift || "";
3513 my $password = shift || "";
3514 my $newuser = shift || "";
3515 my $newpassword = shift || "";
3516 my $newsession = shift || "";
3518 # Get Login session ticket
3519 my $loginticket = read_ticket($loginfile);
3520 return "" unless $loginticket;
3521 # Login ticket file has been used, remove it
3522 unlink($loginfile);
3523 # Get Randomsalt
3524 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3525 return "" unless $Randomsalt;
3526 my $LoginID = $loginticket->{'Session'}->[0];
3527 return "" unless $LoginID;
3529 # Get session ticket
3530 my $sessionticket = read_ticket($sessionfile);
3531 return "" unless $sessionticket;
3532 # Get User credentials for authorization
3533 my $authorization = read_ticket($authorizationfile);
3534 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3535 my $sessionkey = $sessionticket->{'Key'}->[0];
3536 my $serversalt = $authorization->{'Salt'}->[0];
3537 return "" unless $serversalt;
3539 my $storedpassword = $authorization->{'Password'}->[0];
3540 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3541 return "" unless $password eq $Hashedpassword;
3542 my $secretkey = hash_string($storedpassword.$LoginID.$Randomsalt);
3544 # Decrypt the $newpassword
3545 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3546 return "" unless $decryptedPassword;
3548 # Authorization succeeded, create new account
3549 my $newaccount = {};
3550 $newaccount->{'Type'} = ['PASSWORD'];
3551 $newaccount->{'Username'} = [$newuser];
3552 $newaccount->{'Password'} = [$decryptedPassword];
3553 $newaccount->{'Salt'} = [$serversalt];
3554 $newaccount->{'Session'} = ['SESSION'];
3555 if($newsession eq 'IPADDRESS'){$newaccount->{'Session'} = ['IPADDRESS'];};
3556 if($newsession eq 'CHALLENGE'){$newaccount->{'Session'} = ['CHALLENGE'];};
3557 my $timesec = time();
3558 my $gmt_date = gmtime();
3559 $newaccount->{'Time'} = [$timesec];
3560 $newaccount->{'Date'} = [$gmt_date];
3562 # AllowedPaths
3563 my $NewAllowedPaths = "";
3564 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3565 my $currentRoot = "";
3566 $currentRoot = $1 if $PATH_INFO =~ m!^([\w\-\. /]+)!isg;
3567 $currentRoot =~ s![^/]+$!!isg;
3568 if($currentRoot)
3570 $currentRoot .= '/' unless $currentRoot =~ m!/$!;
3571 my $newpath = "^".${currentRoot}.'[\w\-]+\.html?';
3572 $NewAllowedPaths .= 'AllowedPaths: ^'.${currentRoot}.'[\w\-]+\.html?'."\n";
3573 $newaccount->{'AllowedPaths'} = [$newpath];
3575 else
3577 # Tricky PATH_INFO, deny all
3578 $NewAllowedPaths .= "DeniedPaths: ^/\n";
3579 $newaccount->{'DeniedPaths'} = ["DeniedPaths: ^/\n"];
3582 # Construct home directory path
3583 my $FullHomeDirectoryPath = "";
3584 my $currentHome = lc($newuser);
3585 if($currentHome && $currentHome !~ /^\s*\#/)
3587 $currentHome =~ s![^\w]!_!isg;
3588 my $newpath = "^${currentRoot}$currentHome/";
3589 push(@{$newaccount->{'AllowedPaths'}}, $newpath);
3590 # Create home directory
3591 $FullHomeDirectoryPath = $ENV{'HOME'}.${currentRoot}.$currentHome;
3594 # Allowed Paths
3595 CGIexecute::defineCGIvariable('ALLOWEDPATHS', "");
3596 my $allowedpaths = ${"CGIexecute::ALLOWEDPATHS"};
3597 if($allowedpaths && $allowedpaths !~ /^\s*\#/)
3599 $allowedpaths =~ s![^\^\w\./\;\+\*\?\[\]\$]!!isg;
3600 my @pathlist = split(/\;/, $allowedpaths);
3601 foreach my $entry (@pathlist)
3603 push(@{$newaccount->{'AllowedPaths'}}, "^".${currentRoot}.$entry);
3607 # Allowed IP addresses
3608 CGIexecute::defineCGIvariable('IPADDRESS', "");
3609 my $ipaddress = ${"CGIexecute::IPADDRESS"};
3610 if($ipaddress && $ipaddress !~ /^\s*\#/)
3612 $ipaddress =~ s![^\d\.\;]!!isg;
3613 my @iplist = split(/\;/, $ipaddress);
3614 foreach my $entry (@iplist)
3616 next unless $entry =~ /\d/;
3617 next if $entry =~ /^\s*\#/;
3618 $entry =~ s/\./\\./g;
3619 push(@{$newaccount->{'IPaddress'}}, $entry);
3623 # Sign the new ticket
3624 my $Signature = SignTicketWithMasterkey($newaccount, $newaccount->{'Salt'}->[0]);
3626 # Write
3627 my $datetime = gmtime();
3628 my $newuserfile = "";
3629 if(grep(/^CreateUser$/, @{$authorization->{'Capabilities'}}))
3631 my $newuserfilename = lc($newuser);
3632 $newuserfilename =~ s/[^\w]/_/isg;
3633 $newuserfile = $authorizationfile;
3634 $newuserfile =~ s![^/]*$!!isg;
3635 $newuserfile .= $newuserfilename;
3636 if(-s $newuserfile)
3638 $newuserfile = "";
3640 elsif($FullHomeDirectoryPath && !(-d $FullHomeDirectoryPath || -s $FullHomeDirectoryPath))
3642 if(-d "$ENV{'HOME'}${currentRoot}.SkeletonDir")
3644 `cp -r '$ENV{'HOME'}${currentRoot}.SkeletonDir' '$FullHomeDirectoryPath'`;
3646 elsif(-d "$ENV{'HOME'}${currentRoot}SkeletonDir")
3648 `cp -r '$ENV{'HOME'}${currentRoot}SkeletonDir' '$FullHomeDirectoryPath'`;
3650 elsif(-s "$ENV{'HOME'}${currentRoot}UserIndex.html")
3652 mkdir $FullHomeDirectoryPath;
3653 `cp '$ENV{'HOME'}${currentRoot}UserIndex.html' '$FullHomeDirectoryPath/index.html'`;
3655 elsif(-s "$ENV{'HOME'}${currentRoot}index.html")
3657 mkdir $FullHomeDirectoryPath;
3658 `cp '$ENV{'HOME'}${currentRoot}index.html' '$FullHomeDirectoryPath/index.html'`;
3664 my $newaccounttext = write_ticket($newuserfile, $newaccount, $serversalt);
3666 # Re-encrypt the new password for transmission
3667 if($newaccounttext =~ /^(Password\:\s+)(\S+)\s*$/)
3669 my $passwordvalue = $1;
3670 my $reencryptedpassword = XOR_hex_strings($secretkey, $passwordvalue);
3671 my $encryptedpasswordline = "<span id='newaccount'>$reencryptedpassword</span>";
3672 $newaccounttext =~ s/^(Password\:\s+)(\S+)\s*$/\1$encryptedpasswordline/gim;
3674 # No lingering passwords
3675 $passwordvalue = $serversalt;
3677 return $newaccounttext;
3680 # Copy a Challenge ticket file to a new name which is the hash of the new $CHALLENGETICKET and the password
3681 sub copy_challenge_file #($oldchallengefile, $authorizationfile, $sessionpath) -> $CHALLENGETICKET
3683 my $oldchallengefile = shift || "";
3684 my $authorizationfile = shift || "";
3685 my $sessionpath = shift || "";
3686 $sessionpath =~ s!/+$!!g;
3688 # Get Login session ticket
3689 my $oldchallenge = read_ticket($oldchallengefile);
3690 return "" unless $oldchallenge;
3692 # Get Authorization (user) session file
3693 my $authorization = read_ticket($authorizationfile);
3694 return "" unless $authorization;
3695 my $storedpassword = $authorization->{'Password'}->[0];
3696 return "" unless $storedpassword;
3697 my $challengekey = $oldchallenge->{'Key'}->[0];
3698 return "" unless $challengekey;
3700 # Create Random Hash Salt
3701 my $NEWCHALLENGETICKET = get_random_hex();;
3702 my $newchallengefile = hash_string($challengekey.$NEWCHALLENGETICKET);
3703 return "" unless $newchallengefile;
3705 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3706 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3707 ${"CGIexecute::CHALLENGETICKET"} = $NEWCHALLENGETICKET;
3709 # Write Session Ticket
3710 open(OLDCHALLENGE, "<$oldchallengefile") || die "<$oldchallengefile: $!\n";
3711 my @OldChallengeLines = <OLDCHALLENGE>;
3712 close(OLDCHALLENGE);
3713 # Old file should now be removed
3714 unlink($oldchallengefile);
3716 open(SESSION, ">$sessionpath/$newchallengefile") || die "$sessionpath/$newchallengefile: $!\n";
3717 foreach $line (@OldChallengeLines)
3719 print SESSION $line;
3721 close(SESSION);
3723 # No lingering passwords
3724 $storedpassword = $oldchallenge;
3726 return $NEWCHALLENGETICKET;
3729 sub create_login_file #($PasswordDir, $SessionDir, $IPaddress)
3731 my $PasswordDir = shift || "";
3732 my $SessionDir = shift || "";
3733 my $IPaddress = shift || "";
3735 # Create Login Ticket
3736 my $LOGINTICKET= get_random_hex ();
3738 # Create Random Hash Salt
3739 my $RANDOMSALT= get_random_hex();
3741 # Create SALT file if it does not exist
3742 # Remove this, including test account for life system
3743 unless(-d "$SessionDir")
3745 `mkdir -p "$SessionDir"`;
3747 unless(-d "$PasswordDir")
3749 `mkdir -p "$PasswordDir"`;
3751 # Create SERVERSALT and default test account
3752 my $SERVERSALT = "";
3753 unless(-s "$PasswordDir/SALT")
3755 $SERVERSALT= get_random_hex();
3756 open(SALTFILE, ">$PasswordDir/SALT") || die ">$PasswordDir/SALT: $!\n";
3757 print SALTFILE "$SERVERSALT\n";
3758 close(SALTFILE);
3760 # Update test account (should be removed in live system)
3761 my @alltestusers = ("test", "testip", "testchallenge", "admin");
3762 foreach my $testuser (@alltestusers)
3764 if(-s "$PasswordDir/$testuser")
3766 my $plainpassword = $testuser eq 'admin' ? "There is no password like more password" : "testing";
3768 my $storedpassword = hash_string(${plainpassword}.${testuser}.${SERVERSALT});
3769 # Encrypt the new password with the MasterKey
3770 my $authorization = read_ticket("$PasswordDir/$testuser") || return "";
3771 $authorization->{'Salt'} = [$SERVERSALT];
3772 $authorization->{'Type'} = ['INACTIVE PASSWORD'] if $testuser eq 'admin';
3773 set_password($authorization, $SERVERSALT, $plainpassword);
3774 write_ticket("$PasswordDir/$testuser", $authorization, $SERVERSALT);
3775 # No lingering passwords
3776 $storedpassword = $SERVERSALT;
3777 $plainpassword = $SERVERSALT;
3782 # Read in site Salt
3783 open(SALTFILE, "<$PasswordDir/SALT") || die "$PasswordDir/SALT: $!\n";
3784 $SERVERSALT=<SALTFILE>;
3785 close(SALTFILE);
3786 chomp($SERVERSALT);
3788 # Create login session ticket
3789 my $datetime = gmtime();
3790 my $timesec = time();
3791 my $loginticket = {};
3792 $loginticket->{Type} = ['LOGIN'];
3793 $loginticket->{IPaddress} = [$IPaddress];
3794 $loginticket->{Salt} = [$SERVERSALT];
3795 $loginticket->{Session} = [$LOGINTICKET];
3796 $loginticket->{Randomsalt} = [$RANDOMSALT];
3797 $loginticket->{Expires} = ['+600s'];
3798 $loginticket->{Date} = ["$datetime UTC"];
3799 $loginticket->{Time} = [$timesec];
3800 write_ticket("$SessionDir/$LOGINTICKET", $loginticket, $SERVERSALT);
3802 # Set global variables
3803 # $SERVERSALT
3804 $ENV{'SERVERSALT'} = $SERVERSALT;
3805 CGIexecute::defineCGIvariable('SERVERSALT', "");
3806 ${"CGIexecute::SERVERSALT"} = $SERVERSALT;
3808 # $SESSIONTICKET
3809 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3810 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3811 ${"CGIexecute::SESSIONTICKET"} = $SESSIONTICKET;
3813 # $RANDOMSALT
3814 $ENV{'RANDOMSALT'} = $RANDOMSALT;
3815 CGIexecute::defineCGIvariable('RANDOMSALT', "");
3816 ${"CGIexecute::RANDOMSALT"} = $RANDOMSALT;
3818 # $LOGINTICKET
3819 $ENV{'LOGINTICKET'} = $LOGINTICKET;
3820 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3821 ${"CGIexecute::LOGINTICKET"} = $LOGINTICKET;
3823 return $ENV{'LOGINTICKET'};
3826 sub create_session_file #($sessionfile, $loginfile, $authorizationfile, $path) -> Is $loginfile deleted? 0/1
3828 my $sessionfile = shift || "";
3829 my $loginfile = shift || "";
3830 my $authorizationfile = shift || "";
3831 my $path = shift || "";
3833 # Get Login session ticket
3834 my $loginticket = read_ticket($loginfile);
3835 return unlink($loginfile) unless $loginticket;
3837 # Get Authorization (user) session file
3838 my $authorization = read_ticket($authorizationfile);
3839 return unlink($loginfile) unless $authorization;
3841 # For a Session or a Challenge, we need a stored key
3842 my $sessionkey = "";
3843 my $secretkey = "";
3844 if($authorization->{'Session'} && $authorization->{'Session'}->[0] ne 'IPADDRESS')
3846 my $storedpassword = $authorization->{'Password'}->[0];
3847 my $loginticketid = $loginticket->{'Session'}->[0];
3848 my $randomsalt = $loginticket->{'Randomsalt'}->[0];
3849 $sessionkey = hash_string($storedpassword.$loginticketid);
3850 $secretkey = hash_string($storedpassword.$loginticketid.$randomsalt);
3851 # No lingering passwords
3852 $storedpassword = $loginticketid;
3854 # Get Session id
3855 my $sessionid = "";
3856 if($sessionfile =~ m!([^/]+)$!)
3858 $sessionid = $1;
3861 # Convert Authorization content to Session content
3862 my $sessionContent = {};
3863 my $SessionType = $authorization->{'Session'}->[0] ? $authorization->{'Session'}->[0] : "SESSION";
3864 $sessionContent->{Type} = [$SessionType];
3865 $sessionContent->{Username} = [lc($authorization->{'Username'}->[0])];
3866 $sessionContent->{Session} = [$sessionid];
3867 $sessionContent->{Time} = [time];
3868 # Limit communication to the login IP address, except for Tor like situations with VariableREMOTE_ADDR
3869 $sessionContent->{IPaddress} = ['.'];
3870 if($sessionContent->{Type}->[0] eq 'CHALLENGE' && grep(/^VariableREMOTE_ADDR$/, @{$authorization->{'Capabilities'}}))
3872 $sessionContent->{IPaddress} = $authorization->{'IPaddress'} if $authorization->{'IPaddress'};
3874 else
3876 $sessionContent->{IPaddress} = $loginticket->{'IPaddress'};
3878 $sessionContent->{Salt} = $authorization->{'Salt'};
3879 $sessionContent->{Randomsalt} = $loginticket->{'Randomsalt'};
3880 $sessionContent->{AllowedPaths} = $authorization->{'AllowedPaths'};
3881 $sessionContent->{DeniedPaths} = $authorization->{'DeniedPaths'};
3882 $sessionContent->{Expires} = $authorization->{'MaxLifetime'};
3883 $sessionContent->{Capabilities} = $authorization->{'Capabilities'};
3884 foreach my $pattern (keys(%TicketRequiredPatterns))
3886 if($path =~ m#$pattern#)
3888 my ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3889 push(@{$sessionContent->{Expires}}, $validtime);
3892 $sessionContent->{Key} = [$sessionkey] if $sessionkey;
3893 $sessionContent->{Secretkey} = [$secretkey] if $secretkey;
3894 $sessionContent->{Date} = [gmtime()." UTC"];
3896 # Write Session Ticket
3897 write_ticket($sessionfile, $sessionContent, $authorization->{'Salt'}->[0]);
3899 # Login file should now be removed
3900 return unlink($loginfile);
3903 sub check_ticket_validity # ($type, $ticketfile, $address, $path [, $unsigned])
3905 my $type = shift || "SESSION";
3906 my $ticketfile = shift || "";
3907 my $address = shift || "";
3908 my $path = shift || "";
3909 my $unsigned = shift || 0;
3911 # Is there a session ticket of this name?
3912 return 0 unless -s "$ticketfile";
3914 # There is a session ticket, is it linked to this IP address?
3915 my $ticket = read_ticket($ticketfile);
3916 unless($ticket)
3918 print STDERR "Ticket expired or empty: $ticketfile\n";
3919 return;
3922 # Is this the right type of ticket
3923 unless($ticket && $ticket->{'Type'}->[0] eq $type)
3925 print STDERR "Wrong ticket type: $ticket->{'Type'}->[0] eq $type\n";
3926 return;
3929 # Does the IP address match?
3930 my $IPmatches = @{$ticket->{"IPaddress"}} ? 0 : 1;
3931 for $IPpattern (@{$ticket->{"IPaddress"}})
3933 ++$IPmatches if $address =~ m#^$IPpattern#ig;
3935 if($address && ! $IPmatches)
3937 print STDERR "Wrong REMOTE ADDR for $ticket->{'Username'}->[0]: $ticket->{'IPaddress'}->[0] vs $address\n";
3938 return 0;
3941 # Is the path denied
3942 my $Pathmatches = 0;
3943 foreach $Pathpattern (@{$ticket->{"DeniedPaths"}})
3945 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3947 return 0 if @{$ticket->{"DeniedPaths"}} && $Pathmatches;
3949 # Is the path allowed
3950 $Pathmatches = 0;
3951 foreach $Pathpattern (@{$ticket->{"AllowedPaths"}})
3953 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3955 return 0 unless !@{$ticket->{"AllowedPaths"}} || $Pathmatches;
3957 # Check signature if not told to use an unsigned ticket (dangerous)
3958 my $Signature = TicketSignature($ticket, $ticket->{'Salt'}->[0]);
3959 if((! $unsigned) && $Signature && $Signature ne $ticket->{'Signature'}->[0])
3961 print STDERR "Invalid signature for $ticket->{'Type'}: $ticket->{'Username'}\n$ticketfile\n";
3962 return 0;
3965 # Make login values available (will also protect against resetting by query)
3966 $ENV{"LOGINUSERNAME"} = lc($ticket->{'Username'}->[0]);
3967 $ENV{"LOGINIPADDRESS"} = $address;
3968 $ENV{"LOGINPATH"} = $path;
3969 $ENV{"SESSIONTYPE"} = $type unless $type eq "PASSWORD";
3971 # Set Capabilities, if present
3972 if($ticket->{'Username'}->[0] && @{$ticket->{'Capabilities'}})
3974 $ENV{'CAPABILITIES'} = $ticket->{'Username'}->[0];
3975 CGIexecute::defineCGIvariableList('CAPABILITIES', "");
3976 @{"CGIexecute::CAPABILITIES"} = @{$ticket->{'Capabilities'}};
3977 # Capabilities should not be changed anymore by CGI query!
3979 # Capabilities are NOT to be set by the query
3980 CGIexecute::ProtectCGIvariable('CAPABILITIES');
3982 return 1;
3986 sub remove_expired_tickets # ($path) -> number of tickets removed
3988 my $path = shift || "";
3989 return 0 unless $path;
3990 $path =~ s!/+$!!g;
3991 my $removed_tickets = 0;
3992 my @ticketlist = glob("$path/*");
3993 foreach my $ticketfile (@ticketlist)
3995 my $ticket = read_ticket($ticketfile);
3996 unless($ticket)
3998 unlink $ticketfile;
3999 ++$removed_tickets;
4002 return $removed_tickets;
4005 sub set_password # ($ticket, $salt, $plainpassword) -> $password
4007 my $ticket = shift || "";
4008 my $salt = shift || "";
4009 my $plainpassword = shift || "";
4011 my $user = lc($ticket->{'Username'}->[0]);
4012 return "" unless $user;
4013 $salt = $ticket->{'Salt'}->[0] unless $salt;
4015 my $storedpassword = hash_string(${plainpassword}.${user}.${salt});
4016 $ticket->{'Password'} = [$storedpassword];
4017 $ticket->{'Salt'} = [$salt];
4018 # No lingering passwords
4019 $storedpassword = $salt;
4020 $plainpassword = $salt;
4022 return $ticket->{'Password'}->[0];
4025 sub write_ticket # ($ticketfile, $ticket, $salt [, $masterkey]) -> &%ticket
4027 my $ticketfile = shift || "";
4028 my $ticket = shift || "";
4029 my $salt = shift || "";
4030 my $masterkey = shift || $ENV{'CGIMasterKey'};
4032 # Encrypt password
4033 EncryptTicketWithMasterKey($ticket, $salt, $masterkey);
4035 # Sign the new ticket
4036 my $signature = SignTicketWithMasterkey($ticket, $salt, $masterkey);
4038 # Create ordered list with labels
4039 my @orderlist = ('Type', 'Username', 'Password', 'IPaddress', 'AllowedPaths', 'DeniedPaths',
4040 'Expires', 'Capabilities', 'Salt', 'Session', 'Randomsalt',
4041 'Date', 'Time', 'Signature', 'Key', 'Secretkey');
4042 my @labellist = keys(%{$ticket});
4043 foreach my $label (@orderlist)
4045 @labellist = grep(!/\b$label\b/, @labellist);
4048 # Create ticket in text
4049 my $TicketText = "";
4050 foreach my $label (@orderlist, @labellist)
4052 next unless exists($ticket->{$label}) && $ticket->{$label}->[0];
4053 foreach my $value (@{$ticket->{$label}})
4055 $TicketText .= "$label: $value\n";
4058 if($ticketfile)
4060 open(TICKET, ">$ticketfile") || die "$ticketfile: $!\n";
4061 print TICKET $TicketText;
4062 close(TICKET);
4065 return $TicketText;
4068 # Note, read_ticket will return 0 if the ticket has expired!
4069 sub read_ticket # ($ticketfile [, $salt, $masterkey]) -> &%ticket
4071 my $ticketfile = shift || "";
4072 my $serversalt = shift || "";
4073 my $masterkey = shift || $ENV{'CGIMasterKey'};
4075 my $ticket = {};
4076 if($ticketfile && -s $ticketfile)
4078 open(TICKETFILE, "<$ticketfile") || die "$ticketfile: $!\n";
4079 my @alllines = <TICKETFILE>;
4080 close(TICKETFILE);
4081 foreach my $currentline (@alllines)
4083 # Skip empty lines and comments
4084 next unless $currentline =~ /\S/;
4085 next if $currentline =~ /^\s*\#/;
4087 if($currentline =~ /^\s*(\S[^\:]+)\:\s+(.*)\s*$/)
4089 my $Label = $1;
4090 my $Value = $2;
4091 $ticket->{$Label} = () unless exists($ticket->{$Label});
4092 push(@{$ticket->{$Label}}, $Value);
4096 if($masterkey && exists($ticket->{'Password'}) && $ticket->{'Password'}->[0])
4098 # Use the ServerSalt stored in the ticket, if present
4099 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4101 $serversalt = $ticket->{Salt}->[0];
4103 # Decrypt all passwords
4104 DecryptTicketWithMasterKey($ticket, $serversalt, $masterkey) ||
4105 die "Decryption failed: DecryptTicketWithMasterKey ($ticket, $serversalt)\n";
4108 # Check whether the ticket has expired
4109 if(exists($ticket->{Expires}))
4111 my $StartTime = 0;
4112 if(exists($ticket->{Time}) && $ticket->{Time}->[0] > 0)
4114 $StartTime = [(sort(@{$ticket->{Time}}))]->[0];
4116 else
4118 # Get SessionTicket file stats
4119 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)
4120 = stat($ticketfile);
4121 $StartTime = $ctime;
4123 foreach my $Value (@{$ticket->{'Expires'}})
4125 # Recalculate expire date from relative time
4126 if($Value =~ /^\+/)
4128 if($Value =~ /^\+(\d+)\s*d(ays)?\s*$/)
4130 $ExpireTime = 24*3600*$1;
4132 elsif($Value =~ /^\+(\d+)\s*m(inutes)?\s*$/)
4134 $ExpireTime = 60*$1;
4136 elsif($Value =~ /^\+(\d+)\s*h(ours)?\s*$/)
4138 $ExpireTime = 3600*$1;
4140 elsif($Value =~ /^\+(\d+)\s*s(econds)?\s*$/)
4142 $ExpireTime = $1;
4144 elsif($Value =~ /^\+(\d+)\s*$/)
4146 $ExpireTime = $1;
4149 my $absoluteTime = $Value =~ /^\+/ ? $StartTime + $ExpireTime : $Value;
4150 return 0 unless $absoluteTime > time;
4152 @{$ticket->{Expires}} = sort(@{$ticket->{Expires}});
4154 return $ticket;
4157 # Set up a valid ticket from a given text file
4158 # Use from command line. DO NOT USE ONLINE
4159 # Watch out for passwords that get stored in the history file
4161 # perl CGIscriptor.pl --managelogin [options] [files]
4162 # Options:
4163 # salt={file or saltvalue}
4164 # masterkey={file or plaintext}
4165 # newmasterkey={file or plaintext}
4166 # password={file or palintext}
4168 # Followed by one or more file names.
4169 # Options can be interspersed between filenames,
4170 # e.g., password='plaintext'
4171 # Note that passwords are only used once!
4173 sub setup_ticket_file # (@ARGV)
4175 # Stop when run on-line
4176 return if $ENV{'PATH_INFO'} || $ENV{'QUERY_STRING'};
4178 my %Settings = ();
4179 foreach my $input (@_)
4181 if($input =~ /^([\w]+)\=/)
4183 my $name = lc($1);
4184 my $value = $';
4185 chomp($value);
4187 if($value !~ m![^\w\.\~\/\:\-]! && $value !~ /^[\-\.]/ && -s "$value" && ! -d "$value")
4189 # Warn about reading a value from file
4190 print STDERR "Read '$name' from: '$value'\n";
4191 open(INPUTVALUE, "<$value") || die "$value: $!\n";
4192 $value = <INPUTVALUE>;
4193 chomp($value);
4196 $value =~ s/(^\'([^\']*)\'$)/\1/g;
4197 $value =~ s/(^\"([^\"]*)\"$)/\1/g;
4198 $Settings{$name} = $value;
4200 elsif($input !~ m![^\w\.\~\/\:\-]!i && $input !~ /^[\-\.]/i && -s $input)
4202 # We MUST have a salt
4203 $Settings{'salt'} = $ticket->{'Salt'}->[0] unless $Settings{'salt'};
4205 # Set the new masterkey to the old masterkey if there is no new masterkey
4206 $Settings{'newmasterkey'} = $Settings{'masterkey'} unless exists($Settings{'newmasterkey'});
4208 # Get the ticket
4209 my $ticket = read_ticket($input, $Settings{'salt'}, $Settings{'masterkey'});
4211 # Set a new password from plaintext
4212 $ticket->{'Salt'}->[0] = $Settings{'salt'} if $Settings{'salt'} && $Settings{'password'};
4213 set_password ($ticket, $Settings{'salt'}, $Settings{'password'}) if $Settings{'password'};
4214 # Write the ticket back to file
4215 write_ticket($input, $ticket, $Settings{'salt'}, $Settings{'newmasterkey'});
4217 # A password is only used once
4218 $Settings{'password'} = "";
4223 # Add a signature from $masterkey to a ticket in the label $signlabel
4224 sub SignTicketWithMasterkey # ($ticket, $serversalt [, $masterkey, $signlabel]) -> $Signature
4226 my $ticket = shift || return 0;
4227 my $serversalt = shift || "";
4228 my $masterkey = shift || $ENV{'CGIMasterKey'};
4229 my $signlabel = shift || 'Signature';
4231 my $Signature = TicketSignature($ticket, $serversalt, $masterkey);
4233 $ticket->{$signlabel} = [$Signature] if $Signature;
4235 return $Signature;
4238 # Determine ticket signature
4239 sub TicketSignature # ($ticket, $serversalt [, $masterkey]) -> $Signature
4241 my $ticket = shift || return 0;
4242 my $serversalt = shift || "";
4243 my $masterkey = shift || $ENV{'CGIMasterKey'};
4244 my $Signature = "";
4246 if($masterkey)
4248 # If the ServerSalt is not stored in the ticket, the SALT file has to be found
4249 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4251 $serversalt = $ticket->{Salt}->[0];
4253 # Sign
4254 if($serversalt)
4256 my $username = lc($ticket->{'Username'}->[0]);
4257 my $hash1 = hash_string(${masterkey}.${serversalt});
4258 # The order of $username.$hash1 should be different than in DecryptTicketWithMasterKey
4259 my $CryptKey = hash_string($username.${'hash1'});
4260 my $SignText = "Type: ".$ticket->{'Type'}->[0]."\n";
4261 my @tmp = sort(@{$ticket->{'Username'}});
4262 $SignText .= "Username: @tmp\n";
4263 @tmp = sort(@{$ticket->{'IPaddress'}});
4264 $SignText .= "IPaddress: @tmp\n";
4265 @tmp = sort(@{$ticket->{'AllowedPaths'}});
4266 $SignText .= "AllowedPaths: @tmp\n";
4267 @tmp = sort(@{$ticket->{'DeniedPaths'}});
4268 $SignText .= "DeniedPaths: @tmp\n";
4269 @tmp = sort(@{$ticket->{'Session'}});
4270 $SignText .= "Session: @tmp\n";
4271 @tmp = sort(@{$ticket->{'Time'}});
4272 $SignText .= "Time: @tmp\n";
4273 @tmp = sort(@{$ticket->{'Expires'}});
4274 $SignText .= "Expires: @tmp\n";
4275 @tmp = sort(@{$ticket->{'Capabilities'}});
4276 $SignText .= "Capabilities: @tmp\n";
4277 @tmp = sort(@{$ticket->{'MaxLifetime'}});
4278 $SignText .= "MaxLifetime: @tmp\n";
4279 $Signature = HMAC_hex($CryptKey, $SignText);
4282 return $Signature;
4285 # Decrypts a password list IN PLACE
4286 sub DecryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4288 my $ticket = shift || return 0;
4289 my $serversalt = shift || "";
4290 my $masterkey = shift || $ENV{'CGIMasterKey'};
4292 if($masterkey && exists($ticket->{Password}) && $ticket->{Password}->[0])
4294 # If the ServerSalt is not given, read it from the the ticket
4295 if(! $serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4297 $serversalt = $ticket->{Salt}->[0];
4299 # Decrypt password(s)
4300 if($serversalt)
4302 my $hash1 = hash_string(${masterkey}.${serversalt});
4303 my $username = lc($ticket->{'Username'}->[0]);
4304 # The order of $hash1.$username should be different than in TicketSignature
4305 my $CryptKey = hash_string(${'hash1'}.$username);
4306 foreach my $password (@{$ticket->{Password}})
4308 $password = XOR_hex_strings($CryptKey,$password);
4312 return $ticket->{'Password'};
4314 sub EncryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4316 DecryptTicketWithMasterKey(@_);
4319 # Implement HMAC signature hash.
4320 # Blocksize is length in HEX characters, NOT bytes
4321 sub HMAC_hex # ($key, $message [, $blocksizehex]) -> $hex
4323 my $key = shift || "";
4324 my $message = shift || "";
4325 my $blocksizehex = shift || length($key);
4326 $key = hash_string($key) if length($key) > $blocksizehex;
4328 my $innerkey = XOR_hex_byte ($key, "36");
4329 my $outerkey = XOR_hex_byte ($key, "5c");
4330 my $innerhash = hash_string($innerkey.$message);
4331 my $outerhash = hash_string($outerkey.$innerhash);
4333 return $outerhash;
4336 # XOR input with equally long string of repeated 2 hex character (byte)
4337 # string. Input must have even number of hex characters
4338 sub XOR_hex_byte # ($hex1, $hexbyte) -> $hex
4340 my $hex1 = shift || "";
4341 my $hexbyte = shift || "";
4342 my $bytelength = length($hexbyte);
4343 my $hex2 = $hex1;
4344 $hex2 =~ s/.{$bytelength}/$hexbyte/ig;
4345 return XOR_hex_strings($hex1, $hex2);
4348 sub XOR_hex_strings # ($hex1, $hex2) -> $hex
4350 my $hex1 = shift || "";
4351 my $hex2 = shift || "";
4352 my @hex1list = split('', $hex1);
4353 my @hex2list = split('', $hex2);
4354 my @hexresultlist = ();
4355 for(my $i; $i < scalar(@hex1list); ++$i)
4357 my $d1 = hex($hex1list[$i]);
4358 my $d2 = hex($hex2list[$i]);
4359 my $dresult = ($d1 ^ $d2);
4360 $hexresultlist[$i] = sprintf("%x", $dresult);
4362 $hexresult = join('', @hexresultlist);
4363 return $hexresult;
4366 # End of Handle login access
4369 ############################################################################
4371 # Handle foreign interpreters (i.e., scripting languages)
4373 # Insert perl code to execute scripts in foreign scripting languages.
4374 # Actually, the scripts inside the <SCRIPT></SCRIPT> blocks are piped
4375 # into an interpreter.
4376 # The code presented here is fairly confusing because it
4377 # actually writes perl code code to the output.
4379 # A table with the file handles
4380 %SCRIPTINGINPUT = ();
4382 # A function to clean up Client delivered CGI parameter values
4383 # (i.e., quote all odd characters)
4384 %SHRUBcharacterTR =
4386 "\'" => '&#39;',
4387 "\`" => '&#96;',
4388 "\"" => '&quot;',
4389 '&' => '&amper;',
4390 "\\" => '&#92;'
4393 sub shrubCGIparameter # ($String) -> Cleaned string
4395 my $String = shift || "";
4397 # Change all quotes [`'"] into HTML character entities
4398 my ($Char, $Transcript) = ('&', $SHRUBcharacterTR{'&'});
4400 # Protect &
4401 $String =~ s/\Q$Char\E/$Transcript/isg if $Transcript;
4403 while( ($Char, $Transcript) = each %SHRUBcharacterTR)
4405 next if $Char eq '&';
4406 $String =~ s/\Q$Char\E/$Transcript/isg;
4409 # Replace newlines
4410 $String =~ s/[\n]/\\n/g;
4411 # Replace control characters with their backslashed octal ordinal numbers
4412 $String =~ s/([^\S \t])/(sprintf("\\0%o", ord($1)))/eisg; #
4413 $String =~ s/([\x00-\x08\x0A-\x1F])/(sprintf("\\0%o", ord($1)))/eisg; #
4415 return $String;
4419 # The initial open statements: Open a pipe to the foreign script interpreter
4420 sub OpenForeignScript # ($ContentType) -> $DirectivePrefix
4422 my $ContentType = lc(shift) || return "";
4423 my $NewDirective = "";
4425 return $NewDirective if($SCRIPTINGINPUT{$ContentType});
4427 # Construct a unique file handle name
4428 $SCRIPTINGFILEHANDLE = uc($ContentType);
4429 $SCRIPTINGFILEHANDLE =~ s/\W/\_/isg;
4430 $SCRIPTINGINPUT{$ContentType} = $SCRIPTINGFILEHANDLE
4431 unless $SCRIPTINGINPUT{$ContentType};
4433 # Create the relevant script: Open the pipe to the interpreter
4434 $NewDirective .= <<"BLOCKCGISCRIPTOROPEN";
4435 # Open interpreter for '$ContentType'
4436 # Open pipe to interpreter (if it isn't open already)
4437 open($SCRIPTINGINPUT{$ContentType}, "|$ScriptingLanguages{$ContentType}") || main::dieHandler(14, "$ContentType: \$!\\n");
4438 BLOCKCGISCRIPTOROPEN
4440 # Insert Initialization code and CGI variables
4441 $NewDirective .= InitializeForeignScript($ContentType);
4443 # Ready
4444 return $NewDirective;
4448 # The final closing code to stop the interpreter
4449 sub CloseForeignScript # ($ContentType) -> $DirectivePrefix
4451 my $ContentType = lc(shift) || return "";
4452 my $NewDirective = "";
4454 # Do nothing unless the pipe realy IS open
4455 return "" unless $SCRIPTINGINPUT{$ContentType};
4457 # Initial comment
4458 $NewDirective .= "\# Close interpreter for '$ContentType'\n";
4461 # Write the Postfix code
4462 $NewDirective .= CleanupForeignScript($ContentType);
4464 # Create the relevant script: Close the pipe to the interpreter
4465 $NewDirective .= <<"BLOCKCGISCRIPTORCLOSE";
4466 close($SCRIPTINGINPUT{$ContentType}) || main::dieHandler(15, \"$ContentType: \$!\\n\");
4467 select(STDOUT); \$|=1;
4469 BLOCKCGISCRIPTORCLOSE
4471 # Remove the file handler of the foreign script
4472 delete($SCRIPTINGINPUT{$ContentType});
4474 return $NewDirective;
4478 # The initialization code for the foreign script interpreter
4479 sub InitializeForeignScript # ($ContentType) -> $DirectivePrefix
4481 my $ContentType = lc(shift) || return "";
4482 my $NewDirective = "";
4484 # Add initialization code
4485 if($ScriptingInitialization{$ContentType})
4487 $NewDirective .= <<"BLOCKCGISCRIPTORINIT";
4488 # Initialization Code for '$ContentType'
4489 # Select relevant output filehandle
4490 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4492 # The Initialization code (if any)
4493 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}INITIALIZATIONCODE';
4494 $ScriptingInitialization{$ContentType}
4495 ${ContentType}INITIALIZATIONCODE
4497 BLOCKCGISCRIPTORINIT
4500 # Add all CGI variables defined
4501 if(exists($ScriptingCGIvariables{$ContentType}))
4503 # Start writing variable definitions to the Interpreter
4504 if($ScriptingCGIvariables{$ContentType})
4506 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEF";
4507 # CGI variables (from the %default_values table)
4508 print $SCRIPTINGINPUT{$ContentType} << '${ContentType}CGIVARIABLES';
4509 BLOCKCGISCRIPTORVARDEF
4512 my ($N, $V);
4513 foreach $N (keys(%default_values))
4515 # Determine whether the parameter has been defined
4516 # (the eval is a workaround to get at the variable value)
4517 next unless eval("defined(\$CGIexecute::$N)");
4519 # Get the value from the EXECUTION environment
4520 $V = eval("\$CGIexecute::$N");
4521 # protect control characters (i.e., convert them to \0.. form)
4522 $V = shrubCGIparameter($V);
4524 # Protect interpolated variables
4525 eval("\$CGIexecute::$N = '$V';") unless $ScriptingCGIvariables{$ContentType};
4527 # Print the actual declaration for this scripting language
4528 if($ScriptingCGIvariables{$ContentType})
4530 $NewDirective .= sprintf($ScriptingCGIvariables{$ContentType}, $N, $V);
4531 $NewDirective .= "\n";
4535 # Stop writing variable definitions to the Interpreter
4536 if($ScriptingCGIvariables{$ContentType})
4538 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEFEND";
4539 ${ContentType}CGIVARIABLES
4540 BLOCKCGISCRIPTORVARDEFEND
4545 $NewDirective .= << "BLOCKCGISCRIPTOREND";
4547 # Select STDOUT filehandle
4548 select(STDOUT); \$|=1;
4550 BLOCKCGISCRIPTOREND
4552 return $NewDirective;
4556 # The cleanup code for the foreign script interpreter
4557 sub CleanupForeignScript # ($ContentType) -> $DirectivePrefix
4559 my $ContentType = lc(shift) || return "";
4560 my $NewDirective = "";
4562 # Return if not needed
4563 return $NewDirective unless $ScriptingCleanup{$ContentType};
4565 # Create the relevant script: Open the pipe to the interpreter
4566 $NewDirective .= <<"BLOCKCGISCRIPTORSTOP";
4567 # Cleanup Code for '$ContentType'
4568 # Select relevant output filehandle
4569 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4570 # Print Cleanup code to foreign script
4571 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}SCRIPTSTOP';
4572 $ScriptingCleanup{$ContentType}
4573 ${ContentType}SCRIPTSTOP
4575 # Select STDOUT filehandle
4576 select(STDOUT); \$|=1;
4577 BLOCKCGISCRIPTORSTOP
4579 return $NewDirective;
4583 # The prefix code for each <script></script> block
4584 sub PrefixForeignScript # ($ContentType) -> $DirectivePrefix
4586 my $ContentType = lc(shift) || return "";
4587 my $NewDirective = "";
4589 # Return if not needed
4590 return $NewDirective unless $ScriptingPrefix{$ContentType};
4592 my $Quote = "\'";
4593 # If the CGIvariables parameter is defined, but empty, interpolate
4594 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4595 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4596 !$ScriptingCGIvariables{$ContentType};
4598 # Add initialization code
4599 $NewDirective .= <<"BLOCKCGISCRIPTORPREFIX";
4600 # Prefix Code for '$ContentType'
4601 # Select relevant output filehandle
4602 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4604 # The block Prefix code (if any)
4605 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}PREFIXCODE$Quote;
4606 $ScriptingPrefix{$ContentType}
4607 ${ContentType}PREFIXCODE
4608 # Select STDOUT filehandle
4609 select(STDOUT); \$|=1;
4610 BLOCKCGISCRIPTORPREFIX
4612 return $NewDirective;
4616 # The postfix code for each <script></script> block
4617 sub PostfixForeignScript # ($ContentType) -> $DirectivePrefix
4619 my $ContentType = lc(shift) || return "";
4620 my $NewDirective = "";
4622 # Return if not needed
4623 return $NewDirective unless $ScriptingPostfix{$ContentType};
4625 my $Quote = "\'";
4626 # If the CGIvariables parameter is defined, but empty, interpolate
4627 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4628 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4629 !$ScriptingCGIvariables{$ContentType};
4631 # Create the relevant script: Open the pipe to the interpreter
4632 $NewDirective .= <<"BLOCKCGISCRIPTORPOSTFIX";
4633 # Postfix Code for '$ContentType'
4634 # Select filehandle to interpreter
4635 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4636 # Print postfix code to foreign script
4637 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SCRIPTPOSTFIX$Quote;
4638 $ScriptingPostfix{$ContentType}
4639 ${ContentType}SCRIPTPOSTFIX
4640 # Select STDOUT filehandle
4641 select(STDOUT); \$|=1;
4642 BLOCKCGISCRIPTORPOSTFIX
4644 return $NewDirective;
4647 sub InsertForeignScript # ($ContentType, $directive, @SRCfile) -> $NewDirective
4649 my $ContentType = lc(shift) || return "";
4650 my $directive = shift || return "";
4651 my @SRCfile = @_;
4652 my $NewDirective = "";
4654 my $Quote = "\'";
4655 # If the CGIvariables parameter is defined, but empty, interpolate
4656 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4657 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4658 !$ScriptingCGIvariables{$ContentType};
4660 # Create the relevant script
4661 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4662 # Insert Code for '$ContentType'
4663 # Select filehandle to interpreter
4664 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4665 BLOCKCGISCRIPTORINSERT
4667 # Use SRC feature files
4668 my $ThisSRCfile;
4669 while($ThisSRCfile = shift(@_))
4671 # Handle blocks
4672 if($ThisSRCfile =~ /^\s*\{\s*/)
4674 my $Block = $';
4675 $Block = $` if $Block =~ /\s*\}\s*$/;
4676 $NewDirective .= <<"BLOCKCGISCRIPTORSRCBLOCK";
4677 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SRCBLOCKCODE$Quote;
4678 $Block
4679 ${ContentType}SRCBLOCKCODE
4680 BLOCKCGISCRIPTORSRCBLOCK
4682 next;
4685 # Handle files
4686 $NewDirective .= <<"BLOCKCGISCRIPTORSRCFILES";
4687 # Read $ThisSRCfile
4688 open(SCRIPTINGSOURCE, "<$ThisSRCfile") || main::dieHandler(16, "$ThisSRCfILE: \$!");
4689 while(<SCRIPTINGSOURCE>)
4691 print $SCRIPTINGINPUT{$ContentType} \$_;
4693 close(SCRIPTINGSOURCE);
4695 BLOCKCGISCRIPTORSRCFILES
4699 # Add the directive
4700 if($directive)
4702 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4703 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}DIRECTIVECODE$Quote;
4704 $directive
4705 ${ContentType}DIRECTIVECODE
4706 BLOCKCGISCRIPTORINSERT
4710 $NewDirective .= <<"BLOCKCGISCRIPTORSELECT";
4711 # Select STDOUT filehandle
4712 select(STDOUT); \$|=1;
4713 BLOCKCGISCRIPTORSELECT
4715 # Ready
4716 return $NewDirective;
4719 sub CloseAllForeignScripts # Call CloseForeignScript on all open scripts
4721 my $ContentType;
4722 foreach $ContentType (keys(%SCRIPTINGINPUT))
4724 my $directive = CloseForeignScript($ContentType);
4725 print STDERR "\nDirective $CGI_Date: ", $directive;
4726 CGIexecute->evaluate($directive);
4731 # End of handling foreign (external) scripting languages.
4733 ############################################################################
4735 # A subroutine to handle "nested" quotes, it cuts off the leading
4736 # item or quoted substring
4737 # E.g.,
4738 # ' A_word and more words' -> @('A_word', ' and more words')
4739 # '"quoted string" The rest' -> @('quoted string', ' The rest')
4740 # (this is needed for parsing the <TAGS> and their attributes)
4741 my $SupportedQuotes = "\'\"\`\(\{\[";
4742 my %QuotePairs = ('('=>')','['=>']','{'=>'}'); # Brackets
4743 sub ExtractQuotedItem # ($String) -> @($QuotedString, $RestOfString)
4745 my @Result = ();
4746 my $String = shift || return @Result;
4748 if($String =~ /^\s*([\w\/\-\.]+)/is)
4750 push(@Result, $1, $');
4752 elsif($String =~ /^\s*(\\?)([\Q$SupportedQuotes\E])/is)
4754 my $BackSlash = $1 || "";
4755 my $OpenQuote = $2;
4756 my $CloseQuote = $OpenQuote;
4757 $CloseQuote = $QuotePairs{$OpenQuote} if $QuotePairs{$OpenQuote};
4759 if($BackSlash)
4761 $String =~ /^\s*\\\Q$OpenQuote\E/i;
4762 my $Onset = $';
4763 $Onset =~ /\\\Q$CloseQuote\E/i;
4764 my $Rest = $';
4765 my $Item = $`;
4766 push(@Result, $Item, $Rest);
4769 else
4771 $String =~ /^\s*\Q$OpenQuote\E([^\Q$CloseQuote\E]*)\Q$CloseQuote\E/i;
4772 push(@Result, $1, $');
4775 else
4777 push(@Result, "", $String);
4779 return @Result;
4782 # Now, start with the real work
4784 # Control the output of the Content-type: text/html\n\n message
4785 my $SupressContentType = 0;
4787 # Process a file
4788 sub ProcessFile # ($file_path)
4790 my $file_path = shift || return 0;
4793 # Generate a unique file handle (for recursions)
4794 my @SRClist = ();
4795 my $FileHandle = "file";
4796 my $n = 0;
4797 while(!eof($FileHandle.$n)) {++$n;};
4798 $FileHandle .= $n;
4800 # Start HTML output
4801 # Use the default Content-type if this is NOT a raw file
4802 unless(($RawFilePattern && $ENV{'PATH_INFO'} =~ m@($RawFilePattern)$@i)
4803 || $SupressContentType)
4805 $ENV{'PATH_INFO'} =~ m@($FilePattern)$@i;
4806 my $ContentType = $ContentTypeTable{$1};
4807 print "Content-type: $ContentType\n";
4808 if(%SETCOOKIELIST && keys(%SETCOOKIELIST))
4810 foreach my $name (keys(%SETCOOKIELIST))
4812 my $value = $SETCOOKIELIST{$name};
4813 print "Set-Cookie: $name=$value\n";
4815 # Cookies are set only ONCE
4816 %SETCOOKIELIST = ();
4818 print "\n";
4819 $SupressContentType = 1; # Content type has been printed
4823 # Get access to the actual data. This can be from RAM (by way of an
4824 # environment variable) or by opening a file.
4826 # Handle the use of RAM images (file-data is stored in the
4827 # $CGI_FILE_CONTENTS environment variable)
4828 # Note that this environment variable will be cleared, i.e., it is strictly for
4829 # single-use only!
4830 if($ENV{$CGI_FILE_CONTENTS})
4832 # File has been read already
4833 $_ = $ENV{$CGI_FILE_CONTENTS};
4834 # Sorry, you have to do the reading yourself (dynamic document creation?)
4835 # NOTE: you must read the whole document at once
4836 if($_ eq '-')
4838 $_ = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
4840 else # Clear environment variable
4842 $ENV{$CGI_FILE_CONTENTS} = '-';
4845 # Open Only PLAIN TEXT files (or STDIN) and NO executable files (i.e., scripts).
4846 # THIS IS A SECURITY FEATURE!
4847 elsif($file_path eq '-' || (-e "$file_path" && -r _ && -T _ && -f _ && ! (-x _ || -X _) ))
4849 open($FileHandle, $file_path) || dieHandler(17, "<h2>File not found</h2>\n");
4850 push(@OpenFiles, $file_path);
4851 $_ = <$FileHandle>; # Read first line
4853 else
4855 print "<h2>File not found</h2>\n";
4856 dieHandler(18, "$file_path\n");
4859 $| = 1; # Flush output buffers
4861 # Initialize variables
4862 my $METAarguments = ""; # The CGI arguments from the latest META tag
4863 my @METAvalues = (); # The ''-quoted CGI values from the latest META tag
4864 my $ClosedTag = 0; # <TAG> </TAG> versus <TAG/>
4867 # Send document to output
4868 # Process the requested document.
4869 # Do a loop BEFORE reading input again (this catches the RAM/Database
4870 # type of documents).
4871 do {
4874 # Handle translations if needed
4876 performTranslation(\$_) if $TranslationPaths;
4878 # Catch <SCRIPT LANGUAGE="PERL" TYPE="text/ssperl" > directives in $_
4879 # There can be more than 1 <SCRIPT> or META tags on a line
4880 while(/\<\s*(SCRIPT|META|DIV|INS)\s/is)
4882 my $directive = "";
4883 # Store rest of line
4884 my $Before = $`;
4885 my $ScriptTag = $&;
4886 my $After = $';
4887 my $TagType = uc($1);
4888 # The before part can be send to the output
4889 print $Before;
4891 # Read complete Tag from after and/or file
4892 until($After =~ /([^\\])\>/)
4894 $After .= <$FileHandle>;
4895 performTranslation(\$After) if $TranslationPaths;
4898 if($After =~ /([^\\])\>/)
4900 $ScriptTag .= $`.$&; # Keep the Script Tag intact
4901 $After = $';
4903 else
4905 dieHandler(19, "Closing > not found\n");
4908 # The tag could be closed by />, we handle this in the XML way
4909 # and don't process any content (we ignore whitespace)
4910 $ClosedTag = ($ScriptTag =~ m@[^\\]/\s*\>\s*$@) ? 1 : 0;
4913 # TYPE or CLASS?
4914 my $TypeName = ($TagType =~ /META/is) ? "CONTENT" : "TYPE";
4915 $TypeName = "CLASS" if $TagType eq 'DIV' || $TagType eq 'INS';
4917 # Parse <SCRIPT> or <META> directive
4918 # If NOT (TYPE|CONTENT)="text/ssperl" (i.e., $ServerScriptContentType),
4919 # send the line to the output and go to the next loop
4920 my $CurrentContentType = "";
4921 if($ScriptTag =~ /(^|\s)$TypeName\s*=\s*/is)
4923 my ($Type) = ExtractQuotedItem($');
4924 $Type =~ /^\s*([\w\/\-]+)\s*[\,\;]?/;
4925 $CurrentContentType = lc($1); # Note: mime-types are "case-less"
4926 # CSS classes are aliases of $ServerScriptContentType
4927 if($TypeName eq "CLASS" && $CurrentContentType eq $ServerScriptContentClass)
4929 $CurrentContentType = $ServerScriptContentType;
4934 # Not a known server-side content type, print and continue
4935 unless(($CurrentContentType =~
4936 /$ServerScriptContentType|$ShellScriptContentType/is) ||
4937 $ScriptingLanguages{$CurrentContentType})
4939 print $ScriptTag;
4940 $_ = $After;
4941 next;
4945 # A known server-side content type, evaluate
4947 # First, handle \> and \<
4948 $ScriptTag =~ s/\\\>/\>/isg;
4949 $ScriptTag =~ s/\\\</\</isg;
4951 # Extract the CGI, SRC, ID, IF and UNLESS attributes
4952 my %ScriptTagAttributes = ();
4953 while($ScriptTag =~ /(^|\s)(CGI|IF|UNLESS|SRC|ID)\s*=\s*/is)
4955 my $Attribute = $2;
4956 my $Rest = $';
4957 my $Value = "";
4958 ($Value, $ScriptTag) = ExtractQuotedItem($Rest);
4959 $ScriptTagAttributes{uc($Attribute)} = $Value;
4963 # The attribute used to define the CGI variables
4964 # Extract CGI-variables from
4965 # <META CONTENT="text/ssperl; CGI='' SRC=''">
4966 # <SCRIPT TYPE='text/ssperl' CGI='' SRC=''>
4967 # <DIV CLASS='ssperl' CGI='' SRC='' ID=""> tags
4968 # <INS CLASS='ssperl' CGI='' SRC='' ID=""> tags
4969 if($ScriptTagAttributes{'CGI'})
4971 @ARGV = (); # Reset ARGV
4972 $ARGC = 0;
4973 $METAarguments = ""; # Reset the META CGI arguments
4974 @METAvalues = ();
4975 my $Meta_CGI = $ScriptTagAttributes{'CGI'};
4977 # Process default values of variables ($<name> = 'default value')
4978 # Allowed quotes are '', "", ``, (), [], and {}
4979 while($Meta_CGI =~ /(^\s*|[^\\])([\$\@\%]?)([\w\-]+)\s*/is)
4981 my $varType = $2 || '$'; # Variable or list
4982 my $name = $3; # The Name
4983 my $default = "";
4984 $Meta_CGI = $';
4986 if($Meta_CGI =~ /^\s*\=\s*/is)
4988 # Locate (any) default value
4989 ($default, $Meta_CGI) = ExtractQuotedItem($'); # Cut the parameter from the CGI
4991 $RemainingTag = $Meta_CGI;
4994 # Define CGI (or ENV) variable, initalize it from the
4995 # Query string or the default value
4997 # Also construct the @ARGV and @_ arrays. This allows other (SRC=) Perl
4998 # scripts to access the CGI arguments defined in the META tag
4999 # (Not for CGI inside <SCRIPT> tags)
5000 if($varType eq '$')
5002 CGIexecute::defineCGIvariable($name, $default)
5003 || dieHandler(20, "INVALID CGI name/value pair ($name, $default)\n");
5004 push(@METAvalues, "'".${"CGIexecute::$name"}."'");
5005 # Add value to the @ARGV list
5006 push(@ARGV, ${"CGIexecute::$name"});
5007 ++$ARGC;
5009 elsif($varType eq '@')
5011 CGIexecute::defineCGIvariableList($name, $default)
5012 || dieHandler(21, "INVALID CGI name/value list pair ($name, $default)\n");
5013 push(@METAvalues, "'".join("'", @{"CGIexecute::$name"})."'");
5014 # Add value to the @ARGV list
5015 push(@ARGV, @{"CGIexecute::$name"});
5016 $ARGC = scalar(@CGIexecute::ARGV);
5018 elsif($varType eq '%')
5020 CGIexecute::defineCGIvariableHash($name, $default)
5021 || dieHandler(22, "INVALID CGI name/value hash pair ($name, $default)\n");
5022 my @PairList = map {"$_ => ".${"CGIexecute::$name"}{$_}} keys(%{"CGIexecute::$name"});
5023 push(@METAvalues, "'".join("'", @PairList)."'");
5024 # Add value to the @ARGV list
5025 push(@ARGV, %{"CGIexecute::$name"});
5026 $ARGC = scalar(@CGIexecute::ARGV);
5029 # Store the values for internal and later use
5030 $METAarguments .= "$varType".$name.","; # A string of CGI variable names
5032 push(@METAvalues, "\'".eval("\"$varType\{CGIexecute::$name\}\"")."\'"); # ALWAYS add '-quotes around values
5037 # The IF (conditional execution) Attribute
5038 # Evaluate the condition and stop unless it evaluates to true
5039 if($ScriptTagAttributes{'IF'})
5041 my $IFcondition = $ScriptTagAttributes{'IF'};
5043 # Convert SCRIPT calls, ./<script>
5044 $IFcondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5046 # Convert FILE calls, ~/<file>
5047 $IFcondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5049 # Block execution if necessary
5050 unless(CGIexecute->evaluate($IFcondition))
5052 %ScriptTagAttributes = ();
5053 $CurrentContentType = "";
5057 # The UNLESS (conditional execution) Attribute
5058 # Evaluate the condition and stop if it evaluates to true
5059 if($ScriptTagAttributes{'UNLESS'})
5061 my $UNLESScondition = $ScriptTagAttributes{'UNLESS'};
5063 # Convert SCRIPT calls, ./<script>
5064 $UNLESScondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5066 # Convert FILE calls, ~/<file>
5067 $UNLESScondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5069 # Block execution if necessary
5070 if(CGIexecute->evaluate($UNLESScondition))
5072 %ScriptTagAttributes = ();
5073 $CurrentContentType = "";
5077 # The SRC (Source File) Attribute
5078 # Extract any source script files and add them in
5079 # front of the directive
5080 # The SRC list should be emptied
5081 @SRClist = ();
5082 my $SRCtag = "";
5083 my $Prefix = 1;
5084 my $PrefixDirective = "";
5085 my $PostfixDirective = "";
5086 # There is a SRC attribute
5087 if($ScriptTagAttributes{'SRC'})
5089 $SRCtag = $ScriptTagAttributes{'SRC'};
5090 # Remove "file://" prefixes
5091 $SRCtag =~ s@([^\w\/\\]|^)file\://([^\s\/\@\=])@$1$2@gis;
5092 # Expand script filenames "./Script"
5093 $SRCtag =~ s@([^\w\/\\]|^)\./([^\s\/\@\=])@$1$SCRIPT_SUB/$2@gis;
5094 # Expand script filenames "~/Script"
5095 $SRCtag =~ s@([^\w\/\\]|^)\~/([^\s\/\@\=])@$1$HOME_SUB/$2@gis;
5098 # File source tags
5099 while($SRCtag =~ /\S/is)
5101 my $SRCdirective = "";
5103 # Pseudo file, just a switch to go from PREFIXING to POSTFIXING
5104 # SRC files
5105 if($SRCtag =~ /^[\s\;\,]*(POSTFIX|PREFIX)([^$FileAllowedChars]|$)/is)
5107 my $InsertionPlace = $1;
5108 $SRCtag = $2.$';
5110 $Prefix = $InsertionPlace =~ /POSTFIX/i ? 0 : 1;
5111 # Go to next round
5112 next;
5114 # {}-blocks are just evaluated by "do"
5115 elsif($SRCtag =~ /^[\s\;\,]*\{/is)
5117 my $SRCblock = $';
5118 if($SRCblock =~ /\}[\s\;\,]*([^\}]*)$/is)
5120 $SRCblock = $`;
5121 $SRCtag = $1.$';
5122 # SAFEqx shell script blocks
5123 if($CurrentContentType =~ /$ShellScriptContentType/is)
5125 # Handle ''-quotes inside the script
5126 $SRCblock =~ s/[\']/\\$&/gis;
5128 $SRCblock = "print do { SAFEqx(\'".$SRCblock."\'); };'';";
5129 $SRCdirective .= $SRCblock."\n";
5131 # do { SRCblocks }
5132 elsif($CurrentContentType =~ /$ServerScriptContentType/is)
5134 $SRCblock = "print do { $SRCblock };'';";
5135 $SRCdirective .= $SRCblock."\n";
5137 else # The interpreter should handle this
5139 push(@SRClist, "{ $SRCblock }");
5143 else
5144 { dieHandler(23, "Closing \} missing\n");};
5146 # Files are processed as Text or Executable files
5147 elsif($SRCtag =~ /[\s\;\,]*([$FileAllowedChars]+)[\;\,\s]*/is)
5149 my $SrcFile = $1;
5150 $SRCtag = $';
5152 # We are handling one of the external interpreters
5153 if($ScriptingLanguages{$CurrentContentType})
5155 push(@SRClist, $SrcFile);
5157 # We are at the start of a DIV tag, just load all SRC files and/or URL's
5158 elsif($TagType eq 'DIV' || $TagType eq 'INS') # All files are prepended in DIV's
5160 # $SrcFile is a URL pointing to an HTTP or FTP server
5161 if($SrcFile =~ m!^([a-z]+)\://!)
5163 my $URLoutput = CGIscriptor::read_url($SrcFile);
5164 $SRCdirective .= $URLoutput;
5166 # SRC file is an existing file
5167 elsif(-e "$SrcFile")
5169 open(DIVSOURCE, "<$SrcFile") || dieHandler(24, "<$SrcFile: $!\n");
5170 my $Content;
5171 while(sysread(DIVSOURCE, $Content, 1024) > 0)
5173 $SRCdirective .= $Content;
5175 close(DIVSOURCE);
5178 # Executable files are executed as
5179 # `$SrcFile 'ARGV[0]' 'ARGV[1]'`
5180 elsif(-x "$SrcFile")
5182 $SRCdirective .= "print \`$SrcFile @METAvalues\`;'';\n";
5184 # Handle 'standard' files, using ProcessFile
5185 elsif((-T "$SrcFile" || $ENV{$CGI_FILE_CONTENTS})
5186 && $SrcFile =~ m@($FilePattern)$@) # A recursion
5189 # Do not process still open files because it can lead
5190 # to endless recursions
5191 if(grep(/^$SrcFile$/, @OpenFiles))
5192 { dieHandler(25, "$SrcFile allready opened (endless recursion)\n")};
5193 # Prepare meta arguments
5194 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5195 # Process the file
5196 $SRCdirective .= "main::ProcessFile(\'$SrcFile\');'';\n";
5198 elsif($SrcFile =~ m!^([a-z]+)\://!) # URL's are loaded and printed
5200 $SRCdirective .= GET_URL($SrcFile);
5202 elsif(-T "$SrcFile") # Textfiles are "do"-ed (Perl execution)
5204 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5205 $SRCdirective .= "do \'$SrcFile\';'';\n";
5207 else # This one could not be resolved (should be handled by BinaryMapFile)
5209 $SRCdirective .= 'print "'.$SrcFile.' cannot be used"'."\n";
5214 # Postfix or Prefix
5215 if($Prefix)
5217 $PrefixDirective .= $SRCdirective;
5219 else
5221 $PostfixDirective .= $SRCdirective;
5224 # The prefix should be handled immediately
5225 $directive .= $PrefixDirective;
5226 $PrefixDirective = "";
5230 # Handle the content of the <SCRIPT></SCRIPT> tags
5231 # Do not process the content of <SCRIPT/>
5232 if($TagType =~ /SCRIPT/is && !$ClosedTag) # The <SCRIPT> TAG
5234 my $EndScriptTag = "";
5236 # Execute SHELL scripts with SAFEqx()
5237 if($CurrentContentType =~ /$ShellScriptContentType/is)
5239 $directive .= "SAFEqx(\'";
5242 # Extract Program
5243 while($After !~ /\<\s*\/SCRIPT[^\>]*\>/is && !eof($FileHandle))
5245 $After .= <$FileHandle>;
5246 performTranslation(\$After) if $TranslationPaths;
5249 if($After =~ /\<\s*\/SCRIPT[^\>]*\>/is)
5251 $directive .= $`;
5252 $EndScriptTag = $&;
5253 $After = $';
5255 else
5257 dieHandler(26, "Missing </SCRIPT> end tag in $ENV{'PATH_INFO'}\n");
5260 # Process only when content should be executed
5261 if($CurrentContentType)
5264 # Remove all comments from Perl scripts
5265 # (NOT from OS shell scripts)
5266 $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
5267 if $CurrentContentType =~ /$ServerScriptContentType/i;
5269 # Convert SCRIPT calls, ./<script>
5270 $directive =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5272 # Convert FILE calls, ~/<file>
5273 $directive =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5275 # Execute SHELL scripts with SAFEqx(), closing bracket
5276 if($CurrentContentType =~ /$ShellScriptContentType/i)
5278 # Handle ''-quotes inside the script
5279 $directive =~ /SAFEqx\(\'/;
5280 $directive = $`.$&;
5281 my $Executable = $';
5282 $Executable =~ s/[\']/\\$&/gs;
5284 $directive .= $Executable."\');"; # Closing bracket
5287 else
5289 $directive = "";
5292 # Handle the content of the <DIV></DIV> tags
5293 # Do not process the content of <DIV/>
5294 elsif(($TagType eq 'DIV' || $TagType eq 'INS') && !$ClosedTag) # The <DIV> TAGs
5296 my $EndScriptTag = "";
5298 # Extract Text
5299 while($After !~ /\<\s*\/$TagType[^\>]*\>/is && !eof($FileHandle))
5301 $After .= <$FileHandle>;
5302 performTranslation(\$After) if $TranslationPaths;
5305 if($After =~ /\<\s*\/$TagType[^\>]*\>/is)
5307 $directive .= $`;
5308 $EndScriptTag = $&;
5309 $After = $';
5311 else
5313 dieHandler(27, "Missing </$TagType> end tag in $ENV{'PATH_INFO'}\n");
5316 # Add the Postfixed directives (but only when it contains something printable)
5317 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5318 $PostfixDirective = "";
5321 # Process only when content should be handled
5322 if($CurrentContentType)
5325 # Get the name (ID), and clean it (i.e., remove anything that is NOT part of
5326 # a valid Perl name). Names should not contain $, but we can handle it.
5327 my $name = $ScriptTagAttributes{'ID'};
5328 $name =~ /^\s*[\$\@\%]?([\w\-]+)/;
5329 $name = $1;
5331 # Assign DIV contents to $NAME value OUTSIDE the CGI values!
5332 CGIexecute::defineCGIexecuteVariable($name, $directive);
5333 $directive = "";
5336 # Nothing to execute
5337 $directive = "";
5341 # Handle Foreign scripting languages
5342 if($ScriptingLanguages{$CurrentContentType})
5344 my $newDirective = "";
5345 $newDirective .= OpenForeignScript($CurrentContentType); # Only if not already done
5346 $newDirective .= PrefixForeignScript($CurrentContentType);
5347 $newDirective .= InsertForeignScript($CurrentContentType, $directive, @SRClist);
5348 $newDirective .= PostfixForeignScript($CurrentContentType);
5349 $newDirective .= CloseForeignScript($CurrentContentType); # This shouldn't be necessary
5351 $newDirective .= '"";';
5353 $directive = $newDirective;
5357 # Add the Postfixed directives (but only when it contains something printable)
5358 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5359 $PostfixDirective = "";
5362 # EXECUTE the script and print the results
5364 # Use this to debug the program
5365 # print STDERR "Directive $CGI_Date: \n", $directive, "\n\n";
5367 my $Result = CGIexecute->evaluate($directive) if $directive; # Evaluate as PERL code
5368 $Result =~ s/\n$//g; # Remove final newline
5370 # Print the Result of evaluating the directive
5371 # (this will handle LARGE, >64 kB output)
5372 my $BytesWritten = 1;
5373 while($Result && $BytesWritten)
5375 $BytesWritten = syswrite(STDOUT, $Result, 64);
5376 $Result = substr($Result, $BytesWritten);
5378 # print $Result; # Could be used instead of above code
5380 # Store result if wanted, i.e., if $CGIscriptorResults has been
5381 # defined in a <META> tag.
5382 push(@CGIexecute::CGIscriptorResults, $Result)
5383 if exists($default_values{'CGIscriptorResults'});
5385 # Process the rest of the input line (this could contain
5386 # another directive)
5387 $_ = $After;
5389 print $_;
5390 } while(<$FileHandle>); # Read and Test AFTER first loop!
5392 close ($FileHandle);
5393 dieHandler(28, "Error in recursion\n") unless pop(@OpenFiles) == $file_path;
5397 ###############################################################################
5399 # Call the whole package
5401 sub Handle_Request
5403 my $file_path = "";
5405 # Initialization Code
5406 Initialize_Request();
5408 # SECURITY: ACCESS CONTROL
5409 Access_Control();
5411 # Read the POST part of the query, if there is one
5412 Get_POST_part_of_query();
5414 # Start (HTML) output and logging
5415 $file_path = Initialize_output();
5417 # Check login access or divert to login procedure
5418 $Use_Login = Log_In_Access();
5419 $file_path = $Use_Login if $Use_Login;
5421 # Record which files are still open (to avoid endless recursions)
5422 my @OpenFiles = ();
5424 # Record whether the default HTML ContentType has already been printed
5425 # but only if the SERVER uses HTTP or some other protocol that might interpret
5426 # a content MIME type.
5428 $SupressContentType = !("$ENV{'SERVER_PROTOCOL'}" =~ /($ContentTypeServerProtocols)/i);
5430 # Process the specified file
5431 ProcessFile($file_path) if $file_path ne $SS_PUB;
5433 # Cleanup all open external (foreign) interpreters
5434 CloseAllForeignScripts();
5437 "" # SUCCESS
5440 # Make a single call to handle an (empty) request
5441 Handle_Request();
5444 # END OF PACKAGE MAIN
5447 ####################################################################################
5449 # The CGIEXECUTE PACKAGE
5451 ####################################################################################
5453 # Isolate the evaluation of directives as PERL code from the rest of the program.
5454 # Remember that each package has its own name space.
5455 # Note that only the FIRST argument of execute->evaluate is actually evaluated,
5456 # all other arguments are accessible inside the first argument as $_[0] to $_[$#_].
5458 package CGIexecute;
5460 sub evaluate
5462 my $self = shift;
5463 my $directive = shift;
5464 $directive = eval($directive);
5465 warn $@ if $@; # Write an error message to STDERR
5466 $directive; # Return value of directive
5470 # defineCGIexecuteVariable($name [, $value]) -> 0/1
5472 # Define and intialize variables inside CGIexecute
5473 # Does no sanity checking, for internal use only
5475 sub defineCGIexecuteVariable # ($name [, $value]) -> 0/1
5477 my $name = shift || return 0; # The Name
5478 my $value = shift || ""; # The value
5480 ${$name} = $value;
5482 return 1;
5485 # Protect certain CGI variables values when set internally
5486 # If not defined internally, there will be no variable set AT ALL
5487 my %CGIprotectedVariable = ();
5488 sub ProtectCGIvariable # ($name) -> 0/1
5490 my $name = shift || "";
5491 return 0 unless $name && $name =~ /\w/;
5493 ++$CGIprotectedVariable{$name};
5495 return $CGIprotectedVariable{$name};
5498 # defineCGIvariable($name [, $default]) -> 0/1
5500 # Define and intialize CGI variables
5501 # Tries (in order) $ENV{$name}, the Query string and the
5502 # default value.
5503 # Removes all '-quotes etc.
5505 sub defineCGIvariable # ($name [, $default]) -> 0/1
5507 my $name = shift || return 0; # The Name
5508 my $default = shift || ""; # The default value
5510 # Protect variables set internally
5511 return 1 if !$name || exists($CGIprotectedVariable{$name});
5513 # Remove \-quoted characters
5514 $default =~ s/\\(.)/$1/g;
5515 # Store default values
5516 $::default_values{$name} = $default if $default;
5518 # Process variables
5519 my $temp = undef;
5520 # If there is a user supplied value, it replaces the
5521 # default value.
5523 # Environment values have precedence
5524 if(exists($ENV{$name}))
5526 $temp = $ENV{$name};
5528 # Get name and its value from the query string
5529 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5531 $temp = ::YOUR_CGIPARSE($name);
5533 # Defined values must exist for security
5534 elsif(!exists($::default_values{$name}))
5536 $::default_values{$name} = undef;
5539 # SECURITY, do not allow '- and `-quotes in
5540 # client values.
5541 # Remove all existing '-quotes
5542 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5543 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5544 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5545 # If $temp is empty, use the default value (if it exists)
5546 unless($temp =~ /\S/ || length($temp) > 0) # I.e., $temp is empty
5548 $temp = $::default_values{$name};
5549 # Remove all existing '-quotes
5550 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5551 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5552 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5554 else # Store current CGI values and remove defaults
5556 $::default_values{$name} = $temp;
5558 # Define the CGI variable and its value (in the execute package)
5559 ${$name} = $temp;
5561 # return SUCCES
5562 return 1;
5565 sub defineCGIvariableList # ($name [, $default]) -> 0/1)
5567 my $name = shift || return 0; # The Name
5568 my $default = shift || ""; # The default value
5570 # Protect variables set internally
5571 return 1 if !$name || exists($CGIprotectedVariable{$name});
5573 # Defined values must exist for security
5574 if(!exists($::default_values{$name}))
5576 $::default_values{$name} = $default;
5579 my @temp = ();
5582 # For security:
5583 # Environment values have precedence
5584 if(exists($ENV{$name}))
5586 push(@temp, $ENV{$name});
5588 # Get name and its values from the query string
5589 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5591 push(@temp, ::YOUR_CGIPARSE($name, 1)); # Extract LIST
5593 else
5595 push(@temp, $::default_values{$name});
5599 # SECURITY, do not allow '- and `-quotes in
5600 # client values.
5601 # Remove all existing '-quotes
5602 @temp = map {s/([\r\f]+\n)/\n/g; $_} @temp; # Only \n is allowed
5603 @temp = map {s/[\']/&#8217;/igs; $_} @temp; # Remove all single quotes
5604 @temp = map {s/[\`]/&#8216;/igs; $_} @temp; # Remove all backtick quotes
5606 # Store current CGI values and remove defaults
5607 $::default_values{$name} = $temp[0];
5609 # Define the CGI variable and its value (in the execute package)
5610 @{$name} = @temp;
5612 # return SUCCES
5613 return 1;
5616 sub defineCGIvariableHash # ($name [, $default]) -> 0/1) Note: '$name{""} = $default';
5618 my $name = shift || return 0; # The Name
5619 my $default = shift || ""; # The default value
5621 # Protect variables set internally
5622 return 1 if !$name || exists($CGIprotectedVariable{$name});
5624 # Defined values must exist for security
5625 if(!exists($::default_values{$name}))
5627 $::default_values{$name} = $default;
5630 my %temp = ();
5633 # For security:
5634 # Environment values have precedence
5635 if(exists($ENV{$name}))
5637 $temp{""} = $ENV{$name};
5639 # Get name and its values from the query string
5640 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5642 %temp = ::YOUR_CGIPARSE($name, -1); # Extract HASH table
5644 elsif($::default_values{$name} ne "")
5646 $temp{""} = $::default_values{$name};
5650 # SECURITY, do not allow '- and `-quotes in
5651 # client values.
5652 # Remove all existing '-quotes
5653 my $Key;
5654 foreach $Key (keys(%temp))
5656 $temp{$Key} =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5657 $temp{$Key} =~ s/[\']/&#8217;/igs; # Remove all single quotes
5658 $temp{$Key} =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5661 # Store current CGI values and remove defaults
5662 $::default_values{$name} = $temp{""};
5664 # Define the CGI variable and its value (in the execute package)
5665 %{$name} = ();
5666 my $tempKey;
5667 foreach $tempKey (keys(%temp))
5669 ${$name}{$tempKey} = $temp{$tempKey};
5672 # return SUCCES
5673 return 1;
5677 # SAFEqx('CommandString')
5679 # A special function that is a safe alternative to backtick quotes (and qx//)
5680 # with client-supplied CGI values. All CGI variables are surrounded by
5681 # single ''-quotes (except between existing \'\'-quotes, don't try to be
5682 # too smart). All variables are then interpolated. Simple (@) lists are
5683 # expanded with join(' ', @List), and simple (%) hash tables expanded
5684 # as a list of "key=value" pairs. Complex variables, e.g., @$var, are
5685 # evaluated in a scalar context (e.g., as scalar(@$var)). All occurrences of
5686 # $@% that should NOT be interpolated must be preceeded by a "\".
5687 # If the first line of the String starts with "#! interpreter", the
5688 # remainder of the string is piped into interpreter (after interpolation), i.e.,
5689 # open(INTERPRETER, "|interpreter");print INTERPRETER remainder;
5690 # just like in UNIX. There are some problems with quotes. Be carefull in
5691 # using them. You do not have access to the output of any piped (#!)
5692 # process! If you want such access, execute
5693 # <SCRIPT TYPE="text/osshell">echo "script"|interpreter</SCRIPT> or
5694 # <SCRIPT TYPE="text/ssperl">$resultvar = SAFEqx('echo "script"|interpreter');
5695 # </SCRIPT>.
5697 # SAFEqx ONLY WORKS WHEN THE STRING ITSELF IS SURROUNDED BY SINGLE QUOTES
5698 # (SO THAT IT IS NOT INTERPOLATED BEFORE IT CAN BE PROTECTED)
5699 sub SAFEqx # ('String') -> result of executing qx/"String"/
5701 my $CommandString = shift;
5702 my $NewCommandString = "";
5704 # Only interpolate when required (check the On/Off switch)
5705 unless($CGIscriptor::NoShellScriptInterpolation)
5708 # Handle existing single quotes around CGI values
5709 while($CommandString =~ /\'[^\']+\'/s)
5711 my $CurrentQuotedString = $&;
5712 $NewCommandString .= $`;
5713 $CommandString = $'; # The remaining string
5714 # Interpolate CGI variables between quotes
5715 # (e.g., '$CGIscriptorResults[-1]')
5716 $CurrentQuotedString =~
5717 s/(^|[^\\])([\$\@])((\w*)([\{\[][\$\@\%]?[\:\w\-]+[\}\]])*)/if(exists($main::default_values{$4})){
5718 "$1".eval("$2$3")}else{"$&"}/egs;
5720 # Combine result with previous result
5721 $NewCommandString .= $CurrentQuotedString;
5723 $CommandString = $NewCommandString.$CommandString;
5725 # Select known CGI variables and surround them with single quotes,
5726 # then interpolate all variables
5727 $CommandString =~
5728 s/(^|[^\\])([\$\@\%]+)((\w*)([\{\[][\w\:\$\"\-]+[\}\]])*)/
5729 if($2 eq '$' && exists($main::default_values{$4}))
5730 {"$1\'".eval("\$$3")."\'";}
5731 elsif($2 eq '@'){$1.join(' ', @{"$3"});}
5732 elsif($2 eq '%'){my $t=$1;map {$t.=" $_=".${"$3"}{$_}}
5733 keys(%{"$3"});$t}
5734 else{$1.eval("${2}$3");
5735 }/egs;
5737 # Remove backslashed [$@%]
5738 $CommandString =~ s/\\([\$\@\%])/$1/gs;
5741 # Debugging
5742 # return $CommandString;
5744 # Handle UNIX style "#! shell command\n" constructs as
5745 # a pipe into the shell command. The output cannot be tapped.
5746 my $ReturnValue = "";
5747 if($CommandString =~ /^\s*\#\!([^\f\n\r]+)[\f\n\r]/is)
5749 my $ShellScripts = $';
5750 my $ShellCommand = $1;
5751 open(INTERPRETER, "|$ShellCommand") || dieHandler(29, "\'$ShellCommand\' PIPE not opened: &!\n");
5752 select(INTERPRETER);$| = 1;
5753 print INTERPRETER $ShellScripts;
5754 close(INTERPRETER);
5755 select(STDOUT);$| = 1;
5757 # Shell scripts which are redirected to an existing named pipe.
5758 # The output cannot be tapped.
5759 elsif($CGIscriptor::ShellScriptPIPE)
5761 CGIscriptor::printSAFEqxPIPE($CommandString);
5763 else # Plain ``-backtick execution
5765 # Execute the commands
5766 $ReturnValue = qx/$CommandString/;
5768 return $ReturnValue;
5771 ####################################################################################
5773 # The CGIscriptor PACKAGE
5775 ####################################################################################
5777 # Isolate the evaluation of CGIscriptor functions, i.e., those prefixed with
5778 # "CGIscriptor::"
5780 package CGIscriptor;
5783 # The Interpolation On/Off switch
5784 my $NoShellScriptInterpolation = undef;
5785 # The ShellScript redirection pipe
5786 my $ShellScriptPIPE = undef;
5788 # Open a named PIPE for SAFEqx to receive ALL shell scripts
5789 sub RedirectShellScript # ('CommandString')
5791 my $CommandString = shift || undef;
5793 if($CommandString)
5795 $ShellScriptPIPE = "ShellScriptNamedPipe";
5796 open($ShellScriptPIPE, "|$CommandString")
5797 || main::dieHandler(30, "\'|$CommandString\' PIPE open failed: $!\n");
5799 else
5801 close($ShellScriptPIPE);
5802 $ShellScriptPIPE = undef;
5804 return $ShellScriptPIPE;
5807 # Print to redirected shell script pipe
5808 sub printSAFEqxPIPE # ("String") -> print return value
5810 my $String = shift || undef;
5812 select($ShellScriptPIPE); $| = 1;
5813 my $returnvalue = print $ShellScriptPIPE ($String);
5814 select(STDOUT); $| = 1;
5816 return $returnvalue;
5819 # a pointer to CGIexecute::SAFEqx
5820 sub SAFEqx # ('String') -> result of qx/"String"/
5822 my $CommandString = shift;
5823 return CGIexecute::SAFEqx($CommandString);
5827 # a pointer to CGIexecute::defineCGIvariable
5828 sub defineCGIvariable # ($name[, $default]) ->0/1
5830 my $name = shift;
5831 my $default = shift;
5832 return CGIexecute::defineCGIvariable($name, $default);
5836 # a pointer to CGIexecute::defineCGIvariable
5837 sub defineCGIvariableList # ($name[, $default]) ->0/1
5839 my $name = shift;
5840 my $default = shift;
5841 return CGIexecute::defineCGIvariableList($name, $default);
5845 # a pointer to CGIexecute::defineCGIvariable
5846 sub defineCGIvariableHash # ($name[, $default]) ->0/1
5848 my $name = shift;
5849 my $default = shift;
5850 return CGIexecute::defineCGIvariableHash($name, $default);
5854 # Decode URL encoded arguments
5855 sub URLdecode # (URL encoded input) -> string
5857 my $output = "";
5858 my $char;
5859 my $Value;
5860 foreach $Value (@_)
5862 my $EncodedValue = $Value; # Do not change the loop variable
5863 # Convert all "+" to " "
5864 $EncodedValue =~ s/\+/ /g;
5865 # Convert all hexadecimal codes (%FF) to their byte values
5866 while($EncodedValue =~ /\%([0-9A-F]{2})/i)
5868 $output .= $`.chr(hex($1));
5869 $EncodedValue = $';
5871 $output .= $EncodedValue; # The remaining part of $Value
5873 $output;
5876 # Encode arguments as URL codes.
5877 sub URLencode # (input) -> URL encoded string
5879 my $output = "";
5880 my $char;
5881 my $Value;
5882 foreach $Value (@_)
5884 my @CharList = split('', $Value);
5885 foreach $char (@CharList)
5887 if($char =~ /\s/)
5888 { $output .= "+";}
5889 elsif($char =~ /\w\-/)
5890 { $output .= $char;}
5891 else
5893 $output .= uc(sprintf("%%%2.2x", ord($char)));
5897 $output;
5900 # Extract the value of a CGI variable from the URL-encoded $string
5901 # Also extracts the data blocks from a multipart request. Does NOT
5902 # decode the multipart blocks
5903 sub CGIparseValue # (ValueName [, URL_encoded_QueryString [, \$QueryReturnReference]]) -> Decoded value
5905 my $ValueName = shift;
5906 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5907 my $ReturnReference = shift || undef;
5908 my $output = "";
5910 if($QueryString =~ /(^|\&)$ValueName\=([^\&]*)(\&|$)/)
5912 $output = URLdecode($2);
5913 $$ReturnReference = $' if ref($ReturnReference);
5915 # Get multipart POST or PUT methods
5916 elsif($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
5918 my $MultipartType = $2;
5919 my $BoundaryString = $3;
5920 # Remove the boundary-string
5921 my $temp = $QueryString;
5922 $temp =~ /^\Q--$BoundaryString\E/m;
5923 $temp = $';
5925 # Identify the newline character(s), this is the first character in $temp
5926 my $NewLine = "\r\n"; # Actually, this IS the correct one
5927 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
5929 # Is this correct??? I have to check.
5930 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
5931 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
5932 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
5933 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
5936 # search through all data blocks
5937 while($temp =~ /^\Q--$BoundaryString\E/m)
5939 my $DataBlock = $`;
5940 $temp = $';
5941 # Get the empty line after the header
5942 $DataBlock =~ /$NewLine$NewLine/;
5943 $Header = $`;
5944 $output = $';
5945 my $Header = $`;
5946 $output = $';
5948 # Remove newlines from the header
5949 $Header =~ s/$NewLine/ /g;
5951 # Look whether this block is the one you are looking for
5952 # Require the quotes!
5953 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
5955 my $i;
5956 for($i=length($NewLine); $i; --$i)
5958 chop($output);
5960 # OK, get out
5961 last;
5963 # reinitialize the output
5964 $output = "";
5966 $$ReturnReference = $temp if ref($ReturnReference);
5968 elsif($QueryString !~ /(^|\&)$ValueName\=/) # The value simply isn't there
5970 return undef;
5971 $$ReturnReference = undef if ref($ReturnReference);
5973 else
5975 print "ERROR: $ValueName $main::ENV{'CONTENT_TYPE'}\n";
5977 return $output;
5981 # Get a list of values for the same ValueName. Uses CGIparseValue
5983 sub CGIparseValueList # (ValueName [, URL_encoded_QueryString]) -> List of decoded values
5985 my $ValueName = shift;
5986 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5987 my @output = ();
5988 my $RestQueryString;
5989 my $Value;
5990 while($QueryString &&
5991 (($Value = CGIparseValue($ValueName, $QueryString, \$RestQueryString))
5992 || defined($Value)))
5994 push(@output, $Value);
5995 $QueryString = $RestQueryString; # QueryString is consumed!
5997 # ready, return list with values
5998 return @output;
6001 sub CGIparseValueHash # (ValueName [, URL_encoded_QueryString]) -> Hash table of decoded values
6003 my $ValueName = shift;
6004 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6005 my $RestQueryString;
6006 my %output = ();
6007 while($QueryString && $QueryString =~ /(^|\&)$ValueName([\w]*)\=/)
6009 my $Key = $2;
6010 my $Value = CGIparseValue("$ValueName$Key", $QueryString, \$RestQueryString);
6011 $output{$Key} = $Value;
6012 $QueryString = $RestQueryString; # QueryString is consumed!
6014 # ready, return list with values
6015 return %output;
6018 sub CGIparseForm # ([URL_encoded_QueryString]) -> Decoded Form (NO multipart)
6020 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6021 my $output = "";
6023 $QueryString =~ s/\&/\n/g;
6024 $output = URLdecode($QueryString);
6026 $output;
6029 # Extract the header of a multipart CGI variable from the POST input
6030 sub CGIparseHeader # (ValueName [, URL_encoded_QueryString]) -> Decoded value
6032 my $ValueName = shift;
6033 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6034 my $output = "";
6036 if($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
6038 my $MultipartType = $2;
6039 my $BoundaryString = $3;
6040 # Remove the boundary-string
6041 my $temp = $QueryString;
6042 $temp =~ /^\Q--$BoundaryString\E/m;
6043 $temp = $';
6045 # Identify the newline character(s), this is the first character in $temp
6046 my $NewLine = "\r\n"; # Actually, this IS the correct one
6047 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
6049 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
6050 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
6051 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
6052 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
6055 # search through all data blocks
6056 while($temp =~ /^\Q--$BoundaryString\E/m)
6058 my $DataBlock = $`;
6059 $temp = $';
6060 # Get the empty line after the header
6061 $DataBlock =~ /$NewLine$NewLine/;
6062 $Header = $`;
6063 my $Header = $`;
6065 # Remove newlines from the header
6066 $Header =~ s/$NewLine/ /g;
6068 # Look whether this block is the one you are looking for
6069 # Require the quotes!
6070 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
6072 $output = $Header;
6073 last;
6075 # reinitialize the output
6076 $output = "";
6079 return $output;
6083 # Checking variables for security (e.g., file names and email addresses)
6084 # File names are tested against the $::FileAllowedChars and $::BlockPathAccess variables
6085 sub CGIsafeFileName # FileName -> FileName or ""
6087 my $FileName = shift || "";
6088 return "" if $FileName =~ m?[^$::FileAllowedChars]?;
6089 return "" if $FileName =~ m!(^|/|\:)[\-\.]!;
6090 return "" if $FileName =~ m@\.\.\Q$::DirectorySeparator\E@; # Higher directory not allowed
6091 return "" if $FileName =~ m@\Q$::DirectorySeparator\E\.\.@; # Higher directory not allowed
6092 return "" if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@; # Invisible (blocked) file
6094 return $FileName;
6097 sub CGIsafeEmailAddress # email -> email or ""
6099 my $Email = shift || "";
6100 return "" unless $Email =~ m/^[\w\.\-]+[\@][\w\.\-\:]+$/;
6101 return $Email;
6104 # Get a URL from the web. Needs main::GET_URL($URL) function
6105 # (i.e., curl, snarf, or wget)
6106 sub read_url # ($URL) -> page/file
6108 my $URL = shift || return "";
6110 # Get the commands to read the URL, do NOT add a print command
6111 my $URL_command = main::GET_URL($URL, 1);
6112 # execute the commands, i.e., actually read it
6113 my $URLcontent = CGIexecute->evaluate($URL_command);
6115 # Ready, return the content.
6116 return $URLcontent;
6119 ################################################>>>>>>>>>>Start Remove
6121 # BrowseAllDirs(Directory, indexfile)
6123 # usage:
6124 # <SCRIPT TYPE='text/ssperl'>
6125 # CGIscriptor::BrowseAllDirs('Sounds', 'index.html', '\.wav$')
6126 # </SCRIPT>
6128 # Allows to browse all directories. Stops at '/'. If the directory contains
6129 # an indexfile, eg, index.html, that file will be used instead. Files must match
6130 # the $Pattern, if it is given. Default is
6131 # CGIscriptor::BrowseAllDirs('/', 'index.html', '')
6133 sub BrowseAllDirs # (Directory, indexfile, $Pattern) -> Print HTML code
6135 my $Directory = shift || '/';
6136 my $indexfile = shift || 'index.html';
6137 my $Pattern = shift || '';
6138 $Directory =~ s!/$!!g;
6140 # If the index directory exists, use that one
6141 if(-s "$::CGI_HOME$Directory/$indexfile")
6143 return main::ProcessFile("$::CGI_HOME$Directory/$indexfile");
6146 # No indexfile, continue
6147 my @DirectoryList = glob("$::CGI_HOME$Directory");
6148 $CurrentDirectory = shift(@DirectoryList);
6149 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
6150 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
6151 print "<h1>";
6152 print "$CurrentDirectory" if $CurrentDirectory;
6153 print "</h1>\n";
6155 opendir(BROWSE, "$::CGI_HOME$Directory") || main::dieHandler(31, "$::CGI_HOME$Directory $!");
6156 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
6158 # Print directories
6159 my $file;
6160 print "<pre><ul TYPE='NONE'>\n";
6161 foreach $file (@AllFiles)
6163 next unless -d "$::CGI_HOME$Directory/$file";
6164 # Check whether this file should be visible
6165 next if $::BlockPathAccess &&
6166 "$Directory/$file/" =~ m@$::BlockPathAccess@;
6167 print "<dt><a href='$Directory/$file'>$file</a></dt>\n";
6169 print "</ul></pre>\n";
6171 # Print files
6172 print "<pre><ul TYPE='CIRCLE'>\n";
6173 my $TotalSize = 0;
6174 foreach $file (@AllFiles)
6176 next if $file =~ /^\./;
6177 next if -d "$::CGI_HOME$Directory/$file";
6178 next if -l "$::CGI_HOME$Directory/$file";
6179 # Check whether this file should be visible
6180 next if $::BlockPathAccess &&
6181 "$Directory/$file" =~ m@$::BlockPathAccess@;
6183 if(!$Pattern || $file =~ m@$Pattern@)
6185 my $Date = localtime($^T - (-M "$::CGI_HOME$Directory/$file")*3600*24);
6186 my $Size = -s "$::CGI_HOME$Directory/$file";
6187 $Size = sprintf("%6.0F kB", $Size/1024);
6188 my $Type = `file $::CGI_HOME$Directory/$file`;
6189 $Type =~ s@\s*$::CGI_HOME$Directory/$file\s*\:\s*@@ig;
6190 chomp($Type);
6192 print "<li>";
6193 print "<a href='$Directory/$file'>";
6194 printf("%-40s", "$file</a>");
6195 print "\t$Size\t$Date\t$Type";
6196 print "</li>\n";
6199 print "</ul></pre>";
6201 return 1;
6205 ################################################
6207 # BrowseDirs(RootDirectory [, Pattern, Start])
6209 # usage:
6210 # <SCRIPT TYPE='text/ssperl'>
6211 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', 'Speech', 'DIRECTORY')
6212 # </SCRIPT>
6214 # Allows to browse subdirectories. Start should be relative to the RootDirectory,
6215 # e.g., the full path of the directory 'Speech' is '~/Sounds/Speech'.
6216 # Only files which fit /$Pattern/ and directories are displayed.
6217 # Directories down or up the directory tree are supplied with a
6218 # GET request with the name of the CGI variable in the fourth argument (default
6219 # is 'BROWSEDIRS'). So the correct call for a subdirectory could be:
6220 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', $DIRECTORY, 'DIRECTORY')
6222 sub BrowseDirs # (RootDirectory [, Pattern, Start, CGIvariable, HTTPserver]) -> Print HTML code
6224 my $RootDirectory = shift; # || return 0;
6225 my $Pattern = shift || '\S';
6226 my $Start = shift || "";
6227 my $CGIvariable = shift || "BROWSEDIRS";
6228 my $HTTPserver = shift || '';
6230 $Start = CGIscriptor::URLdecode($Start); # Sometimes, too much has been encoded
6231 $Start =~ s@//+@/@g;
6232 $Start =~ s@[^/]+/\.\.@@ig;
6233 $Start =~ s@^\.\.@@ig;
6234 $Start =~ s@/\.$@@ig;
6235 $Start =~ s!/+$!!g;
6236 $Start .= "/" if $Start;
6238 my @Directory = glob("$::CGI_HOME/$RootDirectory/$Start");
6239 $CurrentDirectory = shift(@Directory);
6240 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
6241 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
6242 print "<h1>";
6243 print "$CurrentDirectory" if $CurrentDirectory;
6244 print "</h1>\n";
6245 opendir(BROWSE, "$::CGI_HOME/$RootDirectory/$Start") || main::dieHandler(31, "$::CGI_HOME/$RootDirectory/$Start $!");
6246 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
6248 # Print directories
6249 my $file;
6250 print "<pre><ul TYPE='NONE'>\n";
6251 foreach $file (@AllFiles)
6253 next unless -d "$::CGI_HOME/$RootDirectory/$Start$file";
6254 # Check whether this file should be visible
6255 next if $::BlockPathAccess &&
6256 "/$RootDirectory/$Start$file/" =~ m@$::BlockPathAccess@;
6258 my $NewURL = $Start ? "$Start$file" : $file;
6259 $NewURL = CGIscriptor::URLencode($NewURL);
6260 print "<dt><a href='";
6261 print "$ENV{SCRIPT_NAME}" if $ENV{SCRIPT_NAME} !~ m@[^\w+\-/]@;
6262 print "$ENV{PATH_INFO}?$CGIvariable=$NewURL'>$file</a></dt>\n";
6264 print "</ul></pre>\n";
6266 # Print files
6267 print "<pre><ul TYPE='CIRCLE'>\n";
6268 my $TotalSize = 0;
6269 foreach $file (@AllFiles)
6271 next if $file =~ /^\./;
6272 next if -d "$::CGI_HOME/$RootDirectory/$Start$file";
6273 next if -l "$::CGI_HOME/$RootDirectory/$Start$file";
6274 # Check whether this file should be visible
6275 next if $::BlockPathAccess &&
6276 "$::CGI_HOME/$RootDirectory/$Start$file" =~ m@$::BlockPathAccess@;
6278 if($file =~ m@$Pattern@)
6280 my $Date = localtime($^T - (-M "$::CGI_HOME/$RootDirectory/$Start$file")*3600*24);
6281 my $Size = -s "$::CGI_HOME/$RootDirectory/$Start$file";
6282 $Size = sprintf("%6.0F kB", $Size/1024);
6283 my $Type = `file $::CGI_HOME/$RootDirectory/$Start$file`;
6284 $Type =~ s@\s*$::CGI_HOME/$RootDirectory/$Start$file\s*\:\s*@@ig;
6285 chomp($Type);
6287 print "<li>";
6288 if($HTTPserver =~ /^\s*[\.\~]\s*$/)
6290 print "<a href='$RootDirectory/$Start$file'>";
6292 elsif($HTTPserver)
6294 print "<a href='$HTTPserver/$RootDirectory/$Start$file'>";
6296 printf("%-40s", "$file</a>") if $HTTPserver;
6297 printf("%-40s", "$file") unless $HTTPserver;
6298 print "\t$Size\t$Date\t$Type";
6299 print "</li>\n";
6302 print "</ul></pre>";
6304 return 1;
6308 # ListDocs(Pattern [,ListType])
6310 # usage:
6311 # <SCRIPT TYPE=text/ssperl>
6312 # CGIscriptor::ListDocs("/*", "dl");
6313 # </SCRIPT>
6315 # This subroutine is very usefull to manage collections of independent
6316 # documents. The resulting list will display the tree-like directory
6317 # structure. If this routine is too slow for online use, you can
6318 # store the result and use a link to that stored file.
6320 # List HTML and Text files with title and first header (HTML)
6321 # or filename and first meaningfull line (general text files).
6322 # The listing starts at the ServerRoot directory. Directories are
6323 # listed recursively.
6325 # You can change the list type (default is dl).
6326 # e.g.,
6327 # <dt><a href=<file.html>>title</a>
6328 # <dd>First Header
6329 # <dt><a href=<file.txt>>file.txt</a>
6330 # <dd>First meaningfull line of text
6332 sub ListDocs # ($Pattern [, prefix]) e.g., ("/Books/*", [, "dl"])
6334 my $Pattern = shift;
6335 $Pattern =~ /\*/;
6336 my $ListType = shift || "dl";
6337 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
6338 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
6339 my @FileList = glob("$::CGI_HOME$Pattern");
6340 my ($FileName, $Path, $Link);
6342 # Print List markers
6343 print "<$ListType>\n";
6345 # Glob all files
6346 File: foreach $FileName (@FileList)
6348 # Check whether this file should be visible
6349 next if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@;
6351 # Recursively list files in all directories
6352 if(-d $FileName)
6354 $FileName =~ m@([^/]*)$@;
6355 my $DirName = $1;
6356 print "<$Prefix>$DirName\n";
6357 $Pattern =~ m@([^/]*)$@;
6358 &ListDocs("$`$DirName/$1", $ListType);
6359 next;
6361 # Use textfiles
6362 elsif(-T "$FileName")
6364 open(TextFile, $FileName) || next;
6366 # Ignore all other file types
6367 else
6368 { next;};
6370 # Get file path for link
6371 $FileName =~ /$::CGI_HOME/;
6372 print "<$Prefix><a href=$URL_root$'>";
6373 # Initialize all variables
6374 my $Line = "";
6375 my $TitleFound = 0;
6376 my $Caption = "";
6377 my $Title = "";
6378 # Read file and step through
6379 while(<TextFile>)
6381 chop $_;
6382 $Line = $_;
6383 # HTML files
6384 if($FileName =~ /\.ht[a-zA-Z]*$/i)
6386 # Catch Title
6387 while(!$Title)
6389 if($Line =~ m@<title>([^<]*)</title>@i)
6391 $Title = $1;
6392 $Line = $';
6394 else
6396 $Line .= <TextFile> || goto Print;
6397 chop $Line;
6400 # Catch First Header
6401 while(!$Caption)
6403 if($Line =~ m@</h1>@i)
6405 $Caption = $`;
6406 $Line = $';
6407 $Caption =~ m@<h1>@i;
6408 $Caption = $';
6409 $Line = $`.$Caption.$Line;
6411 else
6413 $Line .= <TextFile> || goto Print;
6414 chop $Line;
6418 # Other text files
6419 else
6421 # Title equals file name
6422 $FileName =~ /([^\/]+)$/;
6423 $Title = $1;
6424 # Catch equals First Meaningfull line
6425 while(!$Caption)
6427 if($Line =~ /[A-Z]/ &&
6428 ($Line =~ /subject|title/i || $Line =~ /^[\w,\.\s\?\:]+$/)
6429 && $Line !~ /Newsgroup/ && $Line !~ /\:\s*$/)
6431 $Line =~ s/\<[^\>]+\>//g;
6432 $Caption = $Line;
6434 else
6436 $Line = <TextFile> || goto Print;
6440 Print: # Print title and subject
6441 print "$Title</a>\n";
6442 print "<dd>$Caption\n" if $ListType eq "dl";
6443 $TitleFound = 0;
6444 $Caption = "";
6445 close TextFile;
6446 next File;
6449 # Print Closing List Marker
6450 print "</$ListType>\n";
6451 ""; # Empty return value
6455 # HTMLdocTree(Pattern [,ListType])
6457 # usage:
6458 # <SCRIPT TYPE=text/ssperl>
6459 # CGIscriptor::HTMLdocTree("/Welcome.html", "dl");
6460 # </SCRIPT>
6462 # The following subroutine is very usefull for checking large document
6463 # trees. Starting from the root (s), it reads all files and prints out
6464 # a nested list of links to all attached files. Non-existing or misplaced
6465 # files are flagged. This is quite a file-i/o intensive routine
6466 # so you would not like it to be accessible to everyone. If you want to
6467 # use the result, save the whole resulting page to disk and use a link
6468 # to this file.
6470 # HTMLdocTree takes an HTML file or file pattern and constructs nested lists
6471 # with links to *local* files (i.e., only links to the local server are
6472 # followed). The list entries are the document titles.
6473 # If the list type is <dl>, the first <H1> header is used too.
6474 # For each file matching the pattern, a list is made recursively of all
6475 # HTML documents that are linked from it and are stored in the same directory
6476 # or a sub-directory. Warnings are given for missing files.
6477 # The listing starts for the ServerRoot directory.
6478 # You can change the default list type <dl> (<dl>, <ul>, <ol>).
6480 %LinkUsed = ();
6482 sub HTMLdocTree # ($Pattern [, listtype])
6483 # e.g., ("/Welcome.html", [, "ul"])
6485 my $Pattern = shift;
6486 my $ListType = shift || "dl";
6487 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
6488 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
6489 my ($Filename, $Path, $Link);
6490 my %LocalLinks = {};
6492 # Read files (glob them for expansion of wildcards)
6493 my @FileList = glob("$::CGI_HOME$Pattern");
6494 foreach $Path (@FileList)
6496 # Get URL_path
6497 $Path =~ /$::CGI_HOME/;
6498 my $URL_path = $';
6499 # Check whether this file should be visible
6500 next if $::BlockPathAccess && $URL_path =~ m@$::BlockPathAccess@;
6502 my $Title = $URL_path;
6503 my $Caption = "";
6504 # Current file should not be used again
6505 ++$LinkUsed{$URL_path};
6506 # Open HTML doc
6507 unless(open(TextFile, $Path))
6509 print "<$Prefix>$Title <blink>(not found)</blink><br>\n";
6510 next;
6512 while(<TextFile>)
6514 chop $_;
6515 $Line = $_;
6516 # Catch Title
6517 while($Line =~ m@<title>@i)
6519 if($Line =~ m@<title>([^<]*)</title>@i)
6521 $Title = $1;
6522 $Line = $';
6524 else
6526 $Line .= <TextFile>;
6527 chop $Line;
6530 # Catch First Header
6531 while(!$Caption && $Line =~ m@<h1>@i)
6533 if($Line =~ m@</h[1-9]>@i)
6535 $Caption = $`;
6536 $Line = $';
6537 $Caption =~ m@<h1>@i;
6538 $Caption = $';
6539 $Line = $`.$Caption.$Line;
6541 else
6543 $Line .= <TextFile>;
6544 chop $Line;
6547 # Catch and print Links
6548 while($Line =~ m@<a href\=([^>]*)>@i)
6550 $Link = $1;
6551 $Line = $';
6552 # Remove quotes
6553 $Link =~ s/\"//g;
6554 # Remove extras
6555 $Link =~ s/[\#\?].*$//g;
6556 # Remove Servername
6557 if($Link =~ m@(http://|^)@i)
6559 $Link = $';
6560 # Only build tree for current server
6561 next unless $Link =~ m@$::ENV{'SERVER_NAME'}|^/@;
6562 # Remove server name and port
6563 $Link =~ s@^[^\/]*@@g;
6565 # Store the current link
6566 next if $LinkUsed{$Link} || $Link eq $URL_path;
6567 ++$LinkUsed{$Link};
6568 ++$LocalLinks{$Link};
6572 close TextFile;
6573 print "<$Prefix>";
6574 print "<a href=http://";
6575 print "$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}$URL_path>";
6576 print "$Title</a>\n";
6577 print "<br>$Caption\n"
6578 if $Caption && $Caption ne $Title && $ListType =~ /dl/i;
6579 print "<$ListType>\n";
6580 foreach $Link (keys(%LocalLinks))
6582 &HTMLdocTree($Link, $ListType);
6584 print "</$ListType>\n";
6588 ###########################<<<<<<<<<<End Remove
6590 # Make require happy
6593 =head1 NAME
6595 CGIscriptor -
6597 =head1 DESCRIPTION
6599 A flexible HTML 4 compliant script/module for CGI-aware
6600 embeded Perl, shell-scripts, and other scripting languages,
6601 executed at the server side.
6603 =head1 README
6605 Executes embeded Perl code in HTML pages with easy
6606 access to CGI variables. Also processes embeded shell
6607 scripts and scripts in any other language with an
6608 interactive interpreter (e.g., in-line Python, Tcl,
6609 Ruby, Awk, Lisp, Xlispstat, Prolog, M4, R, REBOL, Praat,
6610 sh, bash, csh, ksh).
6612 CGIscriptor is very flexible and hides all the specifics
6613 and idiosyncrasies of correct output and CGI coding and naming.
6614 CGIscriptor complies with the W3C HTML 4.0 recommendations.
6616 This Perl program will run on any WWW server that runs
6617 Perl scripts, just add a line like the following to your
6618 srm.conf file (Apache example):
6620 ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
6622 URL's that refer to http://www.your.address/SHTML/... will
6623 now be handled by CGIscriptor.pl, which can use a private
6624 directory tree (default is the DOCUMENT_ROOT directory tree,
6625 but it can be anywhere).
6627 =head1 PREREQUISITES
6630 =head1 COREQUISITES
6633 =pod OSNAMES
6635 Linux, *BSD, *nix, MS WinXP
6637 =pod SCRIPT CATEGORIES
6639 Servers
6643 =cut