Corrected behavior of ACCEPT.lis and REJECT.lis
[CGIscriptor.git] / CGIscriptor.pl
blob7ef39db9982e5e6d6dfe6decafd2097a36477d2c
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 # 06 Feb 2014 - Corrected behavior of ACCEPT.lis and REJECT.lis
65 # 05 Apr 2013 - Renamed COOKIE_JAR to HTTP_COOKIE, added support for
66 # CGI::Cookie in case $ENV{HTTP_COOKIE} is undefined (untested)
67 # 31 Mar 2013 - Added support for Digest::SHA
68 # 13 Mar 2013 - Changed password hash
69 # 10 Jul 2012 - Version 2.4
70 # 11 Jun 2012 - Securing CGIvariable setting. Made
71 # 'if($ENV{QUERY_STRING} =~ /$name/)' into elsif in
72 # defineCGIvariable/List/Hash to give precedence to ENV{$name}
73 # This was a very old security bug. Added ProtectCGIvariable($name).
74 # 06 Jun 2012 - Added IP only session types after login.
75 # 31 May 2012 - Session ticket system added for handling login sessions.
76 # 29 May 2012 - CGIsafeFileName does not accept filenames starting with '.'
77 # 29 May 2012 - Added CGIscriptor::BrowseAllDirs to handle browsing directories
78 # correctly.
79 # 22 May 2012 - Added Access control with Session Tickets linked to
80 # IP Address and PATH_INFO.
81 # 21 May 2012 - Corrected the links generated by CGIscriptor::BrowseDirs
82 # Will link to current base URL when the HTTP server is '.' or '~'
83 # 29 Oct 2009 - Adapted David A. Wheeler's suggestion about filenames:
84 # CGIsafeFileName does not accept filenames starting with '-'
85 # (http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html)
86 # 08 Oct 2009 - Some corrections in the README.txt file, eg, new email address
87 # 28 Jan 2005 - Added a file selector to performTranslation.
88 # Changed %TranslationTable to @TranslationTable
89 # and patterns to lists.
90 # 27 Jan 2005 - Added a %TranslationTable with associated
91 # performTranslation(\$text) function to allow
92 # run changes in the web pages. Say, to translate
93 # legacy pages with <%=...%> delimiters to the new
94 # <SCRIPT TYPE=..></SCRIPT> format.
95 # 27 Jan 2005 - Small bug of extra '\n' in output removed from the
96 # Other Languages Code.
97 # 10 May 2004 - Belated upload of latest version (2.3) to CPAN
98 # 07 Oct 2003 - Corrected error '\s' -> '\\s' in rebol scripting
99 # language call
100 # 07 Oct 2003 - Corrected omitted INS tags in <DIV><INS> handling
101 # 20 May 2003 - Added a --help switch to print the manual.
102 # 06 Mar 2003 - Adapted the blurb at the end of the file.
103 # 03 Mar 2003 - Added a user definable dieHandler function to catch all
104 # "die" calls. Also "enhanced" the STDERR printout.
105 # 10 Feb 2003 - Split off the reading of the POST part of a query
106 # from Initialize_output. This was suggested by Gerd Franke
107 # to allow for the catching of the file_path using a
108 # POST based lookup. That is, he needed the POST part
109 # to change the file_path.
110 # 03 Feb 2003 - %{$name}; => %{$name} = (); in defineCGIvariableHash.
111 # 03 Feb 2003 - \1 better written as $1 in
112 # $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
113 # 29 Jan 2003 - This makes "CLASS="ssperl" CSS-compatible Gerd Franke
114 # added:
115 # $ServerScriptContentClass = "ssperl";
116 # changed in ProcessFile():
117 # unless(($CurrentContentType =~
118 # 28 Jan 2003 - Added 'INS' Tag! Gerd Franke
119 # 20 Dec 2002 - Removed useless $Directoryseparator variable.
120 # Update comments and documentation.
121 # 18 Dec 2002 - Corrected bug in Accept/Reject processing.
122 # Files didn't work.
123 # 24 Jul 2002 - Added .htaccess documentation (from Gerd Franke)
124 # Also added a note that RawFilePattern can be a
125 # complete file name.
126 # 19 Mar 2002 - Added SRC pseudo-files PREFIX and POSTFIX. These
127 # switch to prepending or to appending the content
128 # of the SRC attribute. Default is prefixing. You
129 # can add as many of these switches as you like.
130 # 13 Mar 2002 - Do not search for tag content if a tag closes with
131 # />, i.e., <DIV ... /> will be handled the XML/XHTML way.
132 # 25 Jan 2002 - Added 'curl' and 'snarf' to SRC attribute URL handling
133 # (replaces wget).
134 # 25 Jan 2002 - Found a bug in SAFEqx, now executes qx() in a scalar context
135 # (i.o. a list context). This is necessary for binary results.
136 # 24 Jan 2002 - Disambiguated -T $SRCfile to -T "$SRCfile" (and -e) and
137 # changed the order of if/elsif to allow removing these
138 # conditions in systems with broken -T functions.
139 # (I also removed a spurious ')' bracket)
140 # 17 Jan 2002 - Changed DIV tag SRC from <SOURCE> to sysread(SOURCE,...)
141 # to support binary files.
142 # 17 Jan 2002 - Removed WhiteSpace from $FileAllowedCharacters.
143 # 17 Jan 2002 - Allow "file://" prefix in SRC attribute. It is simply
144 # stipped from the path.
145 # 15 Jan 2002 - Version 2.2
146 # 15 Jan 2002 - Debugged and completed URL support (including
147 # CGIscriptor::read_url() function)
148 # 07 Jan 2002 - Added automatic (magic) URL support to the SRC attribute
149 # with the main::GET_URL function. Uses wget -O underlying.
150 # 04 Jan 2002 - Added initialization of $NewDirective in InsertForeignScript
151 # (i.e., my $NewDirective = "";) to clear old output
152 # (this was a realy anoying bug).
153 # 03 Jan 2002 - Added a <DIV CLASS='text/ssperl' ID='varname'></DIV>
154 # tags that assign the body text as-is (literally)
155 # to $varname. Allows standard HTML-tools to handle
156 # Cascading Style Sheet templates. This implements a
157 # design by Gerd Franke (franke@roo.de).
158 # 03 Jan 2002 - I finaly gave in and allowed SRC files to expand ~/.
159 # 12 Oct 2001 - Normalized spelling of "CGIsafFileName" in documentation.
160 # 09 Oct 2001 - Added $ENV{'CGI_BINARY_FILE'} to log files to
161 # detect unwanted indexing of TAR files by webcrawlers.
162 # 10 Sep 2001 - Added $YOUR_SCRIPTS directory to @INC for 'require'.
163 # 22 Aug 2001 - Added .txt (Content-type: text/plain) as a default
164 # processed file type. Was processed via BinaryMapFile.
165 # 31 May 2001 - Changed =~ inside CGIsafeEmailAddress that was buggy.
166 # 29 May 2001 - Updated $CGI_HOME to point to $ENV{DOCUMENT_ROOT} io
167 # the root of PATH_TRANSLATED. DOCUMENT_ROOT can now
168 # be manipulated to achieve a "Sub Root".
169 # NOTE: you can have $YOUR_HTML_FILES != DOCUMENT_ROOT
170 # 28 May 2001 - Changed CGIscriptor::BrowsDirs function for security
171 # and debugging (it now works).
172 # 21 May 2001 - defineCGIvariableHash will ADD values to existing
173 # hashes,instead of replacing existing hashes.
174 # 17 May 2001 - Interjected a '&' when pasting POST to GET data
175 # 24 Apr 2001 - Blocked direct requests for BinaryMapFile.
176 # 16 Aug 2000 - Added hash table extraction for CGI parameters with
177 # CGIparseValueHash (used with structured parameters).
178 # Use: CGI='%<CGI-partial-name>' (fill in your name in <>)
179 # Will collect all <CGI-partial-name><key>=value pairs in
180 # $<CGI-partial-name>{<key>} = value;
181 # 16 Aug 2000 - Adapted SAFEqx to protect @PARAMETER values.
182 # 09 Aug 2000 - Added support for non-filesystem input by way of
183 # the CGI_FILE_CONTENTS and CGI_DATA_ACCESS_CODE
184 # environment variables.
185 # 26 Jul 2000 - On the command-line, file-path '-' indicates STDIN.
186 # This allows CGIscriptor to be used in pipes.
187 # Default, $BLOCK_STDIN_HTTP_REQUEST=1 will block this
188 # in an HTTP request (i.e., in a web server).
189 # 26 Jul 2000 - Blocked 'Content-type: text/html' if the SERVER_PROTOCOL
190 # is not HTTP or another protocol. Changed the default
191 # source directory to DOCUMENT_ROOT (i.o. the incorrect
192 # SERVER_ROOT).
193 # 24 Jul 2000 - -slim Command-line argument added to remove all
194 # comments, security, etc.. Updated documentation.
195 # 05 Jul 2000 - Added IF and UNLESS attributes to make the
196 # execution of all <META> and <SCRIPT> code
197 # conditional.
198 # 05 Jul 2000 - Rewrote and isolated the code for extracting
199 # quoted items from CGI and SRC attributes.
200 # Now all attributes expect the same set of
201 # quotes: '', "", ``, (), {}, [] and the same
202 # preceded by a \, e.g., "\((aap)\)" will be
203 # extracted as "(aap)".
204 # 17 Jun 2000 - Construct @ARGV list directly in CGIexecute
205 # name-space (i.o. by evaluation) from
206 # CGI attributes to prevent interference with
207 # the processing for non perl scripts.
208 # Changed CGIparseValueList to prevent runaway
209 # loops.
210 # 16 Jun 2000 - Added a direct (interpolated) display mode
211 # (text/ssdisplay) and a user log mode
212 # (text/sslogfile).
213 # 06 Jun 2000 - Replace "print $Result" with a syswrite loop to
214 # allow large string output.
215 # 02 Jun 2000 - Corrected shrubCGIparameter($CGI_VALUE) to realy
216 # remove all control characters. Changed Interpreter
217 # initialization to shrub interpolated CGI parameters.
218 # Added 'text/ssmailto' interpreter script.
219 # 22 May 2000 - Changed some of the comments
220 # 09 May 2000 - Added list extraction for CGI parameters with
221 # CGIparseValueList (used with multiple selections).
222 # Use: CGI='@<CGI-parameter>' (fill in your name in <>)
223 # 09 May 2000 - Added a 'Not Present' condition to CGIparseValue.
224 # 27 Apr 2000 - Updated documentation to reflect changes.
225 # 27 Apr 2000 - SRC attribute "cleaned". Supported for external
226 # interpreters.
227 # 27 Apr 2000 - CGI attribute can be used in <SCRIPT> tag.
228 # 27 Apr 2000 - Gprolog, M4 support added.
229 # 26 Apr 2000 - Lisp (rep) support added.
230 # 20 Apr 2000 - Use of external interpreters now functional.
231 # 20 Apr 2000 - Removed bug from extracting Content types (RegExp)
232 # 10 Mar 2000 - Qualified unconditional removal of '#' that preclude
233 # the use of $#foo, i.e., I changed
234 # s/[^\\]\#[^\n\f\r]*([\n\f\r])/\1/g
235 # to
236 # s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/\1/g
237 # 03 Mar 2000 - Added a '$BlockPathAccess' variable to "hide"
238 # things like, e.g., CVS information in CVS subtrees
239 # 10 Feb 2000 - URLencode/URLdecode have been made case-insensitive
240 # 10 Feb 2000 - Added a BrowseDirs function (CGIscriptor package)
241 # 01 Feb 2000 - A BinaryMapFile in the ~/ directory has precedence
242 # over a "burried" BinaryMapFile.
243 # 04 Oct 1999 - Added two functions to check file names and email addresses
244 # (CGIscriptor::CGIsafeFileName and
245 # CGIscriptor::CGIsafeEmailAddress)
246 # 28 Sept 1999 - Corrected bug in sysread call for reading POST method
247 # to allow LONG posts.
248 # 28 Sept 1999 - Changed CGIparseValue to handle multipart/form-data.
249 # 29 July 1999 - Refer to BinaryMapFile from CGIscriptor directory, if
250 # this directory exists.
251 # 07 June 1999 - Limit file-pattern matching to LAST extension
252 # 04 June 1999 - Default text/html content type is printed only once.
253 # 18 May 1999 - Bug in replacement of ~/ and ./ removed.
254 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
255 # 15 May 1999 - Changed the name of the execute package to CGIexecute.
256 # Changed the processing of the Accept and Reject file.
257 # Added a full expression evaluation to Access Control.
258 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
259 # 27 Apr 1999 - Brought CGIscriptor under the GNU GPL. Made CGIscriptor
260 # Version 1.1 a module that can be called with 'require "CGIscriptor.pl"'.
261 # Requests are serviced by "Handle_Request()". CGIscriptor
262 # can still be called as a isolated perl script and a shell
263 # command.
264 # Changed the "factory default setting" so that it will run
265 # from the DOCUMENT_ROOT directory.
266 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
267 # 29 Mar 1999 - Remove second debugging STDERR switch. Moved most code
268 # to subroutines to change CGIscriptor into a module.
269 # Added mapping to process unsupported file types (e.g., binary
270 # pictures). See $BinaryMapFile.
271 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
272 # 24 Sept 1998 - Changed text of license (Rob van Son, R.J.J.H.vanSon@gmail.com)
273 # Removed a double setting of filepatterns and maximum query
274 # size. Changed email address. Removed some typos from the
275 # comments.
276 # 02 June 1998 - Bug fixed in URLdecode. Changing the foreach loop variable
277 # caused quiting CGIscriptor.(Rob van Son, R.J.J.H.vanSon@gmail.com)
278 # 02 June 1998 - $SS_PUB and $SS_SCRIPT inserted an extra /, removed.
279 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
282 # Known Bugs:
284 # 23 Mar 2000
285 # It is not possible to use operators or variables to construct variable names,
286 # e.g., $bar = \@{$foo}; won't work. However, eval('$bar = \@{'.$foo.'};');
287 # will indeed work. If someone could tell me why, I would be obliged.
290 ############################################################################
292 # OBLIGATORY USER CONFIGURATION
294 # Configure the directories where all user files can be found (this
295 # is the equivalent of the server root directory of a WWW-server).
296 # These directories can be located ANYWHERE. For security reasons, it is
297 # better to locate them outside the WWW-tree of your HTTP server, unless
298 # CGIscripter handles ALL requests.
300 # For convenience, the defaults are set to the root of the WWW server.
301 # However, this might not be safe!
303 # ~/ text files
304 # $YOUR_HTML_FILES = "/usr/pub/WWW/SHTML"; # or SS_PUB as environment var
305 # (patch to use the parent directory of CGIscriptor as document root, should be removed)
306 if($ENV{'SCRIPT_FILENAME'}) # && $ENV{'SCRIPT_FILENAME'} !~ /\Q$ENV{'DOCUMENT_ROOT'}\E/)
308 $ENV{'DOCUMENT_ROOT'} = $ENV{'SCRIPT_FILENAME'};
309 $ENV{'DOCUMENT_ROOT'} =~ s@/CGIscriptor.*$@@ig;
312 # Just enter your own directory path here
313 $YOUR_HTML_FILES = $ENV{'DOCUMENT_ROOT'}; # default is the DOCUMENT_ROOT
315 # ./ script files (recommended to be different from the previous)
316 # $YOUR_SCRIPTS = "/usr/pub/WWW/scripts"; # or SS_SCRIPT as environment var
317 $YOUR_SCRIPTS = $YOUR_HTML_FILES; # This might be a SECURITY RISK
319 # End of obligatory user configuration
320 # (note: there is more non-essential user configuration below)
322 ############################################################################
324 # OPTIONAL USER CONFIGURATION (all values are used CASE INSENSITIVE)
326 # Script content-types: TYPE="Content-type" (user defined mime-type)
327 $ServerScriptContentType = "text/ssperl"; # Server Side Perl scripts
328 # CSS require a simple class
329 $ServerScriptContentClass = $ServerScriptContentType =~ m!/! ?
330 $' : "ssperl"; # Server Side Perl CSS classes
332 $ShellScriptContentType = "text/osshell"; # OS shell scripts
333 # # (Server Side perl ``-execution)
335 # Accessible file patterns, block any request that doesn't match.
336 # Matches any file with the extension .(s)htm(l), .txt, or .xmr
337 # (\. is used in regexp)
338 # Note: die unless $PATH_INFO =~ m@($FilePattern)$@is;
339 $FilePattern = ".shtml|.htm|.html|.xml|.xmr|.txt|.js|.css";
341 # The table with the content type MIME types
342 # (allows to differentiate MIME types, if needed)
343 %ContentTypeTable =
345 '.html' => 'text/html',
346 '.shtml' => 'text/html',
347 '.htm' => 'text/html',
348 '.xml' => 'text/xml',
349 '.txt' => 'text/plain',
350 '.js' => 'text/plain',
351 '.css' => 'text/plain'
355 # File pattern post-processing
356 $FilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
358 # SHAsum command needed for Authorization and Login
359 # (note, these have to be accessible in the HTML pages, ie, the CGIexecute environment)
360 my $shasum = "shasum -a 256";
361 if(qx{uname} =~ /Darwin/)
363 $shasum = "shasum-5.12 -a 256" unless `which shasum`;
365 my $SHASUMCMD = $shasum.' |cut -f 1 -d" "';
366 $ENV{"SHASUMCMD"} = $SHASUMCMD;
367 my $RANDOMHASHCMD = 'dd bs=1 count=64 if=/dev/urandom 2>/dev/null | '.$shasum.' -b |cut -f 1 -d" "';
368 $ENV{"RANDOMHASHCMD"} = $RANDOMHASHCMD;
370 # Hash a string, return hex of hash
371 sub hash_string_cmd # ($string) -> hex_hash
373 my $string = shift || "";
374 # Catch nasty \'-quotes, embed them in '..'"'"'..'
375 $string =~ s/\'/\'\"\'\"\'/isg;
376 my $hash = `printf '%s' '$string'| $ENV{"SHASUMCMD"}`;
377 chomp($hash);
378 return $hash;
381 # Note that you CANNOT replace $RANDOMHASHCMD with a call using hash_string_cmd
382 # as the output of /dev/urandom breaks string handling in Perl.
383 # Generate random hex hash
384 sub get_random_hex_cmd # () -> hex
386 # Create Random Hash Salt
387 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $RANDOMHASHCMD | $!\n";
388 my $RANDOMSALT= <URANDOM>;
389 close(URANDOM);
390 chomp($RANDOMSALT);
392 return $RANDOMSALT;
396 # You can use Digest::SHA (SHA.pm), you need sha256_hex
397 # See http://search.cpan.org/~mshelor/Digest-SHA-5.84/lib/Digest/SHA.pm
398 # > sudo CPAN -i Digest
400 # The following code will check whether Digest::SHA is available and then
401 # use the appropriate function calls.
403 $shaDigestLoaded = (eval("require Digest::SHA;1;") eq "1") ? 1 : 0;
405 sub hash_string_Digest # ($string) -> hex_hash
407 my $string = shift || "";
408 my $digest = Digest::SHA::sha256_hex($string);
409 $string = $digest;
410 return $digest;
413 sub get_random_hex_Digest # () -> hex
415 my $randomstring = "";
416 # Create Random Hash Salt
417 open(URANDOM, "</dev/urandom") || die "/dev/urandom: $!\n";
418 read URANDOM, $randomstring, 64 || die "No random bytes read: $!\n";
419 close(URANDOM);
420 my $RANDOMSALT= hash_string_Digest($randomstring);
422 return $RANDOMSALT;
425 # The final functions
426 sub hash_string # ($string) -> hex_hash
428 if($shaDigestLoaded)
429 { return hash_string_Digest (@_) }
430 else
431 { return hash_string_cmd(@_);};
434 sub get_random_hex # () -> hex
436 if($shaDigestLoaded)
437 { return get_random_hex_Digest () }
438 else
439 { return get_random_hex_cmd();};
442 ######################################################################
444 # File patterns of files which are handled by session tickets.
445 %TicketRequiredPatterns = (
446 '^/Private(/|$)' => "Private/.Sessions\tPrivate/.Passwords\t/Private/Login.html\t+36000"
448 # Used to set cookies, only session cookies supported
449 my %SETCOOKIELIST = ();
450 my %CGI_Cookies = ();
451 # Parse the cookies if $ENV{'HTTP_COOKIE'} is defined, else use CGI::Cookie
452 # if it is available
453 sub Get_All_Cookies
455 $ENV{'HTTP_COOKIE'} = $ENV{'Cookie'} if defined($ENV{'Cookie'}) && !defined($ENV{'HTTP_COOKIE'});
457 if(defined($ENV{'HTTP_COOKIE'}))
459 my @CookieList = split(/[\;\s]+/, $ENV{'HTTP_COOKIE'});
460 foreach my $CookieEntry (@CookieList)
462 my ($k, $v) = split(/\=/, $CookieEntry);
463 # Add new cookie only if it does not already exist
464 $CGI_Cookies{$k} = $v unless exists($CGI_Cookies{$k}) && ($v eq "" || $v eq "-");
465 ($k, $v, $CookieEntry) = (0, 0, 0);
467 @CookieList = ();
468 $ENV{'Cookie'} = "" if defined($ENV{'Cookie'})
470 else
472 my $cookiesLoaded = (eval("require CGI::Cookie;1;") eq "1") ? 1 : 0;
473 if($cookiesLoaded)
475 %CGI_Cookies = fetch CGI::Cookie;
481 # Session Ticket Directory: Private/.Sessions
482 # Password Directory: Private/.Passwords
483 # Login page (url path): /Private/Login.html
484 # Expiration time (s): +3600
485 # +<seconds> = relative time <seconds> is absolute date-time
487 # Manage login
488 # Set up a valid ticket from a given text file
489 # Use from command line. DO NOT USE ONLINE
490 # Watch out for passwords that get stored in the history file
492 # perl CGIscriptor.pl --managelogin [options] [files]
493 # Options:
494 # salt={file or saltvalue}
495 # masterkey={file or plaintext}
496 # newmasterkey={file or plaintext}
497 # password={file or palintext}
499 # Followed by one or more file names.
500 # Options can be interspersed between filenames,
501 # e.g., password='plaintext'
502 # Note that passwords are only used once!
504 if($ARGV[0] =~ /^\-\-managelogin/i)
506 my @arguments = @ARGV;
507 shift(@arguments);
508 setup_ticket_file(@arguments);
509 # Should be run on the command line
510 exit;
515 # Raw files must contain their own Content-type (xmr <- x-multipart-replace).
516 # THIS IS A SUBSET OF THE FILES DEFINED IN $FilePattern
517 $RawFilePattern = ".xmr";
518 # (In principle, this could contain a full file specification, e.g.,
519 # ".xmr|relocated.html")
521 # Raw File pattern post-processing
522 $RawFilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
524 # Server protocols for which "Content-type: text/html\n\n" should be printed
525 # (you should not bother with these, except for HTTP, they are mostly imaginary)
526 $ContentTypeServerProtocols = 'HTTP|MAIL|MIME';
528 # Block access to all (sub-) paths and directories that match the
529 # following (URL) path (is used as:
530 # 'die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;' )
531 $BlockPathAccess = '/(CVS|\.git)/'; # Protect CVS and .git information
533 # All (blocked) other file-types can be mapped to a single "binary-file"
534 # processor (a kind of pseudo-file path). This can either be an error
535 # message (e.g., "illegal file") or contain a script that serves binary
536 # files.
537 # Note: the real file path wil be stored in $ENV{CGI_BINARY_FILE}.
538 $BinaryMapFile = "/BinaryMapFile.xmr";
539 # Allow for the addition of a CGIscriptor directory
540 # Note that a BinaryMapFile in the root "~/" directory has precedence
541 $BinaryMapFile = "/CGIscriptor".$BinaryMapFile
542 if ! -e "$YOUR_HTML_FILES".$BinaryMapFile
543 && -e "$YOUR_HTML_FILES/CGIscriptor".$BinaryMapFile;
546 # List of all characters that are allowed in file names and paths.
547 # All requests containing illegal characters are blocked. This
548 # blocks most tricks (e.g., adding "\000", "\n", or other control
549 # characters, also blocks URI's using %FF)
550 # THIS IS A SECURITY FEATURE
551 # (this is also used to parse filenames in SRC= features, note the
552 # '-quotes, they are essential)
553 $FileAllowedChars = '\w\.\~\/\:\*\?\-'; # Covers Unix and Mac, but NO spaces
555 # Maximum size of the Query (number of characters clients can send
556 # covers both GET & POST combined)
557 $MaximumQuerySize = 2**20 - 1; # = 2**14 - 1
560 # Embeded URL get function used in SRC attributes and CGIscriptor::read_url
561 # (returns a string with the PERL code to transfer the URL contents, e.g.,
562 # "SAFEqx(\'curl \"http://www.fon.hum.uva.nl\"\')")
563 # "SAFEqx(\'wget --quiet --output-document=- \"http://www.fon.hum.uva.nl\"\')")
564 # Be sure to handle <BASE HREF='URL'> and allow BOTH
565 # direct printing GET_URL($URL [, 0]) and extracting the content of
566 # the $URL for post-processing GET_URL($URL, 1).
567 # You get the WHOLE file, including HTML header.
568 # The shell command Use $URL where the URL should go
569 # ('wget', 'snarf' or 'curl', uncomment the one you would like to use)
570 my $GET_URL_shell_command = 'wget --quiet --output-document=- $URL';
571 #my $GET_URL_shell_command = 'snarf $URL -';
572 #my $GET_URL_shell_command = 'curl $URL';
574 sub GET_URL # ($URL, $ValueNotPrint) -> content_of_url
576 my $URL = shift || return;
577 my $ValueNotPrint = shift || 0;
579 # Check URL for illegal characters
580 return "print '<h1>Illegal URL<h1>'\"\n\";" if $URL =~ /[^$FileAllowedChars\%]/;
582 # Include URL in final command
583 my $CurrentCommand = $GET_URL_shell_command;
584 $CurrentCommand =~ s/\$URL/$URL/g;
586 # Print to STDOUT or return a value
587 my $BlockPrint = "print STDOUT ";
588 $BlockPrint = "" if $ValueNotPrint;
590 my $Commands = <<"GETURLCODE";
591 # Get URL
593 my \$Page = "";
595 # Simple, using shell command
596 \$Page = SAFEqx('$CurrentCommand');
598 # Add a BASE tage to the header
599 \$Page =~ s!\\</head!\\<base href='$URL'\\>\\</head!ig unless \$Page =~ m!\\<base!;
601 # Print the URL value, or return it as a value
602 $BlockPrint\$Page;
604 GETURLCODE
605 return $Commands;
608 # As files can get rather large (and binary), you might want to use
609 # some more intelligent reading procedure, e.g.,
610 # Direct Perl
611 # # open(URLHANDLE, '/usr/bin/wget --quiet --output-document=- "$URL"|') || die "wget: \$!";
612 # #open(URLHANDLE, '/usr/bin/snarf "$URL" -|') || die "snarf: \$!";
613 # open(URLHANDLE, '/usr/bin/curl "$URL"|') || die "curl: \$!";
614 # my \$text = "";
615 # while(sysread(URLHANDLE,\$text, 1024) > 0)
617 # \$Page .= \$text;
618 # };
619 # close(URLHANDLE) || die "\$!";
620 # However, this doesn't work with the CGIexecute->evaluate() function.
621 # You get an error: 'No child processes at (eval 16) line 15, <file0> line 8.'
623 # You can forget the next two variables, they are only needed when
624 # you don't want to use a regular file system (i.e., with open)
625 # but use some kind of database/RAM image for accessing (generating)
626 # the data.
628 # Name of the environment variable that contains the file contents
629 # when reading directly from Database/RAM. When this environment variable,
630 # $ENV{$CGI_FILE_CONTENTS}, is not false, no real file will be read.
631 $CGI_FILE_CONTENTS = 'CGI_FILE_CONTENTS';
632 # Uncomment the following if you want to force the use of the data access code
633 # $ENV{$CGI_FILE_CONTENTS} = '-'; # Force use of $ENV{$CGI_DATA_ACCESS_CODE}
635 # Name of the environment variable that contains the RAM access perl
636 # code needed to read additional "files", i.e.,
637 # $ENV{$CGI_FILE_CONTENTS} = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
638 # When $ENV{$CGI_FILE_CONTENTS} eq '-', this code is executed to generate the data.
639 $CGI_DATA_ACCESS_CODE = 'CGI_DATA_ACCESS_CODE';
641 # You can, of course, fill this yourself, e.g.,
642 # $ENV{$CGI_DATA_ACCESS_CODE} =
643 # 'open(INPUT, "<$_[0]"); while(<INPUT>){print;};close(INPUT);'
646 # DEBUGGING
648 # Suppress error messages, this can be changed for debugging or error-logging
649 #open(STDERR, "/dev/null"); # (comment out for use in debugging)
651 # SPECIAL: Remove Comments, security, etc. if the command line is
652 # '>CGIscriptor.pl -slim >slimCGIscriptor.pl'
653 $TrimDownCGIscriptor = 1 if $ARGV[0] =~ /^\-slim/i;
655 # If CGIscriptor is used from the command line, the command line
656 # arguments are interpreted as the file (1st) and the Query String (rest).
657 # Get the arguments
658 $ENV{'PATH_INFO'} = shift(@ARGV) unless exists($ENV{'PATH_INFO'}) || grep(/\-\-help/i, @ARGV);
659 $ENV{'QUERY_STRING'} = join("&", @ARGV) unless exists($ENV{'QUERY_STRING'});
662 # Handle bail-outs in a user definable way.
663 # Catch Die and replace it with your own function.
664 # Ends with a call to "die $_[0];"
666 sub dieHandler # ($ErrorCode, "Message", @_) -> DEAD
668 my $ErrorCode = shift;
669 my $ErrorMessage = shift;
671 # Place your own reporting functions here
673 # Now, kill everything (default)
674 print STDERR "$ErrorCode: $ErrorMessage\n";
675 die $ErrorMessage;
679 # End of optional user configuration
680 # (note: there is more non-essential user configuration below)
682 if(grep(/\-\-help/i, @ARGV))
684 print << 'ENDOFPREHELPTEXT2';
686 ###############################################################################
688 # Author and Copyright (c):
689 # Rob van Son, © 1995,1996,1997,1998,1999,2000,2001,2002-2012
690 # NKI-AVL Amsterdam
691 # r.v.son@nki.nl
692 # Institute of Phonetic Sciences & IFOTT/ACLS
693 # University of Amsterdam
694 # Email: R.J.J.H.vanSon@gmail.com
695 # Email: R.J.J.H.vanSon@gmail.com
696 # WWW : http://www.fon.hum.uva.nl/rob/
698 # License for use and disclaimers
700 # CGIscriptor merges plain ASCII HTML files transparantly
701 # with CGI variables, in-line PERL code, shell commands,
702 # and executable scripts in other scripting languages.
704 # This program is free software; you can redistribute it and/or
705 # modify it under the terms of the GNU General Public License
706 # as published by the Free Software Foundation; either version 2
707 # of the License, or (at your option) any later version.
709 # This program is distributed in the hope that it will be useful,
710 # but WITHOUT ANY WARRANTY; without even the implied warranty of
711 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
712 # GNU General Public License for more details.
714 # You should have received a copy of the GNU General Public License
715 # along with this program; if not, write to the Free Software
716 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
719 # Contributors:
720 # Rob van Son (R.J.J.H.vanSon@gmail.com)
721 # Gerd Franke franke@roo.de (designed the <DIV> behaviour)
723 #######################################################
724 ENDOFPREHELPTEXT2
726 #######################################################>>>>>>>>>>Start Remove
728 # You can skip the following code, it is an auto-splice
729 # procedure.
731 # Construct a slimmed down version of CGIscriptor
732 # (i.e., CGIscriptor.pl -slim > slimCGIscriptor.pl)
734 if($TrimDownCGIscriptor)
736 open(CGISCRIPTOR, "<CGIscriptor.pl")
737 || dieHandler(1, "<CGIscriptor.pl not slimmed down: $!\n");
738 my $SKIPtext = 0;
739 my $SKIPComments = 0;
741 while(<CGISCRIPTOR>)
743 my $SKIPline = 0;
745 ++$LineCount;
747 # Start of SKIP text
748 $SKIPtext = 1 if /[\>]{10}Start Remove/;
749 $SKIPComments = 1 if $SKIPtext == 1;
751 # Skip this line?
752 $SKIPline = 1 if $SKIPtext || ($SKIPComments && /^\s*\#/);
754 ++$PrintCount unless $SKIPline;
756 print STDOUT $_ unless $SKIPline;
758 # End of SKIP text ?
759 $SKIPtext = 0 if /[\<]{10}End Remove/;
761 # Ready!
762 print STDERR "\# Printed $PrintCount out of $LineCount lines\n";
763 exit;
766 #######################################################
768 if(grep(/\-\-help/i, @ARGV))
770 print << 'ENDOFHELPTEXT';
772 # HYPE
774 # CGIscriptor merges plain ASCII HTML files transparantly and safely
775 # with CGI variables, in-line PERL code, shell commands, and executable
776 # scripts in many languages (on-line and real-time). It combines the
777 # "ease of use" of HTML files with the versatillity of specialized
778 # scripts and PERL programs. It hides all the specifics and
779 # idiosyncrasies of correct output and CGI coding and naming. Scripts
780 # do not have to be aware of HTML, HTTP, or CGI conventions just as HTML
781 # files can be ignorant of scripts and the associated values. CGIscriptor
782 # complies with the W3C HTML 4.0 recommendations.
783 # In addition to its use as a WWW embeded CGI processor, it can
784 # be used as a command-line document preprocessor (text-filter).
786 # THIS IS HOW IT WORKS
788 # The aim of CGIscriptor is to execute "plain" scripts inside a text file
789 # using any required CGIparameters and environment variables. It
790 # is optimized to transparantly process HTML files inside a WWW server.
791 # The native language is Perl, but many other scripting languages
792 # can be used.
794 # CGIscriptor reads text files from the requested input file (i.e., from
795 # $YOUR_HTML_FILES$PATH_INFO) and writes them to <STDOUT> (i.e., the
796 # client requesting the service) preceded by the obligatory
797 # "Content-type: text/html\n\n" or "Content-type: text/plain\n\n" string
798 # (except for "raw" files which supply their own Content-type message
799 # and only if the SERVER_PROTOCOL supports HTTP, MAIL, or MIME).
801 # When CGIscriptor encounters an embedded script, indicated by an HTML4 tag
803 # <SCRIPT TYPE="text/ssperl" [CGI="$VAR='default value'"] [SRC="ScriptSource"]>
804 # PERL script
805 # </SCRIPT>
807 # or
809 # <SCRIPT TYPE="text/osshell" [CGI="$name='default value'"] [SRC="ScriptSource"]>
810 # OS Shell script
811 # </SCRIPT>
813 # construct (anything between []-brackets is optional, other MIME-types
814 # and scripting languages are supported), the embedded script is removed
815 # and both the contents of the source file (i.e., "do 'ScriptSource'")
816 # AND the script are evaluated as a PERL program (i.e., by eval()),
817 # shell script (i.e., by a "safe" version of `Command`, qx) or an external
818 # interpreter. The output of the eval() function takes the place of the
819 # original <SCRIPT></SCRIPT> construct in the output string. Any CGI
820 # parameters declared by the CGI attribute are available as simple perl
821 # variables, and can subsequently be made available as variables to other
822 # scripting languages (e.g., bash, python, or lisp).
824 # Example: printing "Hello World"
825 # <HTML><HEAD><TITLE>Hello World</TITLE>
826 # <BODY>
827 # <H1><SCRIPT TYPE="text/ssperl">"Hello World"</SCRIPT></H1>
828 # </BODY></HTML>
830 # Save this in a file, hello.html, in the directory you indicated with
831 # $YOUR_HTML_FILES and access http://your_server/SHTML/hello.html
832 # (or to whatever name you use as an alias for CGIscriptor.pl).
833 # This is realy ALL you need to do to get going.
835 # You can use any values that are delivered in CGI-compliant form (i.e.,
836 # the "?name=value" type URL additions) transparently as "$name" variables
837 # in your scripts IFF you have declared them in the CGI attribute of
838 # a META or SCRIPT tag before e.g.:
839 # <META CONTENT="text/ssperl; CGI='$name = `default value`'
840 # [SRC='ScriptSource']">
841 # or
842 # <SCRIPT TYPE="text/ssperl" CGI="$name = 'default value'"
843 # [SRC='ScriptSource']>
844 # After such a 'CGI' attribute, you can use $name as an ordinary PERL variable
845 # (the ScriptSource file is immediately evaluated with "do 'ScriptSource'").
846 # The CGIscriptor script allows you to write ordinary HTML files which will
847 # include dynamic CGI aware (run time) features, such as on-line answers
848 # to specific CGI requests, queries, or the results of calculations.
850 # For example, if you wanted to answer questions of clients, you could write
851 # a Perl program called "Answer.pl" with a function "AnswerQuestion()"
852 # that prints out the answer to requests given as arguments. You then write
853 # an HTML page "Respond.html" containing the following fragment:
855 # <center>
856 # The Answer to your question
857 # <META CONTENT="text/ssperl; CGI='$Question'">
858 # <h3><SCRIPT TYPE="text/ssperl">$Question</SCRIPT></h3>
859 # is
860 # <h3><SCRIPT TYPE="text/ssperl" SRC="./PATH/Answer.pl">
861 # AnswerQuestion($Question);
862 # </SCRIPT></h3>
863 # </center>
864 # <FORM ACTION=Respond.html METHOD=GET>
865 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
866 # <INPUT TYPE=SUBMIT VALUE="Ask">
867 # </FORM>
869 # The output could look like the following (in HTML-speak):
871 # <CENTER>
872 # The Answer to your question
873 # <h3>What is the capital of the Netherlands?</h3>
874 # is
875 # <h3>Amsterdam</h3>
876 # </CENTER>
877 # <FORM ACTION=Respond.html METHOD=GET>
878 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
879 # <INPUT TYPE=SUBMIT VALUE="Ask">
881 # Note that the function "Answer.pl" does know nothing about CGI or HTML,
882 # it just prints out answers to arguments. Likewise, the text has no
883 # provisions for scripts or CGI like constructs. Also, it is completely
884 # trivial to extend this "program" to use the "Answer" later in the page
885 # to call up other information or pictures/sounds. The final text never
886 # shows any cue as to what the original "source" looked like, i.e.,
887 # where you store your scripts and how they are called.
889 # There are some extra's. The argument of the files called in a SRC= tag
890 # can access the CGI variables declared in the preceding META tag from
891 # the @ARGV array. Executable files are called as:
892 # `file '$ARGV[0]' ... ` (e.g., `Answer.pl \'$Question\'`;)
893 # The files called from SRC can even be (CGIscriptor) html files which are
894 # processed in-line. Furthermore, the SRC= tag can contain a perl block
895 # that is evaluated. That is,
896 # <META CONTENT="text/ssperl; CGI='$Question' SRC='{$Question}'">
897 # will result in the evaluation of "print do {$Question};" and the VALUE
898 # of $Question will be printed. Note that these "SRC-blocks" can be
899 # preceded and followed by other file names, but only a single block is
900 # allowed in a SRC= tag.
902 # One of the major hassles of dynamic WWW pages is the fact that several
903 # mutually incompatible browsers and platforms must be supported. For example,
904 # the way sound is played automatically is different for Netscape and
905 # Internet Explorer, and for each browser it is different again on
906 # Unix, MacOS, and Windows. Realy dangerous is processing user-supplied
907 # (form-) values to construct email addresses, file names, or database
908 # queries. All Apache WWW-server exploits reported in the media are
909 # based on faulty CGI-scripts that didn't check their user-data properly.
911 # There is no panacee for these problems, but a lot of work and problems
912 # can be saved by allowing easy and transparent control over which
913 # <SCRIPT></SCRIPT> blocks are executed on what CGI-data. CGIscriptor
914 # supplies such a method in the form of a pair of attributes:
915 # IF='...condition..' and UNLESS='...condition...'. When added to a
916 # script tag, the whole block (including the SRC attribute) will be
917 # ignored if the condition is false (IF) or true (UNLESS).
918 # For example, the following block will NOT be evaluated if the value
919 # of the CGI variable FILENAME is NOT a valid filename:
921 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
922 # IF='CGIscriptor::CGIsafeFileName($FILENAME)'>
923 # .....
924 # </SCRIPT>
926 # (the function CGIsafeFileName(String) returns an empty string ("")
927 # if the String argument is not a valid filename).
928 # The UNLESS attribute is the mirror image of IF.
930 # A user manual follows the HTML 4 and security paragraphs below.
932 ##########################################################################
934 # HTML 4 compliance
936 # In general, CGIscriptor.pl complies with the HTML 4 recommendations of
937 # the W3C. This means that any software to manage Web sites will be able
938 # to handle CGIscriptor files, as will web agents.
940 # All script code should be placed between <SCRIPT></SCRIPT> tags, the
941 # script type is indicated with TYPE="mime-type", the LANGUAGE
942 # feature is ignored, and a SRC feature is implemented. All CGI specific
943 # features are delegated to the CGI attribute.
945 # However, the behavior deviates from the W3C recommendations at some
946 # points. Most notably:
947 # 0- The scripts are executed at the server side, invissible to the
948 # client (i.e., the browser)
949 # 1- The mime-types are personal and idiosyncratic, but can be adapted.
950 # 2- Code in the body of a <SCRIPT></SCRIPT> tag-pair is still evaluated
951 # when a SRC feature is present.
952 # 3- The SRC attribute reads a list of files.
953 # 4- The files in a SRC attribute are processed according to file type.
954 # 5- The SRC attribute evaluates inline Perl code.
955 # 6- Processed META, DIV, INS tags are removed from the output
956 # document.
957 # 7- All attributes of the processed META tags, except CONTENT, are ignored
958 # (i.e., deleted from the output).
959 # 8- META tags can be placed ANYWHERE in the document.
960 # 9- Through the SRC feature, META tags can have visible output in the
961 # document.
962 # 10- The CGI attribute that declares CGI parameters, can be used
963 # inside the <SCRIPT> tag.
964 # 11- Use of an extended quote set, i.e., '', "", ``, (), {}, []
965 # and their \-slashed combinations: \'\', \"\", \`\`, \(\),
966 # \{\}, \[\].
967 # 12- IF and UNLESS attributes to <SCRIPT>, <META>, <DIV>, <INS> tags.
968 # 13- <DIV> tags cannot be nested, DIV tags are not
969 # rendered with new-lines.
970 # 14- The XML style <TAG .... /> is recognized and handled correctly.
971 # (i.e., no content is processed)
973 # The reasons for these choices are:
974 # You can still write completely HTML4 compliant documents. CGIscriptor
975 # will not force you to write "deviant" code. However, it allows you to
976 # do so (which is, in fact, just as bad). The prime design principle
977 # was to allow users to include plain Perl code. The code itself should
978 # be "enhancement free". Therefore, extra features were needed to
979 # supply easy access to CGI and Web site components. For security
980 # reasons these have to be declared explicitly. The SRC feature
981 # transparently manages access to external files, especially the safe
982 # use of executable files.
983 # The CGI attribute handles the declarations of external (CGI) variables
984 # in the SCRIPT and META tag's.
985 # EVERYTHING THE CGI ATTRIBUTE AND THE META TAG DO CAN BE DONE INSIDE
986 # A <SCRIPT></SCRIPT> TAG CONSTRUCT.
988 # The reason for the IF, UNLESS, and SRC attributes (and their Perl code
989 # evaluation) were build into the META and SCRIPT tags is part laziness,
990 # part security. The SRC blocks allows more compact documents and easier
991 # debugging. The values of the CGI variables can be immediately screened
992 # for security by IF or UNLESS conditions, and even SRC attributes (e.g.,
993 # email addresses and file names), and a few commands can be called
994 # without having to add another Perl TAG pair. This is especially important
995 # for documents that require the use of other (more restricted) "scripting"
996 # languages and facilities that lag transparent control structures.
998 ##########################################################################
1000 # SECURITY
1002 # Your WWW site is a few keystrokes away from a few hundred million internet
1003 # users. A fair percentage of these users knows more about your computer
1004 # than you do. And some of these just might have bad intentions.
1006 # To ensure uncompromized operation of your server and platform, several
1007 # features are incorporated in CGIscriptor.pl to enhance security.
1008 # First of all, you should check the source of this program. No security
1009 # measures will help you when you download programs from anonymous sources.
1010 # If you want to use THIS file, please make sure that it is uncompromized.
1011 # The best way to do this is to contact the source and try to determine
1012 # whether s/he is reliable (and accountable).
1014 # BE AWARE THAT ANY PROGRAMMER CAN CHANGE THIS PROGRAM IN SUCH A WAY THAT
1015 # IT WILL SET THE DOORS TO YOUR SYSTEM WIDE OPEN
1017 # I would like to ask any user who finds bugs that could compromise
1018 # security to report them to me (and any other bug too,
1019 # Email: R.J.J.H.vanSon@gmail.com or ifa@hum.uva.nl).
1021 # Security features
1023 # 1 Invisibility
1024 # The inner workings of the HTML source files are completely hidden
1025 # from the client. Only the HTTP header and the ever changing content
1026 # of the output distinguish it from the output of a plain, fixed HTML
1027 # file. Names, structures, and arguments of the "embedded" scripts
1028 # are invisible to the client. Error output is suppressed except
1029 # during debugging (user configurable).
1031 # 2 Separate directory trees
1032 # Directories containing Inline text and script files can reside on
1033 # separate trees, distinct from those of the HTTP server. This means
1034 # that NEITHER the text files, NOR the script files can be read by
1035 # clients other than through CGIscriptor.pl, UNLESS they are
1036 # EXPLICITELY made available.
1038 # 3 Requests are NEVER "evaluated"
1039 # All client supplied values are used as literal values (''-quoted).
1040 # Client supplied ''-quotes are ALWAYS removed. Therefore, as long as the
1041 # embedded scripts do NOT themselves evaluate these values, clients CANNOT
1042 # supply executable commands. Be sure to AVOID scripts like:
1044 # <META CONTENT="text/ssperl; CGI='$UserValue'">
1045 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 $UserValue`;</SCRIPT>
1047 # These are a recipe for disaster. However, the following quoted
1048 # form should be save (but is still not adviced):
1050 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 \'$UserValue\'`;</SCRIPT>
1052 # A special function, SAFEqx(), will automatically do exactly this,
1053 # e.g., SAFEqx('ls -1 $UserValue') will execute `ls -1 \'$UserValue\'`
1054 # with $UserValue interpolated. I recommend to use SAFEqx() instead
1055 # of backticks whenever you can. The OS shell scripts inside
1057 # <SCRIPT TYPE="text/osshell">ls -1 $UserValue</SCRIPT>
1059 # are handeld by SAFEqx and automatically ''-quoted.
1061 # 4 Logging of requests
1062 # All requests can be logged separate from the Host server. The level of
1063 # detail is user configurable: Including or excluding the actual queries.
1064 # This allows for the inspection of (im-) proper use.
1066 # 5 Access control: Clients
1067 # The Remote addresses can be checked against a list of authorized
1068 # (i.e., accepted) or non-authorized (i.e., rejected) clients. Both
1069 # REMOTE_HOST and REMOTE_ADDR are tested so clients without a proper
1070 # HOST name can be (in-) excluded by their IP-address. Client patterns
1071 # containing all numbers and dots are considered IP-addresses, all others
1072 # domain names. No wild-cards or regexp's are allowed, only partial
1073 # addresses.
1074 # Matching of names is done from the back to the front (domain first,
1075 # i.e., $REMOTE_HOST =~ /\Q$pattern\E$/is), so including ".edu" will
1076 # accept or reject all clients from the domain EDU. Matching of
1077 # IP-addresses is done from the front to the back (domain first, i.e.,
1078 # $REMOTE_ADDR =~ /^\Q$pattern\E/is), so including "128." will (in-)
1079 # exclude all clients whose IP-address starts with 128.
1080 # There are two special symbols: "-" matches HOSTs with no name and "*"
1081 # matches ALL HOSTS/clients.
1082 # For those needing more expressional power, lines starting with
1083 # "-e" are evaluated by the perl eval() function. E.g.,
1084 # '-e $REMOTE_HOST =~ /\.edu$/is;' will accept/reject clients from the
1085 # domain '.edu'.
1087 # 6 Access control: Files
1088 # In principle, CGIscriptor could read ANY file in the directory
1089 # tree as discussed in 1. However, for security reasons this is
1090 # restricted to text files. It can be made more restricted by entering
1091 # a global file pattern (e.g., ".html"). This is done by default.
1092 # For each client requesting access, the file pattern(s) can be made
1093 # more restrictive than the global pattern by entering client specific
1094 # file patterns in the Access Control files (see 5).
1095 # For example: if the ACCEPT file contained the lines
1096 # * DEMO
1097 # .hum.uva.nl LET
1098 # 145.18.230.
1099 # Then all clients could request paths containing "DEMO" or "demo", e.g.
1100 # "/my/demo/file.html" ($PATH_INFO =~ /\Q$pattern\E/), Clients from
1101 # *.hum.uva.nl could also request paths containing "LET or "let", e.g.
1102 # "/my/let/file.html", and clients from the local cluster
1103 # 145.18.230.[0-9]+ could access ALL files.
1104 # Again, for those needing more expressional power, lines starting with
1105 # "-e" are evaluated. For instance:
1106 # '-e $REMOTE_HOST =~ /\.edu$/is && $PATH_INFO =~ m@/DEMO/@is;'
1107 # will accept/reject requests for files from the directory "/demo/" from
1108 # clients from the domain '.edu'.
1109 # Path selections starting with ! or 'not' will be inverted. That is:
1110 # * not .wav
1111 # Will match all file and path names that do NOT contain '.wav'
1113 # 7 Access control: Server side session tickets
1114 # Specific paths can be controlled by Session Tickets which must be
1115 # present as a SESSIONTICKET=<value> CGI variable in the request. These paths
1116 # are defined in %TicketRequiredPatterns as pairs of:
1117 # ('regexp' => 'SessionPath\tPasswordPath\tLogin.html\tExpiration').
1118 # Session Tickets are stored in a separate directory (SessionPath, e.g.,
1119 # "Private/.Session") as files with the exact same name of the SESSIONTICKET
1120 # CGI. The following is an example:
1121 # Type: SESSION
1122 # IPaddress: 127.0.0.1
1123 # AllowedPaths: ^/Private/Name/
1124 # Expires: 3600
1125 # Username: test
1126 # ...
1127 # Other content can follow.
1129 # It is adviced that Session Tickets should be deleted
1130 # after some (idle) time. The IP address should be the IP number at login, and
1131 # the SESSIONTICKET will be rejected if it is presented from another IP address.
1132 # AllowedPaths and DeniedPaths are perl regexps. Be careful how they match. Make sure to delimit
1133 # the names to prevent access to overlapping names, eg, "^/Private/Rob" will also
1134 # match "^/Private/Robert", however, "^/Private/Rob/" will not. Expires is the
1135 # time the ticket will remain valid after creation (file ctime). Time can be given
1136 # in s[econds] (default), m[inutes], h[hours], or d[ays], eg, "24h" means 24 hours.
1137 # None of these need be present, but the Ticket must have a non-zero size.
1139 # Next to Session Tickets, there are two other type of ticket files:
1140 # - LOGIN tickets store information about a current login request
1141 # - PASSWORD ticket store account information to authorize login requests
1143 # 8 Query length limiting
1144 # The length of the Query string can be limited. If CONTENT_LENGTH is larger
1145 # than this limit, the request is rejected. The combined length of the
1146 # Query string and the POST input is checked before any processing is done.
1147 # This will prevent clients from overloading the scripts.
1148 # The actual, combined, Query Size is accessible as a variable through
1149 # $CGI_Content_Length.
1151 # 9 Illegal filenames, paths, and protected directories
1152 # One of the primary security concerns in handling CGI-scripts is the
1153 # use of "funny" characters in the requests that con scripts in executing
1154 # malicious commands. Examples are inserting ';', null bytes, or <newline>
1155 # characters in URL's and filenames, followed by executable commands. A
1156 # special variable $FileAllowedChars stores a string of all allowed
1157 # characters. Any request that translates to a filename with a character
1158 # OUTSIDE this set will be rejected.
1159 # In general, all (readable files) in the DocumentRoot tree are accessible.
1160 # This might not be what you want. For instance, your DocumentRoot directory
1161 # might be the working directory of a CVS project and contain sensitive
1162 # information (e.g., the password to get to the repository). You can block
1163 # access to these subdirectories by adding the corresponding patterns to
1164 # the $BlockPathAccess variable. For instance, $BlockPathAccess = '/CVS/'
1165 # will block any request that contains '/CVS/' or:
1166 # die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;
1168 #10 The execution of code blocks can be controlled in a transparent way
1169 # by adding IF or UNLESS conditions in the tags themselves. That is,
1170 # a simple check of the validity of filenames or email addresses can
1171 # be done before any code is executed.
1173 ###############################################################################
1175 # USER MANUAL (sort of)
1177 # CGIscriptor removes embedded scripts, indicated by an HTML 4 type
1178 # <SCRIPT TYPE='text/ssperl'> </SCRIPT> or <SCRIPT TYPE='text/osshell'>
1179 # </SCRIPT> constructs. CGIscriptor also recognizes XML-type
1180 # <SCRIPT TYPE='text/ssperl'/> constructs. These are usefull when
1181 # the necessary code is already available in the TAG itself (e.g.,
1182 # using external files). The contents of the directive are executed by
1183 # the PERL eval() and `` functions (in a separate name space). The
1184 # result of the eval() function replaces the <SCRIPT> </SCRIPT> construct
1185 # in the output file. You can use the values that are delivered in
1186 # CGI-compliant form (i.e., the "?name=value&.." type URL additions)
1187 # transparently as "$name" variables in your directives after they are
1188 # defined in a <META> or <SCRIPT> tag.
1189 # If you define the variable "$CGIscriptorResults" in a CGI attribute, all
1190 # subsequent <SCRIPT> and <META> results (including the defining
1191 # tag) will also be pushed onto a stack: @CGIscriptorResults. This list
1192 # behaves like any other, ordinary list and can be manipulated.
1194 # Both GET and POST requests are accepted. These two methods are treated
1195 # equal. Variables, i.e., those values that are determined when a file is
1196 # processed, are indicated in the CGI attribute by $<name> or $<name>=<default>
1197 # in which <name> is the name of the variable and <default> is the value
1198 # used when there is NO current CGI value for <name> (you can use
1199 # white-spaces in $<name>=<default> but really DO make sure that the
1200 # default value is followed by white space or is quoted). Names can contain
1201 # any alphanumeric characters and _ (i.e., names match /[\w]+/).
1202 # If the Content-type: is 'multipart/*', the input is treated as a
1203 # MIME multipart message and automatically delimited. CGI variables get
1204 # the "raw" (i.e., undecoded) body of the corresponding message part.
1206 # Variables can be CGI variables, i.e., those from the QUERY_STRING,
1207 # environment variables, e.g., REMOTE_USER, REMOTE_HOST, or REMOTE_ADDR,
1208 # or predefined values, e.g., CGI_Decoded_QS (The complete, decoded,
1209 # query string), CGI_Content_Length (the length of the decoded query
1210 # string), CGI_Year, CGI_Month, CGI_Time, and CGI_Hour (the current
1211 # date and time).
1213 # All these are available when defined in a CGI attribute. All environment
1214 # variables are accessible as $ENV{'name'}. So, to access the REMOTE_HOST
1215 # and the REMOTE_USER, use, e.g.:
1217 # <SCRIPT TYPE='text/ssperl'>
1218 # ($ENV{'REMOTE_HOST'}||"-")." $ENV{'REMOTE_USER'}"
1219 # </SCRIPT>
1221 # (This will print a "-" if REMOTE_HOST is not known)
1222 # Another way to do this is:
1224 # <META CONTENT="text/ssperl; CGI='$REMOTE_HOST = - $REMOTE_USER'">
1225 # <SCRIPT TYPE='text/ssperl'>"$REMOTE_HOST $REMOTE_USER"</SCRIPT>
1226 # or
1227 # <META CONTENT='text/ssperl; CGI="$REMOTE_HOST = - $REMOTE_USER"
1228 # SRC={"$REMOTE_HOST $REMOTE_USER\n"}'>
1230 # This is possible because ALL environment variables are available as
1231 # CGI variables. The environment variables take precedence over CGI
1232 # names in case of a "name clash". For instance:
1233 # <META CONTENT="text/ssperl; CGI='$HOME' SRC={$HOME}">
1234 # Will print the current HOME directory (environment) irrespective whether
1235 # there is a CGI variable from the query
1236 # (e.g., Where do you live? <INPUT TYPE="TEXT" NAME="HOME">)
1237 # THIS IS A SECURITY FEATURE. It prevents clients from changing
1238 # the values of defined environment variables (e.g., by supplying
1239 # a bogus $REMOTE_ADDR). Although $ENV{} is not changed by the META tags,
1240 # it would make the use of declared variables insecure. You can still
1241 # access CGI variables after a name clash with
1242 # CGIscriptor::CGIparseValue(<name>).
1244 # Some CGI variables are present several times in the query string
1245 # (e.g., from multiple selections). These should be defined as
1246 # @VARIABLENAME=default in the CGI attribute. The list @VARIABLENAME
1247 # will contain ALL VARIABLENAME values from the query, or a single
1248 # default value. If there is an ENVIRONMENT variable of the
1249 # same name, it will be used instead of the default AND the query
1250 # values. The corresponding function is
1251 # CGIscriptor::CGIparseValueList(<name>)
1253 # CGI variables collected in a @VARIABLENAME list are unordered.
1254 # When more structured variables are needed, a hash table can be used.
1255 # A variable defined as %VARIABLE=default will collect all
1256 # CGI-parameters whose name start with 'VARIABLE' in a hash table with
1257 # the remainder of the name as a key. For instance, %PERSON will
1258 # collect PERSONname='John Doe', PERSONbirthdate='01 Jan 00', and
1259 # PERSONspouse='Alice' into a hash table %PERSON such that $PERSON{'spouse'}
1260 # equals 'Alice'. Any default value or environment value will be stored
1261 # under the "" key. If there is an ENVIRONMENT variable of the same name,
1262 # it will be used instead of the default AND the query values. The
1263 # corresponding function is CGIscriptor::CGIparseValueHash(<name>)
1265 # This method of first declaring your environment and CGI variables
1266 # before being able to use them in the scripts might seem somewhat
1267 # clumsy, but it protects you from inadvertedly printing out the values of
1268 # system environment variables when their names coincide with those used
1269 # in the CGI forms. It also prevents "clients" from supplying CGI
1270 # parameter values for your private variables.
1271 # THIS IS A SECURITY FEATURE!
1274 # NON-HTML CONTENT TYPES
1276 # Normally, CGIscriptor prints the standard "Content-type: text/html\n\n"
1277 # message before anything is printed. This has been extended to include
1278 # plain text (.txt) files, for which the Content-type (MIME type)
1279 # 'text/plain' is printed. In all other respects, text files are treated
1280 # as HTML files (this can be switched off by removing '.txt' from the
1281 # $FilePattern variable) . When the content type should be something else,
1282 # e.g., with multipart files, use the $RawFilePattern (.xmr, see also next
1283 # item). CGIscriptor will not print a Content-type message for this file
1284 # type (which must supply its OWN Content-type message). Raw files must
1285 # still conform to the <SCRIPT></SCRIPT> and <META> tag specifications.
1288 # NON-HTML FILES
1290 # CGIscriptor is intended to process HTML and text files only. You can
1291 # create documents of any mime-type on-the-fly using "raw" text files,
1292 # e.g., with the .xmr extension. However, CGIscriptor will not process
1293 # binary files of any type, e.g., pictures or sounds. Given the sheer
1294 # number of formats, I do not have any intention to do so. However,
1295 # an escape route has been provided. You can construct a genuine raw
1296 # (.xmr) text file that contains the perl code to service any file type
1297 # you want. If the global $BinaryMapFile variable contains the path to
1298 # this file (e.g., /BinaryMapFile.xmr), this file will be called
1299 # whenever an unsupported (non-HTML) file type is requested. The path
1300 # to the requested binary file is stored in $ENV('CGI_BINARY_FILE')
1301 # and can be used like any other CGI-variable. Servicing binary files
1302 # then becomes supplying the correct Content-type (e.g., print
1303 # "Content-type: image/jpeg\n\n";) and reading the file and writing it
1304 # to STDOUT (e.g., using sysread() and syswrite()).
1307 # THE META TAG
1309 # All attributes of a META tag are ignored, except the
1310 # CONTENT='text/ssperl; CGI=" ... " [SRC=" ... "]' attribute. The string
1311 # inside the quotes following the CONTENT= indication (white-space is
1312 # ignored, "" '' `` (){}[]-quote pairs are allowed, plus their \ versions)
1313 # MUST start with any of the CGIscriptor mime-types (e.g.: text/ssperl or
1314 # text/osshell) and a comma or semicolon.
1315 # The quoted string following CGI= contains a white-space separated list
1316 # of declarations of the CGI (and Environment) values and default values
1317 # used when no CGI values are supplied by the query string.
1319 # If the default value is a longer string containing special characters,
1320 # possibly spanning several lines, the string must be enclosed in quotes.
1321 # You may use any pair of quotes or brackets from the list '', "", ``, (),
1322 # [], or {} to distinguish default values (or preceded by \, e.g., \(...\)
1323 # is different from (...)). The outermost pair will always be used and any
1324 # other quotes inside the string are considered to be part of the string
1325 # value, e.g.,
1327 # $Value = {['this'
1328 # "and" (this)]}
1329 # will result in $Value getting the default value: ['this'
1330 # "and" (this)]
1331 # (NOTE that the newline is part of the default value!).
1333 # Internally, for defining and initializing CGI (ENV) values, the META
1334 # and SCRIPT tags use the functions "defineCGIvariable($name, $default)"
1335 # (scalars) and "defineCGIvariableList($name, $default)" (lists).
1336 # These functions can be used inside scripts as
1337 # "CGIscriptor::defineCGIvariable($name, $default)" and
1338 # "CGIscriptor::defineCGIvariableList($name, $default)".
1339 # "CGIscriptor::defineCGIvariableHash($name, $default)".
1341 # The CGI attribute will be processed exactly identical when used inside
1342 # the <SCRIPT> tag. However, this use is not according to the
1343 # HTML 4.0 specifications of the W3C.
1346 # THE DIV/INS TAGS
1348 # There is a problem when constructing html files containing
1349 # server-side perl scripts with standard HTML tools. These
1350 # tools will refuse to process any text between <SCRIPT></SCRIPT>
1351 # tags. This is quite annoying when you want to use large
1352 # HTML templates where you will fill in values.
1354 # For this purpose, CGIscriptor will read the neutral
1355 # <DIV CLASS="ssperl" ID="varname"></DIV> or
1356 # <INS CLASS="ssperl" ID="varname"></INS>
1357 # tag (in Cascading Style Sheet manner) Note that
1358 # "varname" has NO '$' before it, it is a bare name.
1359 # Any text between these <DIV ...></DIV> or
1360 # <INS ...></INS>tags will be assigned to '$varname'
1361 # as is (e.g., as a literal).
1362 # No processing or interpolation will be performed.
1363 # There is also NO nesting possible. Do NOT nest a
1364 # </DIV> inside a <DIV></DIV>! Moreover, neither INS nor
1365 # DIV tags do ensure a block structure in the final
1366 # rendering (i.e., no empty lines).
1368 # Note that <DIV CLASS="ssperl" ID="varname"/>
1369 # is handled the XML way. No content is processed,
1370 # but varname is defined, and any SRC directives are
1371 # processed.
1373 # You can use $varname like any other variable name.
1374 # However, $varname is NOT a CGI variable and will be
1375 # completely internal to your script. There is NO
1376 # interaction between $varname and the outside world.
1378 # To interpolate a DIV derived text, you can use:
1379 # $varname =~ s/([\]])/\\\1/g; # Mark ']'-quotes
1380 # $varname = eval("qq[$varname]"); # Interpolate all values
1382 # The DIV tags will process IF, UNLESS, CGI and
1383 # SRC attributes. The SRC files will be pre-pended to the
1384 # body text of the tag. SRC blocks are NOT executed.
1386 # CONDITIONAL PROCESSING: THE 'IF' AND 'UNLESS' ATTRIBUTES
1388 # It is often necessary to include code-blocks that should be executed
1389 # conditionally, e.g., only for certain browsers or operating system.
1390 # Furthermore, quite often sanity and security checks are necessary
1391 # before user (form) data can be processed, e.g., with respect to
1392 # email addresses and filenames.
1394 # Checks added to the code are often difficult to find, interpret or
1395 # maintain and in general mess up the code flow. This kind of confussion
1396 # is dangerous.
1397 # Also, for many of the supported "foreign" scripting languages, adding
1398 # these checks is cumbersome or even impossible.
1400 # As a uniform method for asserting the correctness of "context", two
1401 # attributes are added to all supported tags: IF and UNLESS.
1402 # They both evaluate their value and block execution when the
1403 # result is <FALSE> (IF) or <TRUE> (UNLESS) in Perl, e.g.,
1404 # UNLESS='$NUMBER \> 100;' blocks execution if $NUMBER <= 100. Note that
1405 # the backslash in the '\>' is removed and only used to differentiate
1406 # this conditional '>' from the tag-closing '>'. For symmetry, the
1407 # backslash in '\<' is also removed. Inside these conditionals,
1408 # ~/ and ./ are expanded to their respective directory root paths.
1410 # For example, the following tag will be ignored when the filename is
1411 # invalid:
1413 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
1414 # IF='CGIscriptor::CGIsafeFileName($FILENAME);'>
1415 # ...
1416 # </SCRIPT>
1418 # The IF and UNLESS values must be quoted. The same quotes are supported
1419 # as with the other attributes. The SRC attribute is ignored when IF and
1420 # UNLESS block execution.
1422 # NOTE: 'IF' and 'UNLESS' always evaluate perl code.
1425 # THE MAGIC SOURCE ATTRIBUTE (SRC=)
1427 # The SRC attribute inside tags accepts a list of filenames and URL's
1428 # separated by "," comma's (or ";" semicolons).
1429 # ALL the variable values defined in the CGI attribute are available
1430 # in @ARGV as if the file or block was executed from the command line,
1431 # in the exact order in which they were declared in the preceding CGI
1432 # attribute.
1434 # First, a SRC={}-block will be evaluated as if the code inside the
1435 # block was part of a <SCRIPT></SCRIPT> construct, i.e.,
1436 # "print do { code };'';" or `code` (i.e., SAFEqx('code)).
1437 # Only a single block is evaluated. Note that this is processed less
1438 # efficiently than <SCRIPT> </SCRIPT> blocks. Type of evaluation
1439 # depends on the content-type: Perl for text/ssperl and OS shell for
1440 # text/osshell. For other mime types (scripting languages), anything in
1441 # the source block is put in front of the code block "inside" the tag.
1443 # Second, executable files (i.e., -x filename != 0) are evaluated as:
1444 # print `filename \'$ARGV[0]\' \'$ARGV[1]\' ...`
1445 # That is, you can actually call executables savely from the SRC tag.
1447 # Third, text files that match the file pattern, used by CGIscriptor to
1448 # check whether files should be processed ($FilePattern), are
1449 # processed in-line (i.e., recursively) by CGIscriptor as if the code
1450 # was inserted in the original source file. Recursions, i.e., calling
1451 # a file inside itself, are blocked. If you need them, you have to code
1452 # them explicitely using "main::ProcessFile($file_path)".
1454 # Fourth, Perl text files (i.e., -T filename != 0) are evaluated as:
1455 # "do FileName;'';".
1457 # Last, URL's (i.e., starting with 'HTTP://', 'FTP://', 'GOPHER://',
1458 # 'TELNET://', 'WHOIS://' etc.) are loaded
1459 # and printed. The loading and handling of <BASE> and document header
1460 # is done by a command generated by main::GET_URL($URL [, 0]). You can enter your
1461 # own code (default is curl, wget, or snarf and some post-processing to add a <BASE> tag).
1463 # There are two pseudo-file names: PREFIX and POSTFIX. These implement
1464 # a switch from prefixing the SRC code/files (PREFIX, default) before the
1465 # content of the tag to appending the code after the content of the tag
1466 # (POSTFIX). The switches are done in the order in which the PREFIX and
1467 # POSTFIX labels are encountered. You can mix PREFIX and POSTFIX labels
1468 # in any order with the SRC files. Note that the ORDER of file execution
1469 # is determined for prefixed and postfixed files seperately.
1471 # File paths can be preceded by the URL protocol prefix "file://". This
1472 # is simply STRIPPED from the name.
1474 # Example:
1475 # The request
1476 # "http://cgi-bin/Action_Forms.pl/Statistics/Sign_Test.html?positive=8&negative=22
1477 # will result in printing "${SS_PUB}/Statistics/Sign_Test.html"
1478 # With QUERY_STRING = "positive=8&negative=22"
1480 # on encountering the lines:
1481 # <META CONTENT="text/osshell; CGI='$positive=11 $negative=3'">
1482 # <b><SCRIPT LANGUAGE=PERL TYPE="text/ssperl" SRC="./Statistics/SignTest.pl">
1483 # </SCRIPT></b><p>"
1485 # This line will be processed as:
1486 # "<b>`${SS_SCRIPT}/Statistics/SignTest.pl '8' '22'`</b><p>"
1488 # In which "${SS_SCRIPT}/Statistics/SignTest.pl" is an executable script,
1489 # This line will end up printed as:
1490 # "<b>p <= 0.0161</b><p>"
1492 # Note that the META tag itself will never be printed, and is invisible to
1493 # the outside world.
1495 # The SRC files in a DIV or INS tag will be added (pre-pended) to the body
1496 # of the <DIV></DIV> tag. Blocks are NOT executed! If you do not
1497 # need any content, you can use the <DIV...../> format.
1500 # THE CGISCRIPTOR ROOT DIRECTORIES ~/ AND ./
1502 # Inside <SCRIPT></SCRIPT> tags, filepaths starting
1503 # with "~/" are replaced by "$YOUR_HTML_FILES/", this way files in the
1504 # public directories can be accessed without direct reference to the
1505 # actual paths. Filepaths starting with "./" are replaced by
1506 # "$YOUR_SCRIPTS/" and this should only be used for scripts.
1508 # Note: this replacement can seriously affect Perl scripts. Watch
1509 # out for constructs like $a =~ s/aap\./noot./g, use
1510 # $a =~ s@aap\.@noot.@g instead.
1512 # CGIscriptor.pl will assign the values of $SS_PUB and $SS_SCRIPT
1513 # (i.e., $YOUR_HTML_FILES and $YOUR_SCRIPTS) to the environment variables
1514 # $SS_PUB and $SS_SCRIPT. These can be accessed by the scripts that are
1515 # executed.
1516 # Values not preceded by $, ~/, or ./ are used as literals
1519 # OS SHELL SCRIPT EVALUATION (CONTENT-TYPE=TEXT/OSSHELL)
1521 # OS scripts are executed by a "safe" version of the `` operator (i.e.,
1522 # SAFEqx(), see also below) and any output is printed. CGIscriptor will
1523 # interpolate the script and replace all user-supplied CGI-variables by
1524 # their ''-quoted values (actually, all variables defined in CGI attributes
1525 # are quoted). Other Perl variables are interpolated in a simple fasion,
1526 # i.e., $scalar by their value, @list by join(' ', @list), and %hash by
1527 # their name=value pairs. Complex references, e.g., @$variable, are all
1528 # evaluated in a scalar context. Quotes should be used with care.
1529 # NOTE: the results of the shell script evaluation will appear in the
1530 # @CGIscriptorResults stack just as any other result.
1531 # All occurrences of $@% that should NOT be interpolated must be
1532 # preceeded by a "\". Interpolation can be switched off completely by
1533 # setting $CGIscriptor::NoShellScriptInterpolation = 1
1534 # (set to 0 or undef to switch interpolation on again)
1535 # i.e.,
1536 # <SCRIPT TYPE="text/ssperl">
1537 # $CGIscriptor::NoShellScriptInterpolation = 1;
1538 # </SCRIPT>
1541 # RUN TIME TRANSLATION OF INPUT FILES
1543 # Allows general and global conversions of files using Regular Expressions.
1544 # Very handy (but costly) to rewrite legacy pages to a new format.
1545 # Select files to use it on with
1546 # my $TranslationPaths = 'filepattern';
1547 # This is costly. For efficiency, define:
1548 # $TranslationPaths = ''; when not using translations.
1549 # Accepts general regular expressions: [$pattern, $replacement]
1551 # Define:
1552 # my $TranslationPaths = 'filepattern'; # Pattern matching PATH_INFO
1554 # push(@TranslationTable, ['pattern', 'replacement']);
1555 # e.g. (for Ruby Rails):
1556 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
1557 # push(@TranslationTable, ['%>', '</SCRIPT>']);
1559 # Runs:
1560 # my $currentRegExp;
1561 # foreach $currentRegExp (@TranslationTable)
1563 # my ($pattern, $replacement) = @$currentRegExp;
1564 # $$text =~ s!$pattern!$replacement!msg;
1565 # };
1568 # EVALUATION OF OTHER SCRIPTING LANGUAGES
1570 # Adding a MIME-type and an interpreter command to
1571 # %ScriptingLanguages automatically will catch any other
1572 # scripting language in the standard
1573 # <SCRIPT TYPE="[mime]"></SCRIPT> manner.
1574 # E.g., adding: $ScriptingLanguages{'text/sspython'} = 'python';
1575 # will actually execute the folowing code in an HTML page
1576 # (ignore 'REMOTE_HOST' for the moment):
1577 # <SCRIPT TYPE="text/sspython">
1578 # # A Python script
1579 # x = ["A","real","python","script","Hello","World","and", REMOTE_HOST]
1580 # print x[4:8] # Prints the list ["Hello","World","and", REMOTE_HOST]
1581 # </SCRIPT>
1583 # The script code is NOT interpolated by perl, EXCEPT for those
1584 # interpreters that cannot handle variables themselves.
1585 # Currently, several interpreters are pre-installed:
1587 # Perl test - "text/testperl" => 'perl',
1588 # Python - "text/sspython" => 'python',
1589 # Ruby - "text/ssruby" => 'ruby',
1590 # Tcl - "text/sstcl" => 'tcl',
1591 # Awk - "text/ssawk" => 'awk -f-',
1592 # Gnu Lisp - "text/sslisp" => 'rep | tail +5 '.
1593 # "| egrep -v '> |^rep. |^nil\\\$'",
1594 # XLispstat - "text/xlispstat" => 'xlispstat | tail +7 '.
1595 # "| egrep -v '> \\\$|^NIL'",
1596 # Gnu Prolog- "text/ssprolog" => 'gprolog',
1597 # M4 macro's- "text/ssm4" => 'm4',
1598 # Born shell- "text/sh" => 'sh',
1599 # Bash - "text/bash" => 'bash',
1600 # C-shell - "text/csh" => 'csh',
1601 # Korn shell- "text/ksh" => 'ksh',
1602 # Praat - "text/sspraat" => "praat - | sed 's/Praat > //g'",
1603 # R - "text/ssr" => "R --vanilla --slave | sed 's/^[\[0-9\]*] //g'",
1604 # REBOL - "text/ssrebol" =>
1605 # "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\s*\[> \]* //g'",
1606 # PostgreSQL- "text/postgresql" => 'psql 2>/dev/null',
1607 # (psql)
1609 # Note that the "value" of $ScriptingLanguages{mime} must be a command
1610 # that reads Standard Input and writes to standard output. Any extra
1611 # output of interactive interpreters (banners, echo's, prompts)
1612 # should be removed by piping the output through 'tail', 'grep',
1613 # 'sed', or even 'awk' or 'perl'.
1615 # For access to CGI variables there is a special hashtable:
1616 # %ScriptingCGIvariables.
1617 # CGI variables can be accessed in three ways.
1618 # 1. If the mime type is not present in %ScriptingCGIvariables,
1619 # nothing is done and the script itself should parse the relevant
1620 # environment variables.
1621 # 2. If the mime type IS present in %ScriptingCGIvariables, but it's
1622 # value is empty, e.g., $ScriptingCGIvariables{"text/sspraat"} = '';,
1623 # the script text is interpolated by perl. That is, all $var, @array,
1624 # %hash, and \-slashes are replaced by their respective values.
1625 # 3. In all other cases, the CGI and environment variables are added
1626 # in front of the script according to the format stored in
1627 # %ScriptingCGIvariables. That is, the following (pseudo-)code is
1628 # executed for each CGI- or Environment variable defined in the CGI-tag:
1629 # printf(INTERPRETER, $ScriptingCGIvariables{$mime}, $CGI_NAME, $CGI_VALUE);
1631 # For instance, "text/testperl" => '$%s = "%s";' defines variable
1632 # definitions for Perl, and "text/sspython" => '%s = "%s"' for Python
1633 # (note that these definitions are not save, the real ones contain '-quotes).
1635 # THIS WILL NOT WORK FOR @VARIABLES, the (empty) $VARIABLES will be used
1636 # instead.
1638 # The $CGI_VALUE parameters are "shrubed" of all control characters
1639 # and quotes (by &shrubCGIparameter($CGI_VALUE)) for the options 2 and 3.
1640 # Control characters are replaced by \0<octal ascii value> (the exception
1641 # is \015, the newline, which is replaced by \n) and quotes
1642 # and backslashes by their HTML character
1643 # value (' -> &#39; ` -> &#96; " -> &quot; \ -> &#92; & -> &amper;).
1644 # For example:
1645 # if a client would supply the string value (in standard perl, e.g.,
1646 # \n means <newline>)
1647 # "/dev/null';\nrm -rf *;\necho '"
1648 # it would be processed as
1649 # '/dev/null&#39;;\nrm -rf *;\necho &#39;'
1650 # (e.g., sh or bash would process the latter more according to your
1651 # intentions).
1652 # If your intepreter requires different protection measures, you will
1653 # have to supply these in %main::SHRUBcharacterTR (string => translation),
1654 # e.g., $SHRUBcharacterTR{"\'"} = "&#39;";
1656 # Currently, the following definitions are used:
1657 # %ScriptingCGIvariables = (
1658 # "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value' (for testing)
1659 # "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
1660 # "text/ssruby" => '@%s = "%s"', # Ruby @VAR = "value"
1661 # "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
1662 # "text/ssawk" => '%s = "%s";', # Awk VAR = "value";
1663 # "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
1664 # "text/xlispstat" => '(setq %s "%s")', # Xlispstat (setq VAR "value")
1665 # "text/ssprolog" => '', # Gnu prolog (interpolated)
1666 # "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
1667 # "text/sh" => "\%s='\%s';", # Born shell VAR='value';
1668 # "text/bash" => "\%s='\%s';", # Born again shell VAR='value';
1669 # "text/csh" => "\$\%s = '\%s';", # C shell $VAR = 'value';
1670 # "text/ksh" => "\$\%s = '\%s';", # Korn shell $VAR = 'value';
1671 # "text/sspraat" => '', # Praat (interpolation)
1672 # "text/ssr" => '%s <- "%s";', # R VAR <- "value";
1673 # "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
1674 # "text/postgresql" => '', # PostgreSQL (interpolation)
1675 # "" => ""
1676 # );
1678 # Four tables allow fine-tuning of interpreter with code that should be
1679 # added before and after each code block:
1681 # Code added before each script block
1682 # %ScriptingPrefix = (
1683 # "text/testperl" => "\# Prefix Code;", # Perl script testing
1684 # "text/ssm4" => 'divert(0)' # M4 macro's (open STDOUT)
1685 # );
1686 # Code added at the end of each script block
1687 # %ScriptingPostfix = (
1688 # "text/testperl" => "\# Postfix Code;", # Perl script testing
1689 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1690 # );
1691 # Initialization code, inserted directly after opening (NEVER interpolated)
1692 # %ScriptingInitialization = (
1693 # "text/testperl" => "\# Initialization Code;", # Perl script testing
1694 # "text/ssawk" => 'BEGIN {', # Server Side awk scripts
1695 # "text/sslisp" => '(prog1 nil ', # Lisp (rep)
1696 # "text/xlispstat" => '(prog1 nil ', # xlispstat
1697 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1698 # );
1699 # Cleanup code, inserted before closing (NEVER interpolated)
1700 # %ScriptingCleanup = (
1701 # "text/testperl" => "\# Cleanup Code;", # Perl script testing
1702 # "text/sspraat" => 'Quit',
1703 # "text/ssawk" => '};', # Server Side awk scripts
1704 # "text/sslisp" => '(princ "\n" standard-output)).' # Closing print to rep
1705 # "text/xlispstat" => '(print "" *standard-output*)).' # Closing print to xlispstat
1706 # "text/postgresql" => '\q',
1707 # );
1710 # The SRC attribute is NOT magical for these interpreters. In short,
1711 # all code inside a source file or {} block is written verbattim
1712 # to the interpreter. No (pre-)processing or executional magic is done.
1714 # A serious shortcomming of the described mechanism for handling other
1715 # (scripting) languages, with respect to standard perl scripts
1716 # (i.e., 'text/ssperl'), is that the code is only executed when
1717 # the pipe to the interpreter is closed. So the pipe has to be
1718 # closed at the end of each block. This means that the state of the
1719 # interpreter (e.g., all variable values) is lost after the closing of
1720 # the next </SCRIPT> tag. The standard 'text/ssperl' scripts retain
1721 # all values and definitions.
1723 # APPLICATION MIME TYPES
1725 # To ease some important auxilliary functions from within the
1726 # html pages I have added them as MIME types. This uses
1727 # the mechanism that is also used for the evaluation of
1728 # other scripting languages, with interpolation of CGI
1729 # parameters (and perl-variables). Actually, these are
1730 # defined exactly like any other "scripting language".
1732 # text/ssdisplay: display some (HTML) text with interpolated
1733 # variables (uses `cat`).
1734 # text/sslogfile: write (append) the interpolated block to the file
1735 # mentioned on the first, non-empty line
1736 # (the filename can be preceded by 'File: ',
1737 # note the space after the ':',
1738 # uses `awk .... >> <filename>`).
1739 # text/ssmailto: send email directly from within the script block.
1740 # The first line of the body must contain
1741 # To:Name@Valid.Email.Address
1742 # (note: NO space between 'To:' and the email adres)
1743 # For other options see the mailto man pages.
1744 # It works by directly sending the (interpolated)
1745 # content of the text block to a pipe into the
1746 # Linux program 'mailto'.
1748 # In these script blocks, all Perl variables will be
1749 # replaced by their values. All CGI variables are cleaned before
1750 # they are used. These CGI variables must be redefined with a
1751 # CGI attribute to restore their original values.
1752 # In general, this will be more secure than constructing
1753 # e.g., your own email command lines. For instance, Mailto will
1754 # not execute any odd (forged) email addres, but just stops
1755 # when the email address is invalid and awk will construct
1756 # any filename you give it (e.g. '<File;rm\\\040-f' would end up
1757 # as a "valid" UNIX filename). Note that it will also gladly
1758 # store this file anywhere (/../../../etc/passwd will work!).
1759 # Use the CGIscriptor::CGIsafeFileName() function to clean the
1760 # filename.
1762 # SHELL SCRIPT PIPING
1764 # If a shell script starts with the UNIX style "#! <shell command> \n"
1765 # line, the rest of the shell script is piped into the indicated command,
1766 # i.e.,
1767 # open(COMMAND, "| command");print COMMAND $RestOfScript;
1769 # In many ways this is equivalent to the MIME-type profiling for
1770 # evaluating other scripting languages as discussed above. The
1771 # difference breaks down to convenience. Shell script piping is a
1772 # "raw" implementation. It allows you to control all aspects of
1773 # execution. Using the MIME-type profiling is easier, but has a
1774 # lot of defaults built in that might get in the way. Another
1775 # difference is that shell script piping uses the SAFEqx() function,
1776 # and MIME-type profiling does not.
1778 # Execution of shell scripts is under the control of the Perl Script blocks
1779 # in the document. The MIME-type triggered execution of <SCRIPT></SCRIPT>
1780 # blocks can be simulated easily. You can switch to a different shell,
1781 # e.g. tcl, completely by executing the following Perl commands inside
1782 # your document:
1784 # <SCRIPT TYPE="text/ssperl">
1785 # $main::ShellScriptContentType = "text/ssTcl"; # Yes, you can do this
1786 # CGIscriptor::RedirectShellScript('/usr/bin/tcl'); # Pipe to Tcl
1787 # $CGIscriptor::NoShellScriptInterpolation = 1;
1788 # </SCRIPT>
1790 # After this script is executed, CGIscriptor will parse scripts of
1791 # TYPE="text/ssTcl" and pipe their contents into '|/usr/bin/tcl'
1792 # WITHOUT interpolation (i.e., NO substitution of Perl variables).
1793 # The crucial function is :
1794 # CGIscriptor::RedirectShellScript('/usr/bin/tcl')
1795 # After executing this function, all shell scripts AND all
1796 # calls to SAFEqx()) are piped into '|/usr/bin/tcl'. If the argument
1797 # of RedirectShellScript is empty, e.g., '', the original (default)
1798 # value is reset.
1800 # The standard output, STDOUT, of any pipe is send to the client.
1801 # Currently, you should be carefull with quotes in such a piped script.
1802 # The results of a pipe is NOT put on the @CGIscriptorResults stack.
1803 # As a result, you do not have access to the output of any piped (#!)
1804 # process! If you want such access, execute
1805 # <SCRIPT TYPE="text/osshell">echo "script"|command</SCRIPT>
1806 # or
1807 # <SCRIPT TYPE="text/ssperl">
1808 # $resultvar = SAFEqx('echo "script"|command');
1809 # </SCRIPT>.
1811 # Safety is never complete. Although SAFEqx() prevents some of the
1812 # most obvious forms of attacks and security slips, it cannot prevent
1813 # them all. Especially, complex combinations of quotes and intricate
1814 # variable references cannot be handled safely by SAFEqx. So be on
1815 # guard.
1818 # PERL CODE EVALUATION (CONTENT-TYPE=TEXT/SSPERL)
1820 # All PERL scripts are evaluated inside a PERL package. This package
1821 # has a separate name space. This isolated name space protects the
1822 # CGIscriptor.pl program against interference from user code. However,
1823 # some variables, e.g., $_, are global and cannot be protected. You are
1824 # advised NOT to use such global variable names. You CAN write
1825 # directives that directly access the variables in the main program.
1826 # You do so at your own risk (there is definitely enough rope available
1827 # to hang yourself). The behavior of CGIscriptor becomes undefined if
1828 # you change its private variables during run time. The PERL code
1829 # directives are used as in:
1830 # $Result = eval($directive); print $Result;'';
1831 # ($directive contains all text between <SCRIPT></SCRIPT>).
1832 # That is, the <directive> is treated as ''-quoted string and
1833 # the result is treated as a scalar. To prevent the VALUE of the code
1834 # block from appearing on the client's screen, end the directive with
1835 # ';""</SCRIPT>'. Evaluated directives return the last value, just as
1836 # eval(), blocks, and subroutines, but only as a scalar.
1838 # IMPORTANT: All PERL variables defined are persistent. Each <SCRIPT>
1839 # </SCRIPT> construct is evaluated as a {}-block with associated scope
1840 # (e.g., for "my $var;" declarations). This means that values assigned
1841 # to a PERL variable can be used throughout the document unless they
1842 # were declared with "my". The following will actually work as intended
1843 # (note that the ``-quotes in this example are NOT evaluated, but used
1844 # as simple quotes):
1846 # <META CONTENT="text/ssperl; CGI=`$String='abcdefg'`">
1847 # anything ...
1848 # <SCRIPT TYPE=text/ssperl>@List = split('', $String);</SCRIPT>
1849 # anything ...
1850 # <SCRIPT TYPE=text/ssperl>join(", ", @List[1..$#List]);</SCRIPT>
1852 # The first <SCRIPT TYPE=text/ssperl></SCRIPT> construct will return the
1853 # value scalar(@List), the second <SCRIPT TYPE=text/ssperl></SCRIPT>
1854 # construct will print the elements of $String separated by commas, leaving
1855 # out the first element, i.e., $List[0].
1857 # Another warning: './' and '~/' are ALWAYS replaced by the values of
1858 # $YOUR_SCRIPTS and $YOUR_HTML_FILES, respectively . This can interfere
1859 # with pattern matching, e.g., $a =~ s/aap\./noot\./g will result in the
1860 # evaluations of $a =~ s/aap\\${YOUR_SCRIPTS}noot\\${YOUR_SCRIPTS}g. Use
1861 # s@<regexp>.@<replacement>.@g instead.
1864 # SERVER SIDE SESSIONS AND ACCESS CONTROL (LOGIN)
1866 # An infrastructure for user acount authorization and file access control
1867 # is available. Each request is matched against a list of URL path patterns.
1868 # If the request matches, a Session Ticket is required to access the URL.
1869 # This Session Ticket should be present as a CGI parameter or Cookie, eg:
1871 # CGI: SESSIONTICKET=&lt;value&gt;
1872 # Cookie: CGIscriptorSESSION=&lt;value&gt;
1874 # The example implementation stores Session Tickets as files in a local
1875 # directory. To create Session Tickets, a Login request must be given
1876 # with a LOGIN=&lt;value&gt; CGI parameter, a user name and a (doubly hashed)
1877 # password. The user name and (singly hashed) password are stored in a
1878 # PASSWORD ticket with the same name as the user account (name cleaned up
1879 # for security).
1881 # The example session model implements 4 functions:
1882 # - Login
1883 # The password is hashed with the user name and server side salt, and then
1884 # hashed with the REMOTE_HOST and a random salt. Client and Server both
1885 # perform these actions and the Server only grants access if restults are
1886 # the same. The server side only stores the password hashed with the user
1887 # name and server side salt. Neither the plain password, nor the hashed
1888 # password is ever exchanged. Only values hashed with the one-time salt
1889 # are exchanged.
1890 # - Session
1891 # For every access to a restricted URL, the Session Ticket is checked before
1892 # access is granted. There are three session modes. The first uses a fixed
1893 # Session Ticket that is stored as a cookie value in the browser (actually,
1894 # as a sessionStorage value). The second uses only the IP address at login
1895 # to authenticate requests. The third
1896 # is a Challenge mode, where the client has to calculate the value of the
1897 # next one-time Session Ticket from a value derived from the password and
1898 # a random string.
1899 # - Password Change
1900 # A new password is hashed with the user name and server side salt, and
1901 # then encrypted (XORed)
1902 # with the old password hashed with the user name and salt. That value is
1903 # exchanged and XORed with the stored old hashed(password+username+salt).
1904 # Again, the stored password value is never exchanged unencrypted.
1905 # - New Account
1906 # The text of a new account (Type: PASSWORD) file is constructed from
1907 # the new username (CGI: NEWUSERNAME, converted to lowercase) and
1908 # hashed new password (CGI: NEWPASSWORD). The same process is used to encrypt
1909 # the new password as is used for the Password Change function.
1910 # Again, the stored password value is never exchanged unencrypted.
1911 # Some default setting are encoded. For display in the browser, the new password
1912 # is reencrypted (XORed) with a special key, the old password hash
1913 # hashed with a session specific random hex value sent initially with the
1914 # session login ticket ($RANDOMSALT).
1915 # For example for user "NewUser" and password "NewPassword" with filename
1916 # "newuser":
1918 # Type: PASSWORD
1919 # Username: newuser
1920 # Password: 19afeadfba8d5dcd252e157fafd3010859f8762b87682b6b6cdb3e565194fa91
1921 # IPaddress: 127\.0\.0\.1
1922 # AllowedPaths: ^/Private/[\w\-]+\.html?
1923 # AllowedPaths: ^/Private/newuser/
1924 # Salt: e93cf858a1d5626bf095ea5c25df990dfa969ff5a5dc908b22c9a5229b525f65
1925 # Session: SESSION
1926 # Date: Fri Jun 29 12:46:22 2012
1927 # Time: 1340973982
1928 # Signature: 676c35d3aa63540293ea5442f12872bfb0a22665b504f58f804582493b6ef04e
1930 # The password is created with the commands:
1932 # printf '%s' 'NewPasswordnewuser970e68017413fb0ea84d7fe3c463077636dd6d53486910d4a53c693dd4109b1a'|shasum -a 256
1934 # If the CPAN mudule Digest is installed, it is used instead of the commands.
1935 # However, the password account files are protected against unauthorized change.
1936 # To obtain a valid Password account, the following command should be given:
1938 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
1939 # masterkey='Sherlock investigates oleander curry in Bath' \
1940 # password='NewPassword' \
1941 # Private/.Passwords/newuser
1944 # Implementation
1946 # The session authentication mechanism is based on the exchange of ticket
1947 # identifiers. A ticket identifier is just a string of characters, a name
1948 # or a random 64 character hexadecimal string. Authentication is based
1949 # on a (password derived) shared secret and the ability to calculate ticket
1950 # identifiers from this shared secret. Ticket identifiers should be
1951 # "safe" filenames (except user names). There are four types of tickets:
1952 # PASSWORD: User account descriptors, including a user name and password
1953 # LOGIN: Temporary anonymous tickets used during login
1954 # IPADDRESS: Authentication tokens that allow access based on the IP address of the request
1955 # SESSION: Reusable authentication tokens
1956 # CHALLENGE: One-time authentication tokens
1957 # All tickets can have an expiration date in the form of a time duration
1958 # from creation, in seconds, minutes, hours, or days (+duration[smhd]).
1959 # An absolute time can be given in seconds since the epoch of the server host.
1960 # Note that expiration times of CHALLENGE authentication tokens are calculated
1961 # from the last access time. Accounts can include a maximal lifetime
1962 # for session tickets (MaxLifetime).
1964 # A Login page should create a LOGIN ticket file locally and send a
1965 # server specific salt, a Random salt, and a LOGIN ticket
1966 # identifier. The server side compares the username and hashed password,
1967 # actually hashed(hashed(password+serversalt)+Random salt) from the client with
1968 # the values it calculates from the stored Random salt from the LOGIN
1969 # ticket and the hashed(password+serversalt) from the PASSWORD ticket. If
1970 # successful, a new SESSION ticket is generated as a (double) hash sum of the stored
1971 # password and the LOGIN ticket, i.e.
1972 # LoginTicket = hashed(hashed(password+serversalt)+REMOTE_HOST + Random salt) and
1973 # SessionTicket = hashed(hashed(LoginTicket).LoginTicket). This SESSION
1974 # ticket should also be generated by the client and stored as
1975 # sessionStorage and cookie values as needed. The Username, IP address
1976 # and Path are available as $LoginUsername, $LoginIPaddress, and
1977 # $LoginPath, respectively.
1979 # The CHALLENGE protocol stores the single hashed version of the SESSION tickets.
1980 # However, this value is not exchanged, but kept secret in the JavaScript
1981 # sessionStorage object. Instead, every page returned from the
1982 # server will contain a one-time Challenge value ($CHALLENGETICKET) which
1983 # has to be hashed with the stored value to return the current ticket
1984 # id string.
1986 # In the current example implementation, all random values are created as
1987 # full, 256 bit SHA256 hash values (Hex strings) of 64 bytes read from
1988 # /dev/urandom.
1991 # Authorization
1993 # A limited level of authorization tuning is build into the login system.
1994 # Each account file (PASSWORD ticket file) can contain a number of
1995 # Capabilities lines. These control special priveliges. The
1996 # Capabilities can be checked inside the HTML pages as part of the
1997 # ticket information. Two privileges are handled internally:
1998 # CreateUser and VariableREMOTE_ADDR.
1999 # CreateUser allows the logged in user to create a new user account.
2000 # With VariableREMOTE_ADDR, the session of the logged in user is
2001 # not limited to the Remote IP address from which the inital log-in took
2002 # place. Sessions can hop from one apparant (proxy) IP address to another,
2003 # e.g., when using Tor. Any IPaddress patterns given in the PASSWORD
2004 # ticket file remain in effect during the session. For security reasons,
2005 # the VariableREMOTE_ADDR capability is only effective if the session
2006 # type is CHALLENGE.
2009 # Security considerations with Session tickets
2011 # For strong security, please use end-to-end encryption. This can be
2012 # achieved using a VPN (Virtual Private Network), SSH tunnel, or a HTTPS
2013 # capable server with OpenSSL. The session ticket system of CGIscriptor.pl
2014 # is intended to be used as a simple authentication mechanism WITHOUT
2015 # END-TO-END ENCRYPTION. The authenticating mechanism tries to use some
2016 # simple means to protect the authentication process from eavesdropping.
2017 # For this it uses a secure hash function, SHA256. For all practial purposes,
2018 # it is impossible to "decrypt" a SHA256 sum. But this login scheme is
2019 # only as secure as your browser. Which, in general, is not very secure.
2021 # One fundamental weakness of the implemented procedure is that the Client
2022 # obtains the code to encrypt the passwords from the server. It is the JavaScript
2023 # code in the HTML pages. An attacker who could place himself between Server
2024 # and Client, a man in the middle attack (MITM), could change the code to
2025 # reveal the plaintext password and other information. There is no
2026 # real protection against this attack without end-to-end encryption and
2027 # authentication. A simple, but rather cumbersome, way to check for such
2028 # attacks would be to store known good copys of the pages (downloaded
2029 # with a browser or automatically with curl or wget) and
2030 # then use other tools to download new pages at random intervals and compare
2031 # them to the old pages. For instance, the following line would remove
2032 # the variable ticket codes and give a fixed SHA256 sum for the original
2033 # Login.html page+code:
2034 # curl http://localhost:8080/Private/index.html | \
2035 # sed 's/=\"[a-z0-9]\{64\}\"/=""/g' | shasum -a 256
2036 # A simple diff command between old and new files should give only
2037 # differences in half a dozen lines, where only hexadecimal salt values
2038 # will actually differ.
2040 # A sort of solution for the MITM attack problem that might protect at
2041 # least the plaintext password would be to run a trusted web
2042 # page from local storage to handle password input. The solution would be
2043 # to add a hidden iFrame tag loading the untrusted page from the URL and
2044 # extract the needed ticket and salt values. Then run the stored, trusted,
2045 # code with these values. It is not (yet) possible to set the
2046 # required session storage inside the browser, so this method only works
2047 # for IPADDRESS sessions and plain SESSION tickets. There are many
2048 # security problems with this "solution".
2050 # If you are able to ascertain the integrity of the login page using any
2051 # of the above methods, you can check whether the IP address seen by the
2052 # login server is indeed the IP address of your computer. The IP address
2053 # of the REMOTE_HOST (your visible IP address) is part of the login
2054 # "password". It is stored in the login page as a CLIENTIPADDRESS. It can
2055 # can be inspected by clicking the "Check IP address" box. Provided the
2056 # MitM attacker cannot spoof your IP address, you can ensure that the login
2057 # server sees your IP address and not that of an attacker.
2059 # Humans tend to reuse passwords. A compromise of a site running
2060 # CGIscriptor.pl could therefore lead to a compromise of user accounts at
2061 # other sites. Therefore, plain text passwords are never stored, used, or
2062 # exchanged. Instead, the plain password and user name are "encrypted" with
2063 # a server site salt value. Actually, all are concatenated and hashed
2064 # with a one-way secure hash function (SHA256) into a single string.
2065 # Whenever the word "password" is used, this hash sum is meant. Note that
2066 # the salts are generated from /dev/urandom. You should check whether the
2067 # implementation of /dev/urandom on your platform is secure before
2068 # relying on it. This might be a problem when running CGIscriptor under
2069 # Cygwin on MS Windows.
2070 # Note: no attempt is made to slow down the password hash, so bad
2071 # passwords can be cracked by brute force
2073 # As the (hashed) passwords are all that is needed to identify at the site,
2074 # these should not be stored in this form. A site specific passphrase
2075 # can be entered as an environment variable ($ENV{'CGIMasterKey'}). This
2076 # phrase is hashed with the server site salt and the result is hashed with
2077 # the user name and then XORed with the password when it is stored. Also, to
2078 # detect changes to the account (PASSWORD) and session tickets, a
2079 # (HMAC) hash of some of the contents of the ticket with the server salt and
2080 # CGIMasterKey is stored in each ticket.
2082 # Creating a valid (hashed) password, encrypt it with the CGIMasterKey and
2083 # construct a signature of the ticket are non-trivial. This has to be redone
2084 # with every change of the ticket file or CGIMasterKey change. CGIscriptor
2085 # can do this from the command line with the command:
2087 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
2088 # masterkey='Sherlock investigates oleander curry in Bath' \
2089 # password='There is no password like more password' \
2090 # admin
2092 # CGIscriptor will exit after this command with the first option being
2093 # --managelogin. Options have the form:
2095 # salt=[file or string]
2096 # Server salt value to use io the value
2097 # stored in the ticket file. Will replace the stored value if a new
2098 # password is given. If you change the server salt, you have to
2099 # reset all the passwords. There is absolutely no procedure known
2100 # to recover plaintext passwords, except asking the account holders.
2101 # You are strongly adviced to make a backup before you apply such a change
2102 # masterkey=[file or string]
2103 # CGIMasterKey used to read and decrypt the ticket
2104 # newmasterkey=[file or string]
2105 # CGIMasterKey used to encrypt, sign,
2106 # and write the ticket. Defaults to the masterkey. If you change
2107 # the masterkey, you will have to reset all the accounts. You are strongly
2108 # adviced to make a backup before you apply such a change
2109 # password=[file or string]
2110 # New plaintext password
2112 # When the value of an option is a existing file path, the first line of
2113 # that file is used. Options are followed by one or more paths plus names
2114 # of existing ticket files. Each password option is only used for a single
2115 # ticket file. It is most definitely a bad idea to use a password that is
2116 # identical to an existing filepath, as the file will be read instead. Be
2117 # aware that the name of the file should be a cleaned up version of the
2118 # Username. This will not be checked.
2120 # For the authentication and a change of password, the (old) password
2121 # is used to "encrypt" a random one-time token or the new password,
2122 # respectively. For authentication, decryption is not needed, so a secure
2123 # hash function (SHA256) is used to create a one-way hash sum "encryption".
2124 # A new password must be decrypted. New passwords are encryped by XORing
2125 # them with the old password.
2127 # Strong Passwords: It is so easy
2128 # If you only could see what you are typing
2130 # Your password might be vulnerable to brute force guessing
2131 # (https://en.wikipedia.org/wiki/Brute_force_attack).
2132 # Protections against such attacks are costly in terms of code
2133 # complexity, bugs, and execution time. However, there is a very
2134 # simple and secure counter measure. See the XKCD comic
2135 # (http://xkcd.com/936/). The phrase, "There is no password like more
2136 # password" would be both much easier to remember, and still stronger
2137 # than "h4]D%@m:49", at least before this phrase was pasted as an
2138 # example on the Internet.
2140 # For the procedures used at this site, a basic computer setup can
2141 # check in the order of a billion passwords per second. You need a
2142 # password (or phrase) strength in the order of 56 bits to be a
2143 # little secure (one year on a single computer). Please be so kind
2144 # and add the name of your favorite flower, dish, fictional
2145 # character, or small town to your password. Say, Oleander, Curry,
2146 # Sherlock, or Bath, UK (each adds ~12 bits) or even the phrase "Sherlock
2147 # investigates oleander curry in Bath" (adds > 56 bits, note that
2148 # oleander is poisonous, so do not try this curry at home). That
2149 # would be more effective than adding a thousand rounds of encryption.
2150 # Typing long passwords without seeing what you are typing is
2151 # problematic. So a button should be included to make password
2152 # visible.
2155 # Technical matters
2157 # Client side JavaScript code definitions. Variable names starting with '$'
2158 # are CGIscriptor CGI variables. Some of the hashes could be strengthened
2159 # by switching to HMAC signatures. However, the security issues of
2160 # maintaining parallel functions for HMAC in both Perl and Javascript seem
2161 # to be more serious than the attack vectors against the hashes. But HMAC
2162 # is indeed used for the ticket signatures.
2164 # // On Login
2165 # HashPlaintextPassword() {
2166 # var plaintextpassword = document.getElementById('PASSWORD');
2167 # var serversalt = document.getElementById('SERVERSALT');
2168 # var username = document.getElementById('CGIUSERNAME');
2169 # return hex_sha256(plaintextpassword.value+username.value.toLowerCase()+serversalt.value);
2171 # var randomsalt = $RANDOMSALT; // From CGIscriptor
2172 # var loginticket = $LOGINTICKET; // From CGIscriptor
2173 # // Hash plaintext password
2174 # var password = HashPlaintextPassword();
2175 # // Authorize login
2176 # var hashedpassword = hex_sha256(randomsalt+password);
2177 # // Sessionticket
2178 # var sessionticket = hex_sha256(loginticket+password);
2179 # sessionStorage.setItem("CGIscriptorPRIVATE", sessionticket);
2180 # // Secretkey for encrypting new passwords, acts like a one-time pad
2181 # // Is set anew with every login, ie, also whith password changes
2182 # // and for each create new user request
2183 # var secretkey = hex_sha256(password+loginticket+randomsalt);
2184 # sessionStorage.setItem("CGIscriptorSECRET", secretkey);
2186 # // For a SESSION type request
2187 # sessionticket = hex_sha256(sessionStorage.getItem("CGIscriptorPRIVATE"));
2188 # createCookie("CGIscriptorSESSION",sessionticket, 0, "");
2190 // For a CHALLENGE type request
2191 # var sessionset = "$CHALLENGETICKET"; // From CGIscriptor
2192 # var sessionkey = sessionStorage.getItem("CGIscriptorPRIVATE");
2193 # sessionticket = hex_sha256(sessionset+sessionkey);
2194 # createCookie("CGIscriptorCHALLENGE",sessionticket, 0, "");
2196 # // For transmitting a new password
2197 # HashPlaintextNewPassword() {
2198 # var plaintextpassword = document.getElementById('NEWPASSWORD');
2199 # var serversalt = document.getElementById('SERVERSALT');
2200 # var username = document.getElementById('NEWUSERNAME');
2201 # return hex_sha256(plaintextpassword.value+username.value.toLowerCase()+serversalt.value);
2204 # var newpassword = document.getElementById('NEWPASSWORD');
2205 # var newpasswordrep = document.getElementById('NEWPASSWORDREP');
2206 # // Hash plaintext password
2207 # newpassword.value = HashPlaintextNewPassword();
2208 # var secretkey = sessionStorage.getItem("CGIscriptorSECRET");
2210 # var encrypted = XOR_hex_strings(secretkey, newpassword.value);
2211 # newpassword.value = encrypted;
2212 # newpasswordrep.value = encrypted;
2214 # // XOR of hexadecimal strings of equal length
2215 # function XOR_hex_strings(hex1, hex2) {
2216 # var resultHex = "";
2217 # var maxlength = Math.max(hex1.length, hex2.length);
2219 # for(var i=0; i &lt; maxlength; ++i) {
2220 # var h1 = hex1.charAt(i);
2221 # if(! h1) h1='0';
2222 # var h2 = hex2.charAt(i);
2223 # if(! h2) h2 ='0';
2224 # var d1 = parseInt(h1,16);
2225 # var d2 = parseInt(h2,16);
2226 # var resultD = d1^d2;
2227 # resultHex = resultHex+resultD.toString(16);
2228 # };
2229 # return resultHex;
2230 # };
2232 # Password encryption based on $ENV{'CGIMasterKey'}.
2233 # Server side Perl code:
2235 # # Password encryption
2236 # my $masterkey = $ENV{'CGIMasterKey'}
2237 # my $hash1 = hash_string($masterkey.$serversalt);
2238 # my $CryptKey = hash_string($username.$hash1);
2239 # $password = XOR_hex_strings($CryptKey,$password);
2241 # # Key for HMAC signing
2242 # my $hash1 = hash_string($masterkey.$serversalt);
2243 # my $HMACKey = hash_string($username.$hash1);
2247 # USER EXTENSIONS
2249 # A CGIscriptor package is attached to the bottom of this file. With
2250 # this package you can personalize your version of CGIscriptor by
2251 # including often used perl routines. These subroutines can be
2252 # accessed by prefixing their names with CGIscriptor::, e.g.,
2253 # <SCRIPT LANGUAGE=PERL TYPE=text/ssperl>
2254 # CGIscriptor::ListDocs("/Books/*") # List all documents in /Books
2255 # </SCRIPT>
2256 # It already contains some useful subroutines for Document Management.
2257 # As it is a separate package, it has its own namespace, isolated from
2258 # both the evaluator and the main program. To access variables from
2259 # the document <SCRIPT></SCRIPT> blocks, use $CGIexecute::<var>.
2261 # Currently, the following functions are implemented
2262 # (precede them with CGIscriptor::, see below for more information)
2263 # - SAFEqx ('String') -> result of qx/"String"/ # Safe application of ``-quotes
2264 # Is used by text/osshell Shell scripts. Protects all CGI
2265 # (client-supplied) values with single quotes before executing the
2266 # commands (one of the few functions that also works WITHOUT CGIscriptor::
2267 # in front)
2268 # - defineCGIvariable ($name[, $default) -> 0/1 (i.e., failure/success)
2269 # Is used by the META tag to define and initialize CGI and ENV
2270 # name/value pairs. Tries to obtain an initializing value from (in order):
2271 # $ENV{$name}
2272 # The Query string
2273 # The default value given (if any)
2274 # (one of the few functions that also works WITHOUT CGIscriptor::
2275 # in front)
2276 # - CGIsafeFileName (FileName) -> FileName or ""
2277 # Check a string against the Allowed File Characters (and ../ /..).
2278 # Returns an empty string for unsafe filenames.
2279 # - CGIsafeEmailAddress (Email) -> Email or ""
2280 # Check a string against correct email address pattern.
2281 # Returns an empty string for unsafe addresses.
2282 # - RedirectShellScript ('CommandString') -> FILEHANDLER or undef
2283 # Open a named PIPE for SAFEqx to receive ALL shell scripts
2284 # - URLdecode (URL encoded string) -> plain string # Decode URL encoded argument
2285 # - URLencode (plain string) -> URL encoded string # Encode argument as URL code
2286 # - CGIparseValue (ValueName [, URL_encoded_QueryString]) -> Decoded value
2287 # Extract the value of a CGI variable from the global or a private
2288 # URL-encoded query (multipart POST raw, NOT decoded)
2289 # - CGIparseValueList (ValueName [, URL_encoded_QueryString])
2290 # -> List of decoded values
2291 # As CGIparseValue, but now assembles ALL values of ValueName into a list.
2292 # - CGIparseHeader (ValueName [, URL_encoded_QueryString]) -> Header
2293 # Extract the header of a multipart CGI variable from the global or a private
2294 # URL-encoded query ("" when not a multipart variable or absent)
2295 # - CGIparseForm ([URL_encoded_QueryString]) -> Decoded Form
2296 # Decode the complete global URL-encoded query or a private
2297 # URL-encoded query
2298 # - read_url(URL) # Returns the page from URL (with added base tag, both FTP and HTTP)
2299 # Uses main::GET_URL(URL, 1) to get at the command to read the URL.
2300 # - BrowseDirs(RootDirectory [, Pattern, Startdir, CGIname]) # print browsable directories
2301 # - ListDocs(Pattern [,ListType]) # Prints a nested HTML directory listing of
2302 # all documents, e.g., ListDocs("/*", "dl");.
2303 # - HTMLdocTree(Pattern [,ListType]) # Prints a nested HTML listing of all
2304 # local links starting from a given document, e.g.,
2305 # HTMLdocTree("/Welcome.html", "dl");
2308 # THE RESULTS STACK: @CGISCRIPTORRESULTS
2310 # If the pseudo-variable "$CGIscriptorResults" has been defined in a
2311 # META tag, all subsequent SCRIPT and META results are pushed
2312 # on the @CGIscriptorResults stack. This list is just another
2313 # Perl variable and can be used and manipulated like any other list.
2314 # $CGIscriptorResults[-1] is always the last result.
2315 # This is only of limited use, e.g., to use the results of an OS shell
2316 # script inside a Perl script. Will NOT contain the results of Pipes
2317 # or code from MIME-profiling.
2320 # USEFULL CGI PREDEFINED VARIABLES (DO NOT ASSIGN TO THESE)
2322 # $CGI_HOME - The DocumentRoot directory
2323 # $CGI_Decoded_QS - The complete decoded Query String
2324 # $CGI_Content_Length - The ACTUAL length of the Query String
2325 # $CGI_Date - Current date and time
2326 # $CGI_Year $CGI_Month $CGI_Day $CGI_WeekDay - Current Date
2327 # $CGI_Time - Current Time
2328 # $CGI_Hour $CGI_Minutes $CGI_Seconds - Current Time, split
2329 # GMT Date/Time:
2330 # $CGI_GMTYear $CGI_GMTMonth $CGI_GMTDay $CGI_GMTWeekDay $CGI_GMTYearDay
2331 # $CGI_GMTHour $CGI_GMTMinutes $CGI_GMTSeconds $CGI_GMTisdst
2334 # USEFULL CGI ENVIRONMENT VARIABLES
2336 # Variables accessible (in APACHE) as $ENV{<name>}
2337 # (see: "http://hoohoo.ncsa.uiuc.edu/cgi/env.html"):
2339 # QUERY_STRING - The query part of URL, that is, everything that follows the
2340 # question mark.
2341 # PATH_INFO - Extra path information given after the script name
2342 # PATH_TRANSLATED - Extra pathinfo translated through the rule system.
2343 # (This doesn't always make sense.)
2344 # REMOTE_USER - If the server supports user authentication, and the script is
2345 # protected, this is the username they have authenticated as.
2346 # REMOTE_HOST - The hostname making the request. If the server does not have
2347 # this information, it should set REMOTE_ADDR and leave this unset
2348 # REMOTE_ADDR - The IP address of the remote host making the request.
2349 # REMOTE_IDENT - If the HTTP server supports RFC 931 identification, then this
2350 # variable will be set to the remote user name retrieved from
2351 # the server. Usage of this variable should be limited to logging
2352 # only.
2353 # AUTH_TYPE - If the server supports user authentication, and the script
2354 # is protected, this is the protocol-specific authentication
2355 # method used to validate the user.
2356 # CONTENT_TYPE - For queries which have attached information, such as HTTP
2357 # POST and PUT, this is the content type of the data.
2358 # CONTENT_LENGTH - The length of the said content as given by the client.
2359 # SERVER_SOFTWARE - The name and version of the information server software
2360 # answering the request (and running the gateway).
2361 # Format: name/version
2362 # SERVER_NAME - The server's hostname, DNS alias, or IP address as it
2363 # would appear in self-referencing URLs
2364 # GATEWAY_INTERFACE - The revision of the CGI specification to which this
2365 # server complies. Format: CGI/revision
2366 # SERVER_PROTOCOL - The name and revision of the information protocol this
2367 # request came in with. Format: protocol/revision
2368 # SERVER_PORT - The port number to which the request was sent.
2369 # REQUEST_METHOD - The method with which the request was made. For HTTP,
2370 # this is "GET", "HEAD", "POST", etc.
2371 # SCRIPT_NAME - A virtual path to the script being executed, used for
2372 # self-referencing URLs.
2373 # HTTP_ACCEPT - The MIME types which the client will accept, as given by
2374 # HTTP headers. Other protocols may need to get this
2375 # information from elsewhere. Each item in this list should
2376 # be separated by commas as per the HTTP spec.
2377 # Format: type/subtype, type/subtype
2378 # HTTP_USER_AGENT - The browser the client is using to send the request.
2379 # General format: software/version library/version.
2382 # INSTRUCTIONS FOR RUNNING CGIscriptor ON UNIX
2384 # CGIscriptor.pl will run on any WWW server that runs Perl scripts, just add
2385 # a line like the following to your srm.conf file (Apache example):
2387 # ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
2389 # URL's that refer to http://www.your.address/SHTML/... will now be handled
2390 # by CGIscriptor.pl, which can use a private directory tree (default is the
2391 # DOCUMENT_ROOT directory tree, but it can be anywhere, see manual).
2393 # If your hosting ISP won't let you add ScriptAlias lines you can use
2394 # the following "rewrite"-based "scriptalias" in .htaccess
2395 # (from Gerd Franke)
2397 # RewriteEngine On
2398 # RewriteBase /
2399 # RewriteCond %{REQUEST_FILENAME} .html$
2400 # RewriteCond %{SCRIPT_FILENAME} !cgiscriptor.pl$
2401 # RewriteCond %{REQUEST_FILENAME} -f
2402 # RewriteRule ^(.*)$ /cgi-bin/cgiscriptor.pl/$1?&%{QUERY_STRING}
2404 # Everthing with the extension ".html" and not including "cgiscriptor.pl"
2405 # in the url and where the file "path/filename.html" exists is redirected
2406 # to "/cgi.bin/cgiscriptor.pl/path/filename.html?query".
2407 # The user configuration should get the same path-level as the
2408 # .htaccess-file:
2410 # # Just enter your own directory path here
2411 # $YOUR_HTML_FILES = "$ENV{'DOCUMENT_ROOT'}";
2412 # # use DOCUMENT_ROOT only, if .htaccess lies in the root-directory.
2414 # If this .htaccess goes in a specific directory, the path to this
2415 # directory must be added to $ENV{'DOCUMENT_ROOT'}.
2417 # The CGIscriptor file contains all documentation as comments. These
2418 # comments can be removed to speed up loading (e.g., `egrep -v '^#'
2419 # CGIscriptor.pl` > leanScriptor.pl). A bare bones version of
2420 # CGIscriptor.pl, lacking documentation, most comments, access control,
2421 # example functions etc. (but still with the copyright notice and some
2422 # minimal documentation) can be obtained by calling CGIscriptor.pl on the
2423 # command line with the '-slim' command line argument, e.g.,
2425 # >CGIscriptor.pl -slim > slimCGIscriptor.pl
2427 # CGIscriptor.pl can be run from the command line with <path> and <query> as
2428 # arguments, as `CGIscriptor.pl <path> <query>`, inside a perl script
2429 # with 'do CGIscriptor.pl' after setting $ENV{PATH_INFO}
2430 # and $ENV{QUERY_STRING}, or CGIscriptor.pl can be loaded with 'require
2431 # "/real-path/CGIscriptor.pl"'. In the latter case, requests are processed
2432 # by 'Handle_Request();' (again after setting $ENV{PATH_INFO} and
2433 # $ENV{QUERY_STRING}).
2435 # Using the command line execution option, CGIscriptor.pl can be used as a
2436 # document (meta-)preprocessor. If the first argument is '-', STDIN will be read.
2437 # For example:
2439 # > cat MyDynamicDocument.html | CGIscriptor.pl - '[QueryString]' > MyStaticFile.html
2441 # This command line will produce a STATIC file with the DYNAMIC content of
2442 # MyDocument.html "interpolated".
2444 # This option would be very dangerous when available over the internet.
2445 # If someone could sneak a 'http://www.your.domain/-' URL past your
2446 # server, CGIscriptor could EXECUTE any POSTED contend.
2447 # Therefore, for security reasons, STDIN will NOT be read
2448 # if ANY of the HTTP server environment variables is set (e.g.,
2449 # SERVER_PORT, SERVER_PROTOCOL, SERVER_NAME, SERVER_SOFTWARE,
2450 # HTTP_USER_AGENT, REMOTE_ADDR).
2451 # This block on processing STDIN on HTTP requests can be lifted by setting
2452 # $BLOCK_STDIN_HTTP_REQUEST = 0;
2453 # In the security configuration. Butbe carefull when doing this.
2454 # It can be very dangerous.
2456 # Running demo's and more information can be found at
2457 # http://www.fon.hum.uva.nl/~rob/OSS/OSS.html
2459 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site or
2460 # CPAN that can use CGIscriptor.pl as the base of a µWWW server and
2461 # demonstrates its use.
2464 # PROCESSING NON-FILESYSTEM DATA
2466 # Normally, HTTP (WWW) requests map onto file that can be accessed
2467 # using the perl open() function. That is, the web server runs on top of
2468 # some directory structure. However, we can envission (and put to good
2469 # use) other systems that do not use a normal file system. The whole CGI
2470 # was developed to make dynamic document generation possible.
2472 # A special case is where we want to have it both: A normal web server
2473 # with normal "file data", but not a normal files system. For instance,
2474 # we want or normal Web Site to run directly from a RAM hash table or
2475 # other database, instead of from disk. But we do NOT want to code the
2476 # whole site structure in CGI.
2478 # CGIscriptor can do this. If the web server fills an environment variable
2479 # $ENV{'CGI_FILE_CONTENT'} with the content of the "file", then the content
2480 # of this variable is processed instead of opening a file. If this environment
2481 # variable has the value '-', the content of another environment variable,
2482 # $ENV{'CGI_DATA_ACCESS_CODE'} is executed as:
2483 # eval("\@_ = ($file_path); do {$ENV{'CGI_DATA_ACCESS_CODE'}};")
2484 # and the result is processed as if it was the content of the requested
2485 # file.
2486 # (actually, the names of the environment variables are user configurable,
2487 # they are stored in the local variables $CGI_FILE_CONTENT and
2488 # $CGI_DATA_ACCESS_CODE)
2490 # When using this mechanism, the SRC attribute mechanism will only partially work.
2491 # Only the "recursive" calls to CGIscriptor (the ProcessFile() function)
2492 # will work, the automagical execution of SRC files won't. (In this case,
2493 # the SRC attribute won't work either for other scripting languages)
2496 # NON-UNIX PLATFORMS
2498 # CGIscriptor.pl was mainly developed and tested on UNIX. However, as I
2499 # coded part of the time on an Apple Macintosh under MacPerl, I made sure
2500 # CGIscriptor did run under MacPerl (with command line options). But only
2501 # as an independend script, not as part of a HTTP server. I have used it
2502 # under Apache in Windows XP.
2504 ENDOFHELPTEXT
2505 exit;
2507 ###############################################################################
2509 # SECURITY CONFIGURATION
2511 # Special configurations related to SECURITY
2512 # (i.e., optional, see also environment variables below)
2514 # LOGGING
2515 # Log Clients and the requested paths (Redundant when loging Queries)
2517 $ClientLog = "./Client.log"; # (uncomment for use)
2519 # Format: Localtime | REMOTE_USER REMOTE_IDENT REMOTE_HOST REMOTE_ADDRESS \
2520 # PATH_INFO CONTENT_LENGTH (actually, the real query+post length)
2522 # Log Clients and the queries, the CGIQUERYDECODE is required if you want
2523 # to log queries. If you log Queries, the loging of Clients is redundant
2524 # (note that queries can be quite long, so this might not be a good idea)
2526 #$QueryLog = "./Query.log"; # (uncomment for use)
2528 # ACCESS CONTROL
2529 # the Access files should contain Hostnames or IP addresses,
2530 # i.e. REMOTE_HOST or REMOTE_ADDR, each on a separate line
2531 # optionally followed by one ore more file patterns, e.g., "edu /DEMO".
2532 # Matching is done "domain first". For example ".edu" matches all
2533 # clients whose "name" ends in ".edu" or ".EDU". The file pattern
2534 # "/DEMO" matches all paths that contain the strings "/DEMO" or "/demo"
2535 # (both matchings are done case-insensitive).
2536 # The name special symbol "-" matches ALL clients who do not supply a
2537 # REMOTE_HOST name, "*" matches all clients.
2538 # Lines starting with '-e' are evaluated. A non-zero return value indicates
2539 # a match. You can use $REMOTE_HOST, $REMOTE_ADDR, and $PATH_INFO. These
2540 # lines are evaluated in the program's own name-space. So DO NOT assign to
2541 # variables.
2543 # Accept the following users (remove comment # and adapt filename)
2544 $CGI_Accept = -s "$YOUR_SCRIPTS/ACCEPT.lis" ? "$YOUR_SCRIPTS/ACCEPT.lis" : ''; # (uncomment for use)
2546 # Reject requests from the following users (remove comment # and
2547 # adapt filename, this is only of limited use)
2548 $CGI_Reject = -s "$YOUR_SCRIPTS/REJECT.lis" ? "$YOUR_SCRIPTS/REJECT.lis" : ''; # (uncomment for use)
2550 # Empty lines or comment lines starting with '#' are ignored in both
2551 # $CGI_Accept and $CGI_Reject.
2553 # Block STDIN (i.e., '-') requests when servicing an HTTP request
2554 # Comment this out if you realy want to use STDIN in an on-line web server
2555 $BLOCK_STDIN_HTTP_REQUEST = 1;
2558 # End of security configuration
2560 ##################################################<<<<<<<<<<End Remove
2562 # PARSING CGI VALUES FROM THE QUERY STRING (USER CONFIGURABLE)
2564 # The CGI parse commands. These commands extract the values of the
2565 # CGI variables from the URL encoded Query String.
2566 # If you want to use your own CGI decoders, you can call them here
2567 # instead, using your own PATH and commenting/uncommenting the
2568 # appropriate lines
2570 # CGI parse command for individual values
2571 # (if $List > 0, returns a list value, if $List < 0, a hash table, this is optional)
2572 sub YOUR_CGIPARSE # ($Name [, $List]) -> Decoded value
2574 my $Name = shift;
2575 my $List = shift || 0;
2576 # Use one of the following by uncommenting
2577 if(!$List) # Simple value
2579 return CGIscriptor::CGIparseValue($Name) ;
2581 elsif($List < 0) # Hash tables
2583 return CGIscriptor::CGIparseValueHash($Name); # Defined in CGIscriptor below
2585 else # Lists
2587 return CGIscriptor::CGIparseValueList($Name); # Defined in CGIscriptor below
2590 # return `/PATH/cgiparse -value $Name`; # Shell commands
2591 # require "/PATH/cgiparse.pl"; return cgivalue($Name); # Library
2593 # Complete queries
2594 sub YOUR_CGIQUERYDECODE
2596 # Use one of the following by uncommenting
2597 return CGIscriptor::CGIparseForm(); # Defined in CGIscriptor below
2598 # return `/PATH/cgiparse -form`; # Shell commands
2599 # require "/PATH/cgiparse.pl"; return cgiform(); # Library
2602 # End of configuration
2604 #######################################################################
2606 # Translating input files.
2607 # Allows general and global conversions of files using Regular Expressions
2608 # Translations are applied in the order of definition.
2610 # Define:
2611 # my $TranslationPaths = 'pattern'; # Pattern matching PATH_INFO
2613 # push(@TranslationTable, ['pattern', 'replacement']);
2614 # e.g. (for Ruby Rails):
2615 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2616 # push(@TranslationTable, ['%>', '</SCRIPT>']);
2618 # Runs:
2619 # my $currentRegExp;
2620 # foreach $currentRegExp (keys(%TranslationTable))
2622 # my $currentRegExp;
2623 # foreach $currentRegExp (@TranslationTable)
2625 # my ($pattern, $replacement) = @$currentRegExp;
2626 # $$text =~ s!$pattern!$replacement!msg;
2627 # };
2628 # };
2630 # Configuration section
2632 #######################################################################
2634 # The file paths on which to apply the translation
2635 my $TranslationPaths = ''; # NO files
2636 #$TranslationPaths = '.'; # ANY file
2637 # $TranslationPaths = '\.html'; # HTML files
2639 my @TranslationTable = ();
2640 # Some legacy code
2641 push(@TranslationTable, ['\<\s*CGI\s+([^\>])*\>', '\<SCRIPT TYPE=\"text/ssperl\"\>$1\<\/SCRIPT>']);
2642 # Ruby Rails?
2643 push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2644 push(@TranslationTable, ['%>', '</SCRIPT>']);
2646 sub performTranslation # (\$text)
2648 my $text = shift || return;
2649 if(@TranslationTable && $TranslationPaths && $ENV{'PATH_INFO'} =~ m!$TranslationPaths!)
2651 my $currentRegExp;
2652 foreach $currentRegExp (@TranslationTable)
2654 my ($pattern, $replacement) = @$currentRegExp;
2655 $$text =~ s!$pattern!$replacement!msg;
2660 #######################################################################
2662 # Seamless access to other (Scripting) Languages
2663 # TYPE='text/ss<interpreter>'
2665 # Configuration section
2667 #######################################################################
2669 # OTHER SCRIPTING LANGUAGES AT THE SERVER SIDE (MIME => OScommand)
2670 # Yes, it realy is this simple! (unbelievable, isn't it)
2671 # NOTE: Some interpreters require some filtering to obtain "clean" output
2673 %ScriptingLanguages = (
2674 "text/testperl" => 'perl', # Perl for testing
2675 "text/sspython" => 'python', # Python
2676 "text/ssruby" => 'ruby', # Ruby
2677 "text/sstcl" => 'tcl', # TCL
2678 "text/ssawk" => 'awk -f-', # Awk
2679 "text/sslisp" => # lisp (rep, GNU)
2680 'rep | tail +4 '."| egrep -v '> |^rep. |^nil\\\$'",
2681 "text/xlispstat" => # xlispstat
2682 'xlispstat | tail +7 ' ."| egrep -v '> \\\$|^NIL'",
2683 "text/ssprolog" => # Prolog (GNU)
2684 "gprolog | tail +4 | sed 's/^| ?- //'",
2685 "text/ssm4" => 'm4', # M4 macro's
2686 "text/sh" => 'sh', # Born shell
2687 "text/bash" => 'bash', # Born again shell
2688 "text/csh" => 'csh', # C shell
2689 "text/ksh" => 'ksh', # Korn shell
2690 "text/sspraat" => # Praat (sound/speech analysis)
2691 "praat - | sed 's/Praat > //g'",
2692 "text/ssr" => # R
2693 "R --vanilla --slave | sed 's/^[\[0-9\]*] //'",
2694 "text/ssrebol" => # REBOL
2695 "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\\s*\[> \]* //'",
2696 "text/postgresql" => 'psql 2>/dev/null',
2698 # Not real scripting, but the use of other applications
2699 "text/ssmailto" => "awk 'NF||F{F=1;print \\\$0;}'|mailto >/dev/null", # Send mail from server
2700 "text/ssdisplay" => 'cat', # Display, (interpolation)
2701 "text/sslogfile" => # Log to file, (interpolation)
2702 "awk 'NF||L {if(!L){L=tolower(\\\$1)~/^file:\\\$/ ? \\\$2 : \\\$1;}else{print \\\$0 >> L;};}'",
2704 "" => ""
2707 # To be able to access the CGI variables in your script, they
2708 # should be passed to the scripting language in a readable form
2709 # Here you can enter how they should be printed (the first %s
2710 # is replaced by the NAME of the CGI variable as it apears in the # Path selections starting with ! or 'not' will be inverted. That is:
2711 # * not .wav
2712 # Will match all file and path names that do NOT contain '.wav'
2714 # META tag, the second by its VALUE).
2715 # For Perl this would be:
2716 # "text/testperl" => '$%s = "%s";',
2717 # which would be executed as
2718 # printf('$%s = "%s";', $CGI_NAME, $CGI_VALUE);
2720 # If the hash table value doesn't exist, nothing is done
2721 # (you have to parse the Environment variables yourself).
2722 # If it DOES exist but is empty (e.g., "text/sspraat" => '',)
2723 # Perl string interpolation of variables (i.e., $var, @array,
2724 # %hash) is performed. This means that $@%\ must be protected
2725 # with a \.
2727 %ScriptingCGIvariables = (
2728 "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value'; (for testing)
2729 "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
2730 "text/ssruby" => '@%s = "%s"', # Ruby @VAR = 'value'
2731 "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
2732 "text/ssawk" => '%s = "%s";', # Awk VAR = 'value';
2733 "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
2734 "text/xlispstat" => '(setq %s "%s")', # xlispstat (setq VAR "value")
2735 "text/ssprolog" => '', # Gnu prolog (interpolated)
2736 "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
2737 "text/sh" => "\%s='\%s'", # Born shell VAR='value'
2738 "text/bash" => "\%s='\%s'", # Born again shell VAR='value'
2739 "text/csh" => "\$\%s='\%s';", # C shell $VAR = 'value';
2740 "text/ksh" => "\$\%s='\%s';", # Korn shell $VAR = 'value';
2742 "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
2743 "text/sspraat" => '', # Praat (interpolation)
2744 "text/ssr" => '%s <- "%s";', # R VAR <- "value";
2745 "text/postgresql" => '', # PostgreSQL (interpolation)
2747 # Not real scripting, but the use of other applications
2748 "text/ssmailto" => '', # MAILTO, (interpolation)
2749 "text/ssdisplay" => '', # Display, (interpolation)
2750 "text/sslogfile" => '', # Log to file, (interpolation)
2752 "" => ""
2755 # If you want something added in front or at the back of each script
2756 # block as send to the interpreter add it here.
2757 # mime => "string", e.g., "text/sspython" => "python commands"
2758 %ScriptingPrefix = (
2759 "text/testperl" => "\# Prefix Code;", # Perl script testing
2760 "text/ssm4" => 'divert(0)', # M4 macro's (open STDOUT)
2762 "" => ""
2764 # If you want something added at the end of each script block
2765 %ScriptingPostfix = (
2766 "text/testperl" => "\# Postfix Code;", # Perl script testing
2767 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2769 "" => ""
2771 # If you need initialization code, directly after opening
2772 %ScriptingInitialization = (
2773 "text/testperl" => "\# Initialization Code;", # Perl script testing
2774 "text/ssawk" => 'BEGIN {', # Server Side awk scripts (VAR = "value")
2775 "text/sslisp" => '(prog1 nil ', # Lisp (rep)
2776 "text/xlispstat" => '(prog1 nil ', # xlispstat
2777 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2779 "" => ""
2781 # If you need cleanup code before closing
2782 %ScriptingCleanup = (
2783 "text/testperl" => "\# Cleanup Code;", # Perl script testing
2784 "text/sspraat" => 'Quit',
2785 "text/ssawk" => '};', # Server Side awk scripts (VAR = "value")
2786 "text/sslisp" => '(princ "\n" standard-output)).', # Closing print to rep
2787 "text/xlispstat" => '(print ""))', # Closing print to xlispstat
2788 "text/postgresql" => '\q', # quit psql
2789 "text/ssdisplay" => "", # close cat
2791 "" => ""
2794 # End of configuration for foreign scripting languages
2796 ###############################################################################
2798 # Initialization Code
2801 sub Initialize_Request
2803 ###############################################################################
2805 # ENVIRONMENT VARIABLES
2807 # Use environment variables to configure CGIscriptor on a temporary basis.
2808 # If you define any of the configurable variables as environment variables,
2809 # these are used instead of the "hard coded" values above.
2811 $SS_PUB = $ENV{'SS_PUB'} || $YOUR_HTML_FILES;
2812 $SS_SCRIPT = $ENV{'SS_SCRIPT'} || $YOUR_SCRIPTS;
2815 # Substitution strings, these are used internally to handle the
2816 # directory separator strings, e.g., '~/' -> 'SS_PUB:' (Mac)
2817 $HOME_SUB = $SS_PUB;
2818 $SCRIPT_SUB = $SS_SCRIPT;
2821 # Make sure all script are reliably loaded
2822 push(@INC, $SS_SCRIPT);
2825 # Add the directory separator to the "home" directories.
2826 # (This is required for ~/ and ./ substitution)
2827 $HOME_SUB .= '/' if $HOME_SUB;
2828 $SCRIPT_SUB .= '/' if $SCRIPT_SUB;
2830 $CGI_HOME = $ENV{'DOCUMENT_ROOT'};
2831 $ENV{'PATH_TRANSLATED'} =~ /$ENV{'PATH_INFO'}/is;
2832 $CGI_HOME = $` unless $ENV{'DOCUMENT_ROOT'}; # Get the DOCUMENT_ROOT directory
2833 $default_values{'CGI_HOME'} = $CGI_HOME;
2834 $ENV{'HOME'} = $CGI_HOME;
2835 # Set SS_PUB and SS_SCRIPT as Environment variables (make them available
2836 # to the scripts)
2837 $ENV{'SS_PUB'} = $SS_PUB unless $ENV{'SS_PUB'};
2838 $ENV{'SS_SCRIPT'} = $SS_SCRIPT unless $ENV{'SS_SCRIPT'};
2840 $FilePattern = $ENV{'FilePattern'} || $FilePattern;
2841 $MaximumQuerySize = $ENV{'MaximumQuerySize'} || $MaximumQuerySize;
2842 $ClientLog = $ENV{'ClientLog'} || $ClientLog;
2843 $QueryLog = $ENV{'QueryLog'} || $QueryLog;
2844 $CGI_Accept = $ENV{'CGI_Accept'} || $CGI_Accept;
2845 $CGI_Reject = $ENV{'CGI_Reject'} || $CGI_Reject;
2847 # Parse file names
2848 $CGI_Accept =~ s@^\~/@$HOME_SUB@g if $CGI_Accept;
2849 $CGI_Reject =~ s@^\~/@$HOME_SUB@g if $CGI_Reject;
2850 $ClientLog =~ s@^\~/@$HOME_SUB@g if $ClientLog;
2851 $QueryLog =~ s@^\~/@$HOME_SUB@g if $QueryLog;
2853 $CGI_Accept =~ s@^\./@$SCRIPT_SUB@g if $CGI_Accept;
2854 $CGI_Reject =~ s@^\./@$SCRIPT_SUB@g if $CGI_Reject;
2855 $ClientLog =~ s@^\./@$SCRIPT_SUB@g if $ClientLog;
2856 $QueryLog =~ s@^\./@$SCRIPT_SUB@g if $QueryLog;
2858 @CGIscriptorResults = (); # A stack of results
2860 # end of Environment variables
2862 #############################################################################
2864 # Define and Store "standard" values
2866 # BEFORE doing ANYTHING check the size of Query String
2867 length($ENV{'QUERY_STRING'}) <= $MaximumQuerySize || dieHandler(2, "QUERY TOO LONG\n");
2869 # The Translated Query String and the Actual length of the (decoded)
2870 # Query String
2871 if($ENV{'QUERY_STRING'})
2873 # If this can contain '`"-quotes, be carefull to use it QUOTED
2874 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2875 $default_values{CGI_Content_Length} = length($default_values{CGI_Decoded_QS});
2878 # Get the current Date and time and store them as default variables
2880 # Get Local Time
2881 $LocalTime = localtime;
2883 # CGI_Year CGI_Month CGI_Day CGI_WeekDay CGI_Time
2884 # CGI_Hour CGI_Minutes CGI_Seconds
2886 $default_values{CGI_Date} = $LocalTime;
2887 ($default_values{CGI_WeekDay},
2888 $default_values{CGI_Month},
2889 $default_values{CGI_Day},
2890 $default_values{CGI_Time},
2891 $default_values{CGI_Year}) = split(' ', $LocalTime);
2892 ($default_values{CGI_Hour},
2893 $default_values{CGI_Minutes},
2894 $default_values{CGI_Seconds}) = split(':', $default_values{CGI_Time});
2896 # GMT:
2897 # CGI_GMTYear CGI_GMTMonth CGI_GMTDay CGI_GMTWeekDay CGI_GMTYearDay
2898 # CGI_GMTHour CGI_GMTMinutes CGI_GMTSeconds CGI_GMTisdst
2900 ($default_values{CGI_GMTSeconds},
2901 $default_values{CGI_GMTMinutes},
2902 $default_values{CGI_GMTHour},
2903 $default_values{CGI_GMTDay},
2904 $default_values{CGI_GMTMonth},
2905 $default_values{CGI_GMTYear},
2906 $default_values{CGI_GMTWeekDay},
2907 $default_values{CGI_GMTYearDay},
2908 $default_values{CGI_GMTisdst}) = gmtime;
2912 # End of Initialize Request
2914 ###################################################################
2916 # SECURITY: ACCESS CONTROL
2918 # Check the credentials of each client (use pattern matching, domain first).
2919 # This subroutine will kill-off (die) the current process whenever access
2920 # is denied.
2922 sub Access_Control
2924 # >>>>>>>>>>Start Remove
2926 # ACCEPTED CLIENTS
2928 # Only accept clients which are authorized, reject all unnamed clients
2929 # if REMOTE_HOST is given.
2930 # If file patterns are given, check whether the user is authorized for
2931 # THIS file.
2932 if($CGI_Accept)
2934 # Use local variables, REMOTE_HOST becomes '-' if undefined
2935 my $REMOTE_HOST = $ENV{REMOTE_HOST} || '-';
2936 my $REMOTE_ADDR = $ENV{REMOTE_ADDR};
2937 my $PATH_INFO = $ENV{'PATH_INFO'};
2939 open(CGI_Accept, "<$CGI_Accept") || dieHandler(3, "$CGI_Accept: $!\n");
2940 $NoAccess = 1;
2941 while(<CGI_Accept>)
2943 next unless /\S/; # Skip empty lines
2944 next if /^\s*\#/; # Skip comments
2946 # Full expressions
2947 if(/^\s*-e\s/is)
2949 my $Accept = $'; # Get the expression
2950 $NoAccess &&= eval($Accept); # evaluate the expresion
2952 else
2954 my ($Accept, @FilePatternList) = split;
2955 if($Accept eq '*' # Always match
2956 ||$REMOTE_HOST =~ /\Q$Accept\E$/is # REMOTE_HOST matches
2957 || (
2958 $Accept =~ /^[0-9\.]+$/
2959 && $REMOTE_ADDR =~ /^\Q$Accept\E/ # IP address matches
2963 if($FilePatternList[0])
2965 my $invert = 0;
2966 if($FilePatternList[0] eq "!" or $FilePatternList[0] eq "not")
2968 $invert = 1;
2969 shift(@FilePatternList);
2971 foreach $Pattern (@FilePatternList)
2973 # Check whether this patterns is accepted
2974 my $value = ($PATH_INFO !~ m@\Q$Pattern\E@is);
2975 $value = not $value if $invert;
2976 $NoAccess &&= $value;
2979 else
2981 $NoAccess = 0; # No file patterns -> Accepted
2985 # Blocked
2986 last unless $NoAccess;
2988 close(CGI_Accept);
2989 if($NoAccess && $PATH_INFO ne "")
2991 dieHandler(4, "No Access: $PATH_INFO\n");
2992 $ENV{'PATH_INFO'} = "";
2997 # REJECTED CLIENTS
2999 # Reject named clients, accept all unnamed clients
3000 if($CGI_Reject)
3002 # Use local variables, REMOTE_HOST becomes '-' if undefined
3003 my $REMOTE_HOST = $ENV{'REMOTE_HOST'} || '-';
3004 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
3005 my $PATH_INFO = $ENV{'PATH_INFO'};
3007 open(CGI_Reject, "<$CGI_Reject") || dieHandler(5, "$CGI_Reject: $!\n");
3008 $NoAccess = 0;
3009 while(<CGI_Reject>)
3011 next unless /\S/; # Skip empty lines
3012 next if /^\s*\#/; # Skip comments
3014 # Full expressions
3015 if(/^-e\s/is)
3017 my $Reject = $'; # Get the expression
3018 $NoAccess ||= eval($Reject); # evaluate the expresion
3020 else
3022 my ($Reject, @FilePatternList) = split;
3023 if($Reject eq '*' # Always match
3024 ||$REMOTE_HOST =~ /\Q$Reject\E$/is # REMOTE_HOST matches
3025 ||($Reject =~ /^[0-9\.]+$/
3026 && $REMOTE_ADDR =~ /^\Q$Reject\E/is # IP address matches
3030 if($FilePatternList[0])
3032 my $invert = 0;
3033 if($FilePatternList[0] eq "!" or $FilePatternList[0] eq "not")
3035 $invert = 1;
3036 shift(@FilePatternList);
3038 foreach $Pattern (@FilePatternList)
3040 my $value = ($PATH_INFO =~ m@\Q$Pattern\E@is);
3041 $value = not $value if $invert;
3042 $NoAccess ||= $value;
3045 else
3047 $NoAccess = 1; # No file patterns -> Rejected
3051 last if $NoAccess;
3053 close(CGI_Reject);
3054 if($NoAccess && $PATH_INFO ne "")
3056 dieHandler(4, "Request rejected: $PATH_INFO\n");
3057 $ENV{'PATH_INFO'} = "";
3061 ##########################################################<<<<<<<<<<End Remove
3064 # Get the filename
3066 # Does the filename contain any illegal characters (e.g., |, >, or <)
3067 dieHandler(7, "Illegal request: $ENV{'PATH_INFO'}\n") if $ENV{'PATH_INFO'} =~ /[^$FileAllowedChars]/;
3068 # Does the pathname contain an illegal (blocked) "directory"
3069 dieHandler(8, "Illegal request: $ENV{'PATH_INFO'}\n") if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@; # Access is blocked
3070 # Does the pathname contain a direct referencer to BinaryMapFile
3071 dieHandler(9, "Illegal request: $ENV{'PATH_INFO'}\n") if $BinaryMapFile && $ENV{'PATH_INFO'} =~ m@\Q$BinaryMapFile\E@; # Access is blocked
3073 # SECURITY: Is PATH_INFO allowed?
3074 if($FilePattern && $ENV{'PATH_INFO'} && $ENV{'PATH_INFO'} ne '-' &&
3075 ($ENV{'PATH_INFO'} !~ m@($FilePattern)$@is))
3077 # Unsupported file types can be processed by a special raw-file
3078 if($BinaryMapFile)
3080 $ENV{'CGI_BINARY_FILE'} = $ENV{'PATH_INFO'};
3081 $ENV{'PATH_INFO'} = $BinaryMapFile;
3083 else
3085 dieHandler(10, "Illegal file\n");
3091 # End of Security Access Control
3094 ############################################################################
3096 # Get the POST part of the query and add it to the QUERY_STRING.
3098 ## Path selections starting with ! or 'not' will be inverted. That is:
3099 # * not .wav
3100 # Will match all file and path names that do NOT contain '.wav'
3102 sub Get_POST_part_of_query
3105 # If POST, Read data from stdin to QUERY_STRING
3106 if($ENV{'REQUEST_METHOD'} =~ /POST/is)
3108 # SECURITY: Check size of Query String
3109 $ENV{'CONTENT_LENGTH'} <= $MaximumQuerySize || dieHandler(11, "Query too long: $ENV{'CONTENT_LENGTH'}\n"); # Query too long
3110 my $QueryRead = 0;
3111 my $SystemRead = $ENV{'CONTENT_LENGTH'};
3112 $ENV{'QUERY_STRING'} .= '&' if length($ENV{'QUERY_STRING'}) > 0;
3113 while($SystemRead > 0)
3115 $QueryRead = sysread(STDIN, $Post, $SystemRead); # Limit length
3116 $ENV{'QUERY_STRING'} .= $Post;
3117 $SystemRead -= $QueryRead;
3119 # Update decoded Query String
3120 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
3121 $default_values{CGI_Content_Length} =
3122 length($default_values{CGI_Decoded_QS});
3126 # End of getting POST part of query
3129 ############################################################################
3131 # Start (HTML) output and logging
3132 # (if there are irregularities, it can kill the current process)
3135 sub Initialize_output
3137 # Construct the REAL file path (except for STDIN on the command line)
3138 my $file_path = $ENV{'PATH_INFO'} ne '-' ? $SS_PUB . $ENV{'PATH_INFO'} : '-';
3139 $file_path =~ s/\?.*$//; # Remove query
3140 # This is only necessary if your server does not catch ../ directives
3141 $file_path !~ m@\.\./@ || dieHandler(12, "Illegal ../ Construct\n"); # SECURITY: Do not allow ../ constructs
3143 # Block STDIN use (-) if CGIscriptor is servicing a HTTP request
3144 if($file_path eq '-')
3146 dieHandler(13, "STDIN request in On Line system\n") if $BLOCK_STDIN_HTTP_REQUEST
3147 && ($ENV{'SERVER_SOFTWARE'}
3148 || $ENV{'SERVER_NAME'}
3149 || $ENV{'GATEWAY_INTERFACE'}
3150 || $ENV{'SERVER_PROTOCOL'}
3151 || $ENV{'SERVER_PORT'}
3152 || $ENV{'REMOTE_ADDR'}
3153 || $ENV{'HTTP_USER_AGENT'});
3158 if($ClientLog)
3160 open(ClientLog, ">>$ClientLog");
3161 print ClientLog "$LocalTime | ",
3162 ($ENV{REMOTE_USER} || "-"), " ",
3163 ($ENV{REMOTE_IDENT} || "-"), " ",
3164 ($ENV{REMOTE_HOST} || "-"), " ",
3165 $ENV{REMOTE_ADDR}, " ",
3166 $ENV{PATH_INFO}, " ",
3167 $ENV{'CGI_BINARY_FILE'}, " ",
3168 ($default_values{CGI_Content_Length} || "-"),
3169 "\n";
3170 close(ClientLog);
3172 if($QueryLog)
3174 open(QueryLog, ">>$QueryLog");
3175 print QueryLog "$LocalTime\n",
3176 ($ENV{REMOTE_USER} || "-"), " ",
3177 ($ENV{REMOTE_IDENT} || "-"), " ",
3178 ($ENV{REMOTE_HOST} || "-"), " ",
3179 $ENV{REMOTE_ADDR}, ": ",
3180 $ENV{PATH_INFO}, " ",
3181 $ENV{'CGI_BINARY_FILE'}, "\n";
3183 # Write Query to Log file
3184 print QueryLog $default_values{CGI_Decoded_QS}, "\n\n";
3185 close(QueryLog);
3188 # Return the file path
3189 return $file_path;
3192 # End of Initialize output
3195 ############################################################################
3197 # Handle login access
3199 # Access is based on a valid session ticket.
3200 # Session tickets should be dependend on user name
3201 # and IP address. The patterns of URLs for which a
3202 # session ticket is needed and the login URL are stored in
3203 # %TicketRequiredPatterns as:
3204 # 'RegEx pattern' -> 'SessionPath\tPasswordPath\tLogin URL\tExpiration'
3207 sub Log_In_Access # () -> 0 = Access Allowed, Login page if access is not allowed
3209 # No patterns, no login
3210 goto Return unless %TicketRequiredPatterns;
3212 # Get and initialize values (watch out for stuff processed by BinaryMap files)
3213 my ($SessionPath, $PasswordsPath, $Login, $valid_duration) = ("", "", "", 0);
3214 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3215 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
3216 goto Return if $REMOTE_ADDR =~ /[^0-9\.]/;
3217 # Extract TICKETs, starting with returned cookies
3218 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3219 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3220 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3221 Get_All_Cookies();
3222 if(length(keys(%CGI_Cookies)) > 0)
3224 ${"CGIexecute::LOGINTICKET"} = $CGI_Cookies{'CGIscriptorLOGIN'}
3225 if $CGI_Cookies{'CGIscriptorLOGIN'} && $CGI_Cookies{'CGIscriptorLOGIN'} ne "-";
3226 $CGI_Cookies{'CGIscriptorLOGIN'} = "-";
3227 ${"CGIexecute::CHALLENGETICKET"} = $CGI_Cookies{'CGIscriptorCHALLENGE'}
3228 if $CGI_Cookies{'CGIscriptorCHALLENGE'} && $CGI_Cookies{'CGIscriptorCHALLENGE'} ne "-";
3229 $CGI_Cookies{'CGIscriptorCHALLENGE'} = "-";
3230 ${"CGIexecute::SESSIONTICKET"} = $CGI_Cookies{'CGIscriptorSESSION'}
3231 if $CGI_Cookies{'CGIscriptorSESSION'} && $CGI_Cookies{'CGIscriptorSESSION'} ne "-";
3232 $CGI_Cookies{'CGIscriptorSESSION'} = "-";
3234 # Get and check the tickets. Tickets are restricted to word-characters (alphanumeric+_+.)
3235 my $LOGINTICKET = ${"CGIexecute::LOGINTICKET"};
3236 goto Return if ($LOGINTICKET && $LOGINTICKET =~ /[^\w\.]/isg);
3237 my $SESSIONTICKET = ${"CGIexecute::SESSIONTICKET"};
3238 goto Return if ($SESSIONTICKET && $SESSIONTICKET =~ /[^\w\.]/isg);
3239 my $CHALLENGETICKET = ${"CGIexecute::CHALLENGETICKET"};
3240 goto Return if ($CHALLENGETICKET && $CHALLENGETICKET =~ /[^\w\.]/isg);
3241 # Look for a LOGOUT message
3242 my $LOGOUT = $ENV{QUERY_STRING} =~ /(^|\&)LOGOUT([\=\&]|$)/;
3243 # Username and password
3244 CGIexecute::defineCGIvariable('CGIUSERNAME', "");
3245 my $username = lc(${"CGIexecute::CGIUSERNAME"});
3246 goto Return if $username =~ m!^[^\w]!isg || $username =~ m![^\w \-]!isg;
3247 my $userfile = lc($username);
3248 $userfile =~ s/[^\w]/_/isg;
3249 CGIexecute::defineCGIvariable('PASSWORD', "");
3250 my $password = ${"CGIexecute::PASSWORD"};
3251 CGIexecute::defineCGIvariable('NEWUSERNAME', "");
3252 my $newuser = lc(${"CGIexecute::NEWUSERNAME"});
3253 CGIexecute::defineCGIvariable('NEWPASSWORD', "");
3254 my $newpassword = ${"CGIexecute::NEWPASSWORD"};
3256 foreach my $pattern (keys(%TicketRequiredPatterns))
3258 # Check BOTH the real PATH_INFO and the CGI_BINARY_FILE variable
3259 if($ENV{'PATH_INFO'} =~ m#$pattern# || $ENV{'CGI_BINARY_FILE'} =~ m#$pattern#)
3261 # Fall through a sieve of requirements
3262 ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3264 # Is there a change password request?
3265 if($newuser && $LOGINTICKET && $username)
3267 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3268 goto Login unless (-s "$PasswordsPath/$userfile");
3269 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3270 goto Login unless $ticket_valid;
3271 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".", 1);
3272 goto Login unless $ticket_valid;
3274 my ($sessiontype, $currentticket) = ("", "");
3275 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
3276 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
3277 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
3279 if($sessiontype)
3281 goto Login unless (-s "$SessionPath/$currentticket");
3282 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3283 goto Login unless $ticket_valid;
3285 # Authorize
3286 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath, $REMOTE_ADDR);
3287 goto Login unless $TMPTICKET;
3289 # Create a new user account
3290 CGIexecute::defineCGIvariable('NEWSESSION', "");
3291 my $newsession = ${"CGIexecute::NEWSESSION"};
3292 my $newaccount = create_newuser("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket",
3293 "$PasswordsPath/$userfile", $password, $newuser, $newpassword, $newsession);
3294 CGIexecute::defineCGIvariable('NEWACCOUNTTEXT', $newaccount);
3295 ${CGIexecute::NEWACCOUNTTEXT} = $newaccount;
3296 # NEWACCOUNTTEXT is NOT to be set by the query
3297 CGIexecute::ProtectCGIvariable('NEWACCOUNTTEXT');
3300 # Ready
3301 goto Return;
3303 # Is there a change password request?
3304 elsif($newpassword && $LOGINTICKET && $username)
3306 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3307 goto Login unless (-s "$PasswordsPath/$userfile");
3308 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3309 goto Login unless $ticket_valid;
3310 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".", 1);
3311 goto Login unless $ticket_valid;
3313 my ($sessiontype, $currentticket) = ("", "");
3314 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
3315 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
3316 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
3318 if($sessiontype)
3320 goto Login unless (-s "$SessionPath/$currentticket");
3321 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3322 goto Login unless $ticket_valid;
3324 # Authorize
3325 change_password("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket", "$PasswordsPath/$userfile", $password, $newpassword);
3326 # After a change of password, you have to login again for a CHALLENGE
3327 if($CHALLENGETICKET){$CHALLENGETICKET = "";};
3328 # Ready
3329 goto Return;
3331 # Is there a login ticket of this name?
3332 elsif($LOGINTICKET)
3334 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3335 goto Login unless (-s "$PasswordsPath/$userfile");
3336 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3337 goto Login unless $ticket_valid;
3338 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".");
3339 goto Login unless $ticket_valid;
3341 # Authorize
3342 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath, $REMOTE_ADDR);
3343 if($TMPTICKET)
3345 my $authorization = read_ticket("$PasswordsPath/$userfile");
3346 goto Login unless $authorization;
3347 # Session type is read from the userfile
3348 if($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "CHALLENGE")
3350 # Create New Random CHALLENGETICKET
3351 $CHALLENGETICKET = $TMPTICKET;
3352 create_session_file("$SessionPath/$CHALLENGETICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3354 elsif($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "IPADDRESS")
3356 create_session_file("$SessionPath/$REMOTE_ADDR", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3358 else
3360 # Extra hash to protect CHALLENGETICKET use
3361 $SESSIONTICKET = hash_string($TMPTICKET);
3362 $SESSIONTICKET = hash_string($SESSIONTICKET.$TMPTICKET);
3363 create_session_file("$SessionPath/$SESSIONTICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3364 $SETCOOKIELIST{"CGIscriptorSESSION"} = "-";
3365 $TMPTICKET = $SESSIONTICKET;
3368 # Login ticket file has been used, remove it
3369 unlink($loginfile);
3371 # Is there a session ticket of this name?
3372 # CHALLENGE
3373 if($CHALLENGETICKET)
3375 # Do not log into a CHALLENGE account if the SESSION cookie is present
3376 # Uncomment when $SESSIONTICKET does not receive an extra hash
3377 #goto Login if $SESSIONTICKET =~ /\S/;
3378 goto Login unless (-s "$SessionPath/$CHALLENGETICKET");
3379 my $ticket_valid = check_ticket_validity("CHALLENGE", "$SessionPath/$CHALLENGETICKET", $REMOTE_ADDR, $PATH_INFO);
3380 goto Login unless $ticket_valid;
3382 my $oldchallenge = read_ticket("$SessionPath/$CHALLENGETICKET");
3383 goto Login unless $oldchallenge;
3384 # Check whether the login still exists
3385 my $userfile = lc($oldchallenge->{"Username"}->[0]);
3386 $userfile =~ s/[^\w]/_/isg;
3387 goto Login unless (-s "$PasswordsPath/$userfile");
3389 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3390 goto Login unless $ticket_valid;
3392 # This is a LOGOUT request, clean up (Access has already been validated)
3393 if($LOGOUT)
3395 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
3396 $CHALLENGETICKET = "";
3397 goto Login;
3400 my $NEWCHALLENGETICKET = "";
3401 $NEWCHALLENGETICKET = copy_challenge_file("$SessionPath/$CHALLENGETICKET", "$PasswordsPath/$userfile", $SessionPath);
3402 # Sessionticket is available to scripts, do NOT set the cookie
3403 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3404 goto Return;
3406 # IPADDRESS
3407 elsif(-s "$SessionPath/$REMOTE_ADDR")
3409 my $ticket_valid = check_ticket_validity("IPADDRESS", "$SessionPath/$REMOTE_ADDR", $REMOTE_ADDR, $PATH_INFO);
3410 goto Login unless $ticket_valid;
3411 # Check whether the login still exists
3412 my $currentsessionticket = read_ticket("$SessionPath/$REMOTE_ADDR");
3413 my $userfile = lc($currentsessionticket->{"Username"}->[0]);
3414 $userfile =~ s/[^\w]/_/isg;
3415 goto Login unless (-s "$PasswordsPath/$userfile");
3417 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3418 goto Login unless $ticket_valid;
3420 # This is a LOGOUT request, clean up (Access has already been validated)
3421 if($LOGOUT)
3423 unlink "$SessionPath/$REMOTE_ADDR" if (-s "$SessionPath/$REMOTE_ADDR");
3424 goto Login;
3427 goto Return;
3429 # SESSION
3430 elsif($SESSIONTICKET)
3432 goto Login unless (-s "$SessionPath/$SESSIONTICKET");
3433 my $ticket_valid = check_ticket_validity("SESSION", "$SessionPath/$SESSIONTICKET", $REMOTE_ADDR, $PATH_INFO);
3434 goto Login unless $ticket_valid;
3436 # Check whether the login still exists
3437 my $currentsessionticket = read_ticket("$SessionPath/$SESSIONTICKET");
3438 my $userfile = lc($currentsessionticket->{"Username"}->[0]);
3439 $userfile =~ s/[^\w]/_/isg;
3440 goto Login unless (-s "$PasswordsPath/$userfile");
3442 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3443 goto Login unless $ticket_valid;
3445 # This is a LOGOUT request, clean up (Access has already been validated)
3446 if($LOGOUT)
3448 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
3449 $SESSIONTICKET = "";
3450 goto Login;
3453 # Sessionticket is available to scripts
3454 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3455 goto Return;
3458 goto Login;
3459 goto Return;
3462 Return:
3463 # The Masterkey should NOT be accessible by the parsed files
3464 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3465 return 0;
3467 Login:
3468 # To deter DOS attacks, do not remove valid session tickets unless the
3469 # "owner" has accredited herself
3470 my $tickets_removed = remove_expired_tickets($SessionPath);
3471 create_login_file($PasswordsPath, $SessionPath, $REMOTE_ADDR);
3472 # Note, cookies are set only ONCE
3473 $SETCOOKIELIST{"CGIscriptorLOGIN"} = "-";
3474 # The Masterkey should NOT be accessible by the parsed files
3475 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3476 return "$YOUR_HTML_FILES/$Login";
3479 sub authorize_login # ($loginfile, $authorizationfile, $password, $SessionPath, $IPaddress) => SESSIONTICKET First two arguments are file paths
3481 my $loginfile = shift || "";
3482 my $authorizationfile = shift || "";
3483 my $password = shift || "";
3484 my $SessionPath = shift || "";
3485 my $RemoteIPaddress = shift || "";
3487 # Get Login session ticket
3488 my $loginticket = read_ticket($loginfile);
3489 return 0 unless $loginticket;
3490 # Get User credentials for authorization
3491 my $authorization = read_ticket($authorizationfile);
3492 return 0 unless $authorization;
3494 # Get Randomsalt
3495 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3496 return "" unless $Randomsalt;
3498 my $storedpassword = $authorization->{'Password'}->[0];
3499 return "" unless $storedpassword;
3500 my $Hashedpassword = hash_string($storedpassword.$RemoteIPaddress.$Randomsalt);
3501 return "" unless $password eq $Hashedpassword;
3503 # Extract Session Ticket
3504 my $loginsession = $loginticket->{'Session'}->[0];
3505 my $sessionticket = hash_string($storedpassword.$loginsession);
3506 chomp($sessionticket);
3507 $sessionticket = "" if -x "$SessionPath/$sessionticket";
3509 # No lingering password variables
3510 $Hashedpassword = $Randomsalt;
3511 $password = $Randomsalt;
3512 $authorization->{'Password'}->[0] = $Randomsalt;
3514 return $sessionticket;
3517 sub change_password # ($loginfile, $sessionfile, $authorizationfile, $password, $newpassword) First three arguments are file paths
3519 my $loginfile = shift || "";
3520 my $sessionfile = shift || "";
3521 my $authorizationfile = shift || "";
3522 my $password = shift || "";
3523 my $newpassword = shift || "";
3524 # Get Login session ticket
3525 my $loginticket = read_ticket($loginfile);
3526 return "" unless $loginticket;
3527 # Login ticket file has been used, remove it
3528 unlink($loginfile);
3529 # Get Randomsalt
3530 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3531 return "" unless $Randomsalt;
3532 my $LoginID = $loginticket->{'Session'}->[0];
3533 return "" unless $LoginID;
3535 # Get session ticket
3536 my $sessionticket = read_ticket($sessionfile);
3537 return "" unless $sessionticket;
3539 # Get User credentials for authorization
3540 my $authorization = read_ticket($authorizationfile);
3541 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3543 my $storedpassword = $authorization->{'Password'}->[0];
3544 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3545 return "" unless $password eq $Hashedpassword;
3546 my $secretkey = hash_string($storedpassword.$LoginID.$Randomsalt);
3548 # Decrypt the $newpassword
3549 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3550 return "" unless $decryptedPassword;
3551 # Authorization succeeded, change password
3552 $authorization->{'Password'}->[0] = $decryptedPassword;
3553 # Write out
3554 write_ticket($authorizationfile, $authorization, $authorization->{'Salt'}->[0]);
3556 # No lingering password variables
3557 $decryptedPassword = $Randomsalt;
3558 $secretkey = $Randomsalt;
3559 $storedpassword = $Randomsalt;
3560 $Hashedpassword = $Randomsalt;
3561 $authorization->{'Password'}->[0] = $Randomsalt;
3563 return $newpassword;
3565 # First three arguments are file paths
3566 sub create_newuser # ($loginfile, $sessionfile, $authorizationfile, $password, $newuser, $newpassword, $newsession) -> account text
3568 my $loginfile = shift || "";
3569 my $sessionfile = shift || "";
3570 my $authorizationfile = shift || "";
3571 my $password = shift || "";
3572 my $newuser = shift || "";
3573 my $newpassword = shift || "";
3574 my $newsession = shift || "";
3576 # Get Login session ticket
3577 my $loginticket = read_ticket($loginfile);
3578 return "" unless $loginticket;
3579 # Login ticket file has been used, remove it
3580 unlink($loginfile);
3581 # Get Randomsalt
3582 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3583 return "" unless $Randomsalt;
3584 my $LoginID = $loginticket->{'Session'}->[0];
3585 return "" unless $LoginID;
3587 # Get session ticket
3588 my $sessionticket = read_ticket($sessionfile);
3589 return "" unless $sessionticket;
3590 # Get User credentials for authorization
3591 my $authorization = read_ticket($authorizationfile);
3592 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3593 my $sessionkey = $sessionticket->{'Key'}->[0];
3594 my $serversalt = $authorization->{'Salt'}->[0];
3595 return "" unless $serversalt;
3597 my $storedpassword = $authorization->{'Password'}->[0];
3598 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3599 return "" unless $password eq $Hashedpassword;
3600 my $secretkey = hash_string($storedpassword.$LoginID.$Randomsalt);
3602 # Decrypt the $newpassword
3603 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3604 return "" unless $decryptedPassword;
3606 # Authorization succeeded, create new account
3607 my $newaccount = {};
3608 $newaccount->{'Type'} = ['PASSWORD'];
3609 $newaccount->{'Username'} = [$newuser];
3610 $newaccount->{'Password'} = [$decryptedPassword];
3611 $newaccount->{'Salt'} = [$serversalt];
3612 $newaccount->{'Session'} = ['SESSION'];
3613 if($newsession eq 'IPADDRESS'){$newaccount->{'Session'} = ['IPADDRESS'];};
3614 if($newsession eq 'CHALLENGE'){$newaccount->{'Session'} = ['CHALLENGE'];};
3615 my $timesec = time();
3616 my $gmt_date = gmtime();
3617 $newaccount->{'Time'} = [$timesec];
3618 $newaccount->{'Date'} = [$gmt_date];
3620 # AllowedPaths
3621 my $NewAllowedPaths = "";
3622 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3623 my $currentRoot = "";
3624 $currentRoot = $1 if $PATH_INFO =~ m!^([\w\-\. /]+)!isg;
3625 $currentRoot =~ s![^/]+$!!isg;
3626 if($currentRoot)
3628 $currentRoot .= '/' unless $currentRoot =~ m!/$!;
3629 my $newpath = "^".${currentRoot}.'[\w\-]+\.html?';
3630 $NewAllowedPaths .= 'AllowedPaths: ^'.${currentRoot}.'[\w\-]+\.html?'."\n";
3631 $newaccount->{'AllowedPaths'} = [$newpath];
3633 else
3635 # Tricky PATH_INFO, deny all
3636 $NewAllowedPaths .= "DeniedPaths: ^/\n";
3637 $newaccount->{'DeniedPaths'} = ["DeniedPaths: ^/\n"];
3640 # Construct home directory path
3641 my $FullHomeDirectoryPath = "";
3642 my $currentHome = lc($newuser);
3643 if($currentHome && $currentHome !~ /^\s*\#/)
3645 $currentHome =~ s![^\w]!_!isg;
3646 my $newpath = "^${currentRoot}$currentHome/";
3647 push(@{$newaccount->{'AllowedPaths'}}, $newpath);
3648 # Create home directory
3649 $FullHomeDirectoryPath = $ENV{'HOME'}.${currentRoot}.$currentHome;
3652 # Allowed Paths
3653 CGIexecute::defineCGIvariable('ALLOWEDPATHS', "");
3654 my $allowedpaths = ${"CGIexecute::ALLOWEDPATHS"};
3655 if($allowedpaths && $allowedpaths !~ /^\s*\#/)
3657 $allowedpaths =~ s!\#.*$!!isg;
3658 $allowedpaths =~ s![^\^\w\./\;\+\*\?\[\]\$]!!isg;
3659 my @pathlist = split(/\;/, $allowedpaths);
3660 foreach my $entry (@pathlist)
3662 push(@{$newaccount->{'AllowedPaths'}}, "^".${currentRoot}.$entry);
3666 # Allowed IP addresses
3667 CGIexecute::defineCGIvariable('IPADDRESS', "");
3668 my $ipaddress = ${"CGIexecute::IPADDRESS"};
3669 if($ipaddress && $ipaddress !~ /^\s*\#/)
3671 $ipaddress =~ s!\#.*$!!isg;
3672 $ipaddress =~ s![^\d\.\;]!!isg;
3673 my @iplist = split(/\;/, $ipaddress);
3674 foreach my $entry (@iplist)
3676 next unless $entry =~ /\d/;
3677 next if $entry =~ /^\s*\#/;
3678 $entry =~ s/\./\\./g;
3679 push(@{$newaccount->{'IPaddress'}}, $entry);
3683 # Capabilities
3684 CGIexecute::defineCGIvariable('NEWCAPABILITIES', "");
3685 my $capabilities = ${"CGIexecute::NEWCAPABILITIES"};
3686 if($capabilities && $capabilities !~ /^\W*\#/)
3688 $capabilities =~ s!\#.*$!!isg;
3689 $capabilities =~ s![^\w\s]!!isg;
3690 my @caplist = split(/\s/, $capabilities);
3691 foreach my $entry (@caplist)
3693 next unless $entry =~ /\w/;
3694 next if $entry =~ /^\s*\#/;
3695 push(@{$newaccount->{'Capabilities'}}, $entry);
3699 # Sign the new ticket
3700 my $Signature = SignTicketWithMasterkey($newaccount, $newaccount->{'Salt'}->[0]);
3702 # Write
3703 my $datetime = gmtime();
3704 my $newuserfile = "";
3705 if(grep(/^CreateUser$/, @{$authorization->{'Capabilities'}}))
3707 my $newuserfilename = lc($newuser);
3708 $newuserfilename =~ s/[^\w]/_/isg;
3709 $newuserfile = $authorizationfile;
3710 $newuserfile =~ s![^/]*$!!isg;
3711 $newuserfile .= $newuserfilename;
3712 if(-s $newuserfile)
3714 $newuserfile = "";
3716 elsif($FullHomeDirectoryPath && !(-d $FullHomeDirectoryPath || -s $FullHomeDirectoryPath))
3718 if(-d "$ENV{'HOME'}${currentRoot}.SkeletonDir")
3720 `cp -r '$ENV{'HOME'}${currentRoot}.SkeletonDir' '$FullHomeDirectoryPath'`;
3722 elsif(-d "$ENV{'HOME'}${currentRoot}SkeletonDir")
3724 `cp -r '$ENV{'HOME'}${currentRoot}SkeletonDir' '$FullHomeDirectoryPath'`;
3726 elsif(-s "$ENV{'HOME'}${currentRoot}UserIndex.html")
3728 mkdir $FullHomeDirectoryPath;
3729 `cp '$ENV{'HOME'}${currentRoot}UserIndex.html' '$FullHomeDirectoryPath/index.html'`;
3731 elsif(-s "$ENV{'HOME'}${currentRoot}index.html")
3733 mkdir $FullHomeDirectoryPath;
3734 `cp '$ENV{'HOME'}${currentRoot}index.html' '$FullHomeDirectoryPath/index.html'`;
3740 my $newaccounttext = write_ticket($newuserfile, $newaccount, $serversalt);
3742 # Re-encrypt the new password for transmission
3743 if($newaccounttext =~ /^(Password\:\s+)(\S+)\s*$/)
3745 my $passwordvalue = $1;
3746 my $reencryptedpassword = XOR_hex_strings($secretkey, $passwordvalue);
3747 my $encryptedpasswordline = "<span id='newaccount'>$reencryptedpassword</span>";
3748 $newaccounttext =~ s/^(Password\:\s+)(\S+)\s*$/\1$encryptedpasswordline/gim;
3750 # No lingering passwords
3751 $passwordvalue = $serversalt;
3753 return $newaccounttext;
3756 # Copy a Challenge ticket file to a new name which is the hash of the new $CHALLENGETICKET and the password
3757 sub copy_challenge_file #($oldchallengefile, $authorizationfile, $sessionpath) -> $CHALLENGETICKET
3759 my $oldchallengefile = shift || "";
3760 my $authorizationfile = shift || "";
3761 my $sessionpath = shift || "";
3762 $sessionpath =~ s!/+$!!g;
3764 # Get Login session ticket
3765 my $oldchallenge = read_ticket($oldchallengefile);
3766 return "" unless $oldchallenge;
3768 # Get Authorization (user) session file
3769 my $authorization = read_ticket($authorizationfile);
3770 return "" unless $authorization;
3771 my $storedpassword = $authorization->{'Password'}->[0];
3772 return "" unless $storedpassword;
3773 my $challengekey = $oldchallenge->{'Key'}->[0];
3774 return "" unless $challengekey;
3776 # Create Random Hash Salt
3777 my $NEWCHALLENGETICKET = get_random_hex();;
3778 my $newchallengefile = hash_string($challengekey.$NEWCHALLENGETICKET);
3779 return "" unless $newchallengefile;
3781 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3782 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3783 ${"CGIexecute::CHALLENGETICKET"} = $NEWCHALLENGETICKET;
3785 # Write Session Ticket
3786 open(OLDCHALLENGE, "<$oldchallengefile") || die "<$oldchallengefile: $!\n";
3787 my @OldChallengeLines = <OLDCHALLENGE>;
3788 close(OLDCHALLENGE);
3789 # Old file should now be removed
3790 unlink($oldchallengefile);
3792 open(SESSION, ">$sessionpath/$newchallengefile") || die "$sessionpath/$newchallengefile: $!\n";
3793 foreach $line (@OldChallengeLines)
3795 print SESSION $line;
3797 close(SESSION);
3799 # No lingering passwords
3800 $storedpassword = $oldchallenge;
3802 return $NEWCHALLENGETICKET;
3805 sub create_login_file #($PasswordDir, $SessionDir, $IPaddress)
3807 my $PasswordDir = shift || "";
3808 my $SessionDir = shift || "";
3809 my $IPaddress = shift || "";
3811 # Create Login Ticket
3812 my $LOGINTICKET= get_random_hex ();
3814 # Create Random Hash Salt
3815 my $RANDOMSALT= get_random_hex();
3817 # Create SALT file if it does not exist
3818 # Remove this, including test account for life system
3819 unless(-d "$SessionDir")
3821 `mkdir -p "$SessionDir"`;
3823 unless(-d "$PasswordDir")
3825 `mkdir -p "$PasswordDir"`;
3827 # Create SERVERSALT and default test account
3828 my $SERVERSALT = "";
3829 unless(-s "$PasswordDir/SALT")
3831 $SERVERSALT= get_random_hex();
3832 open(SALTFILE, ">$PasswordDir/SALT") || die ">$PasswordDir/SALT: $!\n";
3833 print SALTFILE "$SERVERSALT\n";
3834 close(SALTFILE);
3836 # Update test account (should be removed in live system)
3837 my @alltestusers = ("test", "testip", "testchallenge", "admin");
3838 foreach my $testuser (@alltestusers)
3840 if(-s "$PasswordDir/$testuser")
3842 my $plainpassword = $testuser eq 'admin' ? "There is no password like more password" : "testing";
3844 my $storedpassword = hash_string(${plainpassword}.${testuser}.${SERVERSALT});
3845 # Encrypt the new password with the MasterKey
3846 my $authorization = read_ticket("$PasswordDir/$testuser") || return "";
3847 $authorization->{'Salt'} = [$SERVERSALT];
3848 $authorization->{'Type'} = ['INACTIVE PASSWORD'] if $testuser eq 'admin';
3849 set_password($authorization, $SERVERSALT, $plainpassword);
3850 write_ticket("$PasswordDir/$testuser", $authorization, $SERVERSALT);
3851 # No lingering passwords
3852 $storedpassword = $SERVERSALT;
3853 $plainpassword = $SERVERSALT;
3858 # Read in site Salt
3859 open(SALTFILE, "<$PasswordDir/SALT") || die "$PasswordDir/SALT: $!\n";
3860 $SERVERSALT=<SALTFILE>;
3861 close(SALTFILE);
3862 chomp($SERVERSALT);
3864 # Create login session ticket
3865 my $datetime = gmtime();
3866 my $timesec = time();
3867 my $loginticket = {};
3868 $loginticket->{Type} = ['LOGIN'];
3869 $loginticket->{IPaddress} = [$IPaddress];
3870 $loginticket->{Salt} = [$SERVERSALT];
3871 $loginticket->{Session} = [$LOGINTICKET];
3872 $loginticket->{Randomsalt} = [$RANDOMSALT];
3873 $loginticket->{Expires} = ['+600s'];
3874 $loginticket->{Date} = ["$datetime UTC"];
3875 $loginticket->{Time} = [$timesec];
3876 write_ticket("$SessionDir/$LOGINTICKET", $loginticket, $SERVERSALT);
3878 # Set global variables
3879 # $SERVERSALT
3880 $ENV{'SERVERSALT'} = $SERVERSALT;
3881 CGIexecute::defineCGIvariable('SERVERSALT', "");
3882 ${"CGIexecute::SERVERSALT"} = $SERVERSALT;
3884 # $SESSIONTICKET
3885 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3886 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3887 ${"CGIexecute::SESSIONTICKET"} = $SESSIONTICKET;
3889 # $RANDOMSALT
3890 $ENV{'RANDOMSALT'} = $RANDOMSALT;
3891 CGIexecute::defineCGIvariable('RANDOMSALT', "");
3892 ${"CGIexecute::RANDOMSALT"} = $RANDOMSALT;
3894 # $LOGINTICKET
3895 $ENV{'LOGINTICKET'} = $LOGINTICKET;
3896 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3897 ${"CGIexecute::LOGINTICKET"} = $LOGINTICKET;
3899 return $ENV{'LOGINTICKET'};
3902 sub create_session_file #($sessionfile, $loginfile, $authorizationfile, $path) -> Is $loginfile deleted? 0/1
3904 my $sessionfile = shift || "";
3905 my $loginfile = shift || "";
3906 my $authorizationfile = shift || "";
3907 my $path = shift || "";
3909 # Get Login session ticket
3910 my $loginticket = read_ticket($loginfile);
3911 return unlink($loginfile) unless $loginticket;
3913 # Get Authorization (user) session file
3914 my $authorization = read_ticket($authorizationfile);
3915 return unlink($loginfile) unless $authorization;
3917 # For a Session or a Challenge, we need a stored key
3918 my $sessionkey = "";
3919 my $secretkey = "";
3920 if($authorization->{'Session'} && $authorization->{'Session'}->[0] ne 'IPADDRESS')
3922 my $storedpassword = $authorization->{'Password'}->[0];
3923 my $loginticketid = $loginticket->{'Session'}->[0];
3924 my $randomsalt = $loginticket->{'Randomsalt'}->[0];
3925 $sessionkey = hash_string($storedpassword.$loginticketid);
3926 $secretkey = hash_string($storedpassword.$loginticketid.$randomsalt);
3927 # No lingering passwords
3928 $storedpassword = $loginticketid;
3930 # Get Session id
3931 my $sessionid = "";
3932 if($sessionfile =~ m!([^/]+)$!)
3934 $sessionid = $1;
3937 # Convert Authorization content to Session content
3938 my $sessionContent = {};
3939 my $SessionType = $authorization->{'Session'}->[0] ? $authorization->{'Session'}->[0] : "SESSION";
3940 $sessionContent->{Type} = [$SessionType];
3941 $sessionContent->{Username} = [lc($authorization->{'Username'}->[0])];
3942 $sessionContent->{Session} = [$sessionid];
3943 $sessionContent->{Time} = [time];
3944 # Limit communication to the login IP address, except for Tor like situations with VariableREMOTE_ADDR
3945 $sessionContent->{IPaddress} = ['.'];
3946 if($sessionContent->{Type}->[0] eq 'CHALLENGE' && grep(/^VariableREMOTE_ADDR$/, @{$authorization->{'Capabilities'}}))
3948 $sessionContent->{IPaddress} = $authorization->{'IPaddress'} if $authorization->{'IPaddress'};
3950 else
3952 $sessionContent->{IPaddress} = $loginticket->{'IPaddress'};
3954 $sessionContent->{Salt} = $authorization->{'Salt'};
3955 $sessionContent->{Randomsalt} = $loginticket->{'Randomsalt'};
3956 $sessionContent->{AllowedPaths} = $authorization->{'AllowedPaths'};
3957 $sessionContent->{DeniedPaths} = $authorization->{'DeniedPaths'};
3958 $sessionContent->{Expires} = $authorization->{'MaxLifetime'};
3959 $sessionContent->{Capabilities} = $authorization->{'Capabilities'};
3960 foreach my $pattern (keys(%TicketRequiredPatterns))
3962 if($path =~ m#$pattern#)
3964 my ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3965 push(@{$sessionContent->{Expires}}, $validtime);
3968 $sessionContent->{Key} = [$sessionkey] if $sessionkey;
3969 $sessionContent->{Secretkey} = [$secretkey] if $secretkey;
3970 $sessionContent->{Date} = [gmtime()." UTC"];
3972 # Write Session Ticket
3973 write_ticket($sessionfile, $sessionContent, $authorization->{'Salt'}->[0]);
3975 # Login file should now be removed
3976 return unlink($loginfile);
3979 sub check_ticket_validity # ($type, $ticketfile, $address, $path [, $unsigned])
3981 my $type = shift || "SESSION";
3982 my $ticketfile = shift || "";
3983 my $address = shift || "";
3984 my $path = shift || "";
3985 my $unsigned = shift || 0;
3987 # Is there a session ticket of this name?
3988 return 0 unless -s "$ticketfile";
3990 # There is a session ticket, is it linked to this IP address?
3991 my $ticket = read_ticket($ticketfile);
3992 unless($ticket)
3994 print STDERR "Ticket expired or empty: $ticketfile\n";
3995 return;
3998 # Is this the right type of ticket
3999 unless($ticket && $ticket->{'Type'}->[0] eq $type)
4001 print STDERR "Wrong ticket type: $ticket->{'Type'}->[0] eq $type\n";
4002 return;
4005 # Does the IP address match?
4006 my $IPmatches = @{$ticket->{"IPaddress"}} ? 0 : 1;
4007 for $IPpattern (@{$ticket->{"IPaddress"}})
4009 ++$IPmatches if $address =~ m#^$IPpattern#ig;
4011 if($address && ! $IPmatches)
4013 print STDERR "Wrong REMOTE ADDR for $ticket->{'Username'}->[0]: $ticket->{'IPaddress'}->[0] vs $address\n";
4014 return 0;
4017 # Is the path denied
4018 my $Pathmatches = 0;
4019 foreach $Pathpattern (@{$ticket->{"DeniedPaths"}})
4021 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
4023 return 0 if @{$ticket->{"DeniedPaths"}} && $Pathmatches;
4025 # Is the path allowed
4026 $Pathmatches = 0;
4027 foreach $Pathpattern (@{$ticket->{"AllowedPaths"}})
4029 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
4031 return 0 unless !@{$ticket->{"AllowedPaths"}} || $Pathmatches;
4033 # Check signature if not told to use an unsigned ticket (dangerous)
4034 my $Signature = TicketSignature($ticket, $ticket->{'Salt'}->[0]);
4035 if((! $unsigned) && $Signature && $Signature ne $ticket->{'Signature'}->[0])
4037 print STDERR "Invalid signature for $ticket->{'Type'}: $ticket->{'Username'}\n$ticketfile\n";
4038 return 0;
4041 # Make login values available (will also protect against resetting by query)
4042 $ENV{"LOGINUSERNAME"} = lc($ticket->{'Username'}->[0]);
4043 $ENV{"LOGINIPADDRESS"} = $address;
4044 $ENV{"LOGINPATH"} = $path;
4045 $ENV{"SESSIONTYPE"} = $type unless $type eq "PASSWORD";
4047 # Set Capabilities, if present
4048 if($ticket->{'Username'}->[0] && @{$ticket->{'Capabilities'}})
4050 $ENV{'CAPABILITIES'} = $ticket->{'Username'}->[0];
4051 CGIexecute::defineCGIvariableList('CAPABILITIES', "");
4052 @{"CGIexecute::CAPABILITIES"} = @{$ticket->{'Capabilities'}};
4053 # Capabilities should not be changed anymore by CGI query!
4055 # Capabilities are NOT to be set by the query
4056 CGIexecute::ProtectCGIvariable('CAPABILITIES');
4058 return 1;
4062 # This might be run in a fork()?
4063 sub remove_expired_tickets # ($path) -> number of tickets removed
4065 my $path = shift || "";
4066 return 0 unless $path;
4067 $path =~ s!/+$!!g;
4068 my $removed_tickets = 0;
4069 my @ticketlist = glob("$path/*");
4070 foreach my $ticketfile (@ticketlist)
4072 my $ticket = read_ticket($ticketfile);
4073 unless($ticket)
4075 unlink $ticketfile;
4076 ++$removed_tickets;
4079 return $removed_tickets;
4082 sub set_password # ($ticket, $salt, $plainpassword) -> $password
4084 my $ticket = shift || "";
4085 my $salt = shift || "";
4086 my $plainpassword = shift || "";
4088 my $user = lc($ticket->{'Username'}->[0]);
4089 return "" unless $user;
4090 $salt = $ticket->{'Salt'}->[0] unless $salt;
4092 my $storedpassword = hash_string(${plainpassword}.${user}.${salt});
4093 $ticket->{'Password'} = [$storedpassword];
4094 $ticket->{'Salt'} = [$salt];
4095 # No lingering passwords
4096 $storedpassword = $salt;
4097 $plainpassword = $salt;
4099 return $ticket->{'Password'}->[0];
4102 sub write_ticket # ($ticketfile, $ticket, $salt [, $masterkey]) -> &%ticket
4104 my $ticketfile = shift || "";
4105 my $ticket = shift || "";
4106 my $salt = shift || "";
4107 my $masterkey = shift || $ENV{'CGIMasterKey'};
4109 # Encrypt password
4110 EncryptTicketWithMasterKey($ticket, $salt, $masterkey);
4112 # Sign the new ticket
4113 my $signature = SignTicketWithMasterkey($ticket, $salt, $masterkey);
4115 # Create ordered list with labels
4116 my @orderlist = ('Type', 'Username', 'Password', 'IPaddress', 'AllowedPaths', 'DeniedPaths',
4117 'Expires', 'Capabilities', 'Salt', 'Session', 'Randomsalt',
4118 'Date', 'Time', 'Signature', 'Key', 'Secretkey');
4119 my @labellist = keys(%{$ticket});
4120 foreach my $label (@orderlist)
4122 @labellist = grep(!/\b$label\b/, @labellist);
4125 # Create ticket in text
4126 my $TicketText = "";
4127 foreach my $label (@orderlist, @labellist)
4129 next unless exists($ticket->{$label}) && $ticket->{$label}->[0];
4130 foreach my $value (@{$ticket->{$label}})
4132 $TicketText .= "$label: $value\n";
4135 if($ticketfile)
4137 open(TICKET, ">$ticketfile") || die "$ticketfile: $!\n";
4138 print TICKET $TicketText;
4139 close(TICKET);
4142 return $TicketText;
4145 # Note, read_ticket will return 0 if the ticket has expired!
4146 sub read_ticket # ($ticketfile [, $salt, $masterkey]) -> &%ticket
4148 my $ticketfile = shift || "";
4149 my $serversalt = shift || "";
4150 my $masterkey = shift || $ENV{'CGIMasterKey'};
4152 my $ticket = {};
4153 if($ticketfile && -s $ticketfile)
4155 open(TICKETFILE, "<$ticketfile") || die "$ticketfile: $!\n";
4156 my @alllines = <TICKETFILE>;
4157 close(TICKETFILE);
4158 foreach my $currentline (@alllines)
4160 # Skip empty lines and comments
4161 next unless $currentline =~ /\S/;
4162 next if $currentline =~ /^\s*\#/;
4164 if($currentline =~ /^\s*(\S[^\:]+)\:\s+(.*)\s*$/)
4166 my $Label = $1;
4167 my $Value = $2;
4168 $ticket->{$Label} = () unless exists($ticket->{$Label});
4169 push(@{$ticket->{$Label}}, $Value);
4173 elsif(-z $ticketfile)
4175 return 0;
4177 if($masterkey && exists($ticket->{'Password'}) && $ticket->{'Password'}->[0])
4179 # Use the ServerSalt stored in the ticket, if present
4180 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4182 $serversalt = $ticket->{Salt}->[0];
4184 # Decrypt all passwords
4185 DecryptTicketWithMasterKey($ticket, $serversalt, $masterkey) ||
4186 die "Decryption failed: DecryptTicketWithMasterKey ($ticket, $serversalt)\n";
4189 # Check whether the ticket has expired
4190 if(exists($ticket->{Expires}))
4192 my $StartTime = 0;
4193 if(exists($ticket->{Time}) && $ticket->{Time}->[0] > 0)
4195 $StartTime = [(sort(@{$ticket->{Time}}))]->[0];
4197 else
4199 # Get SessionTicket file stats
4200 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)
4201 = stat($ticketfile);
4202 $StartTime = $ctime;
4204 foreach my $Value (@{$ticket->{'Expires'}})
4206 # Recalculate expire date from relative time
4207 if($Value =~ /^\+/)
4209 if($Value =~ /^\+(\d+)\s*d(ays)?\s*$/)
4211 $ExpireTime = 24*3600*$1;
4213 elsif($Value =~ /^\+(\d+)\s*m(inutes)?\s*$/)
4215 $ExpireTime = 60*$1;
4217 elsif($Value =~ /^\+(\d+)\s*h(ours)?\s*$/)
4219 $ExpireTime = 3600*$1;
4221 elsif($Value =~ /^\+(\d+)\s*s(econds)?\s*$/)
4223 $ExpireTime = $1;
4225 elsif($Value =~ /^\+(\d+)\s*$/)
4227 $ExpireTime = $1;
4230 my $absoluteTime = $Value =~ /^\+/ ? $StartTime + $ExpireTime : $Value;
4231 return 0 unless $absoluteTime > time;
4233 @{$ticket->{Expires}} = sort(@{$ticket->{Expires}});
4235 return $ticket;
4238 # Set up a valid ticket from a given text file
4239 # Use from command line. DO NOT USE ONLINE
4240 # Watch out for passwords that get stored in the history file
4242 # perl CGIscriptor.pl --managelogin [options] [files]
4243 # Options:
4244 # salt={file or saltvalue}
4245 # masterkey={file or plaintext}
4246 # newmasterkey={file or plaintext}
4247 # password={file or palintext}
4249 # Followed by one or more file names.
4250 # Options can be interspersed between filenames,
4251 # e.g., password='plaintext'
4252 # Note that passwords are only used once!
4254 sub setup_ticket_file # (@ARGV)
4256 # Stop when run on-line
4257 return if $ENV{'PATH_INFO'} || $ENV{'QUERY_STRING'};
4259 my %Settings = ();
4260 foreach my $input (@_)
4262 if($input =~ /^([\w]+)\=/)
4264 my $name = lc($1);
4265 my $value = $';
4266 chomp($value);
4268 if($value !~ m![^\w\.\~\/\:\-]! && $value !~ /^[\-\.]/ && -s "$value" && ! -d "$value")
4270 # Warn about reading a value from file
4271 print STDERR "Read '$name' from: '$value'\n";
4272 open(INPUTVALUE, "<$value") || die "$value: $!\n";
4273 $value = <INPUTVALUE>;
4274 chomp($value);
4277 $value =~ s/(^\'([^\']*)\'$)/\1/g;
4278 $value =~ s/(^\"([^\"]*)\"$)/\1/g;
4279 $Settings{$name} = $value;
4281 elsif($input !~ m![^\w\.\~\/\:\-]!i && $input !~ /^[\-\.]/i && -s $input)
4283 # We MUST have a salt
4284 $Settings{'salt'} = $ticket->{'Salt'}->[0] unless $Settings{'salt'};
4286 # Set the new masterkey to the old masterkey if there is no new masterkey
4287 $Settings{'newmasterkey'} = $Settings{'masterkey'} unless exists($Settings{'newmasterkey'});
4289 # Get the ticket
4290 my $ticket = read_ticket($input, $Settings{'salt'}, $Settings{'masterkey'});
4292 # Set a new password from plaintext
4293 $ticket->{'Salt'}->[0] = $Settings{'salt'} if $Settings{'salt'} && $Settings{'password'};
4294 set_password ($ticket, $Settings{'salt'}, $Settings{'password'}) if $Settings{'password'};
4295 # Write the ticket back to file
4296 write_ticket($input, $ticket, $Settings{'salt'}, $Settings{'newmasterkey'});
4298 # A password is only used once
4299 $Settings{'password'} = "";
4304 # Add a signature from $masterkey to a ticket in the label $signlabel
4305 sub SignTicketWithMasterkey # ($ticket, $serversalt [, $masterkey, $signlabel]) -> $Signature
4307 my $ticket = shift || return 0;
4308 my $serversalt = shift || "";
4309 my $masterkey = shift || $ENV{'CGIMasterKey'};
4310 my $signlabel = shift || 'Signature';
4312 my $Signature = TicketSignature($ticket, $serversalt, $masterkey);
4314 $ticket->{$signlabel} = [$Signature] if $Signature;
4316 return $Signature;
4319 # Determine ticket signature
4320 sub TicketSignature # ($ticket, $serversalt [, $masterkey]) -> $Signature
4322 my $ticket = shift || return 0;
4323 my $serversalt = shift || "";
4324 my $masterkey = shift || $ENV{'CGIMasterKey'};
4325 my $Signature = "";
4327 if($masterkey)
4329 # If the ServerSalt is not stored in the ticket, the SALT file has to be found
4330 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4332 $serversalt = $ticket->{Salt}->[0];
4334 # Sign
4335 if($serversalt)
4337 my $username = lc($ticket->{'Username'}->[0]);
4338 my $hash1 = hash_string(${masterkey}.${serversalt});
4339 # The order of $username.$hash1 should be different than in DecryptTicketWithMasterKey
4340 my $CryptKey = hash_string($username.${'hash1'});
4341 my $SignText = "Type: ".$ticket->{'Type'}->[0]."\n";
4342 my @tmp = sort(@{$ticket->{'Username'}});
4343 $SignText .= "Username: @tmp\n";
4344 @tmp = sort(@{$ticket->{'IPaddress'}});
4345 $SignText .= "IPaddress: @tmp\n";
4346 @tmp = sort(@{$ticket->{'AllowedPaths'}});
4347 $SignText .= "AllowedPaths: @tmp\n";
4348 @tmp = sort(@{$ticket->{'DeniedPaths'}});
4349 $SignText .= "DeniedPaths: @tmp\n";
4350 @tmp = sort(@{$ticket->{'Session'}});
4351 $SignText .= "Session: @tmp\n";
4352 @tmp = sort(@{$ticket->{'Time'}});
4353 $SignText .= "Time: @tmp\n";
4354 @tmp = sort(@{$ticket->{'Expires'}});
4355 $SignText .= "Expires: @tmp\n";
4356 @tmp = sort(@{$ticket->{'Capabilities'}});
4357 $SignText .= "Capabilities: @tmp\n";
4358 @tmp = sort(@{$ticket->{'MaxLifetime'}});
4359 $SignText .= "MaxLifetime: @tmp\n";
4360 $Signature = HMAC_hex($CryptKey, $SignText);
4363 return $Signature;
4366 # Decrypts a password list IN PLACE
4367 sub DecryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4369 my $ticket = shift || return 0;
4370 my $serversalt = shift || "";
4371 my $masterkey = shift || $ENV{'CGIMasterKey'};
4373 if($masterkey && exists($ticket->{Password}) && $ticket->{Password}->[0])
4375 # If the ServerSalt is not given, read it from the the ticket
4376 if(! $serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4378 $serversalt = $ticket->{Salt}->[0];
4380 # Decrypt password(s)
4381 if($serversalt)
4383 my $hash1 = hash_string(${masterkey}.${serversalt});
4384 my $username = lc($ticket->{'Username'}->[0]);
4385 # The order of $hash1.$username should be different than in TicketSignature
4386 my $CryptKey = hash_string(${'hash1'}.$username);
4387 foreach my $password (@{$ticket->{Password}})
4389 $password = XOR_hex_strings($CryptKey,$password);
4393 return $ticket->{'Password'};
4395 sub EncryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4397 DecryptTicketWithMasterKey(@_);
4400 # Implement HMAC signature hash.
4401 # Blocksize is length in HEX characters, NOT bytes
4402 sub HMAC_hex # ($key, $message [, $blocksizehex]) -> $hex
4404 my $key = shift || "";
4405 my $message = shift || "";
4406 my $blocksizehex = shift || length($key);
4407 $key = hash_string($key) if length($key) > $blocksizehex;
4409 my $innerkey = XOR_hex_byte ($key, "36");
4410 my $outerkey = XOR_hex_byte ($key, "5c");
4411 my $innerhash = hash_string($innerkey.$message);
4412 my $outerhash = hash_string($outerkey.$innerhash);
4414 return $outerhash;
4417 # XOR input with equally long string of repeated 2 hex character (byte)
4418 # string. Input must have even number of hex characters
4419 sub XOR_hex_byte # ($hex1, $hexbyte) -> $hex
4421 my $hex1 = shift || "";
4422 my $hexbyte = shift || "";
4423 my $bytelength = length($hexbyte);
4424 my $hex2 = $hex1;
4425 $hex2 =~ s/.{$bytelength}/$hexbyte/ig;
4426 return XOR_hex_strings($hex1, $hex2);
4429 sub XOR_hex_strings # ($hex1, $hex2) -> $hex
4431 my $hex1 = shift || "";
4432 my $hex2 = shift || "";
4433 my @hex1list = split('', $hex1);
4434 my @hex2list = split('', $hex2);
4435 my @hexresultlist = ();
4436 for(my $i; $i < scalar(@hex1list); ++$i)
4438 my $d1 = hex($hex1list[$i]);
4439 my $d2 = hex($hex2list[$i]);
4440 my $dresult = ($d1 ^ $d2);
4441 $hexresultlist[$i] = sprintf("%x", $dresult);
4443 $hexresult = join('', @hexresultlist);
4444 return $hexresult;
4447 # End of Handle login access
4450 ############################################################################
4452 # Handle foreign interpreters (i.e., scripting languages)
4454 # Insert perl code to execute scripts in foreign scripting languages.
4455 # Actually, the scripts inside the <SCRIPT></SCRIPT> blocks are piped
4456 # into an interpreter.
4457 # The code presented here is fairly confusing because it
4458 # actually writes perl code code to the output.
4460 # A table with the file handles
4461 %SCRIPTINGINPUT = ();
4463 # A function to clean up Client delivered CGI parameter values
4464 # (i.e., quote all odd characters)
4465 %SHRUBcharacterTR =
4467 "\'" => '&#39;',
4468 "\`" => '&#96;',
4469 "\"" => '&quot;',
4470 '&' => '&amper;',
4471 "\\" => '&#92;'
4474 sub shrubCGIparameter # ($String) -> Cleaned string
4476 my $String = shift || "";
4478 # Change all quotes [`'"] into HTML character entities
4479 my ($Char, $Transcript) = ('&', $SHRUBcharacterTR{'&'});
4481 # Protect &
4482 $String =~ s/\Q$Char\E/$Transcript/isg if $Transcript;
4484 while( ($Char, $Transcript) = each %SHRUBcharacterTR)
4486 next if $Char eq '&';
4487 $String =~ s/\Q$Char\E/$Transcript/isg;
4490 # Replace newlines
4491 $String =~ s/[\n]/\\n/g;
4492 # Replace control characters with their backslashed octal ordinal numbers
4493 $String =~ s/([^\S \t])/(sprintf("\\0%o", ord($1)))/eisg; #
4494 $String =~ s/([\x00-\x08\x0A-\x1F])/(sprintf("\\0%o", ord($1)))/eisg; #
4496 return $String;
4500 # The initial open statements: Open a pipe to the foreign script interpreter
4501 sub OpenForeignScript # ($ContentType) -> $DirectivePrefix
4503 my $ContentType = lc(shift) || return "";
4504 my $NewDirective = "";
4506 return $NewDirective if($SCRIPTINGINPUT{$ContentType});
4508 # Construct a unique file handle name
4509 $SCRIPTINGFILEHANDLE = uc($ContentType);
4510 $SCRIPTINGFILEHANDLE =~ s/\W/\_/isg;
4511 $SCRIPTINGINPUT{$ContentType} = $SCRIPTINGFILEHANDLE
4512 unless $SCRIPTINGINPUT{$ContentType};
4514 # Create the relevant script: Open the pipe to the interpreter
4515 $NewDirective .= <<"BLOCKCGISCRIPTOROPEN";
4516 # Open interpreter for '$ContentType'
4517 # Open pipe to interpreter (if it isn't open already)
4518 open($SCRIPTINGINPUT{$ContentType}, "|$ScriptingLanguages{$ContentType}") || main::dieHandler(14, "$ContentType: \$!\\n");
4519 BLOCKCGISCRIPTOROPEN
4521 # Insert Initialization code and CGI variables
4522 $NewDirective .= InitializeForeignScript($ContentType);
4524 # Ready
4525 return $NewDirective;
4529 # The final closing code to stop the interpreter
4530 sub CloseForeignScript # ($ContentType) -> $DirectivePrefix
4532 my $ContentType = lc(shift) || return "";
4533 my $NewDirective = "";
4535 # Do nothing unless the pipe realy IS open
4536 return "" unless $SCRIPTINGINPUT{$ContentType};
4538 # Initial comment
4539 $NewDirective .= "\# Close interpreter for '$ContentType'\n";
4542 # Write the Postfix code
4543 $NewDirective .= CleanupForeignScript($ContentType);
4545 # Create the relevant script: Close the pipe to the interpreter
4546 $NewDirective .= <<"BLOCKCGISCRIPTORCLOSE";
4547 close($SCRIPTINGINPUT{$ContentType}) || main::dieHandler(15, \"$ContentType: \$!\\n\");
4548 select(STDOUT); \$|=1;
4550 BLOCKCGISCRIPTORCLOSE
4552 # Remove the file handler of the foreign script
4553 delete($SCRIPTINGINPUT{$ContentType});
4555 return $NewDirective;
4559 # The initialization code for the foreign script interpreter
4560 sub InitializeForeignScript # ($ContentType) -> $DirectivePrefix
4562 my $ContentType = lc(shift) || return "";
4563 my $NewDirective = "";
4565 # Add initialization code
4566 if($ScriptingInitialization{$ContentType})
4568 $NewDirective .= <<"BLOCKCGISCRIPTORINIT";
4569 # Initialization Code for '$ContentType'
4570 # Select relevant output filehandle
4571 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4573 # The Initialization code (if any)
4574 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}INITIALIZATIONCODE';
4575 $ScriptingInitialization{$ContentType}
4576 ${ContentType}INITIALIZATIONCODE
4578 BLOCKCGISCRIPTORINIT
4581 # Add all CGI variables defined
4582 if(exists($ScriptingCGIvariables{$ContentType}))
4584 # Start writing variable definitions to the Interpreter
4585 if($ScriptingCGIvariables{$ContentType})
4587 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEF";
4588 # CGI variables (from the %default_values table)
4589 print $SCRIPTINGINPUT{$ContentType} << '${ContentType}CGIVARIABLES';
4590 BLOCKCGISCRIPTORVARDEF
4593 my ($N, $V);
4594 foreach $N (keys(%default_values))
4596 # Determine whether the parameter has been defined
4597 # (the eval is a workaround to get at the variable value)
4598 next unless eval("defined(\$CGIexecute::$N)");
4600 # Get the value from the EXECUTION environment
4601 $V = eval("\$CGIexecute::$N");
4602 # protect control characters (i.e., convert them to \0.. form)
4603 $V = shrubCGIparameter($V);
4605 # Protect interpolated variables
4606 eval("\$CGIexecute::$N = '$V';") unless $ScriptingCGIvariables{$ContentType};
4608 # Print the actual declaration for this scripting language
4609 if($ScriptingCGIvariables{$ContentType})
4611 $NewDirective .= sprintf($ScriptingCGIvariables{$ContentType}, $N, $V);
4612 $NewDirective .= "\n";
4616 # Stop writing variable definitions to the Interpreter
4617 if($ScriptingCGIvariables{$ContentType})
4619 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEFEND";
4620 ${ContentType}CGIVARIABLES
4621 BLOCKCGISCRIPTORVARDEFEND
4626 $NewDirective .= << "BLOCKCGISCRIPTOREND";
4628 # Select STDOUT filehandle
4629 select(STDOUT); \$|=1;
4631 BLOCKCGISCRIPTOREND
4633 return $NewDirective;
4637 # The cleanup code for the foreign script interpreter
4638 sub CleanupForeignScript # ($ContentType) -> $DirectivePrefix
4640 my $ContentType = lc(shift) || return "";
4641 my $NewDirective = "";
4643 # Return if not needed
4644 return $NewDirective unless $ScriptingCleanup{$ContentType};
4646 # Create the relevant script: Open the pipe to the interpreter
4647 $NewDirective .= <<"BLOCKCGISCRIPTORSTOP";
4648 # Cleanup Code for '$ContentType'
4649 # Select relevant output filehandle
4650 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4651 # Print Cleanup code to foreign script
4652 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}SCRIPTSTOP';
4653 $ScriptingCleanup{$ContentType}
4654 ${ContentType}SCRIPTSTOP
4656 # Select STDOUT filehandle
4657 select(STDOUT); \$|=1;
4658 BLOCKCGISCRIPTORSTOP
4660 return $NewDirective;
4664 # The prefix code for each <script></script> block
4665 sub PrefixForeignScript # ($ContentType) -> $DirectivePrefix
4667 my $ContentType = lc(shift) || return "";
4668 my $NewDirective = "";
4670 # Return if not needed
4671 return $NewDirective unless $ScriptingPrefix{$ContentType};
4673 my $Quote = "\'";
4674 # If the CGIvariables parameter is defined, but empty, interpolate
4675 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4676 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4677 !$ScriptingCGIvariables{$ContentType};
4679 # Add initialization code
4680 $NewDirective .= <<"BLOCKCGISCRIPTORPREFIX";
4681 # Prefix Code for '$ContentType'
4682 # Select relevant output filehandle
4683 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4685 # The block Prefix code (if any)
4686 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}PREFIXCODE$Quote;
4687 $ScriptingPrefix{$ContentType}
4688 ${ContentType}PREFIXCODE
4689 # Select STDOUT filehandle
4690 select(STDOUT); \$|=1;
4691 BLOCKCGISCRIPTORPREFIX
4693 return $NewDirective;
4697 # The postfix code for each <script></script> block
4698 sub PostfixForeignScript # ($ContentType) -> $DirectivePrefix
4700 my $ContentType = lc(shift) || return "";
4701 my $NewDirective = "";
4703 # Return if not needed
4704 return $NewDirective unless $ScriptingPostfix{$ContentType};
4706 my $Quote = "\'";
4707 # If the CGIvariables parameter is defined, but empty, interpolate
4708 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4709 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4710 !$ScriptingCGIvariables{$ContentType};
4712 # Create the relevant script: Open the pipe to the interpreter
4713 $NewDirective .= <<"BLOCKCGISCRIPTORPOSTFIX";
4714 # Postfix Code for '$ContentType'
4715 # Select filehandle to interpreter
4716 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4717 # Print postfix code to foreign script
4718 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SCRIPTPOSTFIX$Quote;
4719 $ScriptingPostfix{$ContentType}
4720 ${ContentType}SCRIPTPOSTFIX
4721 # Select STDOUT filehandle
4722 select(STDOUT); \$|=1;
4723 BLOCKCGISCRIPTORPOSTFIX
4725 return $NewDirective;
4728 sub InsertForeignScript # ($ContentType, $directive, @SRCfile) -> $NewDirective
4730 my $ContentType = lc(shift) || return "";
4731 my $directive = shift || return "";
4732 my @SRCfile = @_;
4733 my $NewDirective = "";
4735 my $Quote = "\'";
4736 # If the CGIvariables parameter is defined, but empty, interpolate
4737 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4738 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4739 !$ScriptingCGIvariables{$ContentType};
4741 # Create the relevant script
4742 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4743 # Insert Code for '$ContentType'
4744 # Select filehandle to interpreter
4745 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4746 BLOCKCGISCRIPTORINSERT
4748 # Use SRC feature files
4749 my $ThisSRCfile;
4750 while($ThisSRCfile = shift(@_))
4752 # Handle blocks
4753 if($ThisSRCfile =~ /^\s*\{\s*/)
4755 my $Block = $';
4756 $Block = $` if $Block =~ /\s*\}\s*$/;
4757 $NewDirective .= <<"BLOCKCGISCRIPTORSRCBLOCK";
4758 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SRCBLOCKCODE$Quote;
4759 $Block
4760 ${ContentType}SRCBLOCKCODE
4761 BLOCKCGISCRIPTORSRCBLOCK
4763 next;
4766 # Handle files
4767 $NewDirective .= <<"BLOCKCGISCRIPTORSRCFILES";
4768 # Read $ThisSRCfile
4769 open(SCRIPTINGSOURCE, "<$ThisSRCfile") || main::dieHandler(16, "$ThisSRCfILE: \$!");
4770 while(<SCRIPTINGSOURCE>)
4772 print $SCRIPTINGINPUT{$ContentType} \$_;
4774 close(SCRIPTINGSOURCE);
4776 BLOCKCGISCRIPTORSRCFILES
4780 # Add the directive
4781 if($directive)
4783 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4784 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}DIRECTIVECODE$Quote;
4785 $directive
4786 ${ContentType}DIRECTIVECODE
4787 BLOCKCGISCRIPTORINSERT
4791 $NewDirective .= <<"BLOCKCGISCRIPTORSELECT";
4792 # Select STDOUT filehandle
4793 select(STDOUT); \$|=1;
4794 BLOCKCGISCRIPTORSELECT
4796 # Ready
4797 return $NewDirective;
4800 sub CloseAllForeignScripts # Call CloseForeignScript on all open scripts
4802 my $ContentType;
4803 foreach $ContentType (keys(%SCRIPTINGINPUT))
4805 my $directive = CloseForeignScript($ContentType);
4806 print STDERR "\nDirective $CGI_Date: ", $directive;
4807 CGIexecute->evaluate($directive);
4812 # End of handling foreign (external) scripting languages.
4814 ############################################################################
4816 # A subroutine to handle "nested" quotes, it cuts off the leading
4817 # item or quoted substring
4818 # E.g.,
4819 # ' A_word and more words' -> @('A_word', ' and more words')
4820 # '"quoted string" The rest' -> @('quoted string', ' The rest')
4821 # (this is needed for parsing the <TAGS> and their attributes)
4822 my $SupportedQuotes = "\'\"\`\(\{\[";
4823 my %QuotePairs = ('('=>')','['=>']','{'=>'}'); # Brackets
4824 sub ExtractQuotedItem # ($String) -> @($QuotedString, $RestOfString)
4826 my @Result = ();
4827 my $String = shift || return @Result;
4829 if($String =~ /^\s*([\w\/\-\.]+)/is)
4831 push(@Result, $1, $');
4833 elsif($String =~ /^\s*(\\?)([\Q$SupportedQuotes\E])/is)
4835 my $BackSlash = $1 || "";
4836 my $OpenQuote = $2;
4837 my $CloseQuote = $OpenQuote;
4838 $CloseQuote = $QuotePairs{$OpenQuote} if $QuotePairs{$OpenQuote};
4840 if($BackSlash)
4842 $String =~ /^\s*\\\Q$OpenQuote\E/i;
4843 my $Onset = $';
4844 $Onset =~ /\\\Q$CloseQuote\E/i;
4845 my $Rest = $';
4846 my $Item = $`;
4847 push(@Result, $Item, $Rest);
4850 else
4852 $String =~ /^\s*\Q$OpenQuote\E([^\Q$CloseQuote\E]*)\Q$CloseQuote\E/i;
4853 push(@Result, $1, $');
4856 else
4858 push(@Result, "", $String);
4860 return @Result;
4863 # Now, start with the real work
4865 # Control the output of the Content-type: text/html\n\n message
4866 my $SupressContentType = 0;
4868 # Process a file
4869 sub ProcessFile # ($file_path)
4871 my $file_path = shift || return 0;
4874 # Generate a unique file handle (for recursions)
4875 my @SRClist = ();
4876 my $FileHandle = "file";
4877 my $n = 0;
4878 while(!eof($FileHandle.$n)) {++$n;};
4879 $FileHandle .= $n;
4881 # Start HTML output
4882 # Use the default Content-type if this is NOT a raw file
4883 unless(($RawFilePattern && $ENV{'PATH_INFO'} =~ m@($RawFilePattern)$@i)
4884 || $SupressContentType)
4886 $ENV{'PATH_INFO'} =~ m@($FilePattern)$@i;
4887 my $ContentType = $ContentTypeTable{$1};
4888 print "Content-type: $ContentType\n";
4889 if(%SETCOOKIELIST && keys(%SETCOOKIELIST))
4891 foreach my $name (keys(%SETCOOKIELIST))
4893 my $value = $SETCOOKIELIST{$name};
4894 print "Set-Cookie: $name=$value\n";
4896 # Cookies are set only ONCE
4897 %SETCOOKIELIST = ();
4899 print "\n";
4900 $SupressContentType = 1; # Content type has been printed
4904 # Get access to the actual data. This can be from RAM (by way of an
4905 # environment variable) or by opening a file.
4907 # Handle the use of RAM images (file-data is stored in the
4908 # $CGI_FILE_CONTENTS environment variable)
4909 # Note that this environment variable will be cleared, i.e., it is strictly for
4910 # single-use only!
4911 if($ENV{$CGI_FILE_CONTENTS})
4913 # File has been read already
4914 $_ = $ENV{$CGI_FILE_CONTENTS};
4915 # Sorry, you have to do the reading yourself (dynamic document creation?)
4916 # NOTE: you must read the whole document at once
4917 if($_ eq '-')
4919 $_ = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
4921 else # Clear environment variable
4923 $ENV{$CGI_FILE_CONTENTS} = '-';
4926 # Open Only PLAIN TEXT files (or STDIN) and NO executable files (i.e., scripts).
4927 # THIS IS A SECURITY FEATURE!
4928 elsif($file_path eq '-' || (-e "$file_path" && -r _ && -T _ && -f _ && ! (-x _ || -X _) ))
4930 open($FileHandle, $file_path) || dieHandler(17, "<h2>File not found</h2>\n");
4931 push(@OpenFiles, $file_path);
4932 $_ = <$FileHandle>; # Read first line
4934 else
4936 print "<h2>File not found</h2>\n";
4937 dieHandler(18, "$file_path\n");
4940 $| = 1; # Flush output buffers
4942 # Initialize variables
4943 my $METAarguments = ""; # The CGI arguments from the latest META tag
4944 my @METAvalues = (); # The ''-quoted CGI values from the latest META tag
4945 my $ClosedTag = 0; # <TAG> </TAG> versus <TAG/>
4948 # Send document to output
4949 # Process the requested document.
4950 # Do a loop BEFORE reading input again (this catches the RAM/Database
4951 # type of documents).
4952 do {
4955 # Handle translations if needed
4957 performTranslation(\$_) if $TranslationPaths;
4959 # Catch <SCRIPT LANGUAGE="PERL" TYPE="text/ssperl" > directives in $_
4960 # There can be more than 1 <SCRIPT> or META tags on a line
4961 while(/\<\s*(SCRIPT|META|DIV|INS)\s/is)
4963 my $directive = "";
4964 # Store rest of line
4965 my $Before = $`;
4966 my $ScriptTag = $&;
4967 my $After = $';
4968 my $TagType = uc($1);
4969 # The before part can be send to the output
4970 print $Before;
4972 # Read complete Tag from after and/or file
4973 until($After =~ /([^\\])\>/)
4975 $After .= <$FileHandle>;
4976 performTranslation(\$After) if $TranslationPaths;
4979 if($After =~ /([^\\])\>/)
4981 $ScriptTag .= $`.$&; # Keep the Script Tag intact
4982 $After = $';
4984 else
4986 dieHandler(19, "Closing > not found\n");
4989 # The tag could be closed by />, we handle this in the XML way
4990 # and don't process any content (we ignore whitespace)
4991 $ClosedTag = ($ScriptTag =~ m@[^\\]/\s*\>\s*$@) ? 1 : 0;
4994 # TYPE or CLASS?
4995 my $TypeName = ($TagType =~ /META/is) ? "CONTENT" : "TYPE";
4996 $TypeName = "CLASS" if $TagType eq 'DIV' || $TagType eq 'INS';
4998 # Parse <SCRIPT> or <META> directive
4999 # If NOT (TYPE|CONTENT)="text/ssperl" (i.e., $ServerScriptContentType),
5000 # send the line to the output and go to the next loop
5001 my $CurrentContentType = "";
5002 if($ScriptTag =~ /(^|\s)$TypeName\s*=\s*/is)
5004 my ($Type) = ExtractQuotedItem($');
5005 $Type =~ /^\s*([\w\/\-]+)\s*[\,\;]?/;
5006 $CurrentContentType = lc($1); # Note: mime-types are "case-less"
5007 # CSS classes are aliases of $ServerScriptContentType
5008 if($TypeName eq "CLASS" && $CurrentContentType eq $ServerScriptContentClass)
5010 $CurrentContentType = $ServerScriptContentType;
5015 # Not a known server-side content type, print and continue
5016 unless(($CurrentContentType =~
5017 /$ServerScriptContentType|$ShellScriptContentType/is) ||
5018 $ScriptingLanguages{$CurrentContentType})
5020 print $ScriptTag;
5021 $_ = $After;
5022 next;
5026 # A known server-side content type, evaluate
5028 # First, handle \> and \<
5029 $ScriptTag =~ s/\\\>/\>/isg;
5030 $ScriptTag =~ s/\\\</\</isg;
5032 # Extract the CGI, SRC, ID, IF and UNLESS attributes
5033 my %ScriptTagAttributes = ();
5034 while($ScriptTag =~ /(^|\s)(CGI|IF|UNLESS|SRC|ID)\s*=\s*/is)
5036 my $Attribute = $2;
5037 my $Rest = $';
5038 my $Value = "";
5039 ($Value, $ScriptTag) = ExtractQuotedItem($Rest);
5040 $ScriptTagAttributes{uc($Attribute)} = $Value;
5044 # The attribute used to define the CGI variables
5045 # Extract CGI-variables from
5046 # <META CONTENT="text/ssperl; CGI='' SRC=''">
5047 # <SCRIPT TYPE='text/ssperl' CGI='' SRC=''>
5048 # <DIV CLASS='ssperl' CGI='' SRC='' ID=""> tags
5049 # <INS CLASS='ssperl' CGI='' SRC='' ID=""> tags
5050 if($ScriptTagAttributes{'CGI'})
5052 @ARGV = (); # Reset ARGV
5053 $ARGC = 0;
5054 $METAarguments = ""; # Reset the META CGI arguments
5055 @METAvalues = ();
5056 my $Meta_CGI = $ScriptTagAttributes{'CGI'};
5058 # Process default values of variables ($<name> = 'default value')
5059 # Allowed quotes are '', "", ``, (), [], and {}
5060 while($Meta_CGI =~ /(^\s*|[^\\])([\$\@\%]?)([\w\-]+)\s*/is)
5062 my $varType = $2 || '$'; # Variable or list
5063 my $name = $3; # The Name
5064 my $default = "";
5065 $Meta_CGI = $';
5067 if($Meta_CGI =~ /^\s*\=\s*/is)
5069 # Locate (any) default value
5070 ($default, $Meta_CGI) = ExtractQuotedItem($'); # Cut the parameter from the CGI
5072 $RemainingTag = $Meta_CGI;
5075 # Define CGI (or ENV) variable, initalize it from the
5076 # Query string or the default value
5078 # Also construct the @ARGV and @_ arrays. This allows other (SRC=) Perl
5079 # scripts to access the CGI arguments defined in the META tag
5080 # (Not for CGI inside <SCRIPT> tags)
5081 if($varType eq '$')
5083 CGIexecute::defineCGIvariable($name, $default)
5084 || dieHandler(20, "INVALID CGI name/value pair ($name, $default)\n");
5085 push(@METAvalues, "'".${"CGIexecute::$name"}."'");
5086 # Add value to the @ARGV list
5087 push(@ARGV, ${"CGIexecute::$name"});
5088 ++$ARGC;
5090 elsif($varType eq '@')
5092 CGIexecute::defineCGIvariableList($name, $default)
5093 || dieHandler(21, "INVALID CGI name/value list pair ($name, $default)\n");
5094 push(@METAvalues, "'".join("'", @{"CGIexecute::$name"})."'");
5095 # Add value to the @ARGV list
5096 push(@ARGV, @{"CGIexecute::$name"});
5097 $ARGC = scalar(@CGIexecute::ARGV);
5099 elsif($varType eq '%')
5101 CGIexecute::defineCGIvariableHash($name, $default)
5102 || dieHandler(22, "INVALID CGI name/value hash pair ($name, $default)\n");
5103 my @PairList = map {"$_ => ".${"CGIexecute::$name"}{$_}} keys(%{"CGIexecute::$name"});
5104 push(@METAvalues, "'".join("'", @PairList)."'");
5105 # Add value to the @ARGV list
5106 push(@ARGV, %{"CGIexecute::$name"});
5107 $ARGC = scalar(@CGIexecute::ARGV);
5110 # Store the values for internal and later use
5111 $METAarguments .= "$varType".$name.","; # A string of CGI variable names
5113 push(@METAvalues, "\'".eval("\"$varType\{CGIexecute::$name\}\"")."\'"); # ALWAYS add '-quotes around values
5118 # The IF (conditional execution) Attribute
5119 # Evaluate the condition and stop unless it evaluates to true
5120 if($ScriptTagAttributes{'IF'})
5122 my $IFcondition = $ScriptTagAttributes{'IF'};
5124 # Convert SCRIPT calls, ./<script>
5125 $IFcondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5127 # Convert FILE calls, ~/<file>
5128 $IFcondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5130 # Block execution if necessary
5131 unless(CGIexecute->evaluate($IFcondition))
5133 %ScriptTagAttributes = ();
5134 $CurrentContentType = "";
5138 # The UNLESS (conditional execution) Attribute
5139 # Evaluate the condition and stop if it evaluates to true
5140 if($ScriptTagAttributes{'UNLESS'})
5142 my $UNLESScondition = $ScriptTagAttributes{'UNLESS'};
5144 # Convert SCRIPT calls, ./<script>
5145 $UNLESScondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5147 # Convert FILE calls, ~/<file>
5148 $UNLESScondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5150 # Block execution if necessary
5151 if(CGIexecute->evaluate($UNLESScondition))
5153 %ScriptTagAttributes = ();
5154 $CurrentContentType = "";
5158 # The SRC (Source File) Attribute
5159 # Extract any source script files and add them in
5160 # front of the directive
5161 # The SRC list should be emptied
5162 @SRClist = ();
5163 my $SRCtag = "";
5164 my $Prefix = 1;
5165 my $PrefixDirective = "";
5166 my $PostfixDirective = "";
5167 # There is a SRC attribute
5168 if($ScriptTagAttributes{'SRC'})
5170 $SRCtag = $ScriptTagAttributes{'SRC'};
5171 # Remove "file://" prefixes
5172 $SRCtag =~ s@([^\w\/\\]|^)file\://([^\s\/\@\=])@$1$2@gis;
5173 # Expand script filenames "./Script"
5174 $SRCtag =~ s@([^\w\/\\]|^)\./([^\s\/\@\=])@$1$SCRIPT_SUB/$2@gis;
5175 # Expand script filenames "~/Script"
5176 $SRCtag =~ s@([^\w\/\\]|^)\~/([^\s\/\@\=])@$1$HOME_SUB/$2@gis;
5179 # File source tags
5180 while($SRCtag =~ /\S/is)
5182 my $SRCdirective = "";
5184 # Pseudo file, just a switch to go from PREFIXING to POSTFIXING
5185 # SRC files
5186 if($SRCtag =~ /^[\s\;\,]*(POSTFIX|PREFIX)([^$FileAllowedChars]|$)/is)
5188 my $InsertionPlace = $1;
5189 $SRCtag = $2.$';
5191 $Prefix = $InsertionPlace =~ /POSTFIX/i ? 0 : 1;
5192 # Go to next round
5193 next;
5195 # {}-blocks are just evaluated by "do"
5196 elsif($SRCtag =~ /^[\s\;\,]*\{/is)
5198 my $SRCblock = $';
5199 if($SRCblock =~ /\}[\s\;\,]*([^\}]*)$/is)
5201 $SRCblock = $`;
5202 $SRCtag = $1.$';
5203 # SAFEqx shell script blocks
5204 if($CurrentContentType =~ /$ShellScriptContentType/is)
5206 # Handle ''-quotes inside the script
5207 $SRCblock =~ s/[\']/\\$&/gis;
5209 $SRCblock = "print do { SAFEqx(\'".$SRCblock."\'); };'';";
5210 $SRCdirective .= $SRCblock."\n";
5212 # do { SRCblocks }
5213 elsif($CurrentContentType =~ /$ServerScriptContentType/is)
5215 $SRCblock = "print do { $SRCblock };'';";
5216 $SRCdirective .= $SRCblock."\n";
5218 else # The interpreter should handle this
5220 push(@SRClist, "{ $SRCblock }");
5224 else
5225 { dieHandler(23, "Closing \} missing\n");};
5227 # Files are processed as Text or Executable files
5228 elsif($SRCtag =~ /[\s\;\,]*([$FileAllowedChars]+)[\;\,\s]*/is)
5230 my $SrcFile = $1;
5231 $SRCtag = $';
5233 # We are handling one of the external interpreters
5234 if($ScriptingLanguages{$CurrentContentType})
5236 push(@SRClist, $SrcFile);
5238 # We are at the start of a DIV tag, just load all SRC files and/or URL's
5239 elsif($TagType eq 'DIV' || $TagType eq 'INS') # All files are prepended in DIV's
5241 # $SrcFile is a URL pointing to an HTTP or FTP server
5242 if($SrcFile =~ m!^([a-z]+)\://!)
5244 my $URLoutput = CGIscriptor::read_url($SrcFile);
5245 $SRCdirective .= $URLoutput;
5247 # SRC file is an existing file
5248 elsif(-e "$SrcFile")
5250 open(DIVSOURCE, "<$SrcFile") || dieHandler(24, "<$SrcFile: $!\n");
5251 my $Content;
5252 while(sysread(DIVSOURCE, $Content, 1024) > 0)
5254 $SRCdirective .= $Content;
5256 close(DIVSOURCE);
5259 # Executable files are executed as
5260 # `$SrcFile 'ARGV[0]' 'ARGV[1]'`
5261 elsif(-x "$SrcFile")
5263 $SRCdirective .= "print \`$SrcFile @METAvalues\`;'';\n";
5265 # Handle 'standard' files, using ProcessFile
5266 elsif((-T "$SrcFile" || $ENV{$CGI_FILE_CONTENTS})
5267 && $SrcFile =~ m@($FilePattern)$@) # A recursion
5270 # Do not process still open files because it can lead
5271 # to endless recursions
5272 if(grep(/^$SrcFile$/, @OpenFiles))
5273 { dieHandler(25, "$SrcFile allready opened (endless recursion)\n")};
5274 # Prepare meta arguments
5275 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5276 # Process the file
5277 $SRCdirective .= "main::ProcessFile(\'$SrcFile\');'';\n";
5279 elsif($SrcFile =~ m!^([a-z]+)\://!) # URL's are loaded and printed
5281 $SRCdirective .= GET_URL($SrcFile);
5283 elsif(-T "$SrcFile") # Textfiles are "do"-ed (Perl execution)
5285 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5286 $SRCdirective .= "do \'$SrcFile\';'';\n";
5288 else # This one could not be resolved (should be handled by BinaryMapFile)
5290 $SRCdirective .= 'print "'.$SrcFile.' cannot be used"'."\n";
5295 # Postfix or Prefix
5296 if($Prefix)
5298 $PrefixDirective .= $SRCdirective;
5300 else
5302 $PostfixDirective .= $SRCdirective;
5305 # The prefix should be handled immediately
5306 $directive .= $PrefixDirective;
5307 $PrefixDirective = "";
5311 # Handle the content of the <SCRIPT></SCRIPT> tags
5312 # Do not process the content of <SCRIPT/>
5313 if($TagType =~ /SCRIPT/is && !$ClosedTag) # The <SCRIPT> TAG
5315 my $EndScriptTag = "";
5317 # Execute SHELL scripts with SAFEqx()
5318 if($CurrentContentType =~ /$ShellScriptContentType/is)
5320 $directive .= "SAFEqx(\'";
5323 # Extract Program
5324 while($After !~ /\<\s*\/SCRIPT[^\>]*\>/is && !eof($FileHandle))
5326 $After .= <$FileHandle>;
5327 performTranslation(\$After) if $TranslationPaths;
5330 if($After =~ /\<\s*\/SCRIPT[^\>]*\>/is)
5332 $directive .= $`;
5333 $EndScriptTag = $&;
5334 $After = $';
5336 else
5338 dieHandler(26, "Missing </SCRIPT> end tag in $ENV{'PATH_INFO'}\n");
5341 # Process only when content should be executed
5342 if($CurrentContentType)
5345 # Remove all comments from Perl scripts
5346 # (NOT from OS shell scripts)
5347 $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
5348 if $CurrentContentType =~ /$ServerScriptContentType/i;
5350 # Convert SCRIPT calls, ./<script>
5351 $directive =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5353 # Convert FILE calls, ~/<file>
5354 $directive =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5356 # Execute SHELL scripts with SAFEqx(), closing bracket
5357 if($CurrentContentType =~ /$ShellScriptContentType/i)
5359 # Handle ''-quotes inside the script
5360 $directive =~ /SAFEqx\(\'/;
5361 $directive = $`.$&;
5362 my $Executable = $';
5363 $Executable =~ s/[\']/\\$&/gs;
5365 $directive .= $Executable."\');"; # Closing bracket
5368 else
5370 $directive = "";
5373 # Handle the content of the <DIV></DIV> tags
5374 # Do not process the content of <DIV/>
5375 elsif(($TagType eq 'DIV' || $TagType eq 'INS') && !$ClosedTag) # The <DIV> TAGs
5377 my $EndScriptTag = "";
5379 # Extract Text
5380 while($After !~ /\<\s*\/$TagType[^\>]*\>/is && !eof($FileHandle))
5382 $After .= <$FileHandle>;
5383 performTranslation(\$After) if $TranslationPaths;
5386 if($After =~ /\<\s*\/$TagType[^\>]*\>/is)
5388 $directive .= $`;
5389 $EndScriptTag = $&;
5390 $After = $';
5392 else
5394 dieHandler(27, "Missing </$TagType> end tag in $ENV{'PATH_INFO'}\n");
5397 # Add the Postfixed directives (but only when it contains something printable)
5398 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5399 $PostfixDirective = "";
5402 # Process only when content should be handled
5403 if($CurrentContentType)
5406 # Get the name (ID), and clean it (i.e., remove anything that is NOT part of
5407 # a valid Perl name). Names should not contain $, but we can handle it.
5408 my $name = $ScriptTagAttributes{'ID'};
5409 $name =~ /^\s*[\$\@\%]?([\w\-]+)/;
5410 $name = $1;
5412 # Assign DIV contents to $NAME value OUTSIDE the CGI values!
5413 CGIexecute::defineCGIexecuteVariable($name, $directive);
5414 $directive = "";
5417 # Nothing to execute
5418 $directive = "";
5422 # Handle Foreign scripting languages
5423 if($ScriptingLanguages{$CurrentContentType})
5425 my $newDirective = "";
5426 $newDirective .= OpenForeignScript($CurrentContentType); # Only if not already done
5427 $newDirective .= PrefixForeignScript($CurrentContentType);
5428 $newDirective .= InsertForeignScript($CurrentContentType, $directive, @SRClist);
5429 $newDirective .= PostfixForeignScript($CurrentContentType);
5430 $newDirective .= CloseForeignScript($CurrentContentType); # This shouldn't be necessary
5432 $newDirective .= '"";';
5434 $directive = $newDirective;
5438 # Add the Postfixed directives (but only when it contains something printable)
5439 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5440 $PostfixDirective = "";
5443 # EXECUTE the script and print the results
5445 # Use this to debug the program
5446 # print STDERR "Directive $CGI_Date: \n", $directive, "\n\n";
5448 my $Result = CGIexecute->evaluate($directive) if $directive; # Evaluate as PERL code
5449 $Result =~ s/\n$//g; # Remove final newline
5451 # Print the Result of evaluating the directive
5452 # (this will handle LARGE, >64 kB output)
5453 my $BytesWritten = 1;
5454 while($Result && $BytesWritten)
5456 $BytesWritten = syswrite(STDOUT, $Result, 64);
5457 $Result = substr($Result, $BytesWritten);
5459 # print $Result; # Could be used instead of above code
5461 # Store result if wanted, i.e., if $CGIscriptorResults has been
5462 # defined in a <META> tag.
5463 push(@CGIexecute::CGIscriptorResults, $Result)
5464 if exists($default_values{'CGIscriptorResults'});
5466 # Process the rest of the input line (this could contain
5467 # another directive)
5468 $_ = $After;
5470 print $_;
5471 } while(<$FileHandle>); # Read and Test AFTER first loop!
5473 close ($FileHandle);
5474 dieHandler(28, "Error in recursion\n") unless pop(@OpenFiles) == $file_path;
5478 ###############################################################################
5480 # Call the whole package
5482 sub Handle_Request
5484 my $file_path = "";
5486 # Initialization Code
5487 Initialize_Request();
5489 # SECURITY: ACCESS CONTROL
5490 Access_Control();
5492 # Read the POST part of the query, if there is one
5493 Get_POST_part_of_query();
5495 # Start (HTML) output and logging
5496 $file_path = Initialize_output();
5498 # Check login access or divert to login procedure
5499 $Use_Login = Log_In_Access();
5500 $file_path = $Use_Login if $Use_Login;
5502 # Record which files are still open (to avoid endless recursions)
5503 my @OpenFiles = ();
5505 # Record whether the default HTML ContentType has already been printed
5506 # but only if the SERVER uses HTTP or some other protocol that might interpret
5507 # a content MIME type.
5509 $SupressContentType = !("$ENV{'SERVER_PROTOCOL'}" =~ /($ContentTypeServerProtocols)/i);
5511 # Process the specified file
5512 ProcessFile($file_path) if $file_path ne $SS_PUB;
5514 # Cleanup all open external (foreign) interpreters
5515 CloseAllForeignScripts();
5518 "" # SUCCESS
5521 # Make a single call to handle an (empty) request
5522 Handle_Request();
5525 # END OF PACKAGE MAIN
5528 ####################################################################################
5530 # The CGIEXECUTE PACKAGE
5532 ####################################################################################
5534 # Isolate the evaluation of directives as PERL code from the rest of the program.
5535 # Remember that each package has its own name space.
5536 # Note that only the FIRST argument of execute->evaluate is actually evaluated,
5537 # all other arguments are accessible inside the first argument as $_[0] to $_[$#_].
5539 package CGIexecute;
5541 sub evaluate
5543 my $self = shift;
5544 my $directive = shift;
5545 $directive = eval($directive);
5546 warn $@ if $@; # Write an error message to STDERR
5547 $directive; # Return value of directive
5551 # defineCGIexecuteVariable($name [, $value]) -> 0/1
5553 # Define and intialize variables inside CGIexecute
5554 # Does no sanity checking, for internal use only
5556 sub defineCGIexecuteVariable # ($name [, $value]) -> 0/1
5558 my $name = shift || return 0; # The Name
5559 my $value = shift || ""; # The value
5561 ${$name} = $value;
5563 return 1;
5566 # Protect certain CGI variables values when set internally
5567 # If not defined internally, there will be no variable set AT ALL
5568 my %CGIprotectedVariable = ();
5569 sub ProtectCGIvariable # ($name) -> 0/1
5571 my $name = shift || "";
5572 return 0 unless $name && $name =~ /\w/;
5574 ++$CGIprotectedVariable{$name};
5576 return $CGIprotectedVariable{$name};
5579 # defineCGIvariable($name [, $default]) -> 0/1
5581 # Define and intialize CGI variables
5582 # Tries (in order) $ENV{$name}, the Query string and the
5583 # default value.
5584 # Removes all '-quotes etc.
5586 sub defineCGIvariable # ($name [, $default]) -> 0/1
5588 my $name = shift || return 0; # The Name
5589 my $default = shift || ""; # The default value
5591 # Protect variables set internally
5592 return 1 if !$name || exists($CGIprotectedVariable{$name});
5594 # Remove \-quoted characters
5595 $default =~ s/\\(.)/$1/g;
5596 # Store default values
5597 $::default_values{$name} = $default if $default;
5599 # Process variables
5600 my $temp = undef;
5601 # If there is a user supplied value, it replaces the
5602 # default value.
5604 # Environment values have precedence
5605 if(exists($ENV{$name}))
5607 $temp = $ENV{$name};
5609 # Get name and its value from the query string
5610 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5612 $temp = ::YOUR_CGIPARSE($name);
5614 # Defined values must exist for security
5615 elsif(!exists($::default_values{$name}))
5617 $::default_values{$name} = undef;
5620 # SECURITY, do not allow '- and `-quotes in
5621 # client values.
5622 # Remove all existing '-quotes
5623 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5624 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5625 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5626 # If $temp is empty, use the default value (if it exists)
5627 unless($temp =~ /\S/ || length($temp) > 0) # I.e., $temp is empty
5629 $temp = $::default_values{$name};
5630 # Remove all existing '-quotes
5631 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5632 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5633 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5635 else # Store current CGI values and remove defaults
5637 $::default_values{$name} = $temp;
5639 # Define the CGI variable and its value (in the execute package)
5640 ${$name} = $temp;
5642 # return SUCCES
5643 return 1;
5646 sub defineCGIvariableList # ($name [, $default]) -> 0/1)
5648 my $name = shift || return 0; # The Name
5649 my $default = shift || ""; # The default value
5651 # Protect variables set internally
5652 return 1 if !$name || exists($CGIprotectedVariable{$name});
5654 # Defined values must exist for security
5655 if(!exists($::default_values{$name}))
5657 $::default_values{$name} = $default;
5660 my @temp = ();
5663 # For security:
5664 # Environment values have precedence
5665 if(exists($ENV{$name}))
5667 push(@temp, $ENV{$name});
5669 # Get name and its values from the query string
5670 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5672 push(@temp, ::YOUR_CGIPARSE($name, 1)); # Extract LIST
5674 else
5676 push(@temp, $::default_values{$name});
5680 # SECURITY, do not allow '- and `-quotes in
5681 # client values.
5682 # Remove all existing '-quotes
5683 @temp = map {s/([\r\f]+\n)/\n/g; $_} @temp; # Only \n is allowed
5684 @temp = map {s/[\']/&#8217;/igs; $_} @temp; # Remove all single quotes
5685 @temp = map {s/[\`]/&#8216;/igs; $_} @temp; # Remove all backtick quotes
5687 # Store current CGI values and remove defaults
5688 $::default_values{$name} = $temp[0];
5690 # Define the CGI variable and its value (in the execute package)
5691 @{$name} = @temp;
5693 # return SUCCES
5694 return 1;
5697 sub defineCGIvariableHash # ($name [, $default]) -> 0/1) Note: '$name{""} = $default';
5699 my $name = shift || return 0; # The Name
5700 my $default = shift || ""; # The default value
5702 # Protect variables set internally
5703 return 1 if !$name || exists($CGIprotectedVariable{$name});
5705 # Defined values must exist for security
5706 if(!exists($::default_values{$name}))
5708 $::default_values{$name} = $default;
5711 my %temp = ();
5714 # For security:
5715 # Environment values have precedence
5716 if(exists($ENV{$name}))
5718 $temp{""} = $ENV{$name};
5720 # Get name and its values from the query string
5721 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5723 %temp = ::YOUR_CGIPARSE($name, -1); # Extract HASH table
5725 elsif($::default_values{$name} ne "")
5727 $temp{""} = $::default_values{$name};
5731 # SECURITY, do not allow '- and `-quotes in
5732 # client values.
5733 # Remove all existing '-quotes
5734 my $Key;
5735 foreach $Key (keys(%temp))
5737 $temp{$Key} =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5738 $temp{$Key} =~ s/[\']/&#8217;/igs; # Remove all single quotes
5739 $temp{$Key} =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5742 # Store current CGI values and remove defaults
5743 $::default_values{$name} = $temp{""};
5745 # Define the CGI variable and its value (in the execute package)
5746 %{$name} = ();
5747 my $tempKey;
5748 foreach $tempKey (keys(%temp))
5750 ${$name}{$tempKey} = $temp{$tempKey};
5753 # return SUCCES
5754 return 1;
5758 # SAFEqx('CommandString')
5760 # A special function that is a safe alternative to backtick quotes (and qx//)
5761 # with client-supplied CGI values. All CGI variables are surrounded by
5762 # single ''-quotes (except between existing \'\'-quotes, don't try to be
5763 # too smart). All variables are then interpolated. Simple (@) lists are
5764 # expanded with join(' ', @List), and simple (%) hash tables expanded
5765 # as a list of "key=value" pairs. Complex variables, e.g., @$var, are
5766 # evaluated in a scalar context (e.g., as scalar(@$var)). All occurrences of
5767 # $@% that should NOT be interpolated must be preceeded by a "\".
5768 # If the first line of the String starts with "#! interpreter", the
5769 # remainder of the string is piped into interpreter (after interpolation), i.e.,
5770 # open(INTERPRETER, "|interpreter");print INTERPRETER remainder;
5771 # just like in UNIX. There are some problems with quotes. Be carefull in
5772 # using them. You do not have access to the output of any piped (#!)
5773 # process! If you want such access, execute
5774 # <SCRIPT TYPE="text/osshell">echo "script"|interpreter</SCRIPT> or
5775 # <SCRIPT TYPE="text/ssperl">$resultvar = SAFEqx('echo "script"|interpreter');
5776 # </SCRIPT>.
5778 # SAFEqx ONLY WORKS WHEN THE STRING ITSELF IS SURROUNDED BY SINGLE QUOTES
5779 # (SO THAT IT IS NOT INTERPOLATED BEFORE IT CAN BE PROTECTED)
5780 sub SAFEqx # ('String') -> result of executing qx/"String"/
5782 my $CommandString = shift;
5783 my $NewCommandString = "";
5785 # Only interpolate when required (check the On/Off switch)
5786 unless($CGIscriptor::NoShellScriptInterpolation)
5789 # Handle existing single quotes around CGI values
5790 while($CommandString =~ /\'[^\']+\'/s)
5792 my $CurrentQuotedString = $&;
5793 $NewCommandString .= $`;
5794 $CommandString = $'; # The remaining string
5795 # Interpolate CGI variables between quotes
5796 # (e.g., '$CGIscriptorResults[-1]')
5797 $CurrentQuotedString =~
5798 s/(^|[^\\])([\$\@])((\w*)([\{\[][\$\@\%]?[\:\w\-]+[\}\]])*)/if(exists($main::default_values{$4})){
5799 "$1".eval("$2$3")}else{"$&"}/egs;
5801 # Combine result with previous result
5802 $NewCommandString .= $CurrentQuotedString;
5804 $CommandString = $NewCommandString.$CommandString;
5806 # Select known CGI variables and surround them with single quotes,
5807 # then interpolate all variables
5808 $CommandString =~
5809 s/(^|[^\\])([\$\@\%]+)((\w*)([\{\[][\w\:\$\"\-]+[\}\]])*)/
5810 if($2 eq '$' && exists($main::default_values{$4}))
5811 {"$1\'".eval("\$$3")."\'";}
5812 elsif($2 eq '@'){$1.join(' ', @{"$3"});}
5813 elsif($2 eq '%'){my $t=$1;map {$t.=" $_=".${"$3"}{$_}}
5814 keys(%{"$3"});$t}
5815 else{$1.eval("${2}$3");
5816 }/egs;
5818 # Remove backslashed [$@%]
5819 $CommandString =~ s/\\([\$\@\%])/$1/gs;
5822 # Debugging
5823 # return $CommandString;
5825 # Handle UNIX style "#! shell command\n" constructs as
5826 # a pipe into the shell command. The output cannot be tapped.
5827 my $ReturnValue = "";
5828 if($CommandString =~ /^\s*\#\!([^\f\n\r]+)[\f\n\r]/is)
5830 my $ShellScripts = $';
5831 my $ShellCommand = $1;
5832 open(INTERPRETER, "|$ShellCommand") || dieHandler(29, "\'$ShellCommand\' PIPE not opened: &!\n");
5833 select(INTERPRETER);$| = 1;
5834 print INTERPRETER $ShellScripts;
5835 close(INTERPRETER);
5836 select(STDOUT);$| = 1;
5838 # Shell scripts which are redirected to an existing named pipe.
5839 # The output cannot be tapped.
5840 elsif($CGIscriptor::ShellScriptPIPE)
5842 CGIscriptor::printSAFEqxPIPE($CommandString);
5844 else # Plain ``-backtick execution
5846 # Execute the commands
5847 $ReturnValue = qx/$CommandString/;
5849 return $ReturnValue;
5852 ####################################################################################
5854 # The CGIscriptor PACKAGE
5856 ####################################################################################
5858 # Isolate the evaluation of CGIscriptor functions, i.e., those prefixed with
5859 # "CGIscriptor::"
5861 package CGIscriptor;
5864 # The Interpolation On/Off switch
5865 my $NoShellScriptInterpolation = undef;
5866 # The ShellScript redirection pipe
5867 my $ShellScriptPIPE = undef;
5869 # Open a named PIPE for SAFEqx to receive ALL shell scripts
5870 sub RedirectShellScript # ('CommandString')
5872 my $CommandString = shift || undef;
5874 if($CommandString)
5876 $ShellScriptPIPE = "ShellScriptNamedPipe";
5877 open($ShellScriptPIPE, "|$CommandString")
5878 || main::dieHandler(30, "\'|$CommandString\' PIPE open failed: $!\n");
5880 else
5882 close($ShellScriptPIPE);
5883 $ShellScriptPIPE = undef;
5885 return $ShellScriptPIPE;
5888 # Print to redirected shell script pipe
5889 sub printSAFEqxPIPE # ("String") -> print return value
5891 my $String = shift || undef;
5893 select($ShellScriptPIPE); $| = 1;
5894 my $returnvalue = print $ShellScriptPIPE ($String);
5895 select(STDOUT); $| = 1;
5897 return $returnvalue;
5900 # a pointer to CGIexecute::SAFEqx
5901 sub SAFEqx # ('String') -> result of qx/"String"/
5903 my $CommandString = shift;
5904 return CGIexecute::SAFEqx($CommandString);
5908 # a pointer to CGIexecute::defineCGIvariable
5909 sub defineCGIvariable # ($name[, $default]) ->0/1
5911 my $name = shift;
5912 my $default = shift;
5913 return CGIexecute::defineCGIvariable($name, $default);
5917 # a pointer to CGIexecute::defineCGIvariable
5918 sub defineCGIvariableList # ($name[, $default]) ->0/1
5920 my $name = shift;
5921 my $default = shift;
5922 return CGIexecute::defineCGIvariableList($name, $default);
5926 # a pointer to CGIexecute::defineCGIvariable
5927 sub defineCGIvariableHash # ($name[, $default]) ->0/1
5929 my $name = shift;
5930 my $default = shift;
5931 return CGIexecute::defineCGIvariableHash($name, $default);
5935 # Decode URL encoded arguments
5936 sub URLdecode # (URL encoded input) -> string
5938 my $output = "";
5939 my $char;
5940 my $Value;
5941 foreach $Value (@_)
5943 my $EncodedValue = $Value; # Do not change the loop variable
5944 # Convert all "+" to " "
5945 $EncodedValue =~ s/\+/ /g;
5946 # Convert all hexadecimal codes (%FF) to their byte values
5947 while($EncodedValue =~ /\%([0-9A-F]{2})/i)
5949 $output .= $`.chr(hex($1));
5950 $EncodedValue = $';
5952 $output .= $EncodedValue; # The remaining part of $Value
5954 $output;
5957 # Encode arguments as URL codes.
5958 sub URLencode # (input) -> URL encoded string
5960 my $output = "";
5961 my $char;
5962 my $Value;
5963 foreach $Value (@_)
5965 my @CharList = split('', $Value);
5966 foreach $char (@CharList)
5968 if($char =~ /\s/)
5969 { $output .= "+";}
5970 elsif($char =~ /\w\-/)
5971 { $output .= $char;}
5972 else
5974 $output .= uc(sprintf("%%%2.2x", ord($char)));
5978 $output;
5981 # Extract the value of a CGI variable from the URL-encoded $string
5982 # Also extracts the data blocks from a multipart request. Does NOT
5983 # decode the multipart blocks
5984 sub CGIparseValue # (ValueName [, URL_encoded_QueryString [, \$QueryReturnReference]]) -> Decoded value
5986 my $ValueName = shift;
5987 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5988 my $ReturnReference = shift || undef;
5989 my $output = "";
5991 if($QueryString =~ /(^|\&)$ValueName\=([^\&]*)(\&|$)/)
5993 $output = URLdecode($2);
5994 $$ReturnReference = $' if ref($ReturnReference);
5996 # Get multipart POST or PUT methods
5997 elsif($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
5999 my $MultipartType = $2;
6000 my $BoundaryString = $3;
6001 # Remove the boundary-string
6002 my $temp = $QueryString;
6003 $temp =~ /^\Q--$BoundaryString\E/m;
6004 $temp = $';
6006 # Identify the newline character(s), this is the first character in $temp
6007 my $NewLine = "\r\n"; # Actually, this IS the correct one
6008 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
6010 # Is this correct??? I have to check.
6011 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
6012 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
6013 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
6014 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
6017 # search through all data blocks
6018 while($temp =~ /^\Q--$BoundaryString\E/m)
6020 my $DataBlock = $`;
6021 $temp = $';
6022 # Get the empty line after the header
6023 $DataBlock =~ /$NewLine$NewLine/;
6024 $Header = $`;
6025 $output = $';
6026 my $Header = $`;
6027 $output = $';
6029 # Remove newlines from the header
6030 $Header =~ s/$NewLine/ /g;
6032 # Look whether this block is the one you are looking for
6033 # Require the quotes!
6034 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
6036 my $i;
6037 for($i=length($NewLine); $i; --$i)
6039 chop($output);
6041 # OK, get out
6042 last;
6044 # reinitialize the output
6045 $output = "";
6047 $$ReturnReference = $temp if ref($ReturnReference);
6049 elsif($QueryString !~ /(^|\&)$ValueName\=/) # The value simply isn't there
6051 return undef;
6052 $$ReturnReference = undef if ref($ReturnReference);
6054 else
6056 print "ERROR: $ValueName $main::ENV{'CONTENT_TYPE'}\n";
6058 return $output;
6062 # Get a list of values for the same ValueName. Uses CGIparseValue
6064 sub CGIparseValueList # (ValueName [, URL_encoded_QueryString]) -> List of decoded values
6066 my $ValueName = shift;
6067 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6068 my @output = ();
6069 my $RestQueryString;
6070 my $Value;
6071 while($QueryString &&
6072 (($Value = CGIparseValue($ValueName, $QueryString, \$RestQueryString))
6073 || defined($Value)))
6075 push(@output, $Value);
6076 $QueryString = $RestQueryString; # QueryString is consumed!
6078 # ready, return list with values
6079 return @output;
6082 sub CGIparseValueHash # (ValueName [, URL_encoded_QueryString]) -> Hash table of decoded values
6084 my $ValueName = shift;
6085 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6086 my $RestQueryString;
6087 my %output = ();
6088 while($QueryString && $QueryString =~ /(^|\&)$ValueName([\w]*)\=/)
6090 my $Key = $2;
6091 my $Value = CGIparseValue("$ValueName$Key", $QueryString, \$RestQueryString);
6092 $output{$Key} = $Value;
6093 $QueryString = $RestQueryString; # QueryString is consumed!
6095 # ready, return list with values
6096 return %output;
6099 sub CGIparseForm # ([URL_encoded_QueryString]) -> Decoded Form (NO multipart)
6101 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6102 my $output = "";
6104 $QueryString =~ s/\&/\n/g;
6105 $output = URLdecode($QueryString);
6107 $output;
6110 # Extract the header of a multipart CGI variable from the POST input
6111 sub CGIparseHeader # (ValueName [, URL_encoded_QueryString]) -> Decoded value
6113 my $ValueName = shift;
6114 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6115 my $output = "";
6117 if($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
6119 my $MultipartType = $2;
6120 my $BoundaryString = $3;
6121 # Remove the boundary-string
6122 my $temp = $QueryString;
6123 $temp =~ /^\Q--$BoundaryString\E/m;
6124 $temp = $';
6126 # Identify the newline character(s), this is the first character in $temp
6127 my $NewLine = "\r\n"; # Actually, this IS the correct one
6128 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
6130 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
6131 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
6132 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
6133 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
6136 # search through all data blocks
6137 while($temp =~ /^\Q--$BoundaryString\E/m)
6139 my $DataBlock = $`;
6140 $temp = $';
6141 # Get the empty line after the header
6142 $DataBlock =~ /$NewLine$NewLine/;
6143 $Header = $`;
6144 my $Header = $`;
6146 # Remove newlines from the header
6147 $Header =~ s/$NewLine/ /g;
6149 # Look whether this block is the one you are looking for
6150 # Require the quotes!
6151 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
6153 $output = $Header;
6154 last;
6156 # reinitialize the output
6157 $output = "";
6160 return $output;
6164 # Checking variables for security (e.g., file names and email addresses)
6165 # File names are tested against the $::FileAllowedChars and $::BlockPathAccess variables
6166 sub CGIsafeFileName # FileName -> FileName or ""
6168 my $FileName = shift || "";
6169 return "" if $FileName =~ m?[^$::FileAllowedChars]?;
6170 return "" if $FileName =~ m!(^|/|\:)[\-\.]!;
6171 return "" if $FileName =~ m@\.\.\Q$::DirectorySeparator\E@; # Higher directory not allowed
6172 return "" if $FileName =~ m@\Q$::DirectorySeparator\E\.\.@; # Higher directory not allowed
6173 return "" if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@; # Invisible (blocked) file
6175 return $FileName;
6178 sub CGIsafeEmailAddress # email -> email or ""
6180 my $Email = shift || "";
6181 return "" unless $Email =~ m/^[\w\.\-]+[\@][\w\.\-\:]+$/;
6182 return $Email;
6185 # Get a URL from the web. Needs main::GET_URL($URL) function
6186 # (i.e., curl, snarf, or wget)
6187 sub read_url # ($URL) -> page/file
6189 my $URL = shift || return "";
6191 # Get the commands to read the URL, do NOT add a print command
6192 my $URL_command = main::GET_URL($URL, 1);
6193 # execute the commands, i.e., actually read it
6194 my $URLcontent = CGIexecute->evaluate($URL_command);
6196 # Ready, return the content.
6197 return $URLcontent;
6200 ################################################>>>>>>>>>>Start Remove
6202 # BrowseAllDirs(Directory, indexfile)
6204 # usage:
6205 # <SCRIPT TYPE='text/ssperl'>
6206 # CGIscriptor::BrowseAllDirs('Sounds', 'index.html', '\.wav$')
6207 # </SCRIPT>
6209 # Allows to browse all directories. Stops at '/'. If the directory contains
6210 # an indexfile, eg, index.html, that file will be used instead. Files must match
6211 # the $Pattern, if it is given. Default is
6212 # CGIscriptor::BrowseAllDirs('/', 'index.html', '')
6214 sub BrowseAllDirs # (Directory, indexfile, $Pattern) -> Print HTML code
6216 my $Directory = shift || '/';
6217 my $indexfile = shift || 'index.html';
6218 my $Pattern = shift || '';
6219 $Directory =~ s!/$!!g;
6221 # If the index directory exists, use that one
6222 if(-s "$::CGI_HOME$Directory/$indexfile")
6224 return main::ProcessFile("$::CGI_HOME$Directory/$indexfile");
6227 # No indexfile, continue
6228 my @DirectoryList = glob("$::CGI_HOME$Directory");
6229 $CurrentDirectory = shift(@DirectoryList);
6230 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
6231 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
6232 print "<h1>";
6233 print "$CurrentDirectory" if $CurrentDirectory;
6234 print "</h1>\n";
6236 opendir(BROWSE, "$::CGI_HOME$Directory") || main::dieHandler(31, "$::CGI_HOME$Directory $!");
6237 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
6239 # Print directories
6240 my $file;
6241 print "<pre><ul TYPE='NONE'>\n";
6242 foreach $file (@AllFiles)
6244 next unless -d "$::CGI_HOME$Directory/$file";
6245 # Check whether this file should be visible
6246 next if $::BlockPathAccess &&
6247 "$Directory/$file/" =~ m@$::BlockPathAccess@;
6248 print "<dt><a href='$Directory/$file'>$file</a></dt>\n";
6250 print "</ul></pre>\n";
6252 # Print files
6253 print "<pre><ul TYPE='CIRCLE'>\n";
6254 my $TotalSize = 0;
6255 foreach $file (@AllFiles)
6257 next if $file =~ /^\./;
6258 next if -d "$::CGI_HOME$Directory/$file";
6259 next if -l "$::CGI_HOME$Directory/$file";
6260 # Check whether this file should be visible
6261 next if $::BlockPathAccess &&
6262 "$Directory/$file" =~ m@$::BlockPathAccess@;
6264 if(!$Pattern || $file =~ m@$Pattern@)
6266 my $Date = localtime($^T - (-M "$::CGI_HOME$Directory/$file")*3600*24);
6267 my $Size = -s "$::CGI_HOME$Directory/$file";
6268 $Size = sprintf("%6.0F kB", $Size/1024);
6269 my $Type = `file $::CGI_HOME$Directory/$file`;
6270 $Type =~ s@\s*$::CGI_HOME$Directory/$file\s*\:\s*@@ig;
6271 chomp($Type);
6273 print "<li>";
6274 print "<a href='$Directory/$file'>";
6275 printf("%-40s", "$file</a>");
6276 print "\t$Size\t$Date\t$Type";
6277 print "</li>\n";
6280 print "</ul></pre>";
6282 return 1;
6286 ################################################
6288 # BrowseDirs(RootDirectory [, Pattern, Start])
6290 # usage:
6291 # <SCRIPT TYPE='text/ssperl'>
6292 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', 'Speech', 'DIRECTORY')
6293 # </SCRIPT>
6295 # Allows to browse subdirectories. Start should be relative to the RootDirectory,
6296 # e.g., the full path of the directory 'Speech' is '~/Sounds/Speech'.
6297 # Only files which fit /$Pattern/ and directories are displayed.
6298 # Directories down or up the directory tree are supplied with a
6299 # GET request with the name of the CGI variable in the fourth argument (default
6300 # is 'BROWSEDIRS'). So the correct call for a subdirectory could be:
6301 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', $DIRECTORY, 'DIRECTORY')
6303 sub BrowseDirs # (RootDirectory [, Pattern, Start, CGIvariable, HTTPserver]) -> Print HTML code
6305 my $RootDirectory = shift; # || return 0;
6306 my $Pattern = shift || '\S';
6307 my $Start = shift || "";
6308 my $CGIvariable = shift || "BROWSEDIRS";
6309 my $HTTPserver = shift || '';
6311 $Start = CGIscriptor::URLdecode($Start); # Sometimes, too much has been encoded
6312 $Start =~ s@//+@/@g;
6313 $Start =~ s@[^/]+/\.\.@@ig;
6314 $Start =~ s@^\.\.@@ig;
6315 $Start =~ s@/\.$@@ig;
6316 $Start =~ s!/+$!!g;
6317 $Start .= "/" if $Start;
6319 my @Directory = glob("$::CGI_HOME/$RootDirectory/$Start");
6320 $CurrentDirectory = shift(@Directory);
6321 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
6322 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
6323 print "<h1>";
6324 print "$CurrentDirectory" if $CurrentDirectory;
6325 print "</h1>\n";
6326 opendir(BROWSE, "$::CGI_HOME/$RootDirectory/$Start") || main::dieHandler(31, "$::CGI_HOME/$RootDirectory/$Start $!");
6327 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
6329 # Print directories
6330 my $file;
6331 print "<pre><ul TYPE='NONE'>\n";
6332 foreach $file (@AllFiles)
6334 next unless -d "$::CGI_HOME/$RootDirectory/$Start$file";
6335 # Check whether this file should be visible
6336 next if $::BlockPathAccess &&
6337 "/$RootDirectory/$Start$file/" =~ m@$::BlockPathAccess@;
6339 my $NewURL = $Start ? "$Start$file" : $file;
6340 $NewURL = CGIscriptor::URLencode($NewURL);
6341 print "<dt><a href='";
6342 print "$ENV{SCRIPT_NAME}" if $ENV{SCRIPT_NAME} !~ m@[^\w+\-/]@;
6343 print "$ENV{PATH_INFO}?$CGIvariable=$NewURL'>$file</a></dt>\n";
6345 print "</ul></pre>\n";
6347 # Print files
6348 print "<pre><ul TYPE='CIRCLE'>\n";
6349 my $TotalSize = 0;
6350 foreach $file (@AllFiles)
6352 next if $file =~ /^\./;
6353 next if -d "$::CGI_HOME/$RootDirectory/$Start$file";
6354 next if -l "$::CGI_HOME/$RootDirectory/$Start$file";
6355 # Check whether this file should be visible
6356 next if $::BlockPathAccess &&
6357 "$::CGI_HOME/$RootDirectory/$Start$file" =~ m@$::BlockPathAccess@;
6359 if($file =~ m@$Pattern@)
6361 my $Date = localtime($^T - (-M "$::CGI_HOME/$RootDirectory/$Start$file")*3600*24);
6362 my $Size = -s "$::CGI_HOME/$RootDirectory/$Start$file";
6363 $Size = sprintf("%6.0F kB", $Size/1024);
6364 my $Type = `file $::CGI_HOME/$RootDirectory/$Start$file`;
6365 $Type =~ s@\s*$::CGI_HOME/$RootDirectory/$Start$file\s*\:\s*@@ig;
6366 chomp($Type);
6368 print "<li>";
6369 if($HTTPserver =~ /^\s*[\.\~]\s*$/)
6371 print "<a href='$RootDirectory/$Start$file'>";
6373 elsif($HTTPserver)
6375 print "<a href='$HTTPserver/$RootDirectory/$Start$file'>";
6377 printf("%-40s", "$file</a>") if $HTTPserver;
6378 printf("%-40s", "$file") unless $HTTPserver;
6379 print "\t$Size\t$Date\t$Type";
6380 print "</li>\n";
6383 print "</ul></pre>";
6385 return 1;
6389 # ListDocs(Pattern [,ListType])
6391 # usage:
6392 # <SCRIPT TYPE=text/ssperl>
6393 # CGIscriptor::ListDocs("/*", "dl");
6394 # </SCRIPT>
6396 # This subroutine is very usefull to manage collections of independent
6397 # documents. The resulting list will display the tree-like directory
6398 # structure. If this routine is too slow for online use, you can
6399 # store the result and use a link to that stored file.
6401 # List HTML and Text files with title and first header (HTML)
6402 # or filename and first meaningfull line (general text files).
6403 # The listing starts at the ServerRoot directory. Directories are
6404 # listed recursively.
6406 # You can change the list type (default is dl).
6407 # e.g.,
6408 # <dt><a href=<file.html>>title</a>
6409 # <dd>First Header
6410 # <dt><a href=<file.txt>>file.txt</a>
6411 # <dd>First meaningfull line of text
6413 sub ListDocs # ($Pattern [, prefix]) e.g., ("/Books/*", [, "dl"])
6415 my $Pattern = shift;
6416 $Pattern =~ /\*/;
6417 my $ListType = shift || "dl";
6418 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
6419 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
6420 my @FileList = glob("$::CGI_HOME$Pattern");
6421 my ($FileName, $Path, $Link);
6423 # Print List markers
6424 print "<$ListType>\n";
6426 # Glob all files
6427 File: foreach $FileName (@FileList)
6429 # Check whether this file should be visible
6430 next if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@;
6432 # Recursively list files in all directories
6433 if(-d $FileName)
6435 $FileName =~ m@([^/]*)$@;
6436 my $DirName = $1;
6437 print "<$Prefix>$DirName\n";
6438 $Pattern =~ m@([^/]*)$@;
6439 &ListDocs("$`$DirName/$1", $ListType);
6440 next;
6442 # Use textfiles
6443 elsif(-T "$FileName")
6445 open(TextFile, $FileName) || next;
6447 # Ignore all other file types
6448 else
6449 { next;};
6451 # Get file path for link
6452 $FileName =~ /$::CGI_HOME/;
6453 print "<$Prefix><a href=$URL_root$'>";
6454 # Initialize all variables
6455 my $Line = "";
6456 my $TitleFound = 0;
6457 my $Caption = "";
6458 my $Title = "";
6459 # Read file and step through
6460 while(<TextFile>)
6462 chop $_;
6463 $Line = $_;
6464 # HTML files
6465 if($FileName =~ /\.ht[a-zA-Z]*$/i)
6467 # Catch Title
6468 while(!$Title)
6470 if($Line =~ m@<title>([^<]*)</title>@i)
6472 $Title = $1;
6473 $Line = $';
6475 else
6477 $Line .= <TextFile> || goto Print;
6478 chop $Line;
6481 # Catch First Header
6482 while(!$Caption)
6484 if($Line =~ m@</h1>@i)
6486 $Caption = $`;
6487 $Line = $';
6488 $Caption =~ m@<h1>@i;
6489 $Caption = $';
6490 $Line = $`.$Caption.$Line;
6492 else
6494 $Line .= <TextFile> || goto Print;
6495 chop $Line;
6499 # Other text files
6500 else
6502 # Title equals file name
6503 $FileName =~ /([^\/]+)$/;
6504 $Title = $1;
6505 # Catch equals First Meaningfull line
6506 while(!$Caption)
6508 if($Line =~ /[A-Z]/ &&
6509 ($Line =~ /subject|title/i || $Line =~ /^[\w,\.\s\?\:]+$/)
6510 && $Line !~ /Newsgroup/ && $Line !~ /\:\s*$/)
6512 $Line =~ s/\<[^\>]+\>//g;
6513 $Caption = $Line;
6515 else
6517 $Line = <TextFile> || goto Print;
6521 Print: # Print title and subject
6522 print "$Title</a>\n";
6523 print "<dd>$Caption\n" if $ListType eq "dl";
6524 $TitleFound = 0;
6525 $Caption = "";
6526 close TextFile;
6527 next File;
6530 # Print Closing List Marker
6531 print "</$ListType>\n";
6532 ""; # Empty return value
6536 # HTMLdocTree(Pattern [,ListType])
6538 # usage:
6539 # <SCRIPT TYPE=text/ssperl>
6540 # CGIscriptor::HTMLdocTree("/Welcome.html", "dl");
6541 # </SCRIPT>
6543 # The following subroutine is very usefull for checking large document
6544 # trees. Starting from the root (s), it reads all files and prints out
6545 # a nested list of links to all attached files. Non-existing or misplaced
6546 # files are flagged. This is quite a file-i/o intensive routine
6547 # so you would not like it to be accessible to everyone. If you want to
6548 # use the result, save the whole resulting page to disk and use a link
6549 # to this file.
6551 # HTMLdocTree takes an HTML file or file pattern and constructs nested lists
6552 # with links to *local* files (i.e., only links to the local server are
6553 # followed). The list entries are the document titles.
6554 # If the list type is <dl>, the first <H1> header is used too.
6555 # For each file matching the pattern, a list is made recursively of all
6556 # HTML documents that are linked from it and are stored in the same directory
6557 # or a sub-directory. Warnings are given for missing files.
6558 # The listing starts for the ServerRoot directory.
6559 # You can change the default list type <dl> (<dl>, <ul>, <ol>).
6561 %LinkUsed = ();
6563 sub HTMLdocTree # ($Pattern [, listtype])
6564 # e.g., ("/Welcome.html", [, "ul"])
6566 my $Pattern = shift;
6567 my $ListType = shift || "dl";
6568 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
6569 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
6570 my ($Filename, $Path, $Link);
6571 my %LocalLinks = {};
6573 # Read files (glob them for expansion of wildcards)
6574 my @FileList = glob("$::CGI_HOME$Pattern");
6575 foreach $Path (@FileList)
6577 # Get URL_path
6578 $Path =~ /$::CGI_HOME/;
6579 my $URL_path = $';
6580 # Check whether this file should be visible
6581 next if $::BlockPathAccess && $URL_path =~ m@$::BlockPathAccess@;
6583 my $Title = $URL_path;
6584 my $Caption = "";
6585 # Current file should not be used again
6586 ++$LinkUsed{$URL_path};
6587 # Open HTML doc
6588 unless(open(TextFile, $Path))
6590 print "<$Prefix>$Title <blink>(not found)</blink><br>\n";
6591 next;
6593 while(<TextFile>)
6595 chop $_;
6596 $Line = $_;
6597 # Catch Title
6598 while($Line =~ m@<title>@i)
6600 if($Line =~ m@<title>([^<]*)</title>@i)
6602 $Title = $1;
6603 $Line = $';
6605 else
6607 $Line .= <TextFile>;
6608 chop $Line;
6611 # Catch First Header
6612 while(!$Caption && $Line =~ m@<h1>@i)
6614 if($Line =~ m@</h[1-9]>@i)
6616 $Caption = $`;
6617 $Line = $';
6618 $Caption =~ m@<h1>@i;
6619 $Caption = $';
6620 $Line = $`.$Caption.$Line;
6622 else
6624 $Line .= <TextFile>;
6625 chop $Line;
6628 # Catch and print Links
6629 while($Line =~ m@<a href\=([^>]*)>@i)
6631 $Link = $1;
6632 $Line = $';
6633 # Remove quotes
6634 $Link =~ s/\"//g;
6635 # Remove extras
6636 $Link =~ s/[\#\?].*$//g;
6637 # Remove Servername
6638 if($Link =~ m@(http://|^)@i)
6640 $Link = $';
6641 # Only build tree for current server
6642 next unless $Link =~ m@$::ENV{'SERVER_NAME'}|^/@;
6643 # Remove server name and port
6644 $Link =~ s@^[^\/]*@@g;
6646 # Store the current link
6647 next if $LinkUsed{$Link} || $Link eq $URL_path;
6648 ++$LinkUsed{$Link};
6649 ++$LocalLinks{$Link};
6653 close TextFile;
6654 print "<$Prefix>";
6655 print "<a href=http://";
6656 print "$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}$URL_path>";
6657 print "$Title</a>\n";
6658 print "<br>$Caption\n"
6659 if $Caption && $Caption ne $Title && $ListType =~ /dl/i;
6660 print "<$ListType>\n";
6661 foreach $Link (keys(%LocalLinks))
6663 &HTMLdocTree($Link, $ListType);
6665 print "</$ListType>\n";
6669 ###########################<<<<<<<<<<End Remove
6671 # Make require happy
6674 =head1 NAME
6676 CGIscriptor -
6678 =head1 DESCRIPTION
6680 A flexible HTML 4 compliant script/module for CGI-aware
6681 embeded Perl, shell-scripts, and other scripting languages,
6682 executed at the server side.
6684 =head1 README
6686 Executes embeded Perl code in HTML pages with easy
6687 access to CGI variables. Also processes embeded shell
6688 scripts and scripts in any other language with an
6689 interactive interpreter (e.g., in-line Python, Tcl,
6690 Ruby, Awk, Lisp, Xlispstat, Prolog, M4, R, REBOL, Praat,
6691 sh, bash, csh, ksh).
6693 CGIscriptor is very flexible and hides all the specifics
6694 and idiosyncrasies of correct output and CGI coding and naming.
6695 CGIscriptor complies with the W3C HTML 4.0 recommendations.
6697 This Perl program will run on any WWW server that runs
6698 Perl scripts, just add a line like the following to your
6699 srm.conf file (Apache example):
6701 ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
6703 URL's that refer to http://www.your.address/SHTML/... will
6704 now be handled by CGIscriptor.pl, which can use a private
6705 directory tree (default is the DOCUMENT_ROOT directory tree,
6706 but it can be anywhere).
6708 =head1 PREREQUISITES
6711 =head1 COREQUISITES
6714 =pod OSNAMES
6716 Linux, *BSD, *nix, MS WinXP
6718 =pod SCRIPT CATEGORIES
6720 Servers
6724 =cut