Corrected documentation
[CGIscriptor.git] / CGIscriptor.pl
blob077ba40147bfaa6b37e6298465c886f6b17842dc
1 #! /usr/bin/perl
3 # (configure the first line to contain YOUR path to perl 5.000+)
5 # CGIscriptor.pl
6 # Version 2.4
7 # 10 July 2012
9 # YOU NEED:
11 # perl 5.0 or higher (see: "http://www.perl.org/")
13 # Notes:
15 if(grep(/\-\-help/i, @ARGV))
17 print << 'ENDOFPREHELPTEXT1';
18 # CGIscriptor.pl is a Perl program will run on any WWW server that
19 # runs Perl scripts, just add a line like the following to your
20 # httpd.conf file (Apache example):
22 # ScriptAlias /SHTML/ "/real-path/CGIscriptor.pl/"
24 # URL's that refer to http://www.your.address/SHTML/... will now be handled
25 # by CGIscriptor.pl, which can use a private directory tree (default is the
26 # DOCUMENT_ROOT directory tree, but it can be anywhere, see below).
27 # NOTE: if you cannot use a ScriptAlias, there is a way to use .htaccess
28 # instead. See below.
30 # This file contains all documentation as comments. These comments
31 # can be removed to speed up loading (e.g., `egrep -v '^#' CGIscriptor.pl` >
32 # leanScriptor.pl). A bare bones version of CGIscriptor.pl, lacking
33 # documentation, most comments, access control, example functions etc.
34 # (but still with the copyright notice and some minimal documentation)
35 # can be obtained by calling CGIscriptor.pl with the '-slim'
36 # command line argument, e.g.,
37 # >CGIscriptor.pl -slim >slimCGIscriptor.pl
39 # CGIscriptor.pl can be run from the command line as
40 # `CGIscriptor.pl <path> <query>`, inside a perl script with
41 # 'do CGIscriptor.pl' after setting $ENV{PATH_INFO} and $ENV{QUERY_STRING},
42 # or CGIscriptor.pl can be loaded with 'require "/real-path/CGIscriptor.pl"'.
43 # In the latter case, requests are processed by 'Handle_Request();'
44 # (again after setting $ENV{PATH_INFO} and $ENV{QUERY_STRING}).
46 # The --help command line switch will print the manual.
48 # Running demo's and more information can be found at
49 # http://www.fon.hum.uva.nl/rob/OSS/OSS.html
51 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site
52 # or CPAN that can use CGIscriptor.pl as the base of a µWWW server and
53 # demonstrates its use.
55 ENDOFPREHELPTEXT1
58 # Configuration, copyright notice, and user manual follow the next
59 # (Changes) section.
61 ############################################################################
63 # Changes (document ALL changes with date, name and email here):
64 # 10 Feb 2014 - Added use of FAT fs and spaces in filenames
65 # 06 Feb 2014 - Corrected behavior of ACCEPT.lis and REJECT.lis
66 # 05 Apr 2013 - Renamed COOKIE_JAR to HTTP_COOKIE, added support for
67 # CGI::Cookie in case $ENV{HTTP_COOKIE} is undefined (untested)
68 # 31 Mar 2013 - Added support for Digest::SHA
69 # 13 Mar 2013 - Changed password hash
70 # 10 Jul 2012 - Version 2.4
71 # 11 Jun 2012 - Securing CGIvariable setting. Made
72 # 'if($ENV{QUERY_STRING} =~ /$name/)' into elsif in
73 # defineCGIvariable/List/Hash to give precedence to ENV{$name}
74 # This was a very old security bug. Added ProtectCGIvariable($name).
75 # 06 Jun 2012 - Added IP only session types after login.
76 # 31 May 2012 - Session ticket system added for handling login sessions.
77 # 29 May 2012 - CGIsafeFileName does not accept filenames starting with '.'
78 # 29 May 2012 - Added CGIscriptor::BrowseAllDirs to handle browsing directories
79 # correctly.
80 # 22 May 2012 - Added Access control with Session Tickets linked to
81 # IP Address and PATH_INFO.
82 # 21 May 2012 - Corrected the links generated by CGIscriptor::BrowseDirs
83 # Will link to current base URL when the HTTP server is '.' or '~'
84 # 29 Oct 2009 - Adapted David A. Wheeler's suggestion about filenames:
85 # CGIsafeFileName does not accept filenames starting with '-'
86 # (http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html)
87 # 08 Oct 2009 - Some corrections in the README.txt file, eg, new email address
88 # 28 Jan 2005 - Added a file selector to performTranslation.
89 # Changed %TranslationTable to @TranslationTable
90 # and patterns to lists.
91 # 27 Jan 2005 - Added a %TranslationTable with associated
92 # performTranslation(\$text) function to allow
93 # run changes in the web pages. Say, to translate
94 # legacy pages with <%=...%> delimiters to the new
95 # <SCRIPT TYPE=..></SCRIPT> format.
96 # 27 Jan 2005 - Small bug of extra '\n' in output removed from the
97 # Other Languages Code.
98 # 10 May 2004 - Belated upload of latest version (2.3) to CPAN
99 # 07 Oct 2003 - Corrected error '\s' -> '\\s' in rebol scripting
100 # language call
101 # 07 Oct 2003 - Corrected omitted INS tags in <DIV><INS> handling
102 # 20 May 2003 - Added a --help switch to print the manual.
103 # 06 Mar 2003 - Adapted the blurb at the end of the file.
104 # 03 Mar 2003 - Added a user definable dieHandler function to catch all
105 # "die" calls. Also "enhanced" the STDERR printout.
106 # 10 Feb 2003 - Split off the reading of the POST part of a query
107 # from Initialize_output. This was suggested by Gerd Franke
108 # to allow for the catching of the file_path using a
109 # POST based lookup. That is, he needed the POST part
110 # to change the file_path.
111 # 03 Feb 2003 - %{$name}; => %{$name} = (); in defineCGIvariableHash.
112 # 03 Feb 2003 - \1 better written as $1 in
113 # $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
114 # 29 Jan 2003 - This makes "CLASS="ssperl" CSS-compatible Gerd Franke
115 # added:
116 # $ServerScriptContentClass = "ssperl";
117 # changed in ProcessFile():
118 # unless(($CurrentContentType =~
119 # 28 Jan 2003 - Added 'INS' Tag! Gerd Franke
120 # 20 Dec 2002 - Removed useless $Directoryseparator variable.
121 # Update comments and documentation.
122 # 18 Dec 2002 - Corrected bug in Accept/Reject processing.
123 # Files didn't work.
124 # 24 Jul 2002 - Added .htaccess documentation (from Gerd Franke)
125 # Also added a note that RawFilePattern can be a
126 # complete file name.
127 # 19 Mar 2002 - Added SRC pseudo-files PREFIX and POSTFIX. These
128 # switch to prepending or to appending the content
129 # of the SRC attribute. Default is prefixing. You
130 # can add as many of these switches as you like.
131 # 13 Mar 2002 - Do not search for tag content if a tag closes with
132 # />, i.e., <DIV ... /> will be handled the XML/XHTML way.
133 # 25 Jan 2002 - Added 'curl' and 'snarf' to SRC attribute URL handling
134 # (replaces wget).
135 # 25 Jan 2002 - Found a bug in SAFEqx, now executes qx() in a scalar context
136 # (i.o. a list context). This is necessary for binary results.
137 # 24 Jan 2002 - Disambiguated -T $SRCfile to -T "$SRCfile" (and -e) and
138 # changed the order of if/elsif to allow removing these
139 # conditions in systems with broken -T functions.
140 # (I also removed a spurious ')' bracket)
141 # 17 Jan 2002 - Changed DIV tag SRC from <SOURCE> to sysread(SOURCE,...)
142 # to support binary files.
143 # 17 Jan 2002 - Removed WhiteSpace from $FileAllowedCharacters.
144 # 17 Jan 2002 - Allow "file://" prefix in SRC attribute. It is simply
145 # stipped from the path.
146 # 15 Jan 2002 - Version 2.2
147 # 15 Jan 2002 - Debugged and completed URL support (including
148 # CGIscriptor::read_url() function)
149 # 07 Jan 2002 - Added automatic (magic) URL support to the SRC attribute
150 # with the main::GET_URL function. Uses wget -O underlying.
151 # 04 Jan 2002 - Added initialization of $NewDirective in InsertForeignScript
152 # (i.e., my $NewDirective = "";) to clear old output
153 # (this was a realy anoying bug).
154 # 03 Jan 2002 - Added a <DIV CLASS='text/ssperl' ID='varname'></DIV>
155 # tags that assign the body text as-is (literally)
156 # to $varname. Allows standard HTML-tools to handle
157 # Cascading Style Sheet templates. This implements a
158 # design by Gerd Franke (franke@roo.de).
159 # 03 Jan 2002 - I finaly gave in and allowed SRC files to expand ~/.
160 # 12 Oct 2001 - Normalized spelling of "CGIsafFileName" in documentation.
161 # 09 Oct 2001 - Added $ENV{'CGI_BINARY_FILE'} to log files to
162 # detect unwanted indexing of TAR files by webcrawlers.
163 # 10 Sep 2001 - Added $YOUR_SCRIPTS directory to @INC for 'require'.
164 # 22 Aug 2001 - Added .txt (Content-type: text/plain) as a default
165 # processed file type. Was processed via BinaryMapFile.
166 # 31 May 2001 - Changed =~ inside CGIsafeEmailAddress that was buggy.
167 # 29 May 2001 - Updated $CGI_HOME to point to $ENV{DOCUMENT_ROOT} io
168 # the root of PATH_TRANSLATED. DOCUMENT_ROOT can now
169 # be manipulated to achieve a "Sub Root".
170 # NOTE: you can have $YOUR_HTML_FILES != DOCUMENT_ROOT
171 # 28 May 2001 - Changed CGIscriptor::BrowsDirs function for security
172 # and debugging (it now works).
173 # 21 May 2001 - defineCGIvariableHash will ADD values to existing
174 # hashes,instead of replacing existing hashes.
175 # 17 May 2001 - Interjected a '&' when pasting POST to GET data
176 # 24 Apr 2001 - Blocked direct requests for BinaryMapFile.
177 # 16 Aug 2000 - Added hash table extraction for CGI parameters with
178 # CGIparseValueHash (used with structured parameters).
179 # Use: CGI='%<CGI-partial-name>' (fill in your name in <>)
180 # Will collect all <CGI-partial-name><key>=value pairs in
181 # $<CGI-partial-name>{<key>} = value;
182 # 16 Aug 2000 - Adapted SAFEqx to protect @PARAMETER values.
183 # 09 Aug 2000 - Added support for non-filesystem input by way of
184 # the CGI_FILE_CONTENTS and CGI_DATA_ACCESS_CODE
185 # environment variables.
186 # 26 Jul 2000 - On the command-line, file-path '-' indicates STDIN.
187 # This allows CGIscriptor to be used in pipes.
188 # Default, $BLOCK_STDIN_HTTP_REQUEST=1 will block this
189 # in an HTTP request (i.e., in a web server).
190 # 26 Jul 2000 - Blocked 'Content-type: text/html' if the SERVER_PROTOCOL
191 # is not HTTP or another protocol. Changed the default
192 # source directory to DOCUMENT_ROOT (i.o. the incorrect
193 # SERVER_ROOT).
194 # 24 Jul 2000 - -slim Command-line argument added to remove all
195 # comments, security, etc.. Updated documentation.
196 # 05 Jul 2000 - Added IF and UNLESS attributes to make the
197 # execution of all <META> and <SCRIPT> code
198 # conditional.
199 # 05 Jul 2000 - Rewrote and isolated the code for extracting
200 # quoted items from CGI and SRC attributes.
201 # Now all attributes expect the same set of
202 # quotes: '', "", ``, (), {}, [] and the same
203 # preceded by a \, e.g., "\((aap)\)" will be
204 # extracted as "(aap)".
205 # 17 Jun 2000 - Construct @ARGV list directly in CGIexecute
206 # name-space (i.o. by evaluation) from
207 # CGI attributes to prevent interference with
208 # the processing for non perl scripts.
209 # Changed CGIparseValueList to prevent runaway
210 # loops.
211 # 16 Jun 2000 - Added a direct (interpolated) display mode
212 # (text/ssdisplay) and a user log mode
213 # (text/sslogfile).
214 # 06 Jun 2000 - Replace "print $Result" with a syswrite loop to
215 # allow large string output.
216 # 02 Jun 2000 - Corrected shrubCGIparameter($CGI_VALUE) to realy
217 # remove all control characters. Changed Interpreter
218 # initialization to shrub interpolated CGI parameters.
219 # Added 'text/ssmailto' interpreter script.
220 # 22 May 2000 - Changed some of the comments
221 # 09 May 2000 - Added list extraction for CGI parameters with
222 # CGIparseValueList (used with multiple selections).
223 # Use: CGI='@<CGI-parameter>' (fill in your name in <>)
224 # 09 May 2000 - Added a 'Not Present' condition to CGIparseValue.
225 # 27 Apr 2000 - Updated documentation to reflect changes.
226 # 27 Apr 2000 - SRC attribute "cleaned". Supported for external
227 # interpreters.
228 # 27 Apr 2000 - CGI attribute can be used in <SCRIPT> tag.
229 # 27 Apr 2000 - Gprolog, M4 support added.
230 # 26 Apr 2000 - Lisp (rep) support added.
231 # 20 Apr 2000 - Use of external interpreters now functional.
232 # 20 Apr 2000 - Removed bug from extracting Content types (RegExp)
233 # 10 Mar 2000 - Qualified unconditional removal of '#' that preclude
234 # the use of $#foo, i.e., I changed
235 # s/[^\\]\#[^\n\f\r]*([\n\f\r])/\1/g
236 # to
237 # s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/\1/g
238 # 03 Mar 2000 - Added a '$BlockPathAccess' variable to "hide"
239 # things like, e.g., CVS information in CVS subtrees
240 # 10 Feb 2000 - URLencode/URLdecode have been made case-insensitive
241 # 10 Feb 2000 - Added a BrowseDirs function (CGIscriptor package)
242 # 01 Feb 2000 - A BinaryMapFile in the ~/ directory has precedence
243 # over a "burried" BinaryMapFile.
244 # 04 Oct 1999 - Added two functions to check file names and email addresses
245 # (CGIscriptor::CGIsafeFileName and
246 # CGIscriptor::CGIsafeEmailAddress)
247 # 28 Sept 1999 - Corrected bug in sysread call for reading POST method
248 # to allow LONG posts.
249 # 28 Sept 1999 - Changed CGIparseValue to handle multipart/form-data.
250 # 29 July 1999 - Refer to BinaryMapFile from CGIscriptor directory, if
251 # this directory exists.
252 # 07 June 1999 - Limit file-pattern matching to LAST extension
253 # 04 June 1999 - Default text/html content type is printed only once.
254 # 18 May 1999 - Bug in replacement of ~/ and ./ removed.
255 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
256 # 15 May 1999 - Changed the name of the execute package to CGIexecute.
257 # Changed the processing of the Accept and Reject file.
258 # Added a full expression evaluation to Access Control.
259 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
260 # 27 Apr 1999 - Brought CGIscriptor under the GNU GPL. Made CGIscriptor
261 # Version 1.1 a module that can be called with 'require "CGIscriptor.pl"'.
262 # Requests are serviced by "Handle_Request()". CGIscriptor
263 # can still be called as a isolated perl script and a shell
264 # command.
265 # Changed the "factory default setting" so that it will run
266 # from the DOCUMENT_ROOT directory.
267 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
268 # 29 Mar 1999 - Remove second debugging STDERR switch. Moved most code
269 # to subroutines to change CGIscriptor into a module.
270 # Added mapping to process unsupported file types (e.g., binary
271 # pictures). See $BinaryMapFile.
272 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
273 # 24 Sept 1998 - Changed text of license (Rob van Son, R.J.J.H.vanSon@gmail.com)
274 # Removed a double setting of filepatterns and maximum query
275 # size. Changed email address. Removed some typos from the
276 # comments.
277 # 02 June 1998 - Bug fixed in URLdecode. Changing the foreach loop variable
278 # caused quiting CGIscriptor.(Rob van Son, R.J.J.H.vanSon@gmail.com)
279 # 02 June 1998 - $SS_PUB and $SS_SCRIPT inserted an extra /, removed.
280 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
283 # Known Bugs:
285 # 23 Mar 2000
286 # It is not possible to use operators or variables to construct variable names,
287 # e.g., $bar = \@{$foo}; won't work. However, eval('$bar = \@{'.$foo.'};');
288 # will indeed work. If someone could tell me why, I would be obliged.
291 ############################################################################
293 # OBLIGATORY USER CONFIGURATION
295 # Configure the directories where all user files can be found (this
296 # is the equivalent of the server root directory of a WWW-server).
297 # These directories can be located ANYWHERE. For security reasons, it is
298 # better to locate them outside the WWW-tree of your HTTP server, unless
299 # CGIscripter handles ALL requests.
301 # For convenience, the defaults are set to the root of the WWW server.
302 # However, this might not be safe!
304 # ~/ text files
305 # $YOUR_HTML_FILES = "/usr/pub/WWW/SHTML"; # or SS_PUB as environment var
306 # (patch to use the parent directory of CGIscriptor as document root, should be removed)
307 if($ENV{'SCRIPT_FILENAME'}) # && $ENV{'SCRIPT_FILENAME'} !~ /\Q$ENV{'DOCUMENT_ROOT'}\E/)
309 $ENV{'DOCUMENT_ROOT'} = $ENV{'SCRIPT_FILENAME'};
310 $ENV{'DOCUMENT_ROOT'} =~ s@/CGIscriptor.*$@@ig;
313 # Just enter your own directory path here
314 $YOUR_HTML_FILES = $ENV{'DOCUMENT_ROOT'}; # default is the DOCUMENT_ROOT
316 # ./ script files (recommended to be different from the previous)
317 # $YOUR_SCRIPTS = "/usr/pub/WWW/scripts"; # or SS_SCRIPT as environment var
318 $YOUR_SCRIPTS = $YOUR_HTML_FILES; # This might be a SECURITY RISK
320 # End of obligatory user configuration
321 # (note: there is more non-essential user configuration below)
323 ############################################################################
325 # OPTIONAL USER CONFIGURATION (all values are used CASE INSENSITIVE)
327 # Script content-types: TYPE="Content-type" (user defined mime-type)
328 $ServerScriptContentType = "text/ssperl"; # Server Side Perl scripts
329 # CSS require a simple class
330 $ServerScriptContentClass = $ServerScriptContentType =~ m!/! ?
331 $' : "ssperl"; # Server Side Perl CSS classes
333 $ShellScriptContentType = "text/osshell"; # OS shell scripts
334 # # (Server Side perl ``-execution)
336 # Run from FAT file systems (Windows) based on environment variable
337 $useFAT = $ENV{'USEFAT'};
338 # Accessible file patterns, block any request that doesn't match.
339 # Matches any file with the extension .(s)htm(l), .txt, or .xmr
340 # (\. is used in regexp)
341 # Note: die unless $PATH_INFO =~ m@($FilePattern)$@is;
342 $FilePattern = ".shtml|.htm|.html|.xml|.xmr|.txt|.js|.css";
344 # The table with the content type MIME types
345 # (allows to differentiate MIME types, if needed)
346 %ContentTypeTable =
348 '.html' => 'text/html',
349 '.shtml' => 'text/html',
350 '.htm' => 'text/html',
351 '.xml' => 'text/xml',
352 '.txt' => 'text/plain',
353 '.js' => 'text/plain',
354 '.css' => 'text/plain'
358 # File pattern post-processing
359 $FilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
361 # SHAsum command needed for Authorization and Login
362 # (note, these have to be accessible in the HTML pages, ie, the CGIexecute environment)
363 my $shasum = "shasum -a 256";
364 if(qx{uname} =~ /Darwin/)
366 $shasum = "shasum-5.12 -a 256" unless `which shasum`;
368 my $SHASUMCMD = $shasum.' |cut -f 1 -d" "';
369 $ENV{"SHASUMCMD"} = $SHASUMCMD;
370 my $RANDOMHASHCMD = 'dd bs=1 count=64 if=/dev/urandom 2>/dev/null | '.$shasum.' -b |cut -f 1 -d" "';
371 $ENV{"RANDOMHASHCMD"} = $RANDOMHASHCMD;
373 # Hash a string, return hex of hash
374 sub hash_string_cmd # ($string) -> hex_hash
376 my $string = shift || "";
377 # Catch nasty \'-quotes, embed them in '..'"'"'..'
378 $string =~ s/\'/\'\"\'\"\'/isg;
379 my $hash = `printf '%s' '$string'| $ENV{"SHASUMCMD"}`;
380 chomp($hash);
381 return $hash;
384 # Note that you CANNOT replace $RANDOMHASHCMD with a call using hash_string_cmd
385 # as the output of /dev/urandom breaks string handling in Perl.
386 # Generate random hex hash
387 sub get_random_hex_cmd # () -> hex
389 # Create Random Hash Salt
390 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $RANDOMHASHCMD | $!\n";
391 my $RANDOMSALT= <URANDOM>;
392 close(URANDOM);
393 chomp($RANDOMSALT);
395 return $RANDOMSALT;
399 # You can use Digest::SHA (SHA.pm), you need sha256_hex
400 # See http://search.cpan.org/~mshelor/Digest-SHA-5.84/lib/Digest/SHA.pm
401 # > sudo CPAN -i Digest
403 # The following code will check whether Digest::SHA is available and then
404 # use the appropriate function calls.
406 $shaDigestLoaded = (eval("require Digest::SHA;1;") eq "1") ? 1 : 0;
408 sub hash_string_Digest # ($string) -> hex_hash
410 my $string = shift || "";
411 my $digest = Digest::SHA::sha256_hex($string);
412 $string = $digest;
413 return $digest;
416 sub get_random_hex_Digest # () -> hex
418 my $randomstring = "";
419 # Create Random Hash Salt
420 open(URANDOM, "</dev/urandom") || die "/dev/urandom: $!\n";
421 read URANDOM, $randomstring, 64 || die "No random bytes read: $!\n";
422 close(URANDOM);
423 my $RANDOMSALT= hash_string_Digest($randomstring);
425 return $RANDOMSALT;
428 # The final functions
429 sub hash_string # ($string) -> hex_hash
431 if($shaDigestLoaded)
432 { return hash_string_Digest (@_) }
433 else
434 { return hash_string_cmd(@_);};
437 sub get_random_hex # () -> hex
439 if($shaDigestLoaded)
440 { return get_random_hex_Digest () }
441 else
442 { return get_random_hex_cmd();};
445 ######################################################################
447 # File patterns of files which are handled by session tickets.
448 %TicketRequiredPatterns = (
449 '^/Private(/|$)' => "Private/.Sessions\tPrivate/.Passwords\t/Private/Login.html\t+36000"
451 # Used to set cookies, only session cookies supported
452 my %SETCOOKIELIST = ();
453 my %CGI_Cookies = ();
454 # Parse the cookies if $ENV{'HTTP_COOKIE'} is defined, else use CGI::Cookie
455 # if it is available
456 sub Get_All_Cookies
458 $ENV{'HTTP_COOKIE'} = $ENV{'Cookie'} if defined($ENV{'Cookie'}) && !defined($ENV{'HTTP_COOKIE'});
460 if(defined($ENV{'HTTP_COOKIE'}))
462 my @CookieList = split(/[\;\s]+/, $ENV{'HTTP_COOKIE'});
463 foreach my $CookieEntry (@CookieList)
465 my ($k, $v) = split(/\=/, $CookieEntry);
466 # Add new cookie only if it does not already exist
467 $CGI_Cookies{$k} = $v unless exists($CGI_Cookies{$k}) && ($v eq "" || $v eq "-");
468 ($k, $v, $CookieEntry) = (0, 0, 0);
470 @CookieList = ();
471 $ENV{'Cookie'} = "" if defined($ENV{'Cookie'})
473 else
475 my $cookiesLoaded = (eval("require CGI::Cookie;1;") eq "1") ? 1 : 0;
476 if($cookiesLoaded)
478 %CGI_Cookies = fetch CGI::Cookie;
484 # Session Ticket Directory: Private/.Sessions
485 # Password Directory: Private/.Passwords
486 # Login page (url path): /Private/Login.html
487 # Expiration time (s): +3600
488 # +<seconds> = relative time <seconds> is absolute date-time
490 # Manage login
491 # Set up a valid ticket from a given text file
492 # Use from command line. DO NOT USE ONLINE
493 # Watch out for passwords that get stored in the history file
495 # perl CGIscriptor.pl --managelogin [options] [files]
496 # Options:
497 # salt={file or saltvalue}
498 # masterkey={file or plaintext}
499 # newmasterkey={file or plaintext}
500 # password={file or palintext}
502 # Followed by one or more file names.
503 # Options can be interspersed between filenames,
504 # e.g., password='plaintext'
505 # Note that passwords are only used once!
507 if($ARGV[0] =~ /^\-\-managelogin/i)
509 my @arguments = @ARGV;
510 shift(@arguments);
511 setup_ticket_file(@arguments);
512 # Should be run on the command line
513 exit;
518 # Raw files must contain their own Content-type (xmr <- x-multipart-replace).
519 # THIS IS A SUBSET OF THE FILES DEFINED IN $FilePattern
520 $RawFilePattern = ".xmr";
521 # (In principle, this could contain a full file specification, e.g.,
522 # ".xmr|relocated.html")
524 # Raw File pattern post-processing
525 $RawFilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
527 # Server protocols for which "Content-type: text/html\n\n" should be printed
528 # (you should not bother with these, except for HTTP, they are mostly imaginary)
529 $ContentTypeServerProtocols = 'HTTP|MAIL|MIME';
531 # Block access to all (sub-) paths and directories that match the
532 # following (URL) path (is used as:
533 # 'die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;' )
534 $BlockPathAccess = '/(CVS|\.git)/'; # Protect CVS and .git information
536 # All (blocked) other file-types can be mapped to a single "binary-file"
537 # processor (a kind of pseudo-file path). This can either be an error
538 # message (e.g., "illegal file") or contain a script that serves binary
539 # files.
540 # Note: the real file path wil be stored in $ENV{CGI_BINARY_FILE}.
541 $BinaryMapFile = "/BinaryMapFile.xmr";
542 # Allow for the addition of a CGIscriptor directory
543 # Note that a BinaryMapFile in the root "~/" directory has precedence
544 $BinaryMapFile = "/CGIscriptor".$BinaryMapFile
545 if ! -e "$YOUR_HTML_FILES".$BinaryMapFile
546 && -e "$YOUR_HTML_FILES/CGIscriptor".$BinaryMapFile;
549 # List of all characters that are allowed in file names and paths.
550 # All requests containing illegal characters are blocked. This
551 # blocks most tricks (e.g., adding "\000", "\n", or other control
552 # characters, also blocks URI's using %FF)
553 # THIS IS A SECURITY FEATURE
554 # (this is also used to parse filenames in SRC= features, note the
555 # '-quotes, they are essential)
556 $FileAllowedChars = '\w\.\~\/\:\*\?\-\ '; # Covers Unix and Mac, including spaces
558 # Maximum size of the Query (number of characters clients can send
559 # covers both GET & POST combined)
560 $MaximumQuerySize = 2**20 - 1; # = 2**14 - 1
563 # Embeded URL get function used in SRC attributes and CGIscriptor::read_url
564 # (returns a string with the PERL code to transfer the URL contents, e.g.,
565 # "SAFEqx(\'curl \"http://www.fon.hum.uva.nl\"\')")
566 # "SAFEqx(\'wget --quiet --output-document=- \"http://www.fon.hum.uva.nl\"\')")
567 # Be sure to handle <BASE HREF='URL'> and allow BOTH
568 # direct printing GET_URL($URL [, 0]) and extracting the content of
569 # the $URL for post-processing GET_URL($URL, 1).
570 # You get the WHOLE file, including HTML header.
571 # The shell command Use $URL where the URL should go
572 # ('wget', 'snarf' or 'curl', uncomment the one you would like to use)
573 my $GET_URL_shell_command = 'wget --quiet --output-document=- $URL';
574 #my $GET_URL_shell_command = 'snarf $URL -';
575 #my $GET_URL_shell_command = 'curl $URL';
577 sub GET_URL # ($URL, $ValueNotPrint) -> content_of_url
579 my $URL = shift || return;
580 my $ValueNotPrint = shift || 0;
582 # Check URL for illegal characters
583 return "print '<h1>Illegal URL<h1>'\"\n\";" if $URL =~ /[^$FileAllowedChars\%]/;
585 # Include URL in final command
586 my $CurrentCommand = $GET_URL_shell_command;
587 $CurrentCommand =~ s/\$URL/$URL/g;
589 # Print to STDOUT or return a value
590 my $BlockPrint = "print STDOUT ";
591 $BlockPrint = "" if $ValueNotPrint;
593 my $Commands = <<"GETURLCODE";
594 # Get URL
596 my \$Page = "";
598 # Simple, using shell command
599 \$Page = SAFEqx('$CurrentCommand');
601 # Add a BASE tage to the header
602 \$Page =~ s!\\</head!\\<base href='$URL'\\>\\</head!ig unless \$Page =~ m!\\<base!;
604 # Print the URL value, or return it as a value
605 $BlockPrint\$Page;
607 GETURLCODE
608 return $Commands;
611 # As files can get rather large (and binary), you might want to use
612 # some more intelligent reading procedure, e.g.,
613 # Direct Perl
614 # # open(URLHANDLE, '/usr/bin/wget --quiet --output-document=- "$URL"|') || die "wget: \$!";
615 # #open(URLHANDLE, '/usr/bin/snarf "$URL" -|') || die "snarf: \$!";
616 # open(URLHANDLE, '/usr/bin/curl "$URL"|') || die "curl: \$!";
617 # my \$text = "";
618 # while(sysread(URLHANDLE,\$text, 1024) > 0)
620 # \$Page .= \$text;
621 # };
622 # close(URLHANDLE) || die "\$!";
623 # However, this doesn't work with the CGIexecute->evaluate() function.
624 # You get an error: 'No child processes at (eval 16) line 15, <file0> line 8.'
626 # You can forget the next two variables, they are only needed when
627 # you don't want to use a regular file system (i.e., with open)
628 # but use some kind of database/RAM image for accessing (generating)
629 # the data.
631 # Name of the environment variable that contains the file contents
632 # when reading directly from Database/RAM. When this environment variable,
633 # $ENV{$CGI_FILE_CONTENTS}, is not false, no real file will be read.
634 $CGI_FILE_CONTENTS = 'CGI_FILE_CONTENTS';
635 # Uncomment the following if you want to force the use of the data access code
636 # $ENV{$CGI_FILE_CONTENTS} = '-'; # Force use of $ENV{$CGI_DATA_ACCESS_CODE}
638 # Name of the environment variable that contains the RAM access perl
639 # code needed to read additional "files", i.e.,
640 # $ENV{$CGI_FILE_CONTENTS} = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
641 # When $ENV{$CGI_FILE_CONTENTS} eq '-', this code is executed to generate the data.
642 $CGI_DATA_ACCESS_CODE = 'CGI_DATA_ACCESS_CODE';
644 # You can, of course, fill this yourself, e.g.,
645 # $ENV{$CGI_DATA_ACCESS_CODE} =
646 # 'open(INPUT, "<$_[0]"); while(<INPUT>){print;};close(INPUT);'
649 # DEBUGGING
651 # Suppress error messages, this can be changed for debugging or error-logging
652 #open(STDERR, "/dev/null"); # (comment out for use in debugging)
654 # SPECIAL: Remove Comments, security, etc. if the command line is
655 # '>CGIscriptor.pl -slim >slimCGIscriptor.pl'
656 $TrimDownCGIscriptor = 1 if $ARGV[0] =~ /^\-slim/i;
658 # If CGIscriptor is used from the command line, the command line
659 # arguments are interpreted as the file (1st) and the Query String (rest).
660 # Get the arguments
661 $ENV{'PATH_INFO'} = shift(@ARGV) unless exists($ENV{'PATH_INFO'}) || grep(/\-\-help/i, @ARGV);
662 $ENV{'QUERY_STRING'} = join("&", @ARGV) unless exists($ENV{'QUERY_STRING'});
665 # Handle bail-outs in a user definable way.
666 # Catch Die and replace it with your own function.
667 # Ends with a call to "die $_[0];"
669 sub dieHandler # ($ErrorCode, "Message", @_) -> DEAD
671 my $ErrorCode = shift;
672 my $ErrorMessage = shift;
674 # Place your own reporting functions here
676 # Now, kill everything (default)
677 print STDERR "$ErrorCode: $ErrorMessage\n";
678 die $ErrorMessage;
682 # End of optional user configuration
683 # (note: there is more non-essential user configuration below)
685 if(grep(/\-\-help/i, @ARGV))
687 print << 'ENDOFPREHELPTEXT2';
689 ###############################################################################
691 # Author and Copyright (c):
692 # Rob van Son, © 1995,1996,1997,1998,1999,2000,2001,2002-2012
693 # NKI-AVL Amsterdam
694 # r.v.son@nki.nl
695 # Institute of Phonetic Sciences & IFOTT/ACLS
696 # University of Amsterdam
697 # Email: R.J.J.H.vanSon@gmail.com
698 # Email: R.J.J.H.vanSon@gmail.com
699 # WWW : http://www.fon.hum.uva.nl/rob/
701 # License for use and disclaimers
703 # CGIscriptor merges plain ASCII HTML files transparantly
704 # with CGI variables, in-line PERL code, shell commands,
705 # and executable scripts in other scripting languages.
707 # This program is free software; you can redistribute it and/or
708 # modify it under the terms of the GNU General Public License
709 # as published by the Free Software Foundation; either version 2
710 # of the License, or (at your option) any later version.
712 # This program is distributed in the hope that it will be useful,
713 # but WITHOUT ANY WARRANTY; without even the implied warranty of
714 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
715 # GNU General Public License for more details.
717 # You should have received a copy of the GNU General Public License
718 # along with this program; if not, write to the Free Software
719 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
722 # Contributors:
723 # Rob van Son (R.J.J.H.vanSon@gmail.com)
724 # Gerd Franke franke@roo.de (designed the <DIV> behaviour)
726 #######################################################
727 ENDOFPREHELPTEXT2
729 #######################################################>>>>>>>>>>Start Remove
731 # You can skip the following code, it is an auto-splice
732 # procedure.
734 # Construct a slimmed down version of CGIscriptor
735 # (i.e., CGIscriptor.pl -slim > slimCGIscriptor.pl)
737 if($TrimDownCGIscriptor)
739 open(CGISCRIPTOR, "<CGIscriptor.pl")
740 || dieHandler(1, "<CGIscriptor.pl not slimmed down: $!\n");
741 my $SKIPtext = 0;
742 my $SKIPComments = 0;
744 while(<CGISCRIPTOR>)
746 my $SKIPline = 0;
748 ++$LineCount;
750 # Start of SKIP text
751 $SKIPtext = 1 if /[\>]{10}Start Remove/;
752 $SKIPComments = 1 if $SKIPtext == 1;
754 # Skip this line?
755 $SKIPline = 1 if $SKIPtext || ($SKIPComments && /^\s*\#/);
757 ++$PrintCount unless $SKIPline;
759 print STDOUT $_ unless $SKIPline;
761 # End of SKIP text ?
762 $SKIPtext = 0 if /[\<]{10}End Remove/;
764 # Ready!
765 print STDERR "\# Printed $PrintCount out of $LineCount lines\n";
766 exit;
769 #######################################################
771 if(grep(/\-\-help/i, @ARGV))
773 print << 'ENDOFHELPTEXT';
775 # HYPE
777 # CGIscriptor merges plain ASCII HTML files transparantly and safely
778 # with CGI variables, in-line PERL code, shell commands, and executable
779 # scripts in many languages (on-line and real-time). It combines the
780 # "ease of use" of HTML files with the versatillity of specialized
781 # scripts and PERL programs. It hides all the specifics and
782 # idiosyncrasies of correct output and CGI coding and naming. Scripts
783 # do not have to be aware of HTML, HTTP, or CGI conventions just as HTML
784 # files can be ignorant of scripts and the associated values. CGIscriptor
785 # complies with the W3C HTML 4.0 recommendations.
786 # In addition to its use as a WWW embeded CGI processor, it can
787 # be used as a command-line document preprocessor (text-filter).
789 # THIS IS HOW IT WORKS
791 # The aim of CGIscriptor is to execute "plain" scripts inside a text file
792 # using any required CGIparameters and environment variables. It
793 # is optimized to transparantly process HTML files inside a WWW server.
794 # The native language is Perl, but many other scripting languages
795 # can be used.
797 # CGIscriptor reads text files from the requested input file (i.e., from
798 # $YOUR_HTML_FILES$PATH_INFO) and writes them to <STDOUT> (i.e., the
799 # client requesting the service) preceded by the obligatory
800 # "Content-type: text/html\n\n" or "Content-type: text/plain\n\n" string
801 # (except for "raw" files which supply their own Content-type message
802 # and only if the SERVER_PROTOCOL supports HTTP, MAIL, or MIME).
804 # When CGIscriptor encounters an embedded script, indicated by an HTML4 tag
806 # <SCRIPT TYPE="text/ssperl" [CGI="$VAR='default value'"] [SRC="ScriptSource"]>
807 # PERL script
808 # </SCRIPT>
810 # or
812 # <SCRIPT TYPE="text/osshell" [CGI="$name='default value'"] [SRC="ScriptSource"]>
813 # OS Shell script
814 # </SCRIPT>
816 # construct (anything between []-brackets is optional, other MIME-types
817 # and scripting languages are supported), the embedded script is removed
818 # and both the contents of the source file (i.e., "do 'ScriptSource'")
819 # AND the script are evaluated as a PERL program (i.e., by eval()),
820 # shell script (i.e., by a "safe" version of `Command`, qx) or an external
821 # interpreter. The output of the eval() function takes the place of the
822 # original <SCRIPT></SCRIPT> construct in the output string. Any CGI
823 # parameters declared by the CGI attribute are available as simple perl
824 # variables, and can subsequently be made available as variables to other
825 # scripting languages (e.g., bash, python, or lisp).
827 # Example: printing "Hello World"
828 # <HTML><HEAD><TITLE>Hello World</TITLE>
829 # <BODY>
830 # <H1><SCRIPT TYPE="text/ssperl">"Hello World"</SCRIPT></H1>
831 # </BODY></HTML>
833 # Save this in a file, hello.html, in the directory you indicated with
834 # $YOUR_HTML_FILES and access http://your_server/SHTML/hello.html
835 # (or to whatever name you use as an alias for CGIscriptor.pl).
836 # This is realy ALL you need to do to get going.
838 # You can use any values that are delivered in CGI-compliant form (i.e.,
839 # the "?name=value" type URL additions) transparently as "$name" variables
840 # in your scripts IFF you have declared them in the CGI attribute of
841 # a META or SCRIPT tag before e.g.:
842 # <META CONTENT="text/ssperl; CGI='$name = `default value`'
843 # [SRC='ScriptSource']">
844 # or
845 # <SCRIPT TYPE="text/ssperl" CGI="$name = 'default value'"
846 # [SRC='ScriptSource']>
847 # After such a 'CGI' attribute, you can use $name as an ordinary PERL variable
848 # (the ScriptSource file is immediately evaluated with "do 'ScriptSource'").
849 # The CGIscriptor script allows you to write ordinary HTML files which will
850 # include dynamic CGI aware (run time) features, such as on-line answers
851 # to specific CGI requests, queries, or the results of calculations.
853 # For example, if you wanted to answer questions of clients, you could write
854 # a Perl program called "Answer.pl" with a function "AnswerQuestion()"
855 # that prints out the answer to requests given as arguments. You then write
856 # an HTML page "Respond.html" containing the following fragment:
858 # <center>
859 # The Answer to your question
860 # <META CONTENT="text/ssperl; CGI='$Question'">
861 # <h3><SCRIPT TYPE="text/ssperl">$Question</SCRIPT></h3>
862 # is
863 # <h3><SCRIPT TYPE="text/ssperl" SRC="./PATH/Answer.pl">
864 # AnswerQuestion($Question);
865 # </SCRIPT></h3>
866 # </center>
867 # <FORM ACTION=Respond.html METHOD=GET>
868 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
869 # <INPUT TYPE=SUBMIT VALUE="Ask">
870 # </FORM>
872 # The output could look like the following (in HTML-speak):
874 # <CENTER>
875 # The Answer to your question
876 # <h3>What is the capital of the Netherlands?</h3>
877 # is
878 # <h3>Amsterdam</h3>
879 # </CENTER>
880 # <FORM ACTION=Respond.html METHOD=GET>
881 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
882 # <INPUT TYPE=SUBMIT VALUE="Ask">
884 # Note that the function "Answer.pl" does know nothing about CGI or HTML,
885 # it just prints out answers to arguments. Likewise, the text has no
886 # provisions for scripts or CGI like constructs. Also, it is completely
887 # trivial to extend this "program" to use the "Answer" later in the page
888 # to call up other information or pictures/sounds. The final text never
889 # shows any cue as to what the original "source" looked like, i.e.,
890 # where you store your scripts and how they are called.
892 # There are some extra's. The argument of the files called in a SRC= tag
893 # can access the CGI variables declared in the preceding META tag from
894 # the @ARGV array. Executable files are called as:
895 # `file '$ARGV[0]' ... ` (e.g., `Answer.pl \'$Question\'`;)
896 # The files called from SRC can even be (CGIscriptor) html files which are
897 # processed in-line. Furthermore, the SRC= tag can contain a perl block
898 # that is evaluated. That is,
899 # <META CONTENT="text/ssperl; CGI='$Question' SRC='{$Question}'">
900 # will result in the evaluation of "print do {$Question};" and the VALUE
901 # of $Question will be printed. Note that these "SRC-blocks" can be
902 # preceded and followed by other file names, but only a single block is
903 # allowed in a SRC= tag.
905 # One of the major hassles of dynamic WWW pages is the fact that several
906 # mutually incompatible browsers and platforms must be supported. For example,
907 # the way sound is played automatically is different for Netscape and
908 # Internet Explorer, and for each browser it is different again on
909 # Unix, MacOS, and Windows. Realy dangerous is processing user-supplied
910 # (form-) values to construct email addresses, file names, or database
911 # queries. All Apache WWW-server exploits reported in the media are
912 # based on faulty CGI-scripts that didn't check their user-data properly.
914 # There is no panacee for these problems, but a lot of work and problems
915 # can be saved by allowing easy and transparent control over which
916 # <SCRIPT></SCRIPT> blocks are executed on what CGI-data. CGIscriptor
917 # supplies such a method in the form of a pair of attributes:
918 # IF='...condition..' and UNLESS='...condition...'. When added to a
919 # script tag, the whole block (including the SRC attribute) will be
920 # ignored if the condition is false (IF) or true (UNLESS).
921 # For example, the following block will NOT be evaluated if the value
922 # of the CGI variable FILENAME is NOT a valid filename:
924 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
925 # IF='CGIscriptor::CGIsafeFileName($FILENAME)'>
926 # .....
927 # </SCRIPT>
929 # (the function CGIsafeFileName(String) returns an empty string ("")
930 # if the String argument is not a valid filename).
931 # The UNLESS attribute is the mirror image of IF.
933 # A user manual follows the HTML 4 and security paragraphs below.
935 ##########################################################################
937 # HTML 4 compliance
939 # In general, CGIscriptor.pl complies with the HTML 4 recommendations of
940 # the W3C. This means that any software to manage Web sites will be able
941 # to handle CGIscriptor files, as will web agents.
943 # All script code should be placed between <SCRIPT></SCRIPT> tags, the
944 # script type is indicated with TYPE="mime-type", the LANGUAGE
945 # feature is ignored, and a SRC feature is implemented. All CGI specific
946 # features are delegated to the CGI attribute.
948 # However, the behavior deviates from the W3C recommendations at some
949 # points. Most notably:
950 # 0- The scripts are executed at the server side, invissible to the
951 # client (i.e., the browser)
952 # 1- The mime-types are personal and idiosyncratic, but can be adapted.
953 # 2- Code in the body of a <SCRIPT></SCRIPT> tag-pair is still evaluated
954 # when a SRC feature is present.
955 # 3- The SRC attribute reads a list of files.
956 # 4- The files in a SRC attribute are processed according to file type.
957 # 5- The SRC attribute evaluates inline Perl code.
958 # 6- Processed META, DIV, INS tags are removed from the output
959 # document.
960 # 7- All attributes of the processed META tags, except CONTENT, are ignored
961 # (i.e., deleted from the output).
962 # 8- META tags can be placed ANYWHERE in the document.
963 # 9- Through the SRC feature, META tags can have visible output in the
964 # document.
965 # 10- The CGI attribute that declares CGI parameters, can be used
966 # inside the <SCRIPT> tag.
967 # 11- Use of an extended quote set, i.e., '', "", ``, (), {}, []
968 # and their \-slashed combinations: \'\', \"\", \`\`, \(\),
969 # \{\}, \[\].
970 # 12- IF and UNLESS attributes to <SCRIPT>, <META>, <DIV>, <INS> tags.
971 # 13- <DIV> tags cannot be nested, DIV tags are not
972 # rendered with new-lines.
973 # 14- The XML style <TAG .... /> is recognized and handled correctly.
974 # (i.e., no content is processed)
976 # The reasons for these choices are:
977 # You can still write completely HTML4 compliant documents. CGIscriptor
978 # will not force you to write "deviant" code. However, it allows you to
979 # do so (which is, in fact, just as bad). The prime design principle
980 # was to allow users to include plain Perl code. The code itself should
981 # be "enhancement free". Therefore, extra features were needed to
982 # supply easy access to CGI and Web site components. For security
983 # reasons these have to be declared explicitly. The SRC feature
984 # transparently manages access to external files, especially the safe
985 # use of executable files.
986 # The CGI attribute handles the declarations of external (CGI) variables
987 # in the SCRIPT and META tag's.
988 # EVERYTHING THE CGI ATTRIBUTE AND THE META TAG DO CAN BE DONE INSIDE
989 # A <SCRIPT></SCRIPT> TAG CONSTRUCT.
991 # The reason for the IF, UNLESS, and SRC attributes (and their Perl code
992 # evaluation) were build into the META and SCRIPT tags is part laziness,
993 # part security. The SRC blocks allows more compact documents and easier
994 # debugging. The values of the CGI variables can be immediately screened
995 # for security by IF or UNLESS conditions, and even SRC attributes (e.g.,
996 # email addresses and file names), and a few commands can be called
997 # without having to add another Perl TAG pair. This is especially important
998 # for documents that require the use of other (more restricted) "scripting"
999 # languages and facilities that lag transparent control structures.
1001 ##########################################################################
1003 # SECURITY
1005 # Your WWW site is a few keystrokes away from a few hundred million internet
1006 # users. A fair percentage of these users knows more about your computer
1007 # than you do. And some of these just might have bad intentions.
1009 # To ensure uncompromized operation of your server and platform, several
1010 # features are incorporated in CGIscriptor.pl to enhance security.
1011 # First of all, you should check the source of this program. No security
1012 # measures will help you when you download programs from anonymous sources.
1013 # If you want to use THIS file, please make sure that it is uncompromized.
1014 # The best way to do this is to contact the source and try to determine
1015 # whether s/he is reliable (and accountable).
1017 # BE AWARE THAT ANY PROGRAMMER CAN CHANGE THIS PROGRAM IN SUCH A WAY THAT
1018 # IT WILL SET THE DOORS TO YOUR SYSTEM WIDE OPEN
1020 # I would like to ask any user who finds bugs that could compromise
1021 # security to report them to me (and any other bug too,
1022 # Email: R.J.J.H.vanSon@gmail.com or ifa@hum.uva.nl).
1024 # Security features
1026 # 1 Invisibility
1027 # The inner workings of the HTML source files are completely hidden
1028 # from the client. Only the HTTP header and the ever changing content
1029 # of the output distinguish it from the output of a plain, fixed HTML
1030 # file. Names, structures, and arguments of the "embedded" scripts
1031 # are invisible to the client. Error output is suppressed except
1032 # during debugging (user configurable).
1034 # 2 Separate directory trees
1035 # Directories containing Inline text and script files can reside on
1036 # separate trees, distinct from those of the HTTP server. This means
1037 # that NEITHER the text files, NOR the script files can be read by
1038 # clients other than through CGIscriptor.pl, UNLESS they are
1039 # EXPLICITELY made available.
1041 # 3 Requests are NEVER "evaluated"
1042 # All client supplied values are used as literal values (''-quoted).
1043 # Client supplied ''-quotes are ALWAYS removed. Therefore, as long as the
1044 # embedded scripts do NOT themselves evaluate these values, clients CANNOT
1045 # supply executable commands. Be sure to AVOID scripts like:
1047 # <META CONTENT="text/ssperl; CGI='$UserValue'">
1048 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 $UserValue`;</SCRIPT>
1050 # These are a recipe for disaster. However, the following quoted
1051 # form should be save (but is still not adviced):
1053 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 \'$UserValue\'`;</SCRIPT>
1055 # A special function, SAFEqx(), will automatically do exactly this,
1056 # e.g., SAFEqx('ls -1 $UserValue') will execute `ls -1 \'$UserValue\'`
1057 # with $UserValue interpolated. I recommend to use SAFEqx() instead
1058 # of backticks whenever you can. The OS shell scripts inside
1060 # <SCRIPT TYPE="text/osshell">ls -1 $UserValue</SCRIPT>
1062 # are handeld by SAFEqx and automatically ''-quoted.
1064 # 4 Logging of requests
1065 # All requests can be logged separate from the Host server. The level of
1066 # detail is user configurable: Including or excluding the actual queries.
1067 # This allows for the inspection of (im-) proper use.
1069 # 5 Access control: Clients
1070 # The Remote addresses can be checked against a list of authorized
1071 # (i.e., accepted) or non-authorized (i.e., rejected) clients. Both
1072 # REMOTE_HOST and REMOTE_ADDR are tested so clients without a proper
1073 # HOST name can be (in-) excluded by their IP-address. Client patterns
1074 # containing all numbers and dots are considered IP-addresses, all others
1075 # domain names. No wild-cards or regexp's are allowed, only partial
1076 # addresses.
1077 # Matching of names is done from the back to the front (domain first,
1078 # i.e., $REMOTE_HOST =~ /\Q$pattern\E$/is), so including ".edu" will
1079 # accept or reject all clients from the domain EDU. Matching of
1080 # IP-addresses is done from the front to the back (domain first, i.e.,
1081 # $REMOTE_ADDR =~ /^\Q$pattern\E/is), so including "128." will (in-)
1082 # exclude all clients whose IP-address starts with 128.
1083 # There are two special symbols: "-" matches HOSTs with no name and "*"
1084 # matches ALL HOSTS/clients.
1085 # For those needing more expressional power, lines starting with
1086 # "-e" are evaluated by the perl eval() function. E.g.,
1087 # '-e $REMOTE_HOST =~ /\.edu$/is;' will accept/reject clients from the
1088 # domain '.edu'.
1090 # 6 Access control: Files
1091 # In principle, CGIscriptor could read ANY file in the directory
1092 # tree as discussed in 1. However, for security reasons this is
1093 # restricted to text files. It can be made more restricted by entering
1094 # a global file pattern (e.g., ".html"). This is done by default.
1095 # For each client requesting access, the file pattern(s) can be made
1096 # more restrictive than the global pattern by entering client specific
1097 # file patterns in the Access Control files (see 5).
1098 # For example: if the ACCEPT file contained the lines
1099 # * DEMO
1100 # .hum.uva.nl LET
1101 # 145.18.230.
1102 # Then all clients could request paths containing "DEMO" or "demo", e.g.
1103 # "/my/demo/file.html" ($PATH_INFO =~ /\Q$pattern\E/), Clients from
1104 # *.hum.uva.nl could also request paths containing "LET or "let", e.g.
1105 # "/my/let/file.html", and clients from the local cluster
1106 # 145.18.230.[0-9]+ could access ALL files.
1107 # Again, for those needing more expressional power, lines starting with
1108 # "-e" are evaluated. For instance:
1109 # '-e $REMOTE_HOST =~ /\.edu$/is && $PATH_INFO =~ m@/DEMO/@is;'
1110 # will accept/reject requests for files from the directory "/demo/" from
1111 # clients from the domain '.edu'.
1112 # Path selections starting with ! or 'not' will be inverted. That is:
1113 # * not .wav
1114 # Will match all file and path names that do NOT contain '.wav'
1116 # 7 Access control: Server side session tickets
1117 # Specific paths can be controlled by Session Tickets which must be
1118 # present as a SESSIONTICKET=<value> CGI variable in the request. These paths
1119 # are defined in %TicketRequiredPatterns as pairs of:
1120 # ('regexp' => 'SessionPath\tPasswordPath\tLogin.html\tExpiration').
1121 # Session Tickets are stored in a separate directory (SessionPath, e.g.,
1122 # "Private/.Session") as files with the exact same name of the SESSIONTICKET
1123 # CGI. The following is an example:
1124 # Type: SESSION
1125 # IPaddress: 127.0.0.1
1126 # AllowedPaths: ^/Private/Name/
1127 # Expires: 3600
1128 # Username: test
1129 # ...
1130 # Other content can follow.
1132 # It is adviced that Session Tickets should be deleted
1133 # after some (idle) time. The IP address should be the IP number at login, and
1134 # the SESSIONTICKET will be rejected if it is presented from another IP address.
1135 # AllowedPaths and DeniedPaths are perl regexps. Be careful how they match. Make sure to delimit
1136 # the names to prevent access to overlapping names, eg, "^/Private/Rob" will also
1137 # match "^/Private/Robert", however, "^/Private/Rob/" will not. Expires is the
1138 # time the ticket will remain valid after creation (file ctime). Time can be given
1139 # in s[econds] (default), m[inutes], h[hours], or d[ays], eg, "24h" means 24 hours.
1140 # None of these need be present, but the Ticket must have a non-zero size.
1142 # Next to Session Tickets, there are two other type of ticket files:
1143 # - LOGIN tickets store information about a current login request
1144 # - PASSWORD ticket store account information to authorize login requests
1146 # 8 Query length limiting
1147 # The length of the Query string can be limited. If CONTENT_LENGTH is larger
1148 # than this limit, the request is rejected. The combined length of the
1149 # Query string and the POST input is checked before any processing is done.
1150 # This will prevent clients from overloading the scripts.
1151 # The actual, combined, Query Size is accessible as a variable through
1152 # $CGI_Content_Length.
1154 # 9 Illegal filenames, paths, and protected directories
1155 # One of the primary security concerns in handling CGI-scripts is the
1156 # use of "funny" characters in the requests that con scripts in executing
1157 # malicious commands. Examples are inserting ';', null bytes, or <newline>
1158 # characters in URL's and filenames, followed by executable commands. A
1159 # special variable $FileAllowedChars stores a string of all allowed
1160 # characters. Any request that translates to a filename with a character
1161 # OUTSIDE this set will be rejected.
1162 # In general, all (readable text) files in the DocumentRoot tree are accessible.
1163 # Default, executable files are rejected, but this can be reversed by setting
1164 # the environment variable $ENV{USEFAT}=1 ($useFAT = 1). This allows using
1165 # CGIscriptor on MS FAT filesystems.
1166 # This might not be what you want. For instance, your DocumentRoot directory
1167 # might be the working directory of a CVS project and contain sensitive
1168 # information (e.g., the password to get to the repository). You can block
1169 # access to these subdirectories by adding the corresponding patterns to
1170 # the $BlockPathAccess variable. For instance, $BlockPathAccess = '/CVS/'
1171 # will block any request that contains '/CVS/' or:
1172 # die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;
1174 #10 The execution of code blocks can be controlled in a transparent way
1175 # by adding IF or UNLESS conditions in the tags themselves. That is,
1176 # a simple check of the validity of filenames or email addresses can
1177 # be done before any code is executed.
1179 ###############################################################################
1181 # USER MANUAL (sort of)
1183 # CGIscriptor removes embedded scripts, indicated by an HTML 4 type
1184 # <SCRIPT TYPE='text/ssperl'> </SCRIPT> or <SCRIPT TYPE='text/osshell'>
1185 # </SCRIPT> constructs. CGIscriptor also recognizes XML-type
1186 # <SCRIPT TYPE='text/ssperl'/> constructs. These are usefull when
1187 # the necessary code is already available in the TAG itself (e.g.,
1188 # using external files). The contents of the directive are executed by
1189 # the PERL eval() and `` functions (in a separate name space). The
1190 # result of the eval() function replaces the <SCRIPT> </SCRIPT> construct
1191 # in the output file. You can use the values that are delivered in
1192 # CGI-compliant form (i.e., the "?name=value&.." type URL additions)
1193 # transparently as "$name" variables in your directives after they are
1194 # defined in a <META> or <SCRIPT> tag.
1195 # If you define the variable "$CGIscriptorResults" in a CGI attribute, all
1196 # subsequent <SCRIPT> and <META> results (including the defining
1197 # tag) will also be pushed onto a stack: @CGIscriptorResults. This list
1198 # behaves like any other, ordinary list and can be manipulated.
1200 # Both GET and POST requests are accepted. These two methods are treated
1201 # equal. Variables, i.e., those values that are determined when a file is
1202 # processed, are indicated in the CGI attribute by $<name> or $<name>=<default>
1203 # in which <name> is the name of the variable and <default> is the value
1204 # used when there is NO current CGI value for <name> (you can use
1205 # white-spaces in $<name>=<default> but really DO make sure that the
1206 # default value is followed by white space or is quoted). Names can contain
1207 # any alphanumeric characters and _ (i.e., names match /[\w]+/).
1208 # If the Content-type: is 'multipart/*', the input is treated as a
1209 # MIME multipart message and automatically delimited. CGI variables get
1210 # the "raw" (i.e., undecoded) body of the corresponding message part.
1212 # Variables can be CGI variables, i.e., those from the QUERY_STRING,
1213 # environment variables, e.g., REMOTE_USER, REMOTE_HOST, or REMOTE_ADDR,
1214 # or predefined values, e.g., CGI_Decoded_QS (The complete, decoded,
1215 # query string), CGI_Content_Length (the length of the decoded query
1216 # string), CGI_Year, CGI_Month, CGI_Time, and CGI_Hour (the current
1217 # date and time).
1219 # All these are available when defined in a CGI attribute. All environment
1220 # variables are accessible as $ENV{'name'}. So, to access the REMOTE_HOST
1221 # and the REMOTE_USER, use, e.g.:
1223 # <SCRIPT TYPE='text/ssperl'>
1224 # ($ENV{'REMOTE_HOST'}||"-")." $ENV{'REMOTE_USER'}"
1225 # </SCRIPT>
1227 # (This will print a "-" if REMOTE_HOST is not known)
1228 # Another way to do this is:
1230 # <META CONTENT="text/ssperl; CGI='$REMOTE_HOST = - $REMOTE_USER'">
1231 # <SCRIPT TYPE='text/ssperl'>"$REMOTE_HOST $REMOTE_USER"</SCRIPT>
1232 # or
1233 # <META CONTENT='text/ssperl; CGI="$REMOTE_HOST = - $REMOTE_USER"
1234 # SRC={"$REMOTE_HOST $REMOTE_USER\n"}'>
1236 # This is possible because ALL environment variables are available as
1237 # CGI variables. The environment variables take precedence over CGI
1238 # names in case of a "name clash". For instance:
1239 # <META CONTENT="text/ssperl; CGI='$HOME' SRC={$HOME}">
1240 # Will print the current HOME directory (environment) irrespective whether
1241 # there is a CGI variable from the query
1242 # (e.g., Where do you live? <INPUT TYPE="TEXT" NAME="HOME">)
1243 # THIS IS A SECURITY FEATURE. It prevents clients from changing
1244 # the values of defined environment variables (e.g., by supplying
1245 # a bogus $REMOTE_ADDR). Although $ENV{} is not changed by the META tags,
1246 # it would make the use of declared variables insecure. You can still
1247 # access CGI variables after a name clash with
1248 # CGIscriptor::CGIparseValue(<name>).
1250 # Some CGI variables are present several times in the query string
1251 # (e.g., from multiple selections). These should be defined as
1252 # @VARIABLENAME=default in the CGI attribute. The list @VARIABLENAME
1253 # will contain ALL VARIABLENAME values from the query, or a single
1254 # default value. If there is an ENVIRONMENT variable of the
1255 # same name, it will be used instead of the default AND the query
1256 # values. The corresponding function is
1257 # CGIscriptor::CGIparseValueList(<name>)
1259 # CGI variables collected in a @VARIABLENAME list are unordered.
1260 # When more structured variables are needed, a hash table can be used.
1261 # A variable defined as %VARIABLE=default will collect all
1262 # CGI-parameters whose name start with 'VARIABLE' in a hash table with
1263 # the remainder of the name as a key. For instance, %PERSON will
1264 # collect PERSONname='John Doe', PERSONbirthdate='01 Jan 00', and
1265 # PERSONspouse='Alice' into a hash table %PERSON such that $PERSON{'spouse'}
1266 # equals 'Alice'. Any default value or environment value will be stored
1267 # under the "" key. If there is an ENVIRONMENT variable of the same name,
1268 # it will be used instead of the default AND the query values. The
1269 # corresponding function is CGIscriptor::CGIparseValueHash(<name>)
1271 # This method of first declaring your environment and CGI variables
1272 # before being able to use them in the scripts might seem somewhat
1273 # clumsy, but it protects you from inadvertedly printing out the values of
1274 # system environment variables when their names coincide with those used
1275 # in the CGI forms. It also prevents "clients" from supplying CGI
1276 # parameter values for your private variables.
1277 # THIS IS A SECURITY FEATURE!
1280 # NON-HTML CONTENT TYPES
1282 # Normally, CGIscriptor prints the standard "Content-type: text/html\n\n"
1283 # message before anything is printed. This has been extended to include
1284 # plain text (.txt) files, for which the Content-type (MIME type)
1285 # 'text/plain' is printed. In all other respects, text files are treated
1286 # as HTML files (this can be switched off by removing '.txt' from the
1287 # $FilePattern variable) . When the content type should be something else,
1288 # e.g., with multipart files, use the $RawFilePattern (.xmr, see also next
1289 # item). CGIscriptor will not print a Content-type message for this file
1290 # type (which must supply its OWN Content-type message). Raw files must
1291 # still conform to the <SCRIPT></SCRIPT> and <META> tag specifications.
1294 # NON-HTML FILES
1296 # CGIscriptor is intended to process HTML and text files only. You can
1297 # create documents of any mime-type on-the-fly using "raw" text files,
1298 # e.g., with the .xmr extension. However, CGIscriptor will not process
1299 # binary files of any type, e.g., pictures or sounds. Given the sheer
1300 # number of formats, I do not have any intention to do so. However,
1301 # an escape route has been provided. You can construct a genuine raw
1302 # (.xmr) text file that contains the perl code to service any file type
1303 # you want. If the global $BinaryMapFile variable contains the path to
1304 # this file (e.g., /BinaryMapFile.xmr), this file will be called
1305 # whenever an unsupported (non-HTML) file type is requested. The path
1306 # to the requested binary file is stored in $ENV('CGI_BINARY_FILE')
1307 # and can be used like any other CGI-variable. Servicing binary files
1308 # then becomes supplying the correct Content-type (e.g., print
1309 # "Content-type: image/jpeg\n\n";) and reading the file and writing it
1310 # to STDOUT (e.g., using sysread() and syswrite()).
1313 # THE META TAG
1315 # All attributes of a META tag are ignored, except the
1316 # CONTENT='text/ssperl; CGI=" ... " [SRC=" ... "]' attribute. The string
1317 # inside the quotes following the CONTENT= indication (white-space is
1318 # ignored, "" '' `` (){}[]-quote pairs are allowed, plus their \ versions)
1319 # MUST start with any of the CGIscriptor mime-types (e.g.: text/ssperl or
1320 # text/osshell) and a comma or semicolon.
1321 # The quoted string following CGI= contains a white-space separated list
1322 # of declarations of the CGI (and Environment) values and default values
1323 # used when no CGI values are supplied by the query string.
1325 # If the default value is a longer string containing special characters,
1326 # possibly spanning several lines, the string must be enclosed in quotes.
1327 # You may use any pair of quotes or brackets from the list '', "", ``, (),
1328 # [], or {} to distinguish default values (or preceded by \, e.g., \(...\)
1329 # is different from (...)). The outermost pair will always be used and any
1330 # other quotes inside the string are considered to be part of the string
1331 # value, e.g.,
1333 # $Value = {['this'
1334 # "and" (this)]}
1335 # will result in $Value getting the default value: ['this'
1336 # "and" (this)]
1337 # (NOTE that the newline is part of the default value!).
1339 # Internally, for defining and initializing CGI (ENV) values, the META
1340 # and SCRIPT tags use the functions "defineCGIvariable($name, $default)"
1341 # (scalars) and "defineCGIvariableList($name, $default)" (lists).
1342 # These functions can be used inside scripts as
1343 # "CGIscriptor::defineCGIvariable($name, $default)" and
1344 # "CGIscriptor::defineCGIvariableList($name, $default)".
1345 # "CGIscriptor::defineCGIvariableHash($name, $default)".
1347 # The CGI attribute will be processed exactly identical when used inside
1348 # the <SCRIPT> tag. However, this use is not according to the
1349 # HTML 4.0 specifications of the W3C.
1352 # THE DIV/INS TAGS
1354 # There is a problem when constructing html files containing
1355 # server-side perl scripts with standard HTML tools. These
1356 # tools will refuse to process any text between <SCRIPT></SCRIPT>
1357 # tags. This is quite annoying when you want to use large
1358 # HTML templates where you will fill in values.
1360 # For this purpose, CGIscriptor will read the neutral
1361 # <DIV CLASS="ssperl" ID="varname"></DIV> or
1362 # <INS CLASS="ssperl" ID="varname"></INS>
1363 # tag (in Cascading Style Sheet manner) Note that
1364 # "varname" has NO '$' before it, it is a bare name.
1365 # Any text between these <DIV ...></DIV> or
1366 # <INS ...></INS>tags will be assigned to '$varname'
1367 # as is (e.g., as a literal).
1368 # No processing or interpolation will be performed.
1369 # There is also NO nesting possible. Do NOT nest a
1370 # </DIV> inside a <DIV></DIV>! Moreover, neither INS nor
1371 # DIV tags do ensure a block structure in the final
1372 # rendering (i.e., no empty lines).
1374 # Note that <DIV CLASS="ssperl" ID="varname"/>
1375 # is handled the XML way. No content is processed,
1376 # but varname is defined, and any SRC directives are
1377 # processed.
1379 # You can use $varname like any other variable name.
1380 # However, $varname is NOT a CGI variable and will be
1381 # completely internal to your script. There is NO
1382 # interaction between $varname and the outside world.
1384 # To interpolate a DIV derived text, you can use:
1385 # $varname =~ s/([\]])/\\\1/g; # Mark ']'-quotes
1386 # $varname = eval("qq[$varname]"); # Interpolate all values
1388 # The DIV tags will process IF, UNLESS, CGI and
1389 # SRC attributes. The SRC files will be pre-pended to the
1390 # body text of the tag. SRC blocks are NOT executed.
1392 # CONDITIONAL PROCESSING: THE 'IF' AND 'UNLESS' ATTRIBUTES
1394 # It is often necessary to include code-blocks that should be executed
1395 # conditionally, e.g., only for certain browsers or operating system.
1396 # Furthermore, quite often sanity and security checks are necessary
1397 # before user (form) data can be processed, e.g., with respect to
1398 # email addresses and filenames.
1400 # Checks added to the code are often difficult to find, interpret or
1401 # maintain and in general mess up the code flow. This kind of confussion
1402 # is dangerous.
1403 # Also, for many of the supported "foreign" scripting languages, adding
1404 # these checks is cumbersome or even impossible.
1406 # As a uniform method for asserting the correctness of "context", two
1407 # attributes are added to all supported tags: IF and UNLESS.
1408 # They both evaluate their value and block execution when the
1409 # result is <FALSE> (IF) or <TRUE> (UNLESS) in Perl, e.g.,
1410 # UNLESS='$NUMBER \> 100;' blocks execution if $NUMBER <= 100. Note that
1411 # the backslash in the '\>' is removed and only used to differentiate
1412 # this conditional '>' from the tag-closing '>'. For symmetry, the
1413 # backslash in '\<' is also removed. Inside these conditionals,
1414 # ~/ and ./ are expanded to their respective directory root paths.
1416 # For example, the following tag will be ignored when the filename is
1417 # invalid:
1419 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
1420 # IF='CGIscriptor::CGIsafeFileName($FILENAME);'>
1421 # ...
1422 # </SCRIPT>
1424 # The IF and UNLESS values must be quoted. The same quotes are supported
1425 # as with the other attributes. The SRC attribute is ignored when IF and
1426 # UNLESS block execution.
1428 # NOTE: 'IF' and 'UNLESS' always evaluate perl code.
1431 # THE MAGIC SOURCE ATTRIBUTE (SRC=)
1433 # The SRC attribute inside tags accepts a list of filenames and URL's
1434 # separated by "," comma's (or ";" semicolons).
1435 # ALL the variable values defined in the CGI attribute are available
1436 # in @ARGV as if the file or block was executed from the command line,
1437 # in the exact order in which they were declared in the preceding CGI
1438 # attribute.
1440 # First, a SRC={}-block will be evaluated as if the code inside the
1441 # block was part of a <SCRIPT></SCRIPT> construct, i.e.,
1442 # "print do { code };'';" or `code` (i.e., SAFEqx('code)).
1443 # Only a single block is evaluated. Note that this is processed less
1444 # efficiently than <SCRIPT> </SCRIPT> blocks. Type of evaluation
1445 # depends on the content-type: Perl for text/ssperl and OS shell for
1446 # text/osshell. For other mime types (scripting languages), anything in
1447 # the source block is put in front of the code block "inside" the tag.
1449 # Second, executable files (i.e., -x filename != 0) are evaluated as:
1450 # print `filename \'$ARGV[0]\' \'$ARGV[1]\' ...`
1451 # That is, you can actually call executables savely from the SRC tag.
1453 # Third, text files that match the file pattern, used by CGIscriptor to
1454 # check whether files should be processed ($FilePattern), are
1455 # processed in-line (i.e., recursively) by CGIscriptor as if the code
1456 # was inserted in the original source file. Recursions, i.e., calling
1457 # a file inside itself, are blocked. If you need them, you have to code
1458 # them explicitely using "main::ProcessFile($file_path)".
1460 # Fourth, Perl text files (i.e., -T filename != 0) are evaluated as:
1461 # "do FileName;'';".
1463 # Last, URL's (i.e., starting with 'HTTP://', 'FTP://', 'GOPHER://',
1464 # 'TELNET://', 'WHOIS://' etc.) are loaded
1465 # and printed. The loading and handling of <BASE> and document header
1466 # is done by a command generated by main::GET_URL($URL [, 0]). You can enter your
1467 # own code (default is curl, wget, or snarf and some post-processing to add a <BASE> tag).
1469 # There are two pseudo-file names: PREFIX and POSTFIX. These implement
1470 # a switch from prefixing the SRC code/files (PREFIX, default) before the
1471 # content of the tag to appending the code after the content of the tag
1472 # (POSTFIX). The switches are done in the order in which the PREFIX and
1473 # POSTFIX labels are encountered. You can mix PREFIX and POSTFIX labels
1474 # in any order with the SRC files. Note that the ORDER of file execution
1475 # is determined for prefixed and postfixed files seperately.
1477 # File paths can be preceded by the URL protocol prefix "file://". This
1478 # is simply STRIPPED from the name.
1480 # Example:
1481 # The request
1482 # "http://cgi-bin/Action_Forms.pl/Statistics/Sign_Test.html?positive=8&negative=22
1483 # will result in printing "${SS_PUB}/Statistics/Sign_Test.html"
1484 # With QUERY_STRING = "positive=8&negative=22"
1486 # on encountering the lines:
1487 # <META CONTENT="text/osshell; CGI='$positive=11 $negative=3'">
1488 # <b><SCRIPT LANGUAGE=PERL TYPE="text/ssperl" SRC="./Statistics/SignTest.pl">
1489 # </SCRIPT></b><p>"
1491 # This line will be processed as:
1492 # "<b>`${SS_SCRIPT}/Statistics/SignTest.pl '8' '22'`</b><p>"
1494 # In which "${SS_SCRIPT}/Statistics/SignTest.pl" is an executable script,
1495 # This line will end up printed as:
1496 # "<b>p <= 0.0161</b><p>"
1498 # Note that the META tag itself will never be printed, and is invisible to
1499 # the outside world.
1501 # The SRC files in a DIV or INS tag will be added (pre-pended) to the body
1502 # of the <DIV></DIV> tag. Blocks are NOT executed! If you do not
1503 # need any content, you can use the <DIV...../> format.
1506 # THE CGISCRIPTOR ROOT DIRECTORIES ~/ AND ./
1508 # Inside <SCRIPT></SCRIPT> tags, filepaths starting
1509 # with "~/" are replaced by "$YOUR_HTML_FILES/", this way files in the
1510 # public directories can be accessed without direct reference to the
1511 # actual paths. Filepaths starting with "./" are replaced by
1512 # "$YOUR_SCRIPTS/" and this should only be used for scripts.
1514 # Note: this replacement can seriously affect Perl scripts. Watch
1515 # out for constructs like $a =~ s/aap\./noot./g, use
1516 # $a =~ s@aap\.@noot.@g instead.
1518 # CGIscriptor.pl will assign the values of $SS_PUB and $SS_SCRIPT
1519 # (i.e., $YOUR_HTML_FILES and $YOUR_SCRIPTS) to the environment variables
1520 # $SS_PUB and $SS_SCRIPT. These can be accessed by the scripts that are
1521 # executed.
1522 # Values not preceded by $, ~/, or ./ are used as literals
1525 # OS SHELL SCRIPT EVALUATION (CONTENT-TYPE=TEXT/OSSHELL)
1527 # OS scripts are executed by a "safe" version of the `` operator (i.e.,
1528 # SAFEqx(), see also below) and any output is printed. CGIscriptor will
1529 # interpolate the script and replace all user-supplied CGI-variables by
1530 # their ''-quoted values (actually, all variables defined in CGI attributes
1531 # are quoted). Other Perl variables are interpolated in a simple fasion,
1532 # i.e., $scalar by their value, @list by join(' ', @list), and %hash by
1533 # their name=value pairs. Complex references, e.g., @$variable, are all
1534 # evaluated in a scalar context. Quotes should be used with care.
1535 # NOTE: the results of the shell script evaluation will appear in the
1536 # @CGIscriptorResults stack just as any other result.
1537 # All occurrences of $@% that should NOT be interpolated must be
1538 # preceeded by a "\". Interpolation can be switched off completely by
1539 # setting $CGIscriptor::NoShellScriptInterpolation = 1
1540 # (set to 0 or undef to switch interpolation on again)
1541 # i.e.,
1542 # <SCRIPT TYPE="text/ssperl">
1543 # $CGIscriptor::NoShellScriptInterpolation = 1;
1544 # </SCRIPT>
1547 # RUN TIME TRANSLATION OF INPUT FILES
1549 # Allows general and global conversions of files using Regular Expressions.
1550 # Very handy (but costly) to rewrite legacy pages to a new format.
1551 # Select files to use it on with
1552 # my $TranslationPaths = 'filepattern';
1553 # This is costly. For efficiency, define:
1554 # $TranslationPaths = ''; when not using translations.
1555 # Accepts general regular expressions: [$pattern, $replacement]
1557 # Define:
1558 # my $TranslationPaths = 'filepattern'; # Pattern matching PATH_INFO
1560 # push(@TranslationTable, ['pattern', 'replacement']);
1561 # e.g. (for Ruby Rails):
1562 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
1563 # push(@TranslationTable, ['%>', '</SCRIPT>']);
1565 # Runs:
1566 # my $currentRegExp;
1567 # foreach $currentRegExp (@TranslationTable)
1569 # my ($pattern, $replacement) = @$currentRegExp;
1570 # $$text =~ s!$pattern!$replacement!msg;
1571 # };
1574 # EVALUATION OF OTHER SCRIPTING LANGUAGES
1576 # Adding a MIME-type and an interpreter command to
1577 # %ScriptingLanguages automatically will catch any other
1578 # scripting language in the standard
1579 # <SCRIPT TYPE="[mime]"></SCRIPT> manner.
1580 # E.g., adding: $ScriptingLanguages{'text/sspython'} = 'python';
1581 # will actually execute the folowing code in an HTML page
1582 # (ignore 'REMOTE_HOST' for the moment):
1583 # <SCRIPT TYPE="text/sspython">
1584 # # A Python script
1585 # x = ["A","real","python","script","Hello","World","and", REMOTE_HOST]
1586 # print x[4:8] # Prints the list ["Hello","World","and", REMOTE_HOST]
1587 # </SCRIPT>
1589 # The script code is NOT interpolated by perl, EXCEPT for those
1590 # interpreters that cannot handle variables themselves.
1591 # Currently, several interpreters are pre-installed:
1593 # Perl test - "text/testperl" => 'perl',
1594 # Python - "text/sspython" => 'python',
1595 # Ruby - "text/ssruby" => 'ruby',
1596 # Tcl - "text/sstcl" => 'tcl',
1597 # Awk - "text/ssawk" => 'awk -f-',
1598 # Gnu Lisp - "text/sslisp" => 'rep | tail +5 '.
1599 # "| egrep -v '> |^rep. |^nil\\\$'",
1600 # XLispstat - "text/xlispstat" => 'xlispstat | tail +7 '.
1601 # "| egrep -v '> \\\$|^NIL'",
1602 # Gnu Prolog- "text/ssprolog" => 'gprolog',
1603 # M4 macro's- "text/ssm4" => 'm4',
1604 # Born shell- "text/sh" => 'sh',
1605 # Bash - "text/bash" => 'bash',
1606 # C-shell - "text/csh" => 'csh',
1607 # Korn shell- "text/ksh" => 'ksh',
1608 # Praat - "text/sspraat" => "praat - | sed 's/Praat > //g'",
1609 # R - "text/ssr" => "R --vanilla --slave | sed 's/^[\[0-9\]*] //g'",
1610 # REBOL - "text/ssrebol" =>
1611 # "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\s*\[> \]* //g'",
1612 # PostgreSQL- "text/postgresql" => 'psql 2>/dev/null',
1613 # (psql)
1615 # Note that the "value" of $ScriptingLanguages{mime} must be a command
1616 # that reads Standard Input and writes to standard output. Any extra
1617 # output of interactive interpreters (banners, echo's, prompts)
1618 # should be removed by piping the output through 'tail', 'grep',
1619 # 'sed', or even 'awk' or 'perl'.
1621 # For access to CGI variables there is a special hashtable:
1622 # %ScriptingCGIvariables.
1623 # CGI variables can be accessed in three ways.
1624 # 1. If the mime type is not present in %ScriptingCGIvariables,
1625 # nothing is done and the script itself should parse the relevant
1626 # environment variables.
1627 # 2. If the mime type IS present in %ScriptingCGIvariables, but it's
1628 # value is empty, e.g., $ScriptingCGIvariables{"text/sspraat"} = '';,
1629 # the script text is interpolated by perl. That is, all $var, @array,
1630 # %hash, and \-slashes are replaced by their respective values.
1631 # 3. In all other cases, the CGI and environment variables are added
1632 # in front of the script according to the format stored in
1633 # %ScriptingCGIvariables. That is, the following (pseudo-)code is
1634 # executed for each CGI- or Environment variable defined in the CGI-tag:
1635 # printf(INTERPRETER, $ScriptingCGIvariables{$mime}, $CGI_NAME, $CGI_VALUE);
1637 # For instance, "text/testperl" => '$%s = "%s";' defines variable
1638 # definitions for Perl, and "text/sspython" => '%s = "%s"' for Python
1639 # (note that these definitions are not save, the real ones contain '-quotes).
1641 # THIS WILL NOT WORK FOR @VARIABLES, the (empty) $VARIABLES will be used
1642 # instead.
1644 # The $CGI_VALUE parameters are "shrubed" of all control characters
1645 # and quotes (by &shrubCGIparameter($CGI_VALUE)) for the options 2 and 3.
1646 # Control characters are replaced by \0<octal ascii value> (the exception
1647 # is \015, the newline, which is replaced by \n) and quotes
1648 # and backslashes by their HTML character
1649 # value (' -> &#39; ` -> &#96; " -> &quot; \ -> &#92; & -> &amper;).
1650 # For example:
1651 # if a client would supply the string value (in standard perl, e.g.,
1652 # \n means <newline>)
1653 # "/dev/null';\nrm -rf *;\necho '"
1654 # it would be processed as
1655 # '/dev/null&#39;;\nrm -rf *;\necho &#39;'
1656 # (e.g., sh or bash would process the latter more according to your
1657 # intentions).
1658 # If your intepreter requires different protection measures, you will
1659 # have to supply these in %main::SHRUBcharacterTR (string => translation),
1660 # e.g., $SHRUBcharacterTR{"\'"} = "&#39;";
1662 # Currently, the following definitions are used:
1663 # %ScriptingCGIvariables = (
1664 # "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value' (for testing)
1665 # "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
1666 # "text/ssruby" => '@%s = "%s"', # Ruby @VAR = "value"
1667 # "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
1668 # "text/ssawk" => '%s = "%s";', # Awk VAR = "value";
1669 # "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
1670 # "text/xlispstat" => '(setq %s "%s")', # Xlispstat (setq VAR "value")
1671 # "text/ssprolog" => '', # Gnu prolog (interpolated)
1672 # "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
1673 # "text/sh" => "\%s='\%s';", # Born shell VAR='value';
1674 # "text/bash" => "\%s='\%s';", # Born again shell VAR='value';
1675 # "text/csh" => "\$\%s = '\%s';", # C shell $VAR = 'value';
1676 # "text/ksh" => "\$\%s = '\%s';", # Korn shell $VAR = 'value';
1677 # "text/sspraat" => '', # Praat (interpolation)
1678 # "text/ssr" => '%s <- "%s";', # R VAR <- "value";
1679 # "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
1680 # "text/postgresql" => '', # PostgreSQL (interpolation)
1681 # "" => ""
1682 # );
1684 # Four tables allow fine-tuning of interpreter with code that should be
1685 # added before and after each code block:
1687 # Code added before each script block
1688 # %ScriptingPrefix = (
1689 # "text/testperl" => "\# Prefix Code;", # Perl script testing
1690 # "text/ssm4" => 'divert(0)' # M4 macro's (open STDOUT)
1691 # );
1692 # Code added at the end of each script block
1693 # %ScriptingPostfix = (
1694 # "text/testperl" => "\# Postfix Code;", # Perl script testing
1695 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1696 # );
1697 # Initialization code, inserted directly after opening (NEVER interpolated)
1698 # %ScriptingInitialization = (
1699 # "text/testperl" => "\# Initialization Code;", # Perl script testing
1700 # "text/ssawk" => 'BEGIN {', # Server Side awk scripts
1701 # "text/sslisp" => '(prog1 nil ', # Lisp (rep)
1702 # "text/xlispstat" => '(prog1 nil ', # xlispstat
1703 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1704 # );
1705 # Cleanup code, inserted before closing (NEVER interpolated)
1706 # %ScriptingCleanup = (
1707 # "text/testperl" => "\# Cleanup Code;", # Perl script testing
1708 # "text/sspraat" => 'Quit',
1709 # "text/ssawk" => '};', # Server Side awk scripts
1710 # "text/sslisp" => '(princ "\n" standard-output)).' # Closing print to rep
1711 # "text/xlispstat" => '(print "" *standard-output*)).' # Closing print to xlispstat
1712 # "text/postgresql" => '\q',
1713 # );
1716 # The SRC attribute is NOT magical for these interpreters. In short,
1717 # all code inside a source file or {} block is written verbattim
1718 # to the interpreter. No (pre-)processing or executional magic is done.
1720 # A serious shortcomming of the described mechanism for handling other
1721 # (scripting) languages, with respect to standard perl scripts
1722 # (i.e., 'text/ssperl'), is that the code is only executed when
1723 # the pipe to the interpreter is closed. So the pipe has to be
1724 # closed at the end of each block. This means that the state of the
1725 # interpreter (e.g., all variable values) is lost after the closing of
1726 # the next </SCRIPT> tag. The standard 'text/ssperl' scripts retain
1727 # all values and definitions.
1729 # APPLICATION MIME TYPES
1731 # To ease some important auxilliary functions from within the
1732 # html pages I have added them as MIME types. This uses
1733 # the mechanism that is also used for the evaluation of
1734 # other scripting languages, with interpolation of CGI
1735 # parameters (and perl-variables). Actually, these are
1736 # defined exactly like any other "scripting language".
1738 # text/ssdisplay: display some (HTML) text with interpolated
1739 # variables (uses `cat`).
1740 # text/sslogfile: write (append) the interpolated block to the file
1741 # mentioned on the first, non-empty line
1742 # (the filename can be preceded by 'File: ',
1743 # note the space after the ':',
1744 # uses `awk .... >> <filename>`).
1745 # text/ssmailto: send email directly from within the script block.
1746 # The first line of the body must contain
1747 # To:Name@Valid.Email.Address
1748 # (note: NO space between 'To:' and the email adres)
1749 # For other options see the mailto man pages.
1750 # It works by directly sending the (interpolated)
1751 # content of the text block to a pipe into the
1752 # Linux program 'mailto'.
1754 # In these script blocks, all Perl variables will be
1755 # replaced by their values. All CGI variables are cleaned before
1756 # they are used. These CGI variables must be redefined with a
1757 # CGI attribute to restore their original values.
1758 # In general, this will be more secure than constructing
1759 # e.g., your own email command lines. For instance, Mailto will
1760 # not execute any odd (forged) email addres, but just stops
1761 # when the email address is invalid and awk will construct
1762 # any filename you give it (e.g. '<File;rm\\\040-f' would end up
1763 # as a "valid" UNIX filename). Note that it will also gladly
1764 # store this file anywhere (/../../../etc/passwd will work!).
1765 # Use the CGIscriptor::CGIsafeFileName() function to clean the
1766 # filename.
1768 # SHELL SCRIPT PIPING
1770 # If a shell script starts with the UNIX style "#! <shell command> \n"
1771 # line, the rest of the shell script is piped into the indicated command,
1772 # i.e.,
1773 # open(COMMAND, "| command");print COMMAND $RestOfScript;
1775 # In many ways this is equivalent to the MIME-type profiling for
1776 # evaluating other scripting languages as discussed above. The
1777 # difference breaks down to convenience. Shell script piping is a
1778 # "raw" implementation. It allows you to control all aspects of
1779 # execution. Using the MIME-type profiling is easier, but has a
1780 # lot of defaults built in that might get in the way. Another
1781 # difference is that shell script piping uses the SAFEqx() function,
1782 # and MIME-type profiling does not.
1784 # Execution of shell scripts is under the control of the Perl Script blocks
1785 # in the document. The MIME-type triggered execution of <SCRIPT></SCRIPT>
1786 # blocks can be simulated easily. You can switch to a different shell,
1787 # e.g. tcl, completely by executing the following Perl commands inside
1788 # your document:
1790 # <SCRIPT TYPE="text/ssperl">
1791 # $main::ShellScriptContentType = "text/ssTcl"; # Yes, you can do this
1792 # CGIscriptor::RedirectShellScript('/usr/bin/tcl'); # Pipe to Tcl
1793 # $CGIscriptor::NoShellScriptInterpolation = 1;
1794 # </SCRIPT>
1796 # After this script is executed, CGIscriptor will parse scripts of
1797 # TYPE="text/ssTcl" and pipe their contents into '|/usr/bin/tcl'
1798 # WITHOUT interpolation (i.e., NO substitution of Perl variables).
1799 # The crucial function is :
1800 # CGIscriptor::RedirectShellScript('/usr/bin/tcl')
1801 # After executing this function, all shell scripts AND all
1802 # calls to SAFEqx()) are piped into '|/usr/bin/tcl'. If the argument
1803 # of RedirectShellScript is empty, e.g., '', the original (default)
1804 # value is reset.
1806 # The standard output, STDOUT, of any pipe is send to the client.
1807 # Currently, you should be carefull with quotes in such a piped script.
1808 # The results of a pipe is NOT put on the @CGIscriptorResults stack.
1809 # As a result, you do not have access to the output of any piped (#!)
1810 # process! If you want such access, execute
1811 # <SCRIPT TYPE="text/osshell">echo "script"|command</SCRIPT>
1812 # or
1813 # <SCRIPT TYPE="text/ssperl">
1814 # $resultvar = SAFEqx('echo "script"|command');
1815 # </SCRIPT>.
1817 # Safety is never complete. Although SAFEqx() prevents some of the
1818 # most obvious forms of attacks and security slips, it cannot prevent
1819 # them all. Especially, complex combinations of quotes and intricate
1820 # variable references cannot be handled safely by SAFEqx. So be on
1821 # guard.
1824 # PERL CODE EVALUATION (CONTENT-TYPE=TEXT/SSPERL)
1826 # All PERL scripts are evaluated inside a PERL package. This package
1827 # has a separate name space. This isolated name space protects the
1828 # CGIscriptor.pl program against interference from user code. However,
1829 # some variables, e.g., $_, are global and cannot be protected. You are
1830 # advised NOT to use such global variable names. You CAN write
1831 # directives that directly access the variables in the main program.
1832 # You do so at your own risk (there is definitely enough rope available
1833 # to hang yourself). The behavior of CGIscriptor becomes undefined if
1834 # you change its private variables during run time. The PERL code
1835 # directives are used as in:
1836 # $Result = eval($directive); print $Result;'';
1837 # ($directive contains all text between <SCRIPT></SCRIPT>).
1838 # That is, the <directive> is treated as ''-quoted string and
1839 # the result is treated as a scalar. To prevent the VALUE of the code
1840 # block from appearing on the client's screen, end the directive with
1841 # ';""</SCRIPT>'. Evaluated directives return the last value, just as
1842 # eval(), blocks, and subroutines, but only as a scalar.
1844 # IMPORTANT: All PERL variables defined are persistent. Each <SCRIPT>
1845 # </SCRIPT> construct is evaluated as a {}-block with associated scope
1846 # (e.g., for "my $var;" declarations). This means that values assigned
1847 # to a PERL variable can be used throughout the document unless they
1848 # were declared with "my". The following will actually work as intended
1849 # (note that the ``-quotes in this example are NOT evaluated, but used
1850 # as simple quotes):
1852 # <META CONTENT="text/ssperl; CGI=`$String='abcdefg'`">
1853 # anything ...
1854 # <SCRIPT TYPE=text/ssperl>@List = split('', $String);</SCRIPT>
1855 # anything ...
1856 # <SCRIPT TYPE=text/ssperl>join(", ", @List[1..$#List]);</SCRIPT>
1858 # The first <SCRIPT TYPE=text/ssperl></SCRIPT> construct will return the
1859 # value scalar(@List), the second <SCRIPT TYPE=text/ssperl></SCRIPT>
1860 # construct will print the elements of $String separated by commas, leaving
1861 # out the first element, i.e., $List[0].
1863 # Another warning: './' and '~/' are ALWAYS replaced by the values of
1864 # $YOUR_SCRIPTS and $YOUR_HTML_FILES, respectively . This can interfere
1865 # with pattern matching, e.g., $a =~ s/aap\./noot\./g will result in the
1866 # evaluations of $a =~ s/aap\\${YOUR_SCRIPTS}noot\\${YOUR_SCRIPTS}g. Use
1867 # s@<regexp>.@<replacement>.@g instead.
1870 # SERVER SIDE SESSIONS AND ACCESS CONTROL (LOGIN)
1872 # An infrastructure for user acount authorization and file access control
1873 # is available. Each request is matched against a list of URL path patterns.
1874 # If the request matches, a Session Ticket is required to access the URL.
1875 # This Session Ticket should be present as a CGI parameter or Cookie, eg:
1877 # CGI: SESSIONTICKET=&lt;value&gt;
1878 # Cookie: CGIscriptorSESSION=&lt;value&gt;
1880 # The example implementation stores Session Tickets as files in a local
1881 # directory. To create Session Tickets, a Login request must be given
1882 # with a LOGIN=&lt;value&gt; CGI parameter, a user name and a (doubly hashed)
1883 # password. The user name and (singly hashed) password are stored in a
1884 # PASSWORD ticket with the same name as the user account (name cleaned up
1885 # for security).
1887 # The example session model implements 4 functions:
1888 # - Login
1889 # The password is hashed with the user name and server side salt, and then
1890 # hashed with the REMOTE_HOST and a random salt. Client and Server both
1891 # perform these actions and the Server only grants access if restults are
1892 # the same. The server side only stores the password hashed with the user
1893 # name and server side salt. Neither the plain password, nor the hashed
1894 # password is ever exchanged. Only values hashed with the one-time salt
1895 # are exchanged.
1896 # - Session
1897 # For every access to a restricted URL, the Session Ticket is checked before
1898 # access is granted. There are three session modes. The first uses a fixed
1899 # Session Ticket that is stored as a cookie value in the browser (actually,
1900 # as a sessionStorage value). The second uses only the IP address at login
1901 # to authenticate requests. The third
1902 # is a Challenge mode, where the client has to calculate the value of the
1903 # next one-time Session Ticket from a value derived from the password and
1904 # a random string.
1905 # - Password Change
1906 # A new password is hashed with the user name and server side salt, and
1907 # then encrypted (XORed)
1908 # with the old password hashed with the user name and salt. That value is
1909 # exchanged and XORed with the stored old hashed(password+username+salt).
1910 # Again, the stored password value is never exchanged unencrypted.
1911 # - New Account
1912 # The text of a new account (Type: PASSWORD) file is constructed from
1913 # the new username (CGI: NEWUSERNAME, converted to lowercase) and
1914 # hashed new password (CGI: NEWPASSWORD). The same process is used to encrypt
1915 # the new password as is used for the Password Change function.
1916 # Again, the stored password value is never exchanged unencrypted.
1917 # Some default setting are encoded. For display in the browser, the new password
1918 # is reencrypted (XORed) with a special key, the old password hash
1919 # hashed with a session specific random hex value sent initially with the
1920 # session login ticket ($RANDOMSALT).
1921 # For example for user "NewUser" and password "NewPassword" with filename
1922 # "newuser":
1924 # Type: PASSWORD
1925 # Username: newuser
1926 # Password: 19afeadfba8d5dcd252e157fafd3010859f8762b87682b6b6cdb3e565194fa91
1927 # IPaddress: 127\.0\.0\.1
1928 # AllowedPaths: ^/Private/[\w\-]+\.html?
1929 # AllowedPaths: ^/Private/newuser/
1930 # Salt: e93cf858a1d5626bf095ea5c25df990dfa969ff5a5dc908b22c9a5229b525f65
1931 # Session: SESSION
1932 # Date: Fri Jun 29 12:46:22 2012
1933 # Time: 1340973982
1934 # Signature: 676c35d3aa63540293ea5442f12872bfb0a22665b504f58f804582493b6ef04e
1936 # The password is created with the commands:
1938 # printf '%s' 'NewPasswordnewuser970e68017413fb0ea84d7fe3c463077636dd6d53486910d4a53c693dd4109b1a'|shasum -a 256
1940 # If the CPAN mudule Digest is installed, it is used instead of the commands.
1941 # However, the password account files are protected against unauthorized change.
1942 # To obtain a valid Password account, the following command should be given:
1944 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
1945 # masterkey='Sherlock investigates oleander curry in Bath' \
1946 # password='NewPassword' \
1947 # Private/.Passwords/newuser
1949 # There are four default accounts present: testip, test, testchallenge, and
1950 # admin. The former three have password 'testing', the latter has password
1951 # 'There is no password like more password'. The admin
1952 # account is disabled by default. You can enable it with a new password
1953 # using the --managelogin option. All four accounts are limited to local
1954 # (localhost) requests. When present, testip, test, and
1955 # testchallenge are reactivated with the default password testing
1956 # whenever a new SALT is automatically generated. It is adviced that the test
1957 # accounts are removed when setting up a site.
1960 # Implementation
1962 # The session authentication mechanism is based on the exchange of ticket
1963 # identifiers. A ticket identifier is just a string of characters, a name
1964 # or a random 64 character hexadecimal string. Authentication is based
1965 # on a (password derived) shared secret and the ability to calculate ticket
1966 # identifiers from this shared secret. Ticket identifiers should be
1967 # "safe" filenames (except user names). There are four types of tickets:
1968 # PASSWORD: User account descriptors, including a user name and password
1969 # LOGIN: Temporary anonymous tickets used during login
1970 # IPADDRESS: Authentication tokens that allow access based on the IP address of the request
1971 # SESSION: Reusable authentication tokens
1972 # CHALLENGE: One-time authentication tokens
1973 # All tickets can have an expiration date in the form of a time duration
1974 # from creation, in seconds, minutes, hours, or days (+duration[smhd]).
1975 # An absolute time can be given in seconds since the epoch of the server host.
1976 # Note that expiration times of CHALLENGE authentication tokens are calculated
1977 # from the last access time. Accounts can include a maximal lifetime
1978 # for session tickets (MaxLifetime).
1980 # A Login page should create a LOGIN ticket file locally and send a
1981 # server specific salt, a Random salt, and a LOGIN ticket
1982 # identifier. The server side compares the username and hashed password,
1983 # actually hashed(hashed(password+serversalt)+Random salt) from the client with
1984 # the values it calculates from the stored Random salt from the LOGIN
1985 # ticket and the hashed(password+serversalt) from the PASSWORD ticket. If
1986 # successful, a new SESSION ticket is generated as a (double) hash sum of the stored
1987 # password and the LOGIN ticket, i.e.
1988 # LoginTicket = hashed(hashed(password+serversalt)+REMOTE_HOST + Random salt) and
1989 # SessionTicket = hashed(hashed(LoginTicket).LoginTicket). This SESSION
1990 # ticket should also be generated by the client and stored as
1991 # sessionStorage and cookie values as needed. The Username, IP address
1992 # and Path are available as $LoginUsername, $LoginIPaddress, and
1993 # $LoginPath, respectively.
1995 # The CHALLENGE protocol stores the single hashed version of the SESSION tickets.
1996 # However, this value is not exchanged, but kept secret in the JavaScript
1997 # sessionStorage object. Instead, every page returned from the
1998 # server will contain a one-time Challenge value ($CHALLENGETICKET) which
1999 # has to be hashed with the stored value to return the current ticket
2000 # id string.
2002 # In the current example implementation, all random values are created as
2003 # full, 256 bit SHA256 hash values (Hex strings) of 64 bytes read from
2004 # /dev/urandom.
2007 # Authorization
2009 # A limited level of authorization tuning is build into the login system.
2010 # Each account file (PASSWORD ticket file) can contain a number of
2011 # Capabilities lines. These control special priveliges. The
2012 # Capabilities can be checked inside the HTML pages as part of the
2013 # ticket information. Two privileges are handled internally:
2014 # CreateUser and VariableREMOTE_ADDR.
2015 # CreateUser allows the logged in user to create a new user account.
2016 # With VariableREMOTE_ADDR, the session of the logged in user is
2017 # not limited to the Remote IP address from which the inital log-in took
2018 # place. Sessions can hop from one apparant (proxy) IP address to another,
2019 # e.g., when using Tor. Any IPaddress patterns given in the PASSWORD
2020 # ticket file remain in effect during the session. For security reasons,
2021 # the VariableREMOTE_ADDR capability is only effective if the session
2022 # type is CHALLENGE.
2025 # Security considerations with Session tickets
2027 # For strong security, please use end-to-end encryption. This can be
2028 # achieved using a VPN (Virtual Private Network), SSH tunnel, or a HTTPS
2029 # capable server with OpenSSL. The session ticket system of CGIscriptor.pl
2030 # is intended to be used as a simple authentication mechanism WITHOUT
2031 # END-TO-END ENCRYPTION. The authenticating mechanism tries to use some
2032 # simple means to protect the authentication process from eavesdropping.
2033 # For this it uses a secure hash function, SHA256. For all practial purposes,
2034 # it is impossible to "decrypt" a SHA256 sum. But this login scheme is
2035 # only as secure as your browser. Which, in general, is not very secure.
2037 # One fundamental weakness of the implemented procedure is that the Client
2038 # obtains the code to encrypt the passwords from the server. It is the JavaScript
2039 # code in the HTML pages. An attacker who could place himself between Server
2040 # and Client, a man in the middle attack (MITM), could change the code to
2041 # reveal the plaintext password and other information. There is no
2042 # real protection against this attack without end-to-end encryption and
2043 # authentication. A simple, but rather cumbersome, way to check for such
2044 # attacks would be to store known good copies of the pages (downloaded
2045 # with a browser or automatically with curl or wget) and
2046 # then use other tools to download new pages at random intervals and compare
2047 # them to the old pages. For instance, the following line would remove
2048 # the variable ticket codes and give a fixed SHA256 sum for the original
2049 # Login.html page+code:
2050 # curl http://localhost:8080/Private/index.html | \
2051 # sed 's/=\"[a-z0-9]\{64\}\"/=""/g' | shasum -a 256
2052 # A simple diff command between old and new files should give only
2053 # differences in half a dozen lines, where only hexadecimal salt values
2054 # will actually differ.
2056 # A sort of solution for the MITM attack problem that might protect at
2057 # least the plaintext password would be to run a trusted web
2058 # page from local storage to handle password input. The solution would be
2059 # to add a hidden iFrame tag loading the untrusted page from the URL and
2060 # extract the needed ticket and salt values. Then run the stored, trusted,
2061 # code with these values. It is not (yet) possible to set the
2062 # required session storage inside the browser, so this method only works
2063 # for IPADDRESS sessions and plain SESSION tickets. There are many
2064 # security problems with this "solution".
2066 # If you are able to ascertain the integrity of the login page using any
2067 # of the above methods, you can check whether the IP address seen by the
2068 # login server is indeed the IP address of your computer. The IP address
2069 # of the REMOTE_HOST (your visible IP address) is part of the login
2070 # "password". It is stored in the login page as a CLIENTIPADDRESS. It can
2071 # can be inspected by clicking the "Check IP address" box. Provided the
2072 # MitM attacker cannot spoof your IP address, you can ensure that the login
2073 # server sees your IP address and not that of an attacker.
2075 # Humans tend to reuse passwords. A compromise of a site running
2076 # CGIscriptor.pl could therefore lead to a compromise of user accounts at
2077 # other sites. Therefore, plain text passwords are never stored, used, or
2078 # exchanged. Instead, the plain password and user name are "encrypted" with
2079 # a server site salt value. Actually, all are concatenated and hashed
2080 # with a one-way secure hash function (SHA256) into a single string.
2081 # Whenever the word "password" is used, this hash sum is meant. Note that
2082 # the salts are generated from /dev/urandom. You should check whether the
2083 # implementation of /dev/urandom on your platform is secure before
2084 # relying on it. This might be a problem when running CGIscriptor under
2085 # Cygwin on MS Windows.
2086 # Note: no attempt is made to slow down the password hash, so bad
2087 # passwords can be cracked by brute force
2089 # As the (hashed) passwords are all that is needed to identify at the site,
2090 # these should not be stored in this form. A site specific passphrase
2091 # can be entered as an environment variable ($ENV{'CGIMasterKey'}). This
2092 # phrase is hashed with the server site salt and the result is hashed with
2093 # the user name and then XORed with the password when it is stored. Also, to
2094 # detect changes to the account (PASSWORD) and session tickets, a
2095 # (HMAC) hash of some of the contents of the ticket with the server salt and
2096 # CGIMasterKey is stored in each ticket.
2098 # Creating a valid (hashed) password, encrypt it with the CGIMasterKey and
2099 # construct a signature of the ticket are non-trivial. This has to be redone
2100 # with every change of the ticket file or CGIMasterKey change. CGIscriptor
2101 # can do this from the command line with the command:
2103 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
2104 # masterkey='Sherlock investigates oleander curry in Bath' \
2105 # password='There is no password like more password' \
2106 # admin
2108 # CGIscriptor will exit after this command with the first option being
2109 # --managelogin. Options have the form:
2111 # salt=[file or string]
2112 # Server salt value to use io the value
2113 # stored in the ticket file. Will replace the stored value if a new
2114 # password is given. If you change the server salt, you have to
2115 # reset all the passwords. There is absolutely no procedure known
2116 # to recover plaintext passwords, except asking the account holders.
2117 # You are strongly adviced to make a backup before you apply such a change
2118 # masterkey=[file or string]
2119 # CGIMasterKey used to read and decrypt the ticket
2120 # newmasterkey=[file or string]
2121 # CGIMasterKey used to encrypt, sign,
2122 # and write the ticket. Defaults to the masterkey. If you change
2123 # the masterkey, you will have to reset all the accounts. You are strongly
2124 # adviced to make a backup before you apply such a change
2125 # password=[file or string]
2126 # New plaintext password
2128 # When the value of an option is a existing file path, the first line of
2129 # that file is used. Options are followed by one or more paths plus names
2130 # of existing ticket files. Each password option is only used for a single
2131 # ticket file. It is most definitely a bad idea to use a password that is
2132 # identical to an existing filepath, as the file will be read instead. Be
2133 # aware that the name of the file should be a cleaned up version of the
2134 # Username. This will not be checked.
2136 # For the authentication and a change of password, the (old) password
2137 # is used to "encrypt" a random one-time token or the new password,
2138 # respectively. For authentication, decryption is not needed, so a secure
2139 # hash function (SHA256) is used to create a one-way hash sum "encryption".
2140 # A new password must be decrypted. New passwords are encryped by XORing
2141 # them with the old password.
2143 # Strong Passwords: It is so easy
2144 # If you only could see what you are typing
2146 # Your password might be vulnerable to brute force guessing
2147 # (https://en.wikipedia.org/wiki/Brute_force_attack).
2148 # Protections against such attacks are costly in terms of code
2149 # complexity, bugs, and execution time. However, there is a very
2150 # simple and secure counter measure. See the XKCD comic
2151 # (http://xkcd.com/936/). The phrase, "There is no password like more
2152 # password" would be both much easier to remember, and still stronger
2153 # than "h4]D%@m:49", at least before this phrase was pasted as an
2154 # example on the Internet.
2156 # For the procedures used at this site, a basic computer setup can
2157 # check in the order of a billion passwords per second. You need a
2158 # password (or phrase) strength in the order of 56 bits to be a
2159 # little secure (one year on a single computer). Please be so kind
2160 # and add the name of your favorite flower, dish, fictional
2161 # character, or small town to your password. Say, Oleander, Curry,
2162 # Sherlock, or Bath, UK (each adds ~12 bits) or even the phrase "Sherlock
2163 # investigates oleander curry in Bath" (adds > 56 bits, note that
2164 # oleander is poisonous, so do not try this curry at home). That
2165 # would be more effective than adding a thousand rounds of encryption.
2166 # Typing long passwords without seeing what you are typing is
2167 # problematic. So a button should be included to make password
2168 # visible.
2171 # Technical matters
2173 # Client side JavaScript code definitions. Variable names starting with '$'
2174 # are CGIscriptor CGI variables. Some of the hashes could be strengthened
2175 # by switching to HMAC signatures. However, the security issues of
2176 # maintaining parallel functions for HMAC in both Perl and Javascript seem
2177 # to be more serious than the attack vectors against the hashes. But HMAC
2178 # is indeed used for the ticket signatures.
2180 # // On Login
2181 # HashPlaintextPassword() {
2182 # var plaintextpassword = document.getElementById('PASSWORD');
2183 # var serversalt = document.getElementById('SERVERSALT');
2184 # var username = document.getElementById('CGIUSERNAME');
2185 # return hex_sha256(plaintextpassword.value+username.value.toLowerCase()+serversalt.value);
2187 # var randomsalt = $RANDOMSALT; // From CGIscriptor
2188 # var loginticket = $LOGINTICKET; // From CGIscriptor
2189 # // Hash plaintext password
2190 # var password = HashPlaintextPassword();
2191 # // Authorize login
2192 # var hashedpassword = hex_sha256(randomsalt+password);
2193 # // Sessionticket
2194 # var sessionticket = hex_sha256(loginticket+password);
2195 # sessionStorage.setItem("CGIscriptorPRIVATE", sessionticket);
2196 # // Secretkey for encrypting new passwords, acts like a one-time pad
2197 # // Is set anew with every login, ie, also whith password changes
2198 # // and for each create new user request
2199 # var secretkey = hex_sha256(password+loginticket+randomsalt);
2200 # sessionStorage.setItem("CGIscriptorSECRET", secretkey);
2202 # // For a SESSION type request
2203 # sessionticket = hex_sha256(sessionStorage.getItem("CGIscriptorPRIVATE"));
2204 # createCookie("CGIscriptorSESSION",sessionticket, 0, "");
2206 // For a CHALLENGE type request
2207 # var sessionset = "$CHALLENGETICKET"; // From CGIscriptor
2208 # var sessionkey = sessionStorage.getItem("CGIscriptorPRIVATE");
2209 # sessionticket = hex_sha256(sessionset+sessionkey);
2210 # createCookie("CGIscriptorCHALLENGE",sessionticket, 0, "");
2212 # // For transmitting a new password
2213 # HashPlaintextNewPassword() {
2214 # var plaintextpassword = document.getElementById('NEWPASSWORD');
2215 # var serversalt = document.getElementById('SERVERSALT');
2216 # var username = document.getElementById('NEWUSERNAME');
2217 # return hex_sha256(plaintextpassword.value+username.value.toLowerCase()+serversalt.value);
2220 # var newpassword = document.getElementById('NEWPASSWORD');
2221 # var newpasswordrep = document.getElementById('NEWPASSWORDREP');
2222 # // Hash plaintext password
2223 # newpassword.value = HashPlaintextNewPassword();
2224 # var secretkey = sessionStorage.getItem("CGIscriptorSECRET");
2226 # var encrypted = XOR_hex_strings(secretkey, newpassword.value);
2227 # newpassword.value = encrypted;
2228 # newpasswordrep.value = encrypted;
2230 # // XOR of hexadecimal strings of equal length
2231 # function XOR_hex_strings(hex1, hex2) {
2232 # var resultHex = "";
2233 # var maxlength = Math.max(hex1.length, hex2.length);
2235 # for(var i=0; i &lt; maxlength; ++i) {
2236 # var h1 = hex1.charAt(i);
2237 # if(! h1) h1='0';
2238 # var h2 = hex2.charAt(i);
2239 # if(! h2) h2 ='0';
2240 # var d1 = parseInt(h1,16);
2241 # var d2 = parseInt(h2,16);
2242 # var resultD = d1^d2;
2243 # resultHex = resultHex+resultD.toString(16);
2244 # };
2245 # return resultHex;
2246 # };
2248 # Password encryption based on $ENV{'CGIMasterKey'}.
2249 # Server side Perl code:
2251 # # Password encryption
2252 # my $masterkey = $ENV{'CGIMasterKey'}
2253 # my $hash1 = hash_string($masterkey.$serversalt);
2254 # my $CryptKey = hash_string($username.$hash1);
2255 # $password = XOR_hex_strings($CryptKey,$password);
2257 # # Key for HMAC signing
2258 # my $hash1 = hash_string($masterkey.$serversalt);
2259 # my $HMACKey = hash_string($username.$hash1);
2263 # USER EXTENSIONS
2265 # A CGIscriptor package is attached to the bottom of this file. With
2266 # this package you can personalize your version of CGIscriptor by
2267 # including often used perl routines. These subroutines can be
2268 # accessed by prefixing their names with CGIscriptor::, e.g.,
2269 # <SCRIPT LANGUAGE=PERL TYPE=text/ssperl>
2270 # CGIscriptor::ListDocs("/Books/*") # List all documents in /Books
2271 # </SCRIPT>
2272 # It already contains some useful subroutines for Document Management.
2273 # As it is a separate package, it has its own namespace, isolated from
2274 # both the evaluator and the main program. To access variables from
2275 # the document <SCRIPT></SCRIPT> blocks, use $CGIexecute::<var>.
2277 # Currently, the following functions are implemented
2278 # (precede them with CGIscriptor::, see below for more information)
2279 # - SAFEqx ('String') -> result of qx/"String"/ # Safe application of ``-quotes
2280 # Is used by text/osshell Shell scripts. Protects all CGI
2281 # (client-supplied) values with single quotes before executing the
2282 # commands (one of the few functions that also works WITHOUT CGIscriptor::
2283 # in front)
2284 # - defineCGIvariable ($name[, $default) -> 0/1 (i.e., failure/success)
2285 # Is used by the META tag to define and initialize CGI and ENV
2286 # name/value pairs. Tries to obtain an initializing value from (in order):
2287 # $ENV{$name}
2288 # The Query string
2289 # The default value given (if any)
2290 # (one of the few functions that also works WITHOUT CGIscriptor::
2291 # in front)
2292 # - CGIsafeFileName (FileName) -> FileName or ""
2293 # Check a string against the Allowed File Characters (and ../ /..).
2294 # Returns an empty string for unsafe filenames.
2295 # - CGIsafeEmailAddress (Email) -> Email or ""
2296 # Check a string against correct email address pattern.
2297 # Returns an empty string for unsafe addresses.
2298 # - RedirectShellScript ('CommandString') -> FILEHANDLER or undef
2299 # Open a named PIPE for SAFEqx to receive ALL shell scripts
2300 # - URLdecode (URL encoded string) -> plain string # Decode URL encoded argument
2301 # - URLencode (plain string) -> URL encoded string # Encode argument as URL code
2302 # - CGIparseValue (ValueName [, URL_encoded_QueryString]) -> Decoded value
2303 # Extract the value of a CGI variable from the global or a private
2304 # URL-encoded query (multipart POST raw, NOT decoded)
2305 # - CGIparseValueList (ValueName [, URL_encoded_QueryString])
2306 # -> List of decoded values
2307 # As CGIparseValue, but now assembles ALL values of ValueName into a list.
2308 # - CGIparseHeader (ValueName [, URL_encoded_QueryString]) -> Header
2309 # Extract the header of a multipart CGI variable from the global or a private
2310 # URL-encoded query ("" when not a multipart variable or absent)
2311 # - CGIparseForm ([URL_encoded_QueryString]) -> Decoded Form
2312 # Decode the complete global URL-encoded query or a private
2313 # URL-encoded query
2314 # - read_url(URL) # Returns the page from URL (with added base tag, both FTP and HTTP)
2315 # Uses main::GET_URL(URL, 1) to get at the command to read the URL.
2316 # - BrowseDirs(RootDirectory [, Pattern, Startdir, CGIname]) # print browsable directories
2317 # - ListDocs(Pattern [,ListType]) # Prints a nested HTML directory listing of
2318 # all documents, e.g., ListDocs("/*", "dl");.
2319 # - HTMLdocTree(Pattern [,ListType]) # Prints a nested HTML listing of all
2320 # local links starting from a given document, e.g.,
2321 # HTMLdocTree("/Welcome.html", "dl");
2324 # THE RESULTS STACK: @CGISCRIPTORRESULTS
2326 # If the pseudo-variable "$CGIscriptorResults" has been defined in a
2327 # META tag, all subsequent SCRIPT and META results are pushed
2328 # on the @CGIscriptorResults stack. This list is just another
2329 # Perl variable and can be used and manipulated like any other list.
2330 # $CGIscriptorResults[-1] is always the last result.
2331 # This is only of limited use, e.g., to use the results of an OS shell
2332 # script inside a Perl script. Will NOT contain the results of Pipes
2333 # or code from MIME-profiling.
2336 # USEFULL CGI PREDEFINED VARIABLES (DO NOT ASSIGN TO THESE)
2338 # $CGI_HOME - The DocumentRoot directory
2339 # $CGI_Decoded_QS - The complete decoded Query String
2340 # $CGI_Content_Length - The ACTUAL length of the Query String
2341 # $CGI_Date - Current date and time
2342 # $CGI_Year $CGI_Month $CGI_Day $CGI_WeekDay - Current Date
2343 # $CGI_Time - Current Time
2344 # $CGI_Hour $CGI_Minutes $CGI_Seconds - Current Time, split
2345 # GMT Date/Time:
2346 # $CGI_GMTYear $CGI_GMTMonth $CGI_GMTDay $CGI_GMTWeekDay $CGI_GMTYearDay
2347 # $CGI_GMTHour $CGI_GMTMinutes $CGI_GMTSeconds $CGI_GMTisdst
2350 # USEFULL CGI ENVIRONMENT VARIABLES
2352 # Variables accessible (in APACHE) as $ENV{<name>}
2353 # (see: "http://hoohoo.ncsa.uiuc.edu/cgi/env.html"):
2355 # QUERY_STRING - The query part of URL, that is, everything that follows the
2356 # question mark.
2357 # PATH_INFO - Extra path information given after the script name
2358 # PATH_TRANSLATED - Extra pathinfo translated through the rule system.
2359 # (This doesn't always make sense.)
2360 # REMOTE_USER - If the server supports user authentication, and the script is
2361 # protected, this is the username they have authenticated as.
2362 # REMOTE_HOST - The hostname making the request. If the server does not have
2363 # this information, it should set REMOTE_ADDR and leave this unset
2364 # REMOTE_ADDR - The IP address of the remote host making the request.
2365 # REMOTE_IDENT - If the HTTP server supports RFC 931 identification, then this
2366 # variable will be set to the remote user name retrieved from
2367 # the server. Usage of this variable should be limited to logging
2368 # only.
2369 # AUTH_TYPE - If the server supports user authentication, and the script
2370 # is protected, this is the protocol-specific authentication
2371 # method used to validate the user.
2372 # CONTENT_TYPE - For queries which have attached information, such as HTTP
2373 # POST and PUT, this is the content type of the data.
2374 # CONTENT_LENGTH - The length of the said content as given by the client.
2375 # SERVER_SOFTWARE - The name and version of the information server software
2376 # answering the request (and running the gateway).
2377 # Format: name/version
2378 # SERVER_NAME - The server's hostname, DNS alias, or IP address as it
2379 # would appear in self-referencing URLs
2380 # GATEWAY_INTERFACE - The revision of the CGI specification to which this
2381 # server complies. Format: CGI/revision
2382 # SERVER_PROTOCOL - The name and revision of the information protocol this
2383 # request came in with. Format: protocol/revision
2384 # SERVER_PORT - The port number to which the request was sent.
2385 # REQUEST_METHOD - The method with which the request was made. For HTTP,
2386 # this is "GET", "HEAD", "POST", etc.
2387 # SCRIPT_NAME - A virtual path to the script being executed, used for
2388 # self-referencing URLs.
2389 # HTTP_ACCEPT - The MIME types which the client will accept, as given by
2390 # HTTP headers. Other protocols may need to get this
2391 # information from elsewhere. Each item in this list should
2392 # be separated by commas as per the HTTP spec.
2393 # Format: type/subtype, type/subtype
2394 # HTTP_USER_AGENT - The browser the client is using to send the request.
2395 # General format: software/version library/version.
2398 # INSTRUCTIONS FOR RUNNING CGIscriptor ON UNIX
2400 # CGIscriptor.pl will run on any WWW server that runs Perl scripts, just add
2401 # a line like the following to your srm.conf file (Apache example):
2403 # ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
2405 # URL's that refer to http://www.your.address/SHTML/... will now be handled
2406 # by CGIscriptor.pl, which can use a private directory tree (default is the
2407 # DOCUMENT_ROOT directory tree, but it can be anywhere, see manual).
2409 # If your hosting ISP won't let you add ScriptAlias lines you can use
2410 # the following "rewrite"-based "scriptalias" in .htaccess
2411 # (from Gerd Franke)
2413 # RewriteEngine On
2414 # RewriteBase /
2415 # RewriteCond %{REQUEST_FILENAME} .html$
2416 # RewriteCond %{SCRIPT_FILENAME} !cgiscriptor.pl$
2417 # RewriteCond %{REQUEST_FILENAME} -f
2418 # RewriteRule ^(.*)$ /cgi-bin/cgiscriptor.pl/$1?&%{QUERY_STRING}
2420 # Everthing with the extension ".html" and not including "cgiscriptor.pl"
2421 # in the url and where the file "path/filename.html" exists is redirected
2422 # to "/cgi.bin/cgiscriptor.pl/path/filename.html?query".
2423 # The user configuration should get the same path-level as the
2424 # .htaccess-file:
2426 # # Just enter your own directory path here
2427 # $YOUR_HTML_FILES = "$ENV{'DOCUMENT_ROOT'}";
2428 # # use DOCUMENT_ROOT only, if .htaccess lies in the root-directory.
2430 # If this .htaccess goes in a specific directory, the path to this
2431 # directory must be added to $ENV{'DOCUMENT_ROOT'}.
2433 # The CGIscriptor file contains all documentation as comments. These
2434 # comments can be removed to speed up loading (e.g., `egrep -v '^#'
2435 # CGIscriptor.pl` > leanScriptor.pl). A bare bones version of
2436 # CGIscriptor.pl, lacking documentation, most comments, access control,
2437 # example functions etc. (but still with the copyright notice and some
2438 # minimal documentation) can be obtained by calling CGIscriptor.pl on the
2439 # command line with the '-slim' command line argument, e.g.,
2441 # >CGIscriptor.pl -slim > slimCGIscriptor.pl
2443 # CGIscriptor.pl can be run from the command line with <path> and <query> as
2444 # arguments, as `CGIscriptor.pl <path> <query>`, inside a perl script
2445 # with 'do CGIscriptor.pl' after setting $ENV{PATH_INFO}
2446 # and $ENV{QUERY_STRING}, or CGIscriptor.pl can be loaded with 'require
2447 # "/real-path/CGIscriptor.pl"'. In the latter case, requests are processed
2448 # by 'Handle_Request();' (again after setting $ENV{PATH_INFO} and
2449 # $ENV{QUERY_STRING}).
2451 # Using the command line execution option, CGIscriptor.pl can be used as a
2452 # document (meta-)preprocessor. If the first argument is '-', STDIN will be read.
2453 # For example:
2455 # > cat MyDynamicDocument.html | CGIscriptor.pl - '[QueryString]' > MyStaticFile.html
2457 # This command line will produce a STATIC file with the DYNAMIC content of
2458 # MyDocument.html "interpolated".
2460 # This option would be very dangerous when available over the internet.
2461 # If someone could sneak a 'http://www.your.domain/-' URL past your
2462 # server, CGIscriptor could EXECUTE any POSTED contend.
2463 # Therefore, for security reasons, STDIN will NOT be read
2464 # if ANY of the HTTP server environment variables is set (e.g.,
2465 # SERVER_PORT, SERVER_PROTOCOL, SERVER_NAME, SERVER_SOFTWARE,
2466 # HTTP_USER_AGENT, REMOTE_ADDR).
2467 # This block on processing STDIN on HTTP requests can be lifted by setting
2468 # $BLOCK_STDIN_HTTP_REQUEST = 0;
2469 # In the security configuration. Butbe carefull when doing this.
2470 # It can be very dangerous.
2472 # Running demo's and more information can be found at
2473 # http://www.fon.hum.uva.nl/~rob/OSS/OSS.html
2475 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site or
2476 # CPAN that can use CGIscriptor.pl as the base of a µWWW server and
2477 # demonstrates its use.
2480 # PROCESSING NON-FILESYSTEM DATA
2482 # Normally, HTTP (WWW) requests map onto file that can be accessed
2483 # using the perl open() function. That is, the web server runs on top of
2484 # some directory structure. However, we can envission (and put to good
2485 # use) other systems that do not use a normal file system. The whole CGI
2486 # was developed to make dynamic document generation possible.
2488 # A special case is where we want to have it both: A normal web server
2489 # with normal "file data", but not a normal files system. For instance,
2490 # we want or normal Web Site to run directly from a RAM hash table or
2491 # other database, instead of from disk. But we do NOT want to code the
2492 # whole site structure in CGI.
2494 # CGIscriptor can do this. If the web server fills an environment variable
2495 # $ENV{'CGI_FILE_CONTENT'} with the content of the "file", then the content
2496 # of this variable is processed instead of opening a file. If this environment
2497 # variable has the value '-', the content of another environment variable,
2498 # $ENV{'CGI_DATA_ACCESS_CODE'} is executed as:
2499 # eval("\@_ = ($file_path); do {$ENV{'CGI_DATA_ACCESS_CODE'}};")
2500 # and the result is processed as if it was the content of the requested
2501 # file.
2502 # (actually, the names of the environment variables are user configurable,
2503 # they are stored in the local variables $CGI_FILE_CONTENT and
2504 # $CGI_DATA_ACCESS_CODE)
2506 # When using this mechanism, the SRC attribute mechanism will only partially work.
2507 # Only the "recursive" calls to CGIscriptor (the ProcessFile() function)
2508 # will work, the automagical execution of SRC files won't. (In this case,
2509 # the SRC attribute won't work either for other scripting languages)
2512 # NON-UNIX PLATFORMS
2514 # CGIscriptor.pl was mainly developed and tested on UNIX. However, as I
2515 # coded part of the time on an Apple Macintosh under MacPerl, I made sure
2516 # CGIscriptor did run under MacPerl (with command line options). But only
2517 # as an independend script, not as part of a HTTP server. I have used it
2518 # under Apache in Windows XP.
2520 ENDOFHELPTEXT
2521 exit;
2523 ###############################################################################
2525 # SECURITY CONFIGURATION
2527 # Special configurations related to SECURITY
2528 # (i.e., optional, see also environment variables below)
2530 # LOGGING
2531 # Log Clients and the requested paths (Redundant when loging Queries)
2533 $ClientLog = "./Client.log"; # (uncomment for use)
2535 # Format: Localtime | REMOTE_USER REMOTE_IDENT REMOTE_HOST REMOTE_ADDRESS \
2536 # PATH_INFO CONTENT_LENGTH (actually, the real query+post length)
2538 # Log Clients and the queries, the CGIQUERYDECODE is required if you want
2539 # to log queries. If you log Queries, the loging of Clients is redundant
2540 # (note that queries can be quite long, so this might not be a good idea)
2542 #$QueryLog = "./Query.log"; # (uncomment for use)
2544 # ACCESS CONTROL
2545 # the Access files should contain Hostnames or IP addresses,
2546 # i.e. REMOTE_HOST or REMOTE_ADDR, each on a separate line
2547 # optionally followed by one ore more file patterns, e.g., "edu /DEMO".
2548 # Matching is done "domain first". For example ".edu" matches all
2549 # clients whose "name" ends in ".edu" or ".EDU". The file pattern
2550 # "/DEMO" matches all paths that contain the strings "/DEMO" or "/demo"
2551 # (both matchings are done case-insensitive).
2552 # The name special symbol "-" matches ALL clients who do not supply a
2553 # REMOTE_HOST name, "*" matches all clients.
2554 # Lines starting with '-e' are evaluated. A non-zero return value indicates
2555 # a match. You can use $REMOTE_HOST, $REMOTE_ADDR, and $PATH_INFO. These
2556 # lines are evaluated in the program's own name-space. So DO NOT assign to
2557 # variables.
2559 # Accept the following users (remove comment # and adapt filename)
2560 $CGI_Accept = -s "$YOUR_SCRIPTS/ACCEPT.lis" ? "$YOUR_SCRIPTS/ACCEPT.lis" : ''; # (uncomment for use)
2562 # Reject requests from the following users (remove comment # and
2563 # adapt filename, this is only of limited use)
2564 $CGI_Reject = -s "$YOUR_SCRIPTS/REJECT.lis" ? "$YOUR_SCRIPTS/REJECT.lis" : ''; # (uncomment for use)
2566 # Empty lines or comment lines starting with '#' are ignored in both
2567 # $CGI_Accept and $CGI_Reject.
2569 # Block STDIN (i.e., '-') requests when servicing an HTTP request
2570 # Comment this out if you realy want to use STDIN in an on-line web server
2571 $BLOCK_STDIN_HTTP_REQUEST = 1;
2574 # End of security configuration
2576 ##################################################<<<<<<<<<<End Remove
2578 # PARSING CGI VALUES FROM THE QUERY STRING (USER CONFIGURABLE)
2580 # The CGI parse commands. These commands extract the values of the
2581 # CGI variables from the URL encoded Query String.
2582 # If you want to use your own CGI decoders, you can call them here
2583 # instead, using your own PATH and commenting/uncommenting the
2584 # appropriate lines
2586 # CGI parse command for individual values
2587 # (if $List > 0, returns a list value, if $List < 0, a hash table, this is optional)
2588 sub YOUR_CGIPARSE # ($Name [, $List]) -> Decoded value
2590 my $Name = shift;
2591 my $List = shift || 0;
2592 # Use one of the following by uncommenting
2593 if(!$List) # Simple value
2595 return CGIscriptor::CGIparseValue($Name) ;
2597 elsif($List < 0) # Hash tables
2599 return CGIscriptor::CGIparseValueHash($Name); # Defined in CGIscriptor below
2601 else # Lists
2603 return CGIscriptor::CGIparseValueList($Name); # Defined in CGIscriptor below
2606 # return `/PATH/cgiparse -value $Name`; # Shell commands
2607 # require "/PATH/cgiparse.pl"; return cgivalue($Name); # Library
2609 # Complete queries
2610 sub YOUR_CGIQUERYDECODE
2612 # Use one of the following by uncommenting
2613 return CGIscriptor::CGIparseForm(); # Defined in CGIscriptor below
2614 # return `/PATH/cgiparse -form`; # Shell commands
2615 # require "/PATH/cgiparse.pl"; return cgiform(); # Library
2618 # End of configuration
2620 #######################################################################
2622 # Translating input files.
2623 # Allows general and global conversions of files using Regular Expressions
2624 # Translations are applied in the order of definition.
2626 # Define:
2627 # my $TranslationPaths = 'pattern'; # Pattern matching PATH_INFO
2629 # push(@TranslationTable, ['pattern', 'replacement']);
2630 # e.g. (for Ruby Rails):
2631 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2632 # push(@TranslationTable, ['%>', '</SCRIPT>']);
2634 # Runs:
2635 # my $currentRegExp;
2636 # foreach $currentRegExp (keys(%TranslationTable))
2638 # my $currentRegExp;
2639 # foreach $currentRegExp (@TranslationTable)
2641 # my ($pattern, $replacement) = @$currentRegExp;
2642 # $$text =~ s!$pattern!$replacement!msg;
2643 # };
2644 # };
2646 # Configuration section
2648 #######################################################################
2650 # The file paths on which to apply the translation
2651 my $TranslationPaths = ''; # NO files
2652 #$TranslationPaths = '.'; # ANY file
2653 # $TranslationPaths = '\.html'; # HTML files
2655 my @TranslationTable = ();
2656 # Some legacy code
2657 push(@TranslationTable, ['\<\s*CGI\s+([^\>])*\>', '\<SCRIPT TYPE=\"text/ssperl\"\>$1\<\/SCRIPT>']);
2658 # Ruby Rails?
2659 push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2660 push(@TranslationTable, ['%>', '</SCRIPT>']);
2662 sub performTranslation # (\$text)
2664 my $text = shift || return;
2665 if(@TranslationTable && $TranslationPaths && $ENV{'PATH_INFO'} =~ m!$TranslationPaths!)
2667 my $currentRegExp;
2668 foreach $currentRegExp (@TranslationTable)
2670 my ($pattern, $replacement) = @$currentRegExp;
2671 $$text =~ s!$pattern!$replacement!msg;
2676 #######################################################################
2678 # Seamless access to other (Scripting) Languages
2679 # TYPE='text/ss<interpreter>'
2681 # Configuration section
2683 #######################################################################
2685 # OTHER SCRIPTING LANGUAGES AT THE SERVER SIDE (MIME => OScommand)
2686 # Yes, it realy is this simple! (unbelievable, isn't it)
2687 # NOTE: Some interpreters require some filtering to obtain "clean" output
2689 %ScriptingLanguages = (
2690 "text/testperl" => 'perl', # Perl for testing
2691 "text/sspython" => 'python', # Python
2692 "text/ssruby" => 'ruby', # Ruby
2693 "text/sstcl" => 'tcl', # TCL
2694 "text/ssawk" => 'awk -f-', # Awk
2695 "text/sslisp" => # lisp (rep, GNU)
2696 'rep | tail +4 '."| egrep -v '> |^rep. |^nil\\\$'",
2697 "text/xlispstat" => # xlispstat
2698 'xlispstat | tail +7 ' ."| egrep -v '> \\\$|^NIL'",
2699 "text/ssprolog" => # Prolog (GNU)
2700 "gprolog | tail +4 | sed 's/^| ?- //'",
2701 "text/ssm4" => 'm4', # M4 macro's
2702 "text/sh" => 'sh', # Born shell
2703 "text/bash" => 'bash', # Born again shell
2704 "text/csh" => 'csh', # C shell
2705 "text/ksh" => 'ksh', # Korn shell
2706 "text/sspraat" => # Praat (sound/speech analysis)
2707 "praat - | sed 's/Praat > //g'",
2708 "text/ssr" => # R
2709 "R --vanilla --slave | sed 's/^[\[0-9\]*] //'",
2710 "text/ssrebol" => # REBOL
2711 "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\\s*\[> \]* //'",
2712 "text/postgresql" => 'psql 2>/dev/null',
2714 # Not real scripting, but the use of other applications
2715 "text/ssmailto" => "awk 'NF||F{F=1;print \\\$0;}'|mailto >/dev/null", # Send mail from server
2716 "text/ssdisplay" => 'cat', # Display, (interpolation)
2717 "text/sslogfile" => # Log to file, (interpolation)
2718 "awk 'NF||L {if(!L){L=tolower(\\\$1)~/^file:\\\$/ ? \\\$2 : \\\$1;}else{print \\\$0 >> L;};}'",
2720 "" => ""
2723 # To be able to access the CGI variables in your script, they
2724 # should be passed to the scripting language in a readable form
2725 # Here you can enter how they should be printed (the first %s
2726 # is replaced by the NAME of the CGI variable as it apears in the
2727 # META tag, the second by its VALUE).
2728 # For Perl this would be:
2729 # "text/testperl" => '$%s = "%s";',
2730 # which would be executed as
2731 # printf('$%s = "%s";', $CGI_NAME, $CGI_VALUE);
2733 # If the hash table value doesn't exist, nothing is done
2734 # (you have to parse the Environment variables yourself).
2735 # If it DOES exist but is empty (e.g., "text/sspraat" => '',)
2736 # Perl string interpolation of variables (i.e., $var, @array,
2737 # %hash) is performed. This means that $@%\ must be protected
2738 # with a \.
2740 %ScriptingCGIvariables = (
2741 "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value'; (for testing)
2742 "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
2743 "text/ssruby" => '@%s = "%s"', # Ruby @VAR = 'value'
2744 "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
2745 "text/ssawk" => '%s = "%s";', # Awk VAR = 'value';
2746 "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
2747 "text/xlispstat" => '(setq %s "%s")', # xlispstat (setq VAR "value")
2748 "text/ssprolog" => '', # Gnu prolog (interpolated)
2749 "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
2750 "text/sh" => "\%s='\%s'", # Born shell VAR='value'
2751 "text/bash" => "\%s='\%s'", # Born again shell VAR='value'
2752 "text/csh" => "\$\%s='\%s';", # C shell $VAR = 'value';
2753 "text/ksh" => "\$\%s='\%s';", # Korn shell $VAR = 'value';
2755 "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
2756 "text/sspraat" => '', # Praat (interpolation)
2757 "text/ssr" => '%s <- "%s";', # R VAR <- "value";
2758 "text/postgresql" => '', # PostgreSQL (interpolation)
2760 # Not real scripting, but the use of other applications
2761 "text/ssmailto" => '', # MAILTO, (interpolation)
2762 "text/ssdisplay" => '', # Display, (interpolation)
2763 "text/sslogfile" => '', # Log to file, (interpolation)
2765 "" => ""
2768 # If you want something added in front or at the back of each script
2769 # block as send to the interpreter add it here.
2770 # mime => "string", e.g., "text/sspython" => "python commands"
2771 %ScriptingPrefix = (
2772 "text/testperl" => "\# Prefix Code;", # Perl script testing
2773 "text/ssm4" => 'divert(0)', # M4 macro's (open STDOUT)
2775 "" => ""
2777 # If you want something added at the end of each script block
2778 %ScriptingPostfix = (
2779 "text/testperl" => "\# Postfix Code;", # Perl script testing
2780 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2782 "" => ""
2784 # If you need initialization code, directly after opening
2785 %ScriptingInitialization = (
2786 "text/testperl" => "\# Initialization Code;", # Perl script testing
2787 "text/ssawk" => 'BEGIN {', # Server Side awk scripts (VAR = "value")
2788 "text/sslisp" => '(prog1 nil ', # Lisp (rep)
2789 "text/xlispstat" => '(prog1 nil ', # xlispstat
2790 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2792 "" => ""
2794 # If you need cleanup code before closing
2795 %ScriptingCleanup = (
2796 "text/testperl" => "\# Cleanup Code;", # Perl script testing
2797 "text/sspraat" => 'Quit',
2798 "text/ssawk" => '};', # Server Side awk scripts (VAR = "value")
2799 "text/sslisp" => '(princ "\n" standard-output)).', # Closing print to rep
2800 "text/xlispstat" => '(print ""))', # Closing print to xlispstat
2801 "text/postgresql" => '\q', # quit psql
2802 "text/ssdisplay" => "", # close cat
2804 "" => ""
2807 # End of configuration for foreign scripting languages
2809 ###############################################################################
2811 # Initialization Code
2814 sub Initialize_Request
2816 ###############################################################################
2818 # ENVIRONMENT VARIABLES
2820 # Use environment variables to configure CGIscriptor on a temporary basis.
2821 # If you define any of the configurable variables as environment variables,
2822 # these are used instead of the "hard coded" values above.
2824 $SS_PUB = $ENV{'SS_PUB'} || $YOUR_HTML_FILES;
2825 $SS_SCRIPT = $ENV{'SS_SCRIPT'} || $YOUR_SCRIPTS;
2828 # Substitution strings, these are used internally to handle the
2829 # directory separator strings, e.g., '~/' -> 'SS_PUB:' (Mac)
2830 $HOME_SUB = $SS_PUB;
2831 $SCRIPT_SUB = $SS_SCRIPT;
2834 # Make sure all script are reliably loaded
2835 push(@INC, $SS_SCRIPT);
2838 # Add the directory separator to the "home" directories.
2839 # (This is required for ~/ and ./ substitution)
2840 $HOME_SUB .= '/' if $HOME_SUB;
2841 $SCRIPT_SUB .= '/' if $SCRIPT_SUB;
2843 $CGI_HOME = $ENV{'DOCUMENT_ROOT'};
2844 $ENV{'PATH_TRANSLATED'} =~ /$ENV{'PATH_INFO'}/is;
2845 $CGI_HOME = $` unless $ENV{'DOCUMENT_ROOT'}; # Get the DOCUMENT_ROOT directory
2846 $default_values{'CGI_HOME'} = $CGI_HOME;
2847 $ENV{'HOME'} = $CGI_HOME;
2848 # Set SS_PUB and SS_SCRIPT as Environment variables (make them available
2849 # to the scripts)
2850 $ENV{'SS_PUB'} = $SS_PUB unless $ENV{'SS_PUB'};
2851 $ENV{'SS_SCRIPT'} = $SS_SCRIPT unless $ENV{'SS_SCRIPT'};
2853 $FilePattern = $ENV{'FilePattern'} || $FilePattern;
2854 $MaximumQuerySize = $ENV{'MaximumQuerySize'} || $MaximumQuerySize;
2855 $ClientLog = $ENV{'ClientLog'} || $ClientLog;
2856 $QueryLog = $ENV{'QueryLog'} || $QueryLog;
2857 $CGI_Accept = $ENV{'CGI_Accept'} || $CGI_Accept;
2858 $CGI_Reject = $ENV{'CGI_Reject'} || $CGI_Reject;
2860 # Parse file names
2861 $CGI_Accept =~ s@^\~/@$HOME_SUB@g if $CGI_Accept;
2862 $CGI_Reject =~ s@^\~/@$HOME_SUB@g if $CGI_Reject;
2863 $ClientLog =~ s@^\~/@$HOME_SUB@g if $ClientLog;
2864 $QueryLog =~ s@^\~/@$HOME_SUB@g if $QueryLog;
2866 $CGI_Accept =~ s@^\./@$SCRIPT_SUB@g if $CGI_Accept;
2867 $CGI_Reject =~ s@^\./@$SCRIPT_SUB@g if $CGI_Reject;
2868 $ClientLog =~ s@^\./@$SCRIPT_SUB@g if $ClientLog;
2869 $QueryLog =~ s@^\./@$SCRIPT_SUB@g if $QueryLog;
2871 @CGIscriptorResults = (); # A stack of results
2873 # end of Environment variables
2875 #############################################################################
2877 # Define and Store "standard" values
2879 # BEFORE doing ANYTHING check the size of Query String
2880 length($ENV{'QUERY_STRING'}) <= $MaximumQuerySize || dieHandler(2, "QUERY TOO LONG\n");
2882 # The Translated Query String and the Actual length of the (decoded)
2883 # Query String
2884 if($ENV{'QUERY_STRING'})
2886 # If this can contain '`"-quotes, be carefull to use it QUOTED
2887 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2888 $default_values{CGI_Content_Length} = length($default_values{CGI_Decoded_QS});
2891 # Get the current Date and time and store them as default variables
2893 # Get Local Time
2894 $LocalTime = localtime;
2896 # CGI_Year CGI_Month CGI_Day CGI_WeekDay CGI_Time
2897 # CGI_Hour CGI_Minutes CGI_Seconds
2899 $default_values{CGI_Date} = $LocalTime;
2900 ($default_values{CGI_WeekDay},
2901 $default_values{CGI_Month},
2902 $default_values{CGI_Day},
2903 $default_values{CGI_Time},
2904 $default_values{CGI_Year}) = split(' ', $LocalTime);
2905 ($default_values{CGI_Hour},
2906 $default_values{CGI_Minutes},
2907 $default_values{CGI_Seconds}) = split(':', $default_values{CGI_Time});
2909 # GMT:
2910 # CGI_GMTYear CGI_GMTMonth CGI_GMTDay CGI_GMTWeekDay CGI_GMTYearDay
2911 # CGI_GMTHour CGI_GMTMinutes CGI_GMTSeconds CGI_GMTisdst
2913 ($default_values{CGI_GMTSeconds},
2914 $default_values{CGI_GMTMinutes},
2915 $default_values{CGI_GMTHour},
2916 $default_values{CGI_GMTDay},
2917 $default_values{CGI_GMTMonth},
2918 $default_values{CGI_GMTYear},
2919 $default_values{CGI_GMTWeekDay},
2920 $default_values{CGI_GMTYearDay},
2921 $default_values{CGI_GMTisdst}) = gmtime;
2925 # End of Initialize Request
2927 ###################################################################
2929 # SECURITY: ACCESS CONTROL
2931 # Check the credentials of each client (use pattern matching, domain first).
2932 # This subroutine will kill-off (die) the current process whenever access
2933 # is denied.
2935 sub Access_Control
2937 # >>>>>>>>>>Start Remove
2939 # ACCEPTED CLIENTS
2941 # Only accept clients which are authorized, reject all unnamed clients
2942 # if REMOTE_HOST is given.
2943 # If file patterns are given, check whether the user is authorized for
2944 # THIS file.
2945 if($CGI_Accept)
2947 # Use local variables, REMOTE_HOST becomes '-' if undefined
2948 my $REMOTE_HOST = $ENV{REMOTE_HOST} || '-';
2949 my $REMOTE_ADDR = $ENV{REMOTE_ADDR};
2950 my $PATH_INFO = $ENV{'PATH_INFO'};
2952 open(CGI_Accept, "<$CGI_Accept") || dieHandler(3, "$CGI_Accept: $!\n");
2953 $NoAccess = 1;
2954 while(<CGI_Accept>)
2956 next unless /\S/; # Skip empty lines
2957 next if /^\s*\#/; # Skip comments
2958 chomp;
2960 # Full expressions
2961 if(/^\s*-e\s/is)
2963 my $Accept = $'; # Get the expression
2964 $NoAccess &&= eval($Accept); # evaluate the expresion
2966 elsif($PATH_INFO ne "")
2968 my ($Accept, @FilePatternList) = split;
2969 if($Accept eq '*' # Always match
2970 ||$REMOTE_HOST =~ /\Q$Accept\E$/is # REMOTE_HOST matches
2971 || (
2972 $Accept =~ /^[0-9\.]+$/
2973 && $REMOTE_ADDR =~ /^\Q$Accept\E/ # IP address matches
2977 if($FilePatternList[0])
2979 my $invert = 0;
2980 if($FilePatternList[0] eq "!" or $FilePatternList[0] eq "not")
2982 $invert = 1;
2983 shift(@FilePatternList);
2985 foreach $Pattern (@FilePatternList)
2987 # Check whether this patterns is accepted
2988 my $value = ($PATH_INFO !~ m@\Q$Pattern\E@is);
2989 $value = not $value if $invert;
2990 $NoAccess &&= $value;
2993 else
2995 $NoAccess = 0; # No file patterns -> Accepted
2999 # Blocked
3000 last unless $NoAccess;
3002 close(CGI_Accept);
3003 if($NoAccess && $PATH_INFO ne "")
3005 dieHandler(4, "No Access: $PATH_INFO\n");
3006 $ENV{'PATH_INFO'} = "";
3011 # REJECTED CLIENTS
3013 # Reject named clients, accept all unnamed clients
3014 if($CGI_Reject)
3016 # Use local variables, REMOTE_HOST becomes '-' if undefined
3017 my $REMOTE_HOST = $ENV{'REMOTE_HOST'} || '-';
3018 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
3019 my $PATH_INFO = $ENV{'PATH_INFO'};
3021 open(CGI_Reject, "<$CGI_Reject") || dieHandler(5, "$CGI_Reject: $!\n");
3022 $NoAccess = 0;
3023 while(<CGI_Reject>)
3025 next unless /\S/; # Skip empty lines
3026 next if /^\s*\#/; # Skip comments
3027 chomp;
3029 # Full expressions
3030 if(/^-e\s/is)
3032 my $Reject = $'; # Get the expression
3033 $NoAccess ||= eval($Reject); # evaluate the expresion
3035 elsif($PATH_INFO ne "")
3037 my ($Reject, @FilePatternList) = split;
3038 if($Reject eq '*' # Always match
3039 ||$REMOTE_HOST =~ /\Q$Reject\E$/is # REMOTE_HOST matches
3040 ||($Reject =~ /^[0-9\.]+$/
3041 && $REMOTE_ADDR =~ /^\Q$Reject\E/is # IP address matches
3045 if($FilePatternList[0])
3047 my $invert = 0;
3048 if($FilePatternList[0] eq "!" or $FilePatternList[0] eq "not")
3050 $invert = 1;
3051 shift(@FilePatternList);
3053 foreach $Pattern (@FilePatternList)
3055 my $value = ($PATH_INFO =~ m@\Q$Pattern\E@is);
3056 $value = not $value if $invert;
3057 $NoAccess ||= $value;
3060 else
3062 $NoAccess = 1; # No file patterns -> Rejected
3066 last if $NoAccess;
3068 close(CGI_Reject);
3069 if($NoAccess && $PATH_INFO ne "")
3071 dieHandler(4, "Request rejected: $PATH_INFO\n");
3072 $ENV{'PATH_INFO'} = "";
3076 ##########################################################<<<<<<<<<<End Remove
3079 # Get the filename
3081 # Does the filename contain any illegal characters (e.g., |, >, or <)
3082 dieHandler(7, "Illegal request: $ENV{'PATH_INFO'}\n") if $ENV{'PATH_INFO'} =~ /[^$FileAllowedChars]/;
3083 # Does the pathname contain an illegal (blocked) "directory"
3084 dieHandler(8, "Illegal request: $ENV{'PATH_INFO'}\n") if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@; # Access is blocked
3085 # Does the pathname contain a direct referencer to BinaryMapFile
3086 dieHandler(9, "Illegal request: $ENV{'PATH_INFO'}\n") if $BinaryMapFile && $ENV{'PATH_INFO'} =~ m@\Q$BinaryMapFile\E@; # Access is blocked
3088 # SECURITY: Is PATH_INFO allowed?
3089 if($FilePattern && $ENV{'PATH_INFO'} && $ENV{'PATH_INFO'} ne '-' &&
3090 ($ENV{'PATH_INFO'} !~ m@($FilePattern)$@is))
3092 # Unsupported file types can be processed by a special raw-file
3093 if($BinaryMapFile)
3095 $ENV{'CGI_BINARY_FILE'} = $ENV{'PATH_INFO'};
3096 $ENV{'PATH_INFO'} = $BinaryMapFile;
3098 else
3100 dieHandler(10, "Illegal file\n");
3106 # End of Security Access Control
3109 ############################################################################
3111 # Get the POST part of the query and add it to the QUERY_STRING.
3114 sub Get_POST_part_of_query
3117 # If POST, Read data from stdin to QUERY_STRING
3118 if($ENV{'REQUEST_METHOD'} =~ /POST/is)
3120 # SECURITY: Check size of Query String
3121 $ENV{'CONTENT_LENGTH'} <= $MaximumQuerySize || dieHandler(11, "Query too long: $ENV{'CONTENT_LENGTH'}\n"); # Query too long
3122 my $QueryRead = 0;
3123 my $SystemRead = $ENV{'CONTENT_LENGTH'};
3124 $ENV{'QUERY_STRING'} .= '&' if length($ENV{'QUERY_STRING'}) > 0;
3125 while($SystemRead > 0)
3127 $QueryRead = sysread(STDIN, $Post, $SystemRead); # Limit length
3128 $ENV{'QUERY_STRING'} .= $Post;
3129 $SystemRead -= $QueryRead;
3131 # Update decoded Query String
3132 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
3133 $default_values{CGI_Content_Length} =
3134 length($default_values{CGI_Decoded_QS});
3138 # End of getting POST part of query
3141 ############################################################################
3143 # Start (HTML) output and logging
3144 # (if there are irregularities, it can kill the current process)
3147 sub Initialize_output
3149 # Construct the REAL file path (except for STDIN on the command line)
3150 my $file_path = $ENV{'PATH_INFO'} ne '-' ? $SS_PUB . $ENV{'PATH_INFO'} : '-';
3151 $file_path =~ s/\?.*$//; # Remove query
3152 # This is only necessary if your server does not catch ../ directives
3153 $file_path !~ m@\.\./@ || dieHandler(12, "Illegal ../ Construct\n"); # SECURITY: Do not allow ../ constructs
3155 # Block STDIN use (-) if CGIscriptor is servicing a HTTP request
3156 if($file_path eq '-')
3158 dieHandler(13, "STDIN request in On Line system\n") if $BLOCK_STDIN_HTTP_REQUEST
3159 && ($ENV{'SERVER_SOFTWARE'}
3160 || $ENV{'SERVER_NAME'}
3161 || $ENV{'GATEWAY_INTERFACE'}
3162 || $ENV{'SERVER_PROTOCOL'}
3163 || $ENV{'SERVER_PORT'}
3164 || $ENV{'REMOTE_ADDR'}
3165 || $ENV{'HTTP_USER_AGENT'});
3170 if($ClientLog)
3172 open(ClientLog, ">>$ClientLog");
3173 print ClientLog "$LocalTime | ",
3174 ($ENV{REMOTE_USER} || "-"), " ",
3175 ($ENV{REMOTE_IDENT} || "-"), " ",
3176 ($ENV{REMOTE_HOST} || "-"), " ",
3177 $ENV{REMOTE_ADDR}, " ",
3178 $ENV{PATH_INFO}, " ",
3179 $ENV{'CGI_BINARY_FILE'}, " ",
3180 ($default_values{CGI_Content_Length} || "-"),
3181 "\n";
3182 close(ClientLog);
3184 if($QueryLog)
3186 open(QueryLog, ">>$QueryLog");
3187 print QueryLog "$LocalTime\n",
3188 ($ENV{REMOTE_USER} || "-"), " ",
3189 ($ENV{REMOTE_IDENT} || "-"), " ",
3190 ($ENV{REMOTE_HOST} || "-"), " ",
3191 $ENV{REMOTE_ADDR}, ": ",
3192 $ENV{PATH_INFO}, " ",
3193 $ENV{'CGI_BINARY_FILE'}, "\n";
3195 # Write Query to Log file
3196 print QueryLog $default_values{CGI_Decoded_QS}, "\n\n";
3197 close(QueryLog);
3200 # Return the file path
3201 return $file_path;
3204 # End of Initialize output
3207 ############################################################################
3209 # Handle login access
3211 # Access is based on a valid session ticket.
3212 # Session tickets should be dependend on user name
3213 # and IP address. The patterns of URLs for which a
3214 # session ticket is needed and the login URL are stored in
3215 # %TicketRequiredPatterns as:
3216 # 'RegEx pattern' -> 'SessionPath\tPasswordPath\tLogin URL\tExpiration'
3219 sub Log_In_Access # () -> 0 = Access Allowed, Login page if access is not allowed
3221 # No patterns, no login
3222 goto Return unless %TicketRequiredPatterns;
3224 # Get and initialize values (watch out for stuff processed by BinaryMap files)
3225 my ($SessionPath, $PasswordsPath, $Login, $valid_duration) = ("", "", "", 0);
3226 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3227 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
3228 goto Return if $REMOTE_ADDR =~ /[^0-9\.]/;
3229 # Extract TICKETs, starting with returned cookies
3230 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3231 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3232 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3233 Get_All_Cookies();
3234 if(length(keys(%CGI_Cookies)) > 0)
3236 ${"CGIexecute::LOGINTICKET"} = $CGI_Cookies{'CGIscriptorLOGIN'}
3237 if $CGI_Cookies{'CGIscriptorLOGIN'} && $CGI_Cookies{'CGIscriptorLOGIN'} ne "-";
3238 $CGI_Cookies{'CGIscriptorLOGIN'} = "-";
3239 ${"CGIexecute::CHALLENGETICKET"} = $CGI_Cookies{'CGIscriptorCHALLENGE'}
3240 if $CGI_Cookies{'CGIscriptorCHALLENGE'} && $CGI_Cookies{'CGIscriptorCHALLENGE'} ne "-";
3241 $CGI_Cookies{'CGIscriptorCHALLENGE'} = "-";
3242 ${"CGIexecute::SESSIONTICKET"} = $CGI_Cookies{'CGIscriptorSESSION'}
3243 if $CGI_Cookies{'CGIscriptorSESSION'} && $CGI_Cookies{'CGIscriptorSESSION'} ne "-";
3244 $CGI_Cookies{'CGIscriptorSESSION'} = "-";
3246 # Get and check the tickets. Tickets are restricted to word-characters (alphanumeric+_+.)
3247 my $LOGINTICKET = ${"CGIexecute::LOGINTICKET"};
3248 goto Return if ($LOGINTICKET && $LOGINTICKET =~ /[^\w\.]/isg);
3249 my $SESSIONTICKET = ${"CGIexecute::SESSIONTICKET"};
3250 goto Return if ($SESSIONTICKET && $SESSIONTICKET =~ /[^\w\.]/isg);
3251 my $CHALLENGETICKET = ${"CGIexecute::CHALLENGETICKET"};
3252 goto Return if ($CHALLENGETICKET && $CHALLENGETICKET =~ /[^\w\.]/isg);
3253 # Look for a LOGOUT message
3254 my $LOGOUT = $ENV{QUERY_STRING} =~ /(^|\&)LOGOUT([\=\&]|$)/;
3255 # Username and password
3256 CGIexecute::defineCGIvariable('CGIUSERNAME', "");
3257 my $username = lc(${"CGIexecute::CGIUSERNAME"});
3258 goto Return if $username =~ m!^[^\w]!isg || $username =~ m![^\w \-]!isg;
3259 my $userfile = lc($username);
3260 $userfile =~ s/[^\w]/_/isg;
3261 CGIexecute::defineCGIvariable('PASSWORD', "");
3262 my $password = ${"CGIexecute::PASSWORD"};
3263 CGIexecute::defineCGIvariable('NEWUSERNAME', "");
3264 my $newuser = lc(${"CGIexecute::NEWUSERNAME"});
3265 CGIexecute::defineCGIvariable('NEWPASSWORD', "");
3266 my $newpassword = ${"CGIexecute::NEWPASSWORD"};
3268 foreach my $pattern (keys(%TicketRequiredPatterns))
3270 # Check BOTH the real PATH_INFO and the CGI_BINARY_FILE variable
3271 if($ENV{'PATH_INFO'} =~ m#$pattern# || $ENV{'CGI_BINARY_FILE'} =~ m#$pattern#)
3273 # Fall through a sieve of requirements
3274 ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3276 # Is there a change password request?
3277 if($newuser && $LOGINTICKET && $username)
3279 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3280 goto Login unless (-s "$PasswordsPath/$userfile");
3281 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3282 goto Login unless $ticket_valid;
3283 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".", 1);
3284 goto Login unless $ticket_valid;
3286 my ($sessiontype, $currentticket) = ("", "");
3287 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
3288 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
3289 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
3291 if($sessiontype)
3293 goto Login unless (-s "$SessionPath/$currentticket");
3294 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3295 goto Login unless $ticket_valid;
3297 # Authorize
3298 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath, $REMOTE_ADDR);
3299 goto Login unless $TMPTICKET;
3301 # Create a new user account
3302 CGIexecute::defineCGIvariable('NEWSESSION', "");
3303 my $newsession = ${"CGIexecute::NEWSESSION"};
3304 my $newaccount = create_newuser("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket",
3305 "$PasswordsPath/$userfile", $password, $newuser, $newpassword, $newsession);
3306 CGIexecute::defineCGIvariable('NEWACCOUNTTEXT', $newaccount);
3307 ${CGIexecute::NEWACCOUNTTEXT} = $newaccount;
3308 # NEWACCOUNTTEXT is NOT to be set by the query
3309 CGIexecute::ProtectCGIvariable('NEWACCOUNTTEXT');
3312 # Ready
3313 goto Return;
3315 # Is there a change password request?
3316 elsif($newpassword && $LOGINTICKET && $username)
3318 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3319 goto Login unless (-s "$PasswordsPath/$userfile");
3320 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3321 goto Login unless $ticket_valid;
3322 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".", 1);
3323 goto Login unless $ticket_valid;
3325 my ($sessiontype, $currentticket) = ("", "");
3326 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
3327 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
3328 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
3330 if($sessiontype)
3332 goto Login unless (-s "$SessionPath/$currentticket");
3333 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3334 goto Login unless $ticket_valid;
3336 # Authorize
3337 change_password("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket", "$PasswordsPath/$userfile", $password, $newpassword);
3338 # After a change of password, you have to login again for a CHALLENGE
3339 if($CHALLENGETICKET){$CHALLENGETICKET = "";};
3340 # Ready
3341 goto Return;
3343 # Is there a login ticket of this name?
3344 elsif($LOGINTICKET)
3346 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3347 goto Login unless (-s "$PasswordsPath/$userfile");
3348 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3349 goto Login unless $ticket_valid;
3350 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".");
3351 goto Login unless $ticket_valid;
3353 # Authorize
3354 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath, $REMOTE_ADDR);
3355 if($TMPTICKET)
3357 my $authorization = read_ticket("$PasswordsPath/$userfile");
3358 goto Login unless $authorization;
3359 # Session type is read from the userfile
3360 if($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "CHALLENGE")
3362 # Create New Random CHALLENGETICKET
3363 $CHALLENGETICKET = $TMPTICKET;
3364 create_session_file("$SessionPath/$CHALLENGETICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3366 elsif($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "IPADDRESS")
3368 create_session_file("$SessionPath/$REMOTE_ADDR", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3370 else
3372 # Extra hash to protect CHALLENGETICKET use
3373 $SESSIONTICKET = hash_string($TMPTICKET);
3374 $SESSIONTICKET = hash_string($SESSIONTICKET.$TMPTICKET);
3375 create_session_file("$SessionPath/$SESSIONTICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3376 $SETCOOKIELIST{"CGIscriptorSESSION"} = "-";
3377 $TMPTICKET = $SESSIONTICKET;
3380 # Login ticket file has been used, remove it
3381 unlink($loginfile);
3383 # Is there a session ticket of this name?
3384 # CHALLENGE
3385 if($CHALLENGETICKET)
3387 # Do not log into a CHALLENGE account if the SESSION cookie is present
3388 # Uncomment when $SESSIONTICKET does not receive an extra hash
3389 #goto Login if $SESSIONTICKET =~ /\S/;
3390 goto Login unless (-s "$SessionPath/$CHALLENGETICKET");
3391 my $ticket_valid = check_ticket_validity("CHALLENGE", "$SessionPath/$CHALLENGETICKET", $REMOTE_ADDR, $PATH_INFO);
3392 goto Login unless $ticket_valid;
3394 my $oldchallenge = read_ticket("$SessionPath/$CHALLENGETICKET");
3395 goto Login unless $oldchallenge;
3396 # Check whether the login still exists
3397 my $userfile = lc($oldchallenge->{"Username"}->[0]);
3398 $userfile =~ s/[^\w]/_/isg;
3399 goto Login unless (-s "$PasswordsPath/$userfile");
3401 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3402 goto Login unless $ticket_valid;
3404 # This is a LOGOUT request, clean up (Access has already been validated)
3405 if($LOGOUT)
3407 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
3408 $CHALLENGETICKET = "";
3409 goto Login;
3412 my $NEWCHALLENGETICKET = "";
3413 $NEWCHALLENGETICKET = copy_challenge_file("$SessionPath/$CHALLENGETICKET", "$PasswordsPath/$userfile", $SessionPath);
3414 # Sessionticket is available to scripts, do NOT set the cookie
3415 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3416 goto Return;
3418 # IPADDRESS
3419 elsif(-s "$SessionPath/$REMOTE_ADDR")
3421 my $ticket_valid = check_ticket_validity("IPADDRESS", "$SessionPath/$REMOTE_ADDR", $REMOTE_ADDR, $PATH_INFO);
3422 goto Login unless $ticket_valid;
3423 # Check whether the login still exists
3424 my $currentsessionticket = read_ticket("$SessionPath/$REMOTE_ADDR");
3425 my $userfile = lc($currentsessionticket->{"Username"}->[0]);
3426 $userfile =~ s/[^\w]/_/isg;
3427 goto Login unless (-s "$PasswordsPath/$userfile");
3429 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3430 goto Login unless $ticket_valid;
3432 # This is a LOGOUT request, clean up (Access has already been validated)
3433 if($LOGOUT)
3435 unlink "$SessionPath/$REMOTE_ADDR" if (-s "$SessionPath/$REMOTE_ADDR");
3436 goto Login;
3439 goto Return;
3441 # SESSION
3442 elsif($SESSIONTICKET)
3444 goto Login unless (-s "$SessionPath/$SESSIONTICKET");
3445 my $ticket_valid = check_ticket_validity("SESSION", "$SessionPath/$SESSIONTICKET", $REMOTE_ADDR, $PATH_INFO);
3446 goto Login unless $ticket_valid;
3448 # Check whether the login still exists
3449 my $currentsessionticket = read_ticket("$SessionPath/$SESSIONTICKET");
3450 my $userfile = lc($currentsessionticket->{"Username"}->[0]);
3451 $userfile =~ s/[^\w]/_/isg;
3452 goto Login unless (-s "$PasswordsPath/$userfile");
3454 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3455 goto Login unless $ticket_valid;
3457 # This is a LOGOUT request, clean up (Access has already been validated)
3458 if($LOGOUT)
3460 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
3461 $SESSIONTICKET = "";
3462 goto Login;
3465 # Sessionticket is available to scripts
3466 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3467 goto Return;
3470 goto Login;
3471 goto Return;
3474 Return:
3475 # The Masterkey should NOT be accessible by the parsed files
3476 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3477 return 0;
3479 Login:
3480 # To deter DOS attacks, do not remove valid session tickets unless the
3481 # "owner" has accredited herself
3482 my $tickets_removed = remove_expired_tickets($SessionPath);
3483 create_login_file($PasswordsPath, $SessionPath, $REMOTE_ADDR);
3484 # Note, cookies are set only ONCE
3485 $SETCOOKIELIST{"CGIscriptorLOGIN"} = "-";
3486 # The Masterkey should NOT be accessible by the parsed files
3487 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3488 return "$YOUR_HTML_FILES/$Login";
3491 sub authorize_login # ($loginfile, $authorizationfile, $password, $SessionPath, $IPaddress) => SESSIONTICKET First two arguments are file paths
3493 my $loginfile = shift || "";
3494 my $authorizationfile = shift || "";
3495 my $password = shift || "";
3496 my $SessionPath = shift || "";
3497 my $RemoteIPaddress = shift || "";
3499 # Get Login session ticket
3500 my $loginticket = read_ticket($loginfile);
3501 return 0 unless $loginticket;
3502 # Get User credentials for authorization
3503 my $authorization = read_ticket($authorizationfile);
3504 return 0 unless $authorization;
3506 # Get Randomsalt
3507 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3508 return "" unless $Randomsalt;
3510 my $storedpassword = $authorization->{'Password'}->[0];
3511 return "" unless $storedpassword;
3512 my $Hashedpassword = hash_string($storedpassword.$RemoteIPaddress.$Randomsalt);
3513 return "" unless $password eq $Hashedpassword;
3515 # Extract Session Ticket
3516 my $loginsession = $loginticket->{'Session'}->[0];
3517 my $sessionticket = hash_string($storedpassword.$loginsession);
3518 chomp($sessionticket);
3519 $sessionticket = "" if -x "$SessionPath/$sessionticket";
3521 # No lingering password variables
3522 $Hashedpassword = $Randomsalt;
3523 $password = $Randomsalt;
3524 $authorization->{'Password'}->[0] = $Randomsalt;
3526 return $sessionticket;
3529 sub change_password # ($loginfile, $sessionfile, $authorizationfile, $password, $newpassword) First three arguments are file paths
3531 my $loginfile = shift || "";
3532 my $sessionfile = shift || "";
3533 my $authorizationfile = shift || "";
3534 my $password = shift || "";
3535 my $newpassword = shift || "";
3536 # Get Login session ticket
3537 my $loginticket = read_ticket($loginfile);
3538 return "" unless $loginticket;
3539 # Login ticket file has been used, remove it
3540 unlink($loginfile);
3541 # Get Randomsalt
3542 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3543 return "" unless $Randomsalt;
3544 my $LoginID = $loginticket->{'Session'}->[0];
3545 return "" unless $LoginID;
3547 # Get session ticket
3548 my $sessionticket = read_ticket($sessionfile);
3549 return "" unless $sessionticket;
3551 # Get User credentials for authorization
3552 my $authorization = read_ticket($authorizationfile);
3553 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3555 my $storedpassword = $authorization->{'Password'}->[0];
3556 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3557 return "" unless $password eq $Hashedpassword;
3558 my $secretkey = hash_string($storedpassword.$LoginID.$Randomsalt);
3560 # Decrypt the $newpassword
3561 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3562 return "" unless $decryptedPassword;
3563 # Authorization succeeded, change password
3564 $authorization->{'Password'}->[0] = $decryptedPassword;
3565 # Write out
3566 write_ticket($authorizationfile, $authorization, $authorization->{'Salt'}->[0]);
3568 # No lingering password variables
3569 $decryptedPassword = $Randomsalt;
3570 $secretkey = $Randomsalt;
3571 $storedpassword = $Randomsalt;
3572 $Hashedpassword = $Randomsalt;
3573 $authorization->{'Password'}->[0] = $Randomsalt;
3575 return $newpassword;
3577 # First three arguments are file paths
3578 sub create_newuser # ($loginfile, $sessionfile, $authorizationfile, $password, $newuser, $newpassword, $newsession) -> account text
3580 my $loginfile = shift || "";
3581 my $sessionfile = shift || "";
3582 my $authorizationfile = shift || "";
3583 my $password = shift || "";
3584 my $newuser = shift || "";
3585 my $newpassword = shift || "";
3586 my $newsession = shift || "";
3588 # Get Login session ticket
3589 my $loginticket = read_ticket($loginfile);
3590 return "" unless $loginticket;
3591 # Login ticket file has been used, remove it
3592 unlink($loginfile);
3593 # Get Randomsalt
3594 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3595 return "" unless $Randomsalt;
3596 my $LoginID = $loginticket->{'Session'}->[0];
3597 return "" unless $LoginID;
3599 # Get session ticket
3600 my $sessionticket = read_ticket($sessionfile);
3601 return "" unless $sessionticket;
3602 # Get User credentials for authorization
3603 my $authorization = read_ticket($authorizationfile);
3604 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3605 my $sessionkey = $sessionticket->{'Key'}->[0];
3606 my $serversalt = $authorization->{'Salt'}->[0];
3607 return "" unless $serversalt;
3609 my $storedpassword = $authorization->{'Password'}->[0];
3610 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3611 return "" unless $password eq $Hashedpassword;
3612 my $secretkey = hash_string($storedpassword.$LoginID.$Randomsalt);
3614 # Decrypt the $newpassword
3615 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3616 return "" unless $decryptedPassword;
3618 # Authorization succeeded, create new account
3619 my $newaccount = {};
3620 $newaccount->{'Type'} = ['PASSWORD'];
3621 $newaccount->{'Username'} = [$newuser];
3622 $newaccount->{'Password'} = [$decryptedPassword];
3623 $newaccount->{'Salt'} = [$serversalt];
3624 $newaccount->{'Session'} = ['SESSION'];
3625 if($newsession eq 'IPADDRESS'){$newaccount->{'Session'} = ['IPADDRESS'];};
3626 if($newsession eq 'CHALLENGE'){$newaccount->{'Session'} = ['CHALLENGE'];};
3627 my $timesec = time();
3628 my $gmt_date = gmtime();
3629 $newaccount->{'Time'} = [$timesec];
3630 $newaccount->{'Date'} = [$gmt_date];
3632 # AllowedPaths
3633 my $NewAllowedPaths = "";
3634 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3635 my $currentRoot = "";
3636 $currentRoot = $1 if $PATH_INFO =~ m!^([\w\-\. /]+)!isg;
3637 $currentRoot =~ s![^/]+$!!isg;
3638 if($currentRoot)
3640 $currentRoot .= '/' unless $currentRoot =~ m!/$!;
3641 my $newpath = "^".${currentRoot}.'[\w\-]+\.html?';
3642 $NewAllowedPaths .= 'AllowedPaths: ^'.${currentRoot}.'[\w\-]+\.html?'."\n";
3643 $newaccount->{'AllowedPaths'} = [$newpath];
3645 else
3647 # Tricky PATH_INFO, deny all
3648 $NewAllowedPaths .= "DeniedPaths: ^/\n";
3649 $newaccount->{'DeniedPaths'} = ["DeniedPaths: ^/\n"];
3652 # Construct home directory path
3653 my $FullHomeDirectoryPath = "";
3654 my $currentHome = lc($newuser);
3655 if($currentHome && $currentHome !~ /^\s*\#/)
3657 $currentHome =~ s![^\w]!_!isg;
3658 my $newpath = "^${currentRoot}$currentHome/";
3659 push(@{$newaccount->{'AllowedPaths'}}, $newpath);
3660 # Create home directory
3661 $FullHomeDirectoryPath = $ENV{'HOME'}.${currentRoot}.$currentHome;
3664 # Allowed Paths
3665 CGIexecute::defineCGIvariable('ALLOWEDPATHS', "");
3666 my $allowedpaths = ${"CGIexecute::ALLOWEDPATHS"};
3667 if($allowedpaths && $allowedpaths !~ /^\s*\#/)
3669 $allowedpaths =~ s!\#.*$!!isg;
3670 $allowedpaths =~ s![^\^\w\./\;\+\*\?\[\]\$]!!isg;
3671 my @pathlist = split(/\;/, $allowedpaths);
3672 foreach my $entry (@pathlist)
3674 push(@{$newaccount->{'AllowedPaths'}}, "^".${currentRoot}.$entry);
3678 # Allowed IP addresses
3679 CGIexecute::defineCGIvariable('IPADDRESS', "");
3680 my $ipaddress = ${"CGIexecute::IPADDRESS"};
3681 if($ipaddress && $ipaddress !~ /^\s*\#/)
3683 $ipaddress =~ s!\#.*$!!isg;
3684 $ipaddress =~ s![^\d\.\;]!!isg;
3685 my @iplist = split(/\;/, $ipaddress);
3686 foreach my $entry (@iplist)
3688 next unless $entry =~ /\d/;
3689 next if $entry =~ /^\s*\#/;
3690 $entry =~ s/\./\\./g;
3691 push(@{$newaccount->{'IPaddress'}}, $entry);
3695 # Capabilities
3696 CGIexecute::defineCGIvariable('NEWCAPABILITIES', "");
3697 my $capabilities = ${"CGIexecute::NEWCAPABILITIES"};
3698 if($capabilities && $capabilities !~ /^\W*\#/)
3700 $capabilities =~ s!\#.*$!!isg;
3701 $capabilities =~ s![^\w\s]!!isg;
3702 my @caplist = split(/\s/, $capabilities);
3703 foreach my $entry (@caplist)
3705 next unless $entry =~ /\w/;
3706 next if $entry =~ /^\s*\#/;
3707 push(@{$newaccount->{'Capabilities'}}, $entry);
3711 # Sign the new ticket
3712 my $Signature = SignTicketWithMasterkey($newaccount, $newaccount->{'Salt'}->[0]);
3714 # Write
3715 my $datetime = gmtime();
3716 my $newuserfile = "";
3717 if(grep(/^CreateUser$/, @{$authorization->{'Capabilities'}}))
3719 my $newuserfilename = lc($newuser);
3720 $newuserfilename =~ s/[^\w]/_/isg;
3721 $newuserfile = $authorizationfile;
3722 $newuserfile =~ s![^/]*$!!isg;
3723 $newuserfile .= $newuserfilename;
3724 if(-s $newuserfile)
3726 $newuserfile = "";
3728 elsif($FullHomeDirectoryPath && !(-d $FullHomeDirectoryPath || -s $FullHomeDirectoryPath))
3730 if(-d "$ENV{'HOME'}${currentRoot}.SkeletonDir")
3732 `cp -r '$ENV{'HOME'}${currentRoot}.SkeletonDir' '$FullHomeDirectoryPath'`;
3734 elsif(-d "$ENV{'HOME'}${currentRoot}SkeletonDir")
3736 `cp -r '$ENV{'HOME'}${currentRoot}SkeletonDir' '$FullHomeDirectoryPath'`;
3738 elsif(-s "$ENV{'HOME'}${currentRoot}UserIndex.html")
3740 mkdir $FullHomeDirectoryPath;
3741 `cp '$ENV{'HOME'}${currentRoot}UserIndex.html' '$FullHomeDirectoryPath/index.html'`;
3743 elsif(-s "$ENV{'HOME'}${currentRoot}index.html")
3745 mkdir $FullHomeDirectoryPath;
3746 `cp '$ENV{'HOME'}${currentRoot}index.html' '$FullHomeDirectoryPath/index.html'`;
3752 my $newaccounttext = write_ticket($newuserfile, $newaccount, $serversalt);
3754 # Re-encrypt the new password for transmission
3755 if($newaccounttext =~ /^(Password\:\s+)(\S+)\s*$/)
3757 my $passwordvalue = $1;
3758 my $reencryptedpassword = XOR_hex_strings($secretkey, $passwordvalue);
3759 my $encryptedpasswordline = "<span id='newaccount'>$reencryptedpassword</span>";
3760 $newaccounttext =~ s/^(Password\:\s+)(\S+)\s*$/\1$encryptedpasswordline/gim;
3762 # No lingering passwords
3763 $passwordvalue = $serversalt;
3765 return $newaccounttext;
3768 # Copy a Challenge ticket file to a new name which is the hash of the new $CHALLENGETICKET and the password
3769 sub copy_challenge_file #($oldchallengefile, $authorizationfile, $sessionpath) -> $CHALLENGETICKET
3771 my $oldchallengefile = shift || "";
3772 my $authorizationfile = shift || "";
3773 my $sessionpath = shift || "";
3774 $sessionpath =~ s!/+$!!g;
3776 # Get Login session ticket
3777 my $oldchallenge = read_ticket($oldchallengefile);
3778 return "" unless $oldchallenge;
3780 # Get Authorization (user) session file
3781 my $authorization = read_ticket($authorizationfile);
3782 return "" unless $authorization;
3783 my $storedpassword = $authorization->{'Password'}->[0];
3784 return "" unless $storedpassword;
3785 my $challengekey = $oldchallenge->{'Key'}->[0];
3786 return "" unless $challengekey;
3788 # Create Random Hash Salt
3789 my $NEWCHALLENGETICKET = get_random_hex();;
3790 my $newchallengefile = hash_string($challengekey.$NEWCHALLENGETICKET);
3791 return "" unless $newchallengefile;
3793 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3794 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3795 ${"CGIexecute::CHALLENGETICKET"} = $NEWCHALLENGETICKET;
3797 # Write Session Ticket
3798 open(OLDCHALLENGE, "<$oldchallengefile") || die "<$oldchallengefile: $!\n";
3799 my @OldChallengeLines = <OLDCHALLENGE>;
3800 close(OLDCHALLENGE);
3801 # Old file should now be removed
3802 unlink($oldchallengefile);
3804 open(SESSION, ">$sessionpath/$newchallengefile") || die "$sessionpath/$newchallengefile: $!\n";
3805 foreach $line (@OldChallengeLines)
3807 print SESSION $line;
3809 close(SESSION);
3811 # No lingering passwords
3812 $storedpassword = $oldchallenge;
3814 return $NEWCHALLENGETICKET;
3817 sub create_login_file #($PasswordDir, $SessionDir, $IPaddress)
3819 my $PasswordDir = shift || "";
3820 my $SessionDir = shift || "";
3821 my $IPaddress = shift || "";
3823 # Create Login Ticket
3824 my $LOGINTICKET= get_random_hex ();
3826 # Create Random Hash Salt
3827 my $RANDOMSALT= get_random_hex();
3829 # Create SALT file if it does not exist
3830 # Remove this, including test account for life system
3831 unless(-d "$SessionDir")
3833 `mkdir -p "$SessionDir"`;
3835 unless(-d "$PasswordDir")
3837 `mkdir -p "$PasswordDir"`;
3839 # Create SERVERSALT and default test account
3840 my $SERVERSALT = "";
3841 unless(-s "$PasswordDir/SALT")
3843 $SERVERSALT= get_random_hex();
3844 open(SALTFILE, ">$PasswordDir/SALT") || die ">$PasswordDir/SALT: $!\n";
3845 print SALTFILE "$SERVERSALT\n";
3846 close(SALTFILE);
3848 # Update test account (should be removed in live system)
3849 my @alltestusers = ("test", "testip", "testchallenge", "admin");
3850 foreach my $testuser (@alltestusers)
3852 if(-s "$PasswordDir/$testuser")
3854 my $plainpassword = $testuser eq 'admin' ? "There is no password like more password" : "testing";
3856 my $storedpassword = hash_string(${plainpassword}.${testuser}.${SERVERSALT});
3857 # Encrypt the new password with the MasterKey
3858 my $authorization = read_ticket("$PasswordDir/$testuser") || return "";
3859 $authorization->{'Salt'} = [$SERVERSALT];
3860 $authorization->{'Type'} = ['INACTIVE PASSWORD'] if $testuser eq 'admin';
3861 set_password($authorization, $SERVERSALT, $plainpassword);
3862 write_ticket("$PasswordDir/$testuser", $authorization, $SERVERSALT);
3863 # No lingering passwords
3864 $storedpassword = $SERVERSALT;
3865 $plainpassword = $SERVERSALT;
3870 # Read in site Salt
3871 open(SALTFILE, "<$PasswordDir/SALT") || die "$PasswordDir/SALT: $!\n";
3872 $SERVERSALT=<SALTFILE>;
3873 close(SALTFILE);
3874 chomp($SERVERSALT);
3876 # Create login session ticket
3877 my $datetime = gmtime();
3878 my $timesec = time();
3879 my $loginticket = {};
3880 $loginticket->{Type} = ['LOGIN'];
3881 $loginticket->{IPaddress} = [$IPaddress];
3882 $loginticket->{Salt} = [$SERVERSALT];
3883 $loginticket->{Session} = [$LOGINTICKET];
3884 $loginticket->{Randomsalt} = [$RANDOMSALT];
3885 $loginticket->{Expires} = ['+600s'];
3886 $loginticket->{Date} = ["$datetime UTC"];
3887 $loginticket->{Time} = [$timesec];
3888 write_ticket("$SessionDir/$LOGINTICKET", $loginticket, $SERVERSALT);
3890 # Set global variables
3891 # $SERVERSALT
3892 $ENV{'SERVERSALT'} = $SERVERSALT;
3893 CGIexecute::defineCGIvariable('SERVERSALT', "");
3894 ${"CGIexecute::SERVERSALT"} = $SERVERSALT;
3896 # $SESSIONTICKET
3897 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3898 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3899 ${"CGIexecute::SESSIONTICKET"} = $SESSIONTICKET;
3901 # $RANDOMSALT
3902 $ENV{'RANDOMSALT'} = $RANDOMSALT;
3903 CGIexecute::defineCGIvariable('RANDOMSALT', "");
3904 ${"CGIexecute::RANDOMSALT"} = $RANDOMSALT;
3906 # $LOGINTICKET
3907 $ENV{'LOGINTICKET'} = $LOGINTICKET;
3908 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3909 ${"CGIexecute::LOGINTICKET"} = $LOGINTICKET;
3911 return $ENV{'LOGINTICKET'};
3914 sub create_session_file #($sessionfile, $loginfile, $authorizationfile, $path) -> Is $loginfile deleted? 0/1
3916 my $sessionfile = shift || "";
3917 my $loginfile = shift || "";
3918 my $authorizationfile = shift || "";
3919 my $path = shift || "";
3921 # Get Login session ticket
3922 my $loginticket = read_ticket($loginfile);
3923 return unlink($loginfile) unless $loginticket;
3925 # Get Authorization (user) session file
3926 my $authorization = read_ticket($authorizationfile);
3927 return unlink($loginfile) unless $authorization;
3929 # For a Session or a Challenge, we need a stored key
3930 my $sessionkey = "";
3931 my $secretkey = "";
3932 if($authorization->{'Session'} && $authorization->{'Session'}->[0] ne 'IPADDRESS')
3934 my $storedpassword = $authorization->{'Password'}->[0];
3935 my $loginticketid = $loginticket->{'Session'}->[0];
3936 my $randomsalt = $loginticket->{'Randomsalt'}->[0];
3937 $sessionkey = hash_string($storedpassword.$loginticketid);
3938 $secretkey = hash_string($storedpassword.$loginticketid.$randomsalt);
3939 # No lingering passwords
3940 $storedpassword = $loginticketid;
3942 # Get Session id
3943 my $sessionid = "";
3944 if($sessionfile =~ m!([^/]+)$!)
3946 $sessionid = $1;
3949 # Convert Authorization content to Session content
3950 my $sessionContent = {};
3951 my $SessionType = $authorization->{'Session'}->[0] ? $authorization->{'Session'}->[0] : "SESSION";
3952 $sessionContent->{Type} = [$SessionType];
3953 $sessionContent->{Username} = [lc($authorization->{'Username'}->[0])];
3954 $sessionContent->{Session} = [$sessionid];
3955 $sessionContent->{Time} = [time];
3956 # Limit communication to the login IP address, except for Tor like situations with VariableREMOTE_ADDR
3957 $sessionContent->{IPaddress} = ['.'];
3958 if($sessionContent->{Type}->[0] eq 'CHALLENGE' && grep(/^VariableREMOTE_ADDR$/, @{$authorization->{'Capabilities'}}))
3960 $sessionContent->{IPaddress} = $authorization->{'IPaddress'} if $authorization->{'IPaddress'};
3962 else
3964 $sessionContent->{IPaddress} = $loginticket->{'IPaddress'};
3966 $sessionContent->{Salt} = $authorization->{'Salt'};
3967 $sessionContent->{Randomsalt} = $loginticket->{'Randomsalt'};
3968 $sessionContent->{AllowedPaths} = $authorization->{'AllowedPaths'};
3969 $sessionContent->{DeniedPaths} = $authorization->{'DeniedPaths'};
3970 $sessionContent->{Expires} = $authorization->{'MaxLifetime'};
3971 $sessionContent->{Capabilities} = $authorization->{'Capabilities'};
3972 foreach my $pattern (keys(%TicketRequiredPatterns))
3974 if($path =~ m#$pattern#)
3976 my ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3977 push(@{$sessionContent->{Expires}}, $validtime);
3980 $sessionContent->{Key} = [$sessionkey] if $sessionkey;
3981 $sessionContent->{Secretkey} = [$secretkey] if $secretkey;
3982 $sessionContent->{Date} = [gmtime()." UTC"];
3984 # Write Session Ticket
3985 write_ticket($sessionfile, $sessionContent, $authorization->{'Salt'}->[0]);
3987 # Login file should now be removed
3988 return unlink($loginfile);
3991 sub check_ticket_validity # ($type, $ticketfile, $address, $path [, $unsigned])
3993 my $type = shift || "SESSION";
3994 my $ticketfile = shift || "";
3995 my $address = shift || "";
3996 my $path = shift || "";
3997 my $unsigned = shift || 0;
3999 # Is there a session ticket of this name?
4000 return 0 unless -s "$ticketfile";
4002 # There is a session ticket, is it linked to this IP address?
4003 my $ticket = read_ticket($ticketfile);
4004 unless($ticket)
4006 print STDERR "Ticket expired or empty: $ticketfile\n";
4007 return;
4010 # Is this the right type of ticket
4011 unless($ticket && $ticket->{'Type'}->[0] eq $type)
4013 print STDERR "Wrong ticket type: $ticket->{'Type'}->[0] eq $type\n";
4014 return;
4017 # Does the IP address match?
4018 my $IPmatches = @{$ticket->{"IPaddress"}} ? 0 : 1;
4019 for $IPpattern (@{$ticket->{"IPaddress"}})
4021 ++$IPmatches if $address =~ m#^$IPpattern#ig;
4023 if($address && ! $IPmatches)
4025 print STDERR "Wrong REMOTE ADDR for $ticket->{'Username'}->[0]: $ticket->{'IPaddress'}->[0] vs $address\n";
4026 return 0;
4029 # Is the path denied
4030 my $Pathmatches = 0;
4031 foreach $Pathpattern (@{$ticket->{"DeniedPaths"}})
4033 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
4035 return 0 if @{$ticket->{"DeniedPaths"}} && $Pathmatches;
4037 # Is the path allowed
4038 $Pathmatches = 0;
4039 foreach $Pathpattern (@{$ticket->{"AllowedPaths"}})
4041 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
4043 return 0 unless !@{$ticket->{"AllowedPaths"}} || $Pathmatches;
4045 # Check signature if not told to use an unsigned ticket (dangerous)
4046 my $Signature = TicketSignature($ticket, $ticket->{'Salt'}->[0]);
4047 if((! $unsigned) && $Signature && $Signature ne $ticket->{'Signature'}->[0])
4049 print STDERR "Invalid signature for $ticket->{'Type'}: $ticket->{'Username'}\n$ticketfile\n";
4050 return 0;
4053 # Make login values available (will also protect against resetting by query)
4054 $ENV{"LOGINUSERNAME"} = lc($ticket->{'Username'}->[0]);
4055 $ENV{"LOGINIPADDRESS"} = $address;
4056 $ENV{"LOGINPATH"} = $path;
4057 $ENV{"SESSIONTYPE"} = $type unless $type eq "PASSWORD";
4059 # Set Capabilities, if present
4060 if($ticket->{'Username'}->[0] && @{$ticket->{'Capabilities'}})
4062 $ENV{'CAPABILITIES'} = $ticket->{'Username'}->[0];
4063 CGIexecute::defineCGIvariableList('CAPABILITIES', "");
4064 @{"CGIexecute::CAPABILITIES"} = @{$ticket->{'Capabilities'}};
4065 # Capabilities should not be changed anymore by CGI query!
4067 # Capabilities are NOT to be set by the query
4068 CGIexecute::ProtectCGIvariable('CAPABILITIES');
4070 return 1;
4074 # This might be run in a fork()?
4075 sub remove_expired_tickets # ($path) -> number of tickets removed
4077 my $path = shift || "";
4078 return 0 unless $path;
4079 $path =~ s!/+$!!g;
4080 my $removed_tickets = 0;
4081 my @ticketlist = glob("$path/*");
4082 foreach my $ticketfile (@ticketlist)
4084 my $ticket = read_ticket($ticketfile);
4085 unless($ticket)
4087 unlink $ticketfile;
4088 ++$removed_tickets;
4091 return $removed_tickets;
4094 sub set_password # ($ticket, $salt, $plainpassword) -> $password
4096 my $ticket = shift || "";
4097 my $salt = shift || "";
4098 my $plainpassword = shift || "";
4100 my $user = lc($ticket->{'Username'}->[0]);
4101 return "" unless $user;
4102 $salt = $ticket->{'Salt'}->[0] unless $salt;
4104 my $storedpassword = hash_string(${plainpassword}.${user}.${salt});
4105 $ticket->{'Password'} = [$storedpassword];
4106 $ticket->{'Salt'} = [$salt];
4107 # No lingering passwords
4108 $storedpassword = $salt;
4109 $plainpassword = $salt;
4111 return $ticket->{'Password'}->[0];
4114 sub write_ticket # ($ticketfile, $ticket, $salt [, $masterkey]) -> &%ticket
4116 my $ticketfile = shift || "";
4117 my $ticket = shift || "";
4118 my $salt = shift || "";
4119 my $masterkey = shift || $ENV{'CGIMasterKey'};
4121 # Encrypt password
4122 EncryptTicketWithMasterKey($ticket, $salt, $masterkey);
4124 # Sign the new ticket
4125 my $signature = SignTicketWithMasterkey($ticket, $salt, $masterkey);
4127 # Create ordered list with labels
4128 my @orderlist = ('Type', 'Username', 'Password', 'IPaddress', 'AllowedPaths', 'DeniedPaths',
4129 'Expires', 'Capabilities', 'Salt', 'Session', 'Randomsalt',
4130 'Date', 'Time', 'Signature', 'Key', 'Secretkey');
4131 my @labellist = keys(%{$ticket});
4132 foreach my $label (@orderlist)
4134 @labellist = grep(!/\b$label\b/, @labellist);
4137 # Create ticket in text
4138 my $TicketText = "";
4139 foreach my $label (@orderlist, @labellist)
4141 next unless exists($ticket->{$label}) && $ticket->{$label}->[0];
4142 foreach my $value (@{$ticket->{$label}})
4144 $TicketText .= "$label: $value\n";
4147 if($ticketfile)
4149 open(TICKET, ">$ticketfile") || die "$ticketfile: $!\n";
4150 print TICKET $TicketText;
4151 close(TICKET);
4154 return $TicketText;
4157 # Note, read_ticket will return 0 if the ticket has expired!
4158 sub read_ticket # ($ticketfile [, $salt, $masterkey]) -> &%ticket
4160 my $ticketfile = shift || "";
4161 my $serversalt = shift || "";
4162 my $masterkey = shift || $ENV{'CGIMasterKey'};
4164 my $ticket = {};
4165 if($ticketfile && -s $ticketfile)
4167 open(TICKETFILE, "<$ticketfile") || die "$ticketfile: $!\n";
4168 my @alllines = <TICKETFILE>;
4169 close(TICKETFILE);
4170 foreach my $currentline (@alllines)
4172 # Skip empty lines and comments
4173 next unless $currentline =~ /\S/;
4174 next if $currentline =~ /^\s*\#/;
4176 if($currentline =~ /^\s*(\S[^\:]+)\:\s+(.*)\s*$/)
4178 my $Label = $1;
4179 my $Value = $2;
4180 $ticket->{$Label} = () unless exists($ticket->{$Label});
4181 push(@{$ticket->{$Label}}, $Value);
4185 elsif(-z $ticketfile)
4187 return 0;
4189 if($masterkey && exists($ticket->{'Password'}) && $ticket->{'Password'}->[0])
4191 # Use the ServerSalt stored in the ticket, if present
4192 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4194 $serversalt = $ticket->{Salt}->[0];
4196 # Decrypt all passwords
4197 DecryptTicketWithMasterKey($ticket, $serversalt, $masterkey) ||
4198 die "Decryption failed: DecryptTicketWithMasterKey ($ticket, $serversalt)\n";
4201 # Check whether the ticket has expired
4202 if(exists($ticket->{Expires}))
4204 my $StartTime = 0;
4205 if(exists($ticket->{Time}) && $ticket->{Time}->[0] > 0)
4207 $StartTime = [(sort(@{$ticket->{Time}}))]->[0];
4209 else
4211 # Get SessionTicket file stats
4212 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)
4213 = stat($ticketfile);
4214 $StartTime = $ctime;
4216 foreach my $Value (@{$ticket->{'Expires'}})
4218 # Recalculate expire date from relative time
4219 if($Value =~ /^\+/)
4221 if($Value =~ /^\+(\d+)\s*d(ays)?\s*$/)
4223 $ExpireTime = 24*3600*$1;
4225 elsif($Value =~ /^\+(\d+)\s*m(inutes)?\s*$/)
4227 $ExpireTime = 60*$1;
4229 elsif($Value =~ /^\+(\d+)\s*h(ours)?\s*$/)
4231 $ExpireTime = 3600*$1;
4233 elsif($Value =~ /^\+(\d+)\s*s(econds)?\s*$/)
4235 $ExpireTime = $1;
4237 elsif($Value =~ /^\+(\d+)\s*$/)
4239 $ExpireTime = $1;
4242 my $absoluteTime = $Value =~ /^\+/ ? $StartTime + $ExpireTime : $Value;
4243 return 0 unless $absoluteTime > time;
4245 @{$ticket->{Expires}} = sort(@{$ticket->{Expires}});
4247 return $ticket;
4250 # Set up a valid ticket from a given text file
4251 # Use from command line. DO NOT USE ONLINE
4252 # Watch out for passwords that get stored in the history file
4254 # perl CGIscriptor.pl --managelogin [options] [files]
4255 # Options:
4256 # salt={file or saltvalue}
4257 # masterkey={file or plaintext}
4258 # newmasterkey={file or plaintext}
4259 # password={file or palintext}
4261 # Followed by one or more file names.
4262 # Options can be interspersed between filenames,
4263 # e.g., password='plaintext'
4264 # Note that passwords are only used once!
4266 sub setup_ticket_file # (@ARGV)
4268 # Stop when run on-line
4269 return if $ENV{'PATH_INFO'} || $ENV{'QUERY_STRING'};
4271 my %Settings = ();
4272 foreach my $input (@_)
4274 if($input =~ /^([\w]+)\=/)
4276 my $name = lc($1);
4277 my $value = $';
4278 chomp($value);
4280 if($value !~ m![^\w\.\~\/\:\-]! && $value !~ /^[\-\.]/ && -s "$value" && ! -d "$value")
4282 # Warn about reading a value from file
4283 print STDERR "Read '$name' from: '$value'\n";
4284 open(INPUTVALUE, "<$value") || die "$value: $!\n";
4285 $value = <INPUTVALUE>;
4286 chomp($value);
4289 $value =~ s/(^\'([^\']*)\'$)/\1/g;
4290 $value =~ s/(^\"([^\"]*)\"$)/\1/g;
4291 $Settings{$name} = $value;
4293 elsif($input !~ m![^\w\.\~\/\:\-]!i && $input !~ /^[\-\.]/i && -s $input)
4295 # We MUST have a salt
4296 $Settings{'salt'} = $ticket->{'Salt'}->[0] unless $Settings{'salt'};
4298 # Set the new masterkey to the old masterkey if there is no new masterkey
4299 $Settings{'newmasterkey'} = $Settings{'masterkey'} unless exists($Settings{'newmasterkey'});
4301 # Get the ticket
4302 my $ticket = read_ticket($input, $Settings{'salt'}, $Settings{'masterkey'});
4304 # Set a new password from plaintext
4305 $ticket->{'Salt'}->[0] = $Settings{'salt'} if $Settings{'salt'} && $Settings{'password'};
4306 set_password ($ticket, $Settings{'salt'}, $Settings{'password'}) if $Settings{'password'};
4307 # Write the ticket back to file
4308 write_ticket($input, $ticket, $Settings{'salt'}, $Settings{'newmasterkey'});
4310 # A password is only used once
4311 $Settings{'password'} = "";
4316 # Add a signature from $masterkey to a ticket in the label $signlabel
4317 sub SignTicketWithMasterkey # ($ticket, $serversalt [, $masterkey, $signlabel]) -> $Signature
4319 my $ticket = shift || return 0;
4320 my $serversalt = shift || "";
4321 my $masterkey = shift || $ENV{'CGIMasterKey'};
4322 my $signlabel = shift || 'Signature';
4324 my $Signature = TicketSignature($ticket, $serversalt, $masterkey);
4326 $ticket->{$signlabel} = [$Signature] if $Signature;
4328 return $Signature;
4331 # Determine ticket signature
4332 sub TicketSignature # ($ticket, $serversalt [, $masterkey]) -> $Signature
4334 my $ticket = shift || return 0;
4335 my $serversalt = shift || "";
4336 my $masterkey = shift || $ENV{'CGIMasterKey'};
4337 my $Signature = "";
4339 if($masterkey)
4341 # If the ServerSalt is not stored in the ticket, the SALT file has to be found
4342 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4344 $serversalt = $ticket->{Salt}->[0];
4346 # Sign
4347 if($serversalt)
4349 my $username = lc($ticket->{'Username'}->[0]);
4350 my $hash1 = hash_string(${masterkey}.${serversalt});
4351 # The order of $username.$hash1 should be different than in DecryptTicketWithMasterKey
4352 my $CryptKey = hash_string($username.${'hash1'});
4353 my $SignText = "Type: ".$ticket->{'Type'}->[0]."\n";
4354 my @tmp = sort(@{$ticket->{'Username'}});
4355 $SignText .= "Username: @tmp\n";
4356 @tmp = sort(@{$ticket->{'IPaddress'}});
4357 $SignText .= "IPaddress: @tmp\n";
4358 @tmp = sort(@{$ticket->{'AllowedPaths'}});
4359 $SignText .= "AllowedPaths: @tmp\n";
4360 @tmp = sort(@{$ticket->{'DeniedPaths'}});
4361 $SignText .= "DeniedPaths: @tmp\n";
4362 @tmp = sort(@{$ticket->{'Session'}});
4363 $SignText .= "Session: @tmp\n";
4364 @tmp = sort(@{$ticket->{'Time'}});
4365 $SignText .= "Time: @tmp\n";
4366 @tmp = sort(@{$ticket->{'Expires'}});
4367 $SignText .= "Expires: @tmp\n";
4368 @tmp = sort(@{$ticket->{'Capabilities'}});
4369 $SignText .= "Capabilities: @tmp\n";
4370 @tmp = sort(@{$ticket->{'MaxLifetime'}});
4371 $SignText .= "MaxLifetime: @tmp\n";
4372 $Signature = HMAC_hex($CryptKey, $SignText);
4375 return $Signature;
4378 # Decrypts a password list IN PLACE
4379 sub DecryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4381 my $ticket = shift || return 0;
4382 my $serversalt = shift || "";
4383 my $masterkey = shift || $ENV{'CGIMasterKey'};
4385 if($masterkey && exists($ticket->{Password}) && $ticket->{Password}->[0])
4387 # If the ServerSalt is not given, read it from the the ticket
4388 if(! $serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4390 $serversalt = $ticket->{Salt}->[0];
4392 # Decrypt password(s)
4393 if($serversalt)
4395 my $hash1 = hash_string(${masterkey}.${serversalt});
4396 my $username = lc($ticket->{'Username'}->[0]);
4397 # The order of $hash1.$username should be different than in TicketSignature
4398 my $CryptKey = hash_string(${'hash1'}.$username);
4399 foreach my $password (@{$ticket->{Password}})
4401 $password = XOR_hex_strings($CryptKey,$password);
4405 return $ticket->{'Password'};
4407 sub EncryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4409 DecryptTicketWithMasterKey(@_);
4412 # Implement HMAC signature hash.
4413 # Blocksize is length in HEX characters, NOT bytes
4414 sub HMAC_hex # ($key, $message [, $blocksizehex]) -> $hex
4416 my $key = shift || "";
4417 my $message = shift || "";
4418 my $blocksizehex = shift || length($key);
4419 $key = hash_string($key) if length($key) > $blocksizehex;
4421 my $innerkey = XOR_hex_byte ($key, "36");
4422 my $outerkey = XOR_hex_byte ($key, "5c");
4423 my $innerhash = hash_string($innerkey.$message);
4424 my $outerhash = hash_string($outerkey.$innerhash);
4426 return $outerhash;
4429 # XOR input with equally long string of repeated 2 hex character (byte)
4430 # string. Input must have even number of hex characters
4431 sub XOR_hex_byte # ($hex1, $hexbyte) -> $hex
4433 my $hex1 = shift || "";
4434 my $hexbyte = shift || "";
4435 my $bytelength = length($hexbyte);
4436 my $hex2 = $hex1;
4437 $hex2 =~ s/.{$bytelength}/$hexbyte/ig;
4438 return XOR_hex_strings($hex1, $hex2);
4441 sub XOR_hex_strings # ($hex1, $hex2) -> $hex
4443 my $hex1 = shift || "";
4444 my $hex2 = shift || "";
4445 my @hex1list = split('', $hex1);
4446 my @hex2list = split('', $hex2);
4447 my @hexresultlist = ();
4448 for(my $i; $i < scalar(@hex1list); ++$i)
4450 my $d1 = hex($hex1list[$i]);
4451 my $d2 = hex($hex2list[$i]);
4452 my $dresult = ($d1 ^ $d2);
4453 $hexresultlist[$i] = sprintf("%x", $dresult);
4455 $hexresult = join('', @hexresultlist);
4456 return $hexresult;
4459 # End of Handle login access
4462 ############################################################################
4464 # Handle foreign interpreters (i.e., scripting languages)
4466 # Insert perl code to execute scripts in foreign scripting languages.
4467 # Actually, the scripts inside the <SCRIPT></SCRIPT> blocks are piped
4468 # into an interpreter.
4469 # The code presented here is fairly confusing because it
4470 # actually writes perl code code to the output.
4472 # A table with the file handles
4473 %SCRIPTINGINPUT = ();
4475 # A function to clean up Client delivered CGI parameter values
4476 # (i.e., quote all odd characters)
4477 %SHRUBcharacterTR =
4479 "\'" => '&#39;',
4480 "\`" => '&#96;',
4481 "\"" => '&quot;',
4482 '&' => '&amper;',
4483 "\\" => '&#92;'
4486 sub shrubCGIparameter # ($String) -> Cleaned string
4488 my $String = shift || "";
4490 # Change all quotes [`'"] into HTML character entities
4491 my ($Char, $Transcript) = ('&', $SHRUBcharacterTR{'&'});
4493 # Protect &
4494 $String =~ s/\Q$Char\E/$Transcript/isg if $Transcript;
4496 while( ($Char, $Transcript) = each %SHRUBcharacterTR)
4498 next if $Char eq '&';
4499 $String =~ s/\Q$Char\E/$Transcript/isg;
4502 # Replace newlines
4503 $String =~ s/[\n]/\\n/g;
4504 # Replace control characters with their backslashed octal ordinal numbers
4505 $String =~ s/([^\S \t])/(sprintf("\\0%o", ord($1)))/eisg; #
4506 $String =~ s/([\x00-\x08\x0A-\x1F])/(sprintf("\\0%o", ord($1)))/eisg; #
4508 return $String;
4512 # The initial open statements: Open a pipe to the foreign script interpreter
4513 sub OpenForeignScript # ($ContentType) -> $DirectivePrefix
4515 my $ContentType = lc(shift) || return "";
4516 my $NewDirective = "";
4518 return $NewDirective if($SCRIPTINGINPUT{$ContentType});
4520 # Construct a unique file handle name
4521 $SCRIPTINGFILEHANDLE = uc($ContentType);
4522 $SCRIPTINGFILEHANDLE =~ s/\W/\_/isg;
4523 $SCRIPTINGINPUT{$ContentType} = $SCRIPTINGFILEHANDLE
4524 unless $SCRIPTINGINPUT{$ContentType};
4526 # Create the relevant script: Open the pipe to the interpreter
4527 $NewDirective .= <<"BLOCKCGISCRIPTOROPEN";
4528 # Open interpreter for '$ContentType'
4529 # Open pipe to interpreter (if it isn't open already)
4530 open($SCRIPTINGINPUT{$ContentType}, "|$ScriptingLanguages{$ContentType}") || main::dieHandler(14, "$ContentType: \$!\\n");
4531 BLOCKCGISCRIPTOROPEN
4533 # Insert Initialization code and CGI variables
4534 $NewDirective .= InitializeForeignScript($ContentType);
4536 # Ready
4537 return $NewDirective;
4541 # The final closing code to stop the interpreter
4542 sub CloseForeignScript # ($ContentType) -> $DirectivePrefix
4544 my $ContentType = lc(shift) || return "";
4545 my $NewDirective = "";
4547 # Do nothing unless the pipe realy IS open
4548 return "" unless $SCRIPTINGINPUT{$ContentType};
4550 # Initial comment
4551 $NewDirective .= "\# Close interpreter for '$ContentType'\n";
4554 # Write the Postfix code
4555 $NewDirective .= CleanupForeignScript($ContentType);
4557 # Create the relevant script: Close the pipe to the interpreter
4558 $NewDirective .= <<"BLOCKCGISCRIPTORCLOSE";
4559 close($SCRIPTINGINPUT{$ContentType}) || main::dieHandler(15, \"$ContentType: \$!\\n\");
4560 select(STDOUT); \$|=1;
4562 BLOCKCGISCRIPTORCLOSE
4564 # Remove the file handler of the foreign script
4565 delete($SCRIPTINGINPUT{$ContentType});
4567 return $NewDirective;
4571 # The initialization code for the foreign script interpreter
4572 sub InitializeForeignScript # ($ContentType) -> $DirectivePrefix
4574 my $ContentType = lc(shift) || return "";
4575 my $NewDirective = "";
4577 # Add initialization code
4578 if($ScriptingInitialization{$ContentType})
4580 $NewDirective .= <<"BLOCKCGISCRIPTORINIT";
4581 # Initialization Code for '$ContentType'
4582 # Select relevant output filehandle
4583 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4585 # The Initialization code (if any)
4586 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}INITIALIZATIONCODE';
4587 $ScriptingInitialization{$ContentType}
4588 ${ContentType}INITIALIZATIONCODE
4590 BLOCKCGISCRIPTORINIT
4593 # Add all CGI variables defined
4594 if(exists($ScriptingCGIvariables{$ContentType}))
4596 # Start writing variable definitions to the Interpreter
4597 if($ScriptingCGIvariables{$ContentType})
4599 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEF";
4600 # CGI variables (from the %default_values table)
4601 print $SCRIPTINGINPUT{$ContentType} << '${ContentType}CGIVARIABLES';
4602 BLOCKCGISCRIPTORVARDEF
4605 my ($N, $V);
4606 foreach $N (keys(%default_values))
4608 # Determine whether the parameter has been defined
4609 # (the eval is a workaround to get at the variable value)
4610 next unless eval("defined(\$CGIexecute::$N)");
4612 # Get the value from the EXECUTION environment
4613 $V = eval("\$CGIexecute::$N");
4614 # protect control characters (i.e., convert them to \0.. form)
4615 $V = shrubCGIparameter($V);
4617 # Protect interpolated variables
4618 eval("\$CGIexecute::$N = '$V';") unless $ScriptingCGIvariables{$ContentType};
4620 # Print the actual declaration for this scripting language
4621 if($ScriptingCGIvariables{$ContentType})
4623 $NewDirective .= sprintf($ScriptingCGIvariables{$ContentType}, $N, $V);
4624 $NewDirective .= "\n";
4628 # Stop writing variable definitions to the Interpreter
4629 if($ScriptingCGIvariables{$ContentType})
4631 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEFEND";
4632 ${ContentType}CGIVARIABLES
4633 BLOCKCGISCRIPTORVARDEFEND
4638 $NewDirective .= << "BLOCKCGISCRIPTOREND";
4640 # Select STDOUT filehandle
4641 select(STDOUT); \$|=1;
4643 BLOCKCGISCRIPTOREND
4645 return $NewDirective;
4649 # The cleanup code for the foreign script interpreter
4650 sub CleanupForeignScript # ($ContentType) -> $DirectivePrefix
4652 my $ContentType = lc(shift) || return "";
4653 my $NewDirective = "";
4655 # Return if not needed
4656 return $NewDirective unless $ScriptingCleanup{$ContentType};
4658 # Create the relevant script: Open the pipe to the interpreter
4659 $NewDirective .= <<"BLOCKCGISCRIPTORSTOP";
4660 # Cleanup Code for '$ContentType'
4661 # Select relevant output filehandle
4662 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4663 # Print Cleanup code to foreign script
4664 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}SCRIPTSTOP';
4665 $ScriptingCleanup{$ContentType}
4666 ${ContentType}SCRIPTSTOP
4668 # Select STDOUT filehandle
4669 select(STDOUT); \$|=1;
4670 BLOCKCGISCRIPTORSTOP
4672 return $NewDirective;
4676 # The prefix code for each <script></script> block
4677 sub PrefixForeignScript # ($ContentType) -> $DirectivePrefix
4679 my $ContentType = lc(shift) || return "";
4680 my $NewDirective = "";
4682 # Return if not needed
4683 return $NewDirective unless $ScriptingPrefix{$ContentType};
4685 my $Quote = "\'";
4686 # If the CGIvariables parameter is defined, but empty, interpolate
4687 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4688 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4689 !$ScriptingCGIvariables{$ContentType};
4691 # Add initialization code
4692 $NewDirective .= <<"BLOCKCGISCRIPTORPREFIX";
4693 # Prefix Code for '$ContentType'
4694 # Select relevant output filehandle
4695 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4697 # The block Prefix code (if any)
4698 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}PREFIXCODE$Quote;
4699 $ScriptingPrefix{$ContentType}
4700 ${ContentType}PREFIXCODE
4701 # Select STDOUT filehandle
4702 select(STDOUT); \$|=1;
4703 BLOCKCGISCRIPTORPREFIX
4705 return $NewDirective;
4709 # The postfix code for each <script></script> block
4710 sub PostfixForeignScript # ($ContentType) -> $DirectivePrefix
4712 my $ContentType = lc(shift) || return "";
4713 my $NewDirective = "";
4715 # Return if not needed
4716 return $NewDirective unless $ScriptingPostfix{$ContentType};
4718 my $Quote = "\'";
4719 # If the CGIvariables parameter is defined, but empty, interpolate
4720 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4721 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4722 !$ScriptingCGIvariables{$ContentType};
4724 # Create the relevant script: Open the pipe to the interpreter
4725 $NewDirective .= <<"BLOCKCGISCRIPTORPOSTFIX";
4726 # Postfix Code for '$ContentType'
4727 # Select filehandle to interpreter
4728 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4729 # Print postfix code to foreign script
4730 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SCRIPTPOSTFIX$Quote;
4731 $ScriptingPostfix{$ContentType}
4732 ${ContentType}SCRIPTPOSTFIX
4733 # Select STDOUT filehandle
4734 select(STDOUT); \$|=1;
4735 BLOCKCGISCRIPTORPOSTFIX
4737 return $NewDirective;
4740 sub InsertForeignScript # ($ContentType, $directive, @SRCfile) -> $NewDirective
4742 my $ContentType = lc(shift) || return "";
4743 my $directive = shift || return "";
4744 my @SRCfile = @_;
4745 my $NewDirective = "";
4747 my $Quote = "\'";
4748 # If the CGIvariables parameter is defined, but empty, interpolate
4749 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4750 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4751 !$ScriptingCGIvariables{$ContentType};
4753 # Create the relevant script
4754 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4755 # Insert Code for '$ContentType'
4756 # Select filehandle to interpreter
4757 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4758 BLOCKCGISCRIPTORINSERT
4760 # Use SRC feature files
4761 my $ThisSRCfile;
4762 while($ThisSRCfile = shift(@_))
4764 # Handle blocks
4765 if($ThisSRCfile =~ /^\s*\{\s*/)
4767 my $Block = $';
4768 $Block = $` if $Block =~ /\s*\}\s*$/;
4769 $NewDirective .= <<"BLOCKCGISCRIPTORSRCBLOCK";
4770 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SRCBLOCKCODE$Quote;
4771 $Block
4772 ${ContentType}SRCBLOCKCODE
4773 BLOCKCGISCRIPTORSRCBLOCK
4775 next;
4778 # Handle files
4779 $NewDirective .= <<"BLOCKCGISCRIPTORSRCFILES";
4780 # Read $ThisSRCfile
4781 open(SCRIPTINGSOURCE, "<$ThisSRCfile") || main::dieHandler(16, "$ThisSRCfILE: \$!");
4782 while(<SCRIPTINGSOURCE>)
4784 print $SCRIPTINGINPUT{$ContentType} \$_;
4786 close(SCRIPTINGSOURCE);
4788 BLOCKCGISCRIPTORSRCFILES
4792 # Add the directive
4793 if($directive)
4795 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4796 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}DIRECTIVECODE$Quote;
4797 $directive
4798 ${ContentType}DIRECTIVECODE
4799 BLOCKCGISCRIPTORINSERT
4803 $NewDirective .= <<"BLOCKCGISCRIPTORSELECT";
4804 # Select STDOUT filehandle
4805 select(STDOUT); \$|=1;
4806 BLOCKCGISCRIPTORSELECT
4808 # Ready
4809 return $NewDirective;
4812 sub CloseAllForeignScripts # Call CloseForeignScript on all open scripts
4814 my $ContentType;
4815 foreach $ContentType (keys(%SCRIPTINGINPUT))
4817 my $directive = CloseForeignScript($ContentType);
4818 print STDERR "\nDirective $CGI_Date: ", $directive;
4819 CGIexecute->evaluate($directive);
4824 # End of handling foreign (external) scripting languages.
4826 ############################################################################
4828 # A subroutine to handle "nested" quotes, it cuts off the leading
4829 # item or quoted substring
4830 # E.g.,
4831 # ' A_word and more words' -> @('A_word', ' and more words')
4832 # '"quoted string" The rest' -> @('quoted string', ' The rest')
4833 # (this is needed for parsing the <TAGS> and their attributes)
4834 my $SupportedQuotes = "\'\"\`\(\{\[";
4835 my %QuotePairs = ('('=>')','['=>']','{'=>'}'); # Brackets
4836 sub ExtractQuotedItem # ($String) -> @($QuotedString, $RestOfString)
4838 my @Result = ();
4839 my $String = shift || return @Result;
4841 if($String =~ /^\s*([\w\/\-\.]+)/is)
4843 push(@Result, $1, $');
4845 elsif($String =~ /^\s*(\\?)([\Q$SupportedQuotes\E])/is)
4847 my $BackSlash = $1 || "";
4848 my $OpenQuote = $2;
4849 my $CloseQuote = $OpenQuote;
4850 $CloseQuote = $QuotePairs{$OpenQuote} if $QuotePairs{$OpenQuote};
4852 if($BackSlash)
4854 $String =~ /^\s*\\\Q$OpenQuote\E/i;
4855 my $Onset = $';
4856 $Onset =~ /\\\Q$CloseQuote\E/i;
4857 my $Rest = $';
4858 my $Item = $`;
4859 push(@Result, $Item, $Rest);
4862 else
4864 $String =~ /^\s*\Q$OpenQuote\E([^\Q$CloseQuote\E]*)\Q$CloseQuote\E/i;
4865 push(@Result, $1, $');
4868 else
4870 push(@Result, "", $String);
4872 return @Result;
4875 # Now, start with the real work
4877 # Control the output of the Content-type: text/html\n\n message
4878 my $SupressContentType = 0;
4880 # Process a file
4881 sub ProcessFile # ($file_path)
4883 my $file_path = shift || return 0;
4886 # Generate a unique file handle (for recursions)
4887 my @SRClist = ();
4888 my $FileHandle = "file";
4889 my $n = 0;
4890 while(!eof($FileHandle.$n)) {++$n;};
4891 $FileHandle .= $n;
4893 # Start HTML output
4894 # Use the default Content-type if this is NOT a raw file
4895 unless(($RawFilePattern && $ENV{'PATH_INFO'} =~ m@($RawFilePattern)$@i)
4896 || $SupressContentType)
4898 $ENV{'PATH_INFO'} =~ m@($FilePattern)$@i;
4899 my $ContentType = $ContentTypeTable{$1};
4900 print "Content-type: $ContentType\n";
4901 if(%SETCOOKIELIST && keys(%SETCOOKIELIST))
4903 foreach my $name (keys(%SETCOOKIELIST))
4905 my $value = $SETCOOKIELIST{$name};
4906 print "Set-Cookie: $name=$value\n";
4908 # Cookies are set only ONCE
4909 %SETCOOKIELIST = ();
4911 print "\n";
4912 $SupressContentType = 1; # Content type has been printed
4916 # Get access to the actual data. This can be from RAM (by way of an
4917 # environment variable) or by opening a file.
4919 # Handle the use of RAM images (file-data is stored in the
4920 # $CGI_FILE_CONTENTS environment variable)
4921 # Note that this environment variable will be cleared, i.e., it is strictly for
4922 # single-use only!
4923 if($ENV{$CGI_FILE_CONTENTS})
4925 # File has been read already
4926 $_ = $ENV{$CGI_FILE_CONTENTS};
4927 # Sorry, you have to do the reading yourself (dynamic document creation?)
4928 # NOTE: you must read the whole document at once
4929 if($_ eq '-')
4931 $_ = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
4933 else # Clear environment variable
4935 $ENV{$CGI_FILE_CONTENTS} = '-';
4938 # Open Only PLAIN TEXT files (or STDIN) and NO executable files (i.e., scripts).
4939 # THIS IS A SECURITY FEATURE!
4940 elsif($file_path eq '-' || (-e "$file_path" && -r _ && -T _ && -f _ && ! (-x _ || -X _) ))
4942 open($FileHandle, $file_path) || dieHandler(17, "<h2>File not found</h2>\n");
4943 push(@OpenFiles, $file_path);
4944 $_ = <$FileHandle>; # Read first line
4946 elsif( -e "$file_path" && -r _ && -T _ && -f _ && $useFAT )
4948 open($FileHandle, $file_path) || dieHandler(17, "<h2>File not found</h2>\n");
4949 push(@OpenFiles, $file_path);
4950 $_ = <$FileHandle>; # Read first line
4952 else
4954 print "<h2>File not found</h2>\n";
4955 dieHandler(18, "$file_path\n");
4958 $| = 1; # Flush output buffers
4960 # Initialize variables
4961 my $METAarguments = ""; # The CGI arguments from the latest META tag
4962 my @METAvalues = (); # The ''-quoted CGI values from the latest META tag
4963 my $ClosedTag = 0; # <TAG> </TAG> versus <TAG/>
4966 # Send document to output
4967 # Process the requested document.
4968 # Do a loop BEFORE reading input again (this catches the RAM/Database
4969 # type of documents).
4970 do {
4973 # Handle translations if needed
4975 performTranslation(\$_) if $TranslationPaths;
4977 # Catch <SCRIPT LANGUAGE="PERL" TYPE="text/ssperl" > directives in $_
4978 # There can be more than 1 <SCRIPT> or META tags on a line
4979 while(/\<\s*(SCRIPT|META|DIV|INS)\s/is)
4981 my $directive = "";
4982 # Store rest of line
4983 my $Before = $`;
4984 my $ScriptTag = $&;
4985 my $After = $';
4986 my $TagType = uc($1);
4987 # The before part can be send to the output
4988 print $Before;
4990 # Read complete Tag from after and/or file
4991 until($After =~ /([^\\])\>/)
4993 $After .= <$FileHandle>;
4994 performTranslation(\$After) if $TranslationPaths;
4997 if($After =~ /([^\\])\>/)
4999 $ScriptTag .= $`.$&; # Keep the Script Tag intact
5000 $After = $';
5002 else
5004 dieHandler(19, "Closing > not found\n");
5007 # The tag could be closed by />, we handle this in the XML way
5008 # and don't process any content (we ignore whitespace)
5009 $ClosedTag = ($ScriptTag =~ m@[^\\]/\s*\>\s*$@) ? 1 : 0;
5012 # TYPE or CLASS?
5013 my $TypeName = ($TagType =~ /META/is) ? "CONTENT" : "TYPE";
5014 $TypeName = "CLASS" if $TagType eq 'DIV' || $TagType eq 'INS';
5016 # Parse <SCRIPT> or <META> directive
5017 # If NOT (TYPE|CONTENT)="text/ssperl" (i.e., $ServerScriptContentType),
5018 # send the line to the output and go to the next loop
5019 my $CurrentContentType = "";
5020 if($ScriptTag =~ /(^|\s)$TypeName\s*=\s*/is)
5022 my ($Type) = ExtractQuotedItem($');
5023 $Type =~ /^\s*([\w\/\-]+)\s*[\,\;]?/;
5024 $CurrentContentType = lc($1); # Note: mime-types are "case-less"
5025 # CSS classes are aliases of $ServerScriptContentType
5026 if($TypeName eq "CLASS" && $CurrentContentType eq $ServerScriptContentClass)
5028 $CurrentContentType = $ServerScriptContentType;
5033 # Not a known server-side content type, print and continue
5034 unless(($CurrentContentType =~
5035 /$ServerScriptContentType|$ShellScriptContentType/is) ||
5036 $ScriptingLanguages{$CurrentContentType})
5038 print $ScriptTag;
5039 $_ = $After;
5040 next;
5044 # A known server-side content type, evaluate
5046 # First, handle \> and \<
5047 $ScriptTag =~ s/\\\>/\>/isg;
5048 $ScriptTag =~ s/\\\</\</isg;
5050 # Extract the CGI, SRC, ID, IF and UNLESS attributes
5051 my %ScriptTagAttributes = ();
5052 while($ScriptTag =~ /(^|\s)(CGI|IF|UNLESS|SRC|ID)\s*=\s*/is)
5054 my $Attribute = $2;
5055 my $Rest = $';
5056 my $Value = "";
5057 ($Value, $ScriptTag) = ExtractQuotedItem($Rest);
5058 $ScriptTagAttributes{uc($Attribute)} = $Value;
5062 # The attribute used to define the CGI variables
5063 # Extract CGI-variables from
5064 # <META CONTENT="text/ssperl; CGI='' SRC=''">
5065 # <SCRIPT TYPE='text/ssperl' CGI='' SRC=''>
5066 # <DIV CLASS='ssperl' CGI='' SRC='' ID=""> tags
5067 # <INS CLASS='ssperl' CGI='' SRC='' ID=""> tags
5068 if($ScriptTagAttributes{'CGI'})
5070 @ARGV = (); # Reset ARGV
5071 $ARGC = 0;
5072 $METAarguments = ""; # Reset the META CGI arguments
5073 @METAvalues = ();
5074 my $Meta_CGI = $ScriptTagAttributes{'CGI'};
5076 # Process default values of variables ($<name> = 'default value')
5077 # Allowed quotes are '', "", ``, (), [], and {}
5078 while($Meta_CGI =~ /(^\s*|[^\\])([\$\@\%]?)([\w\-]+)\s*/is)
5080 my $varType = $2 || '$'; # Variable or list
5081 my $name = $3; # The Name
5082 my $default = "";
5083 $Meta_CGI = $';
5085 if($Meta_CGI =~ /^\s*\=\s*/is)
5087 # Locate (any) default value
5088 ($default, $Meta_CGI) = ExtractQuotedItem($'); # Cut the parameter from the CGI
5090 $RemainingTag = $Meta_CGI;
5093 # Define CGI (or ENV) variable, initalize it from the
5094 # Query string or the default value
5096 # Also construct the @ARGV and @_ arrays. This allows other (SRC=) Perl
5097 # scripts to access the CGI arguments defined in the META tag
5098 # (Not for CGI inside <SCRIPT> tags)
5099 if($varType eq '$')
5101 CGIexecute::defineCGIvariable($name, $default)
5102 || dieHandler(20, "INVALID CGI name/value pair ($name, $default)\n");
5103 push(@METAvalues, "'".${"CGIexecute::$name"}."'");
5104 # Add value to the @ARGV list
5105 push(@ARGV, ${"CGIexecute::$name"});
5106 ++$ARGC;
5108 elsif($varType eq '@')
5110 CGIexecute::defineCGIvariableList($name, $default)
5111 || dieHandler(21, "INVALID CGI name/value list pair ($name, $default)\n");
5112 push(@METAvalues, "'".join("'", @{"CGIexecute::$name"})."'");
5113 # Add value to the @ARGV list
5114 push(@ARGV, @{"CGIexecute::$name"});
5115 $ARGC = scalar(@CGIexecute::ARGV);
5117 elsif($varType eq '%')
5119 CGIexecute::defineCGIvariableHash($name, $default)
5120 || dieHandler(22, "INVALID CGI name/value hash pair ($name, $default)\n");
5121 my @PairList = map {"$_ => ".${"CGIexecute::$name"}{$_}} keys(%{"CGIexecute::$name"});
5122 push(@METAvalues, "'".join("'", @PairList)."'");
5123 # Add value to the @ARGV list
5124 push(@ARGV, %{"CGIexecute::$name"});
5125 $ARGC = scalar(@CGIexecute::ARGV);
5128 # Store the values for internal and later use
5129 $METAarguments .= "$varType".$name.","; # A string of CGI variable names
5131 push(@METAvalues, "\'".eval("\"$varType\{CGIexecute::$name\}\"")."\'"); # ALWAYS add '-quotes around values
5136 # The IF (conditional execution) Attribute
5137 # Evaluate the condition and stop unless it evaluates to true
5138 if($ScriptTagAttributes{'IF'})
5140 my $IFcondition = $ScriptTagAttributes{'IF'};
5142 # Convert SCRIPT calls, ./<script>
5143 $IFcondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5145 # Convert FILE calls, ~/<file>
5146 $IFcondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5148 # Block execution if necessary
5149 unless(CGIexecute->evaluate($IFcondition))
5151 %ScriptTagAttributes = ();
5152 $CurrentContentType = "";
5156 # The UNLESS (conditional execution) Attribute
5157 # Evaluate the condition and stop if it evaluates to true
5158 if($ScriptTagAttributes{'UNLESS'})
5160 my $UNLESScondition = $ScriptTagAttributes{'UNLESS'};
5162 # Convert SCRIPT calls, ./<script>
5163 $UNLESScondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5165 # Convert FILE calls, ~/<file>
5166 $UNLESScondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5168 # Block execution if necessary
5169 if(CGIexecute->evaluate($UNLESScondition))
5171 %ScriptTagAttributes = ();
5172 $CurrentContentType = "";
5176 # The SRC (Source File) Attribute
5177 # Extract any source script files and add them in
5178 # front of the directive
5179 # The SRC list should be emptied
5180 @SRClist = ();
5181 my $SRCtag = "";
5182 my $Prefix = 1;
5183 my $PrefixDirective = "";
5184 my $PostfixDirective = "";
5185 # There is a SRC attribute
5186 if($ScriptTagAttributes{'SRC'})
5188 $SRCtag = $ScriptTagAttributes{'SRC'};
5189 # Remove "file://" prefixes
5190 $SRCtag =~ s@([^\w\/\\]|^)file\://([^\s\/\@\=])@$1$2@gis;
5191 # Expand script filenames "./Script"
5192 $SRCtag =~ s@([^\w\/\\]|^)\./([^\s\/\@\=])@$1$SCRIPT_SUB/$2@gis;
5193 # Expand script filenames "~/Script"
5194 $SRCtag =~ s@([^\w\/\\]|^)\~/([^\s\/\@\=])@$1$HOME_SUB/$2@gis;
5197 # File source tags
5198 while($SRCtag =~ /\S/is)
5200 my $SRCdirective = "";
5202 # Pseudo file, just a switch to go from PREFIXING to POSTFIXING
5203 # SRC files
5204 if($SRCtag =~ /^[\s\;\,]*(POSTFIX|PREFIX)([^$FileAllowedChars]|$)/is)
5206 my $InsertionPlace = $1;
5207 $SRCtag = $2.$';
5209 $Prefix = $InsertionPlace =~ /POSTFIX/i ? 0 : 1;
5210 # Go to next round
5211 next;
5213 # {}-blocks are just evaluated by "do"
5214 elsif($SRCtag =~ /^[\s\;\,]*\{/is)
5216 my $SRCblock = $';
5217 if($SRCblock =~ /\}[\s\;\,]*([^\}]*)$/is)
5219 $SRCblock = $`;
5220 $SRCtag = $1.$';
5221 # SAFEqx shell script blocks
5222 if($CurrentContentType =~ /$ShellScriptContentType/is)
5224 # Handle ''-quotes inside the script
5225 $SRCblock =~ s/[\']/\\$&/gis;
5227 $SRCblock = "print do { SAFEqx(\'".$SRCblock."\'); };'';";
5228 $SRCdirective .= $SRCblock."\n";
5230 # do { SRCblocks }
5231 elsif($CurrentContentType =~ /$ServerScriptContentType/is)
5233 $SRCblock = "print do { $SRCblock };'';";
5234 $SRCdirective .= $SRCblock."\n";
5236 else # The interpreter should handle this
5238 push(@SRClist, "{ $SRCblock }");
5242 else
5243 { dieHandler(23, "Closing \} missing\n");};
5245 # Files are processed as Text or Executable files
5246 elsif($SRCtag =~ /[\s\;\,]*([$FileAllowedChars]+)[\;\,\s]*/is)
5248 my $SrcFile = $1;
5249 $SRCtag = $';
5251 # We are handling one of the external interpreters
5252 if($ScriptingLanguages{$CurrentContentType})
5254 push(@SRClist, $SrcFile);
5256 # We are at the start of a DIV tag, just load all SRC files and/or URL's
5257 elsif($TagType eq 'DIV' || $TagType eq 'INS') # All files are prepended in DIV's
5259 # $SrcFile is a URL pointing to an HTTP or FTP server
5260 if($SrcFile =~ m!^([a-z]+)\://!)
5262 my $URLoutput = CGIscriptor::read_url($SrcFile);
5263 $SRCdirective .= $URLoutput;
5265 # SRC file is an existing file
5266 elsif(-e "$SrcFile")
5268 open(DIVSOURCE, "<$SrcFile") || dieHandler(24, "<$SrcFile: $!\n");
5269 my $Content;
5270 while(sysread(DIVSOURCE, $Content, 1024) > 0)
5272 $SRCdirective .= $Content;
5274 close(DIVSOURCE);
5277 # Executable files are executed as
5278 # `$SrcFile 'ARGV[0]' 'ARGV[1]'`
5279 elsif(-x "$SrcFile")
5281 $SRCdirective .= "print \`$SrcFile @METAvalues\`;'';\n";
5283 # Handle 'standard' files, using ProcessFile
5284 elsif((-T "$SrcFile" || $ENV{$CGI_FILE_CONTENTS})
5285 && $SrcFile =~ m@($FilePattern)$@) # A recursion
5288 # Do not process still open files because it can lead
5289 # to endless recursions
5290 if(grep(/^$SrcFile$/, @OpenFiles))
5291 { dieHandler(25, "$SrcFile allready opened (endless recursion)\n")};
5292 # Prepare meta arguments
5293 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5294 # Process the file
5295 $SRCdirective .= "main::ProcessFile(\'$SrcFile\');'';\n";
5297 elsif($SrcFile =~ m!^([a-z]+)\://!) # URL's are loaded and printed
5299 $SRCdirective .= GET_URL($SrcFile);
5301 elsif(-T "$SrcFile") # Textfiles are "do"-ed (Perl execution)
5303 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5304 $SRCdirective .= "do \'$SrcFile\';'';\n";
5306 else # This one could not be resolved (should be handled by BinaryMapFile)
5308 $SRCdirective .= 'print "'.$SrcFile.' cannot be used"'."\n";
5313 # Postfix or Prefix
5314 if($Prefix)
5316 $PrefixDirective .= $SRCdirective;
5318 else
5320 $PostfixDirective .= $SRCdirective;
5323 # The prefix should be handled immediately
5324 $directive .= $PrefixDirective;
5325 $PrefixDirective = "";
5329 # Handle the content of the <SCRIPT></SCRIPT> tags
5330 # Do not process the content of <SCRIPT/>
5331 if($TagType =~ /SCRIPT/is && !$ClosedTag) # The <SCRIPT> TAG
5333 my $EndScriptTag = "";
5335 # Execute SHELL scripts with SAFEqx()
5336 if($CurrentContentType =~ /$ShellScriptContentType/is)
5338 $directive .= "SAFEqx(\'";
5341 # Extract Program
5342 while($After !~ /\<\s*\/SCRIPT[^\>]*\>/is && !eof($FileHandle))
5344 $After .= <$FileHandle>;
5345 performTranslation(\$After) if $TranslationPaths;
5348 if($After =~ /\<\s*\/SCRIPT[^\>]*\>/is)
5350 $directive .= $`;
5351 $EndScriptTag = $&;
5352 $After = $';
5354 else
5356 dieHandler(26, "Missing </SCRIPT> end tag in $ENV{'PATH_INFO'}\n");
5359 # Process only when content should be executed
5360 if($CurrentContentType)
5363 # Remove all comments from Perl scripts
5364 # (NOT from OS shell scripts)
5365 $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
5366 if $CurrentContentType =~ /$ServerScriptContentType/i;
5368 # Convert SCRIPT calls, ./<script>
5369 $directive =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5371 # Convert FILE calls, ~/<file>
5372 $directive =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5374 # Execute SHELL scripts with SAFEqx(), closing bracket
5375 if($CurrentContentType =~ /$ShellScriptContentType/i)
5377 # Handle ''-quotes inside the script
5378 $directive =~ /SAFEqx\(\'/;
5379 $directive = $`.$&;
5380 my $Executable = $';
5381 $Executable =~ s/[\']/\\$&/gs;
5383 $directive .= $Executable."\');"; # Closing bracket
5386 else
5388 $directive = "";
5391 # Handle the content of the <DIV></DIV> tags
5392 # Do not process the content of <DIV/>
5393 elsif(($TagType eq 'DIV' || $TagType eq 'INS') && !$ClosedTag) # The <DIV> TAGs
5395 my $EndScriptTag = "";
5397 # Extract Text
5398 while($After !~ /\<\s*\/$TagType[^\>]*\>/is && !eof($FileHandle))
5400 $After .= <$FileHandle>;
5401 performTranslation(\$After) if $TranslationPaths;
5404 if($After =~ /\<\s*\/$TagType[^\>]*\>/is)
5406 $directive .= $`;
5407 $EndScriptTag = $&;
5408 $After = $';
5410 else
5412 dieHandler(27, "Missing </$TagType> end tag in $ENV{'PATH_INFO'}\n");
5415 # Add the Postfixed directives (but only when it contains something printable)
5416 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5417 $PostfixDirective = "";
5420 # Process only when content should be handled
5421 if($CurrentContentType)
5424 # Get the name (ID), and clean it (i.e., remove anything that is NOT part of
5425 # a valid Perl name). Names should not contain $, but we can handle it.
5426 my $name = $ScriptTagAttributes{'ID'};
5427 $name =~ /^\s*[\$\@\%]?([\w\-]+)/;
5428 $name = $1;
5430 # Assign DIV contents to $NAME value OUTSIDE the CGI values!
5431 CGIexecute::defineCGIexecuteVariable($name, $directive);
5432 $directive = "";
5435 # Nothing to execute
5436 $directive = "";
5440 # Handle Foreign scripting languages
5441 if($ScriptingLanguages{$CurrentContentType})
5443 my $newDirective = "";
5444 $newDirective .= OpenForeignScript($CurrentContentType); # Only if not already done
5445 $newDirective .= PrefixForeignScript($CurrentContentType);
5446 $newDirective .= InsertForeignScript($CurrentContentType, $directive, @SRClist);
5447 $newDirective .= PostfixForeignScript($CurrentContentType);
5448 $newDirective .= CloseForeignScript($CurrentContentType); # This shouldn't be necessary
5450 $newDirective .= '"";';
5452 $directive = $newDirective;
5456 # Add the Postfixed directives (but only when it contains something printable)
5457 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5458 $PostfixDirective = "";
5461 # EXECUTE the script and print the results
5463 # Use this to debug the program
5464 # print STDERR "Directive $CGI_Date: \n", $directive, "\n\n";
5466 my $Result = CGIexecute->evaluate($directive) if $directive; # Evaluate as PERL code
5467 $Result =~ s/\n$//g; # Remove final newline
5469 # Print the Result of evaluating the directive
5470 # (this will handle LARGE, >64 kB output)
5471 my $BytesWritten = 1;
5472 while($Result && $BytesWritten)
5474 $BytesWritten = syswrite(STDOUT, $Result, 64);
5475 $Result = substr($Result, $BytesWritten);
5477 # print $Result; # Could be used instead of above code
5479 # Store result if wanted, i.e., if $CGIscriptorResults has been
5480 # defined in a <META> tag.
5481 push(@CGIexecute::CGIscriptorResults, $Result)
5482 if exists($default_values{'CGIscriptorResults'});
5484 # Process the rest of the input line (this could contain
5485 # another directive)
5486 $_ = $After;
5488 print $_;
5489 } while(<$FileHandle>); # Read and Test AFTER first loop!
5491 close ($FileHandle);
5492 dieHandler(28, "Error in recursion\n") unless pop(@OpenFiles) == $file_path;
5496 ###############################################################################
5498 # Call the whole package
5500 sub Handle_Request
5502 my $file_path = "";
5504 # Initialization Code
5505 Initialize_Request();
5507 # SECURITY: ACCESS CONTROL
5508 Access_Control();
5510 # Read the POST part of the query, if there is one
5511 Get_POST_part_of_query();
5513 # Start (HTML) output and logging
5514 $file_path = Initialize_output();
5516 # Check login access or divert to login procedure
5517 $Use_Login = Log_In_Access();
5518 $file_path = $Use_Login if $Use_Login;
5520 # Record which files are still open (to avoid endless recursions)
5521 my @OpenFiles = ();
5523 # Record whether the default HTML ContentType has already been printed
5524 # but only if the SERVER uses HTTP or some other protocol that might interpret
5525 # a content MIME type.
5527 $SupressContentType = !("$ENV{'SERVER_PROTOCOL'}" =~ /($ContentTypeServerProtocols)/i);
5529 # Process the specified file
5530 ProcessFile($file_path) if $file_path ne $SS_PUB;
5532 # Cleanup all open external (foreign) interpreters
5533 CloseAllForeignScripts();
5536 "" # SUCCESS
5539 # Make a single call to handle an (empty) request
5540 Handle_Request();
5543 # END OF PACKAGE MAIN
5546 ####################################################################################
5548 # The CGIEXECUTE PACKAGE
5550 ####################################################################################
5552 # Isolate the evaluation of directives as PERL code from the rest of the program.
5553 # Remember that each package has its own name space.
5554 # Note that only the FIRST argument of execute->evaluate is actually evaluated,
5555 # all other arguments are accessible inside the first argument as $_[0] to $_[$#_].
5557 package CGIexecute;
5559 sub evaluate
5561 my $self = shift;
5562 my $directive = shift;
5563 $directive = eval($directive);
5564 warn $@ if $@; # Write an error message to STDERR
5565 $directive; # Return value of directive
5569 # defineCGIexecuteVariable($name [, $value]) -> 0/1
5571 # Define and intialize variables inside CGIexecute
5572 # Does no sanity checking, for internal use only
5574 sub defineCGIexecuteVariable # ($name [, $value]) -> 0/1
5576 my $name = shift || return 0; # The Name
5577 my $value = shift || ""; # The value
5579 ${$name} = $value;
5581 return 1;
5584 # Protect certain CGI variables values when set internally
5585 # If not defined internally, there will be no variable set AT ALL
5586 my %CGIprotectedVariable = ();
5587 sub ProtectCGIvariable # ($name) -> 0/1
5589 my $name = shift || "";
5590 return 0 unless $name && $name =~ /\w/;
5592 ++$CGIprotectedVariable{$name};
5594 return $CGIprotectedVariable{$name};
5597 # defineCGIvariable($name [, $default]) -> 0/1
5599 # Define and intialize CGI variables
5600 # Tries (in order) $ENV{$name}, the Query string and the
5601 # default value.
5602 # Removes all '-quotes etc.
5604 sub defineCGIvariable # ($name [, $default]) -> 0/1
5606 my $name = shift || return 0; # The Name
5607 my $default = shift || ""; # The default value
5609 # Protect variables set internally
5610 return 1 if !$name || exists($CGIprotectedVariable{$name});
5612 # Remove \-quoted characters
5613 $default =~ s/\\(.)/$1/g;
5614 # Store default values
5615 $::default_values{$name} = $default if $default;
5617 # Process variables
5618 my $temp = undef;
5619 # If there is a user supplied value, it replaces the
5620 # default value.
5622 # Environment values have precedence
5623 if(exists($ENV{$name}))
5625 $temp = $ENV{$name};
5627 # Get name and its value from the query string
5628 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5630 $temp = ::YOUR_CGIPARSE($name);
5632 # Defined values must exist for security
5633 elsif(!exists($::default_values{$name}))
5635 $::default_values{$name} = undef;
5638 # SECURITY, do not allow '- and `-quotes in
5639 # client values.
5640 # Remove all existing '-quotes
5641 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5642 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5643 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5644 # If $temp is empty, use the default value (if it exists)
5645 unless($temp =~ /\S/ || length($temp) > 0) # I.e., $temp is empty
5647 $temp = $::default_values{$name};
5648 # Remove all existing '-quotes
5649 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5650 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5651 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5653 else # Store current CGI values and remove defaults
5655 $::default_values{$name} = $temp;
5657 # Define the CGI variable and its value (in the execute package)
5658 ${$name} = $temp;
5660 # return SUCCES
5661 return 1;
5664 sub defineCGIvariableList # ($name [, $default]) -> 0/1)
5666 my $name = shift || return 0; # The Name
5667 my $default = shift || ""; # The default value
5669 # Protect variables set internally
5670 return 1 if !$name || exists($CGIprotectedVariable{$name});
5672 # Defined values must exist for security
5673 if(!exists($::default_values{$name}))
5675 $::default_values{$name} = $default;
5678 my @temp = ();
5681 # For security:
5682 # Environment values have precedence
5683 if(exists($ENV{$name}))
5685 push(@temp, $ENV{$name});
5687 # Get name and its values from the query string
5688 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5690 push(@temp, ::YOUR_CGIPARSE($name, 1)); # Extract LIST
5692 else
5694 push(@temp, $::default_values{$name});
5698 # SECURITY, do not allow '- and `-quotes in
5699 # client values.
5700 # Remove all existing '-quotes
5701 @temp = map {s/([\r\f]+\n)/\n/g; $_} @temp; # Only \n is allowed
5702 @temp = map {s/[\']/&#8217;/igs; $_} @temp; # Remove all single quotes
5703 @temp = map {s/[\`]/&#8216;/igs; $_} @temp; # Remove all backtick quotes
5705 # Store current CGI values and remove defaults
5706 $::default_values{$name} = $temp[0];
5708 # Define the CGI variable and its value (in the execute package)
5709 @{$name} = @temp;
5711 # return SUCCES
5712 return 1;
5715 sub defineCGIvariableHash # ($name [, $default]) -> 0/1) Note: '$name{""} = $default';
5717 my $name = shift || return 0; # The Name
5718 my $default = shift || ""; # The default value
5720 # Protect variables set internally
5721 return 1 if !$name || exists($CGIprotectedVariable{$name});
5723 # Defined values must exist for security
5724 if(!exists($::default_values{$name}))
5726 $::default_values{$name} = $default;
5729 my %temp = ();
5732 # For security:
5733 # Environment values have precedence
5734 if(exists($ENV{$name}))
5736 $temp{""} = $ENV{$name};
5738 # Get name and its values from the query string
5739 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5741 %temp = ::YOUR_CGIPARSE($name, -1); # Extract HASH table
5743 elsif($::default_values{$name} ne "")
5745 $temp{""} = $::default_values{$name};
5749 # SECURITY, do not allow '- and `-quotes in
5750 # client values.
5751 # Remove all existing '-quotes
5752 my $Key;
5753 foreach $Key (keys(%temp))
5755 $temp{$Key} =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5756 $temp{$Key} =~ s/[\']/&#8217;/igs; # Remove all single quotes
5757 $temp{$Key} =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5760 # Store current CGI values and remove defaults
5761 $::default_values{$name} = $temp{""};
5763 # Define the CGI variable and its value (in the execute package)
5764 %{$name} = ();
5765 my $tempKey;
5766 foreach $tempKey (keys(%temp))
5768 ${$name}{$tempKey} = $temp{$tempKey};
5771 # return SUCCES
5772 return 1;
5776 # SAFEqx('CommandString')
5778 # A special function that is a safe alternative to backtick quotes (and qx//)
5779 # with client-supplied CGI values. All CGI variables are surrounded by
5780 # single ''-quotes (except between existing \'\'-quotes, don't try to be
5781 # too smart). All variables are then interpolated. Simple (@) lists are
5782 # expanded with join(' ', @List), and simple (%) hash tables expanded
5783 # as a list of "key=value" pairs. Complex variables, e.g., @$var, are
5784 # evaluated in a scalar context (e.g., as scalar(@$var)). All occurrences of
5785 # $@% that should NOT be interpolated must be preceeded by a "\".
5786 # If the first line of the String starts with "#! interpreter", the
5787 # remainder of the string is piped into interpreter (after interpolation), i.e.,
5788 # open(INTERPRETER, "|interpreter");print INTERPRETER remainder;
5789 # just like in UNIX. There are some problems with quotes. Be carefull in
5790 # using them. You do not have access to the output of any piped (#!)
5791 # process! If you want such access, execute
5792 # <SCRIPT TYPE="text/osshell">echo "script"|interpreter</SCRIPT> or
5793 # <SCRIPT TYPE="text/ssperl">$resultvar = SAFEqx('echo "script"|interpreter');
5794 # </SCRIPT>.
5796 # SAFEqx ONLY WORKS WHEN THE STRING ITSELF IS SURROUNDED BY SINGLE QUOTES
5797 # (SO THAT IT IS NOT INTERPOLATED BEFORE IT CAN BE PROTECTED)
5798 sub SAFEqx # ('String') -> result of executing qx/"String"/
5800 my $CommandString = shift;
5801 my $NewCommandString = "";
5803 # Only interpolate when required (check the On/Off switch)
5804 unless($CGIscriptor::NoShellScriptInterpolation)
5807 # Handle existing single quotes around CGI values
5808 while($CommandString =~ /\'[^\']+\'/s)
5810 my $CurrentQuotedString = $&;
5811 $NewCommandString .= $`;
5812 $CommandString = $'; # The remaining string
5813 # Interpolate CGI variables between quotes
5814 # (e.g., '$CGIscriptorResults[-1]')
5815 $CurrentQuotedString =~
5816 s/(^|[^\\])([\$\@])((\w*)([\{\[][\$\@\%]?[\:\w\-]+[\}\]])*)/if(exists($main::default_values{$4})){
5817 "$1".eval("$2$3")}else{"$&"}/egs;
5819 # Combine result with previous result
5820 $NewCommandString .= $CurrentQuotedString;
5822 $CommandString = $NewCommandString.$CommandString;
5824 # Select known CGI variables and surround them with single quotes,
5825 # then interpolate all variables
5826 $CommandString =~
5827 s/(^|[^\\])([\$\@\%]+)((\w*)([\{\[][\w\:\$\"\-]+[\}\]])*)/
5828 if($2 eq '$' && exists($main::default_values{$4}))
5829 {"$1\'".eval("\$$3")."\'";}
5830 elsif($2 eq '@'){$1.join(' ', @{"$3"});}
5831 elsif($2 eq '%'){my $t=$1;map {$t.=" $_=".${"$3"}{$_}}
5832 keys(%{"$3"});$t}
5833 else{$1.eval("${2}$3");
5834 }/egs;
5836 # Remove backslashed [$@%]
5837 $CommandString =~ s/\\([\$\@\%])/$1/gs;
5840 # Debugging
5841 # return $CommandString;
5843 # Handle UNIX style "#! shell command\n" constructs as
5844 # a pipe into the shell command. The output cannot be tapped.
5845 my $ReturnValue = "";
5846 if($CommandString =~ /^\s*\#\!([^\f\n\r]+)[\f\n\r]/is)
5848 my $ShellScripts = $';
5849 my $ShellCommand = $1;
5850 open(INTERPRETER, "|$ShellCommand") || dieHandler(29, "\'$ShellCommand\' PIPE not opened: &!\n");
5851 select(INTERPRETER);$| = 1;
5852 print INTERPRETER $ShellScripts;
5853 close(INTERPRETER);
5854 select(STDOUT);$| = 1;
5856 # Shell scripts which are redirected to an existing named pipe.
5857 # The output cannot be tapped.
5858 elsif($CGIscriptor::ShellScriptPIPE)
5860 CGIscriptor::printSAFEqxPIPE($CommandString);
5862 else # Plain ``-backtick execution
5864 # Execute the commands
5865 $ReturnValue = qx/$CommandString/;
5867 return $ReturnValue;
5870 ####################################################################################
5872 # The CGIscriptor PACKAGE
5874 ####################################################################################
5876 # Isolate the evaluation of CGIscriptor functions, i.e., those prefixed with
5877 # "CGIscriptor::"
5879 package CGIscriptor;
5882 # The Interpolation On/Off switch
5883 my $NoShellScriptInterpolation = undef;
5884 # The ShellScript redirection pipe
5885 my $ShellScriptPIPE = undef;
5887 # Open a named PIPE for SAFEqx to receive ALL shell scripts
5888 sub RedirectShellScript # ('CommandString')
5890 my $CommandString = shift || undef;
5892 if($CommandString)
5894 $ShellScriptPIPE = "ShellScriptNamedPipe";
5895 open($ShellScriptPIPE, "|$CommandString")
5896 || main::dieHandler(30, "\'|$CommandString\' PIPE open failed: $!\n");
5898 else
5900 close($ShellScriptPIPE);
5901 $ShellScriptPIPE = undef;
5903 return $ShellScriptPIPE;
5906 # Print to redirected shell script pipe
5907 sub printSAFEqxPIPE # ("String") -> print return value
5909 my $String = shift || undef;
5911 select($ShellScriptPIPE); $| = 1;
5912 my $returnvalue = print $ShellScriptPIPE ($String);
5913 select(STDOUT); $| = 1;
5915 return $returnvalue;
5918 # a pointer to CGIexecute::SAFEqx
5919 sub SAFEqx # ('String') -> result of qx/"String"/
5921 my $CommandString = shift;
5922 return CGIexecute::SAFEqx($CommandString);
5926 # a pointer to CGIexecute::defineCGIvariable
5927 sub defineCGIvariable # ($name[, $default]) ->0/1
5929 my $name = shift;
5930 my $default = shift;
5931 return CGIexecute::defineCGIvariable($name, $default);
5935 # a pointer to CGIexecute::defineCGIvariable
5936 sub defineCGIvariableList # ($name[, $default]) ->0/1
5938 my $name = shift;
5939 my $default = shift;
5940 return CGIexecute::defineCGIvariableList($name, $default);
5944 # a pointer to CGIexecute::defineCGIvariable
5945 sub defineCGIvariableHash # ($name[, $default]) ->0/1
5947 my $name = shift;
5948 my $default = shift;
5949 return CGIexecute::defineCGIvariableHash($name, $default);
5953 # Decode URL encoded arguments
5954 sub URLdecode # (URL encoded input) -> string
5956 my $output = "";
5957 my $char;
5958 my $Value;
5959 foreach $Value (@_)
5961 my $EncodedValue = $Value; # Do not change the loop variable
5962 # Convert all "+" to " "
5963 $EncodedValue =~ s/\+/ /g;
5964 # Convert all hexadecimal codes (%FF) to their byte values
5965 while($EncodedValue =~ /\%([0-9A-F]{2})/i)
5967 $output .= $`.chr(hex($1));
5968 $EncodedValue = $';
5970 $output .= $EncodedValue; # The remaining part of $Value
5972 $output;
5975 # Encode arguments as URL codes.
5976 sub URLencode # (input) -> URL encoded string
5978 my $output = "";
5979 my $char;
5980 my $Value;
5981 foreach $Value (@_)
5983 my @CharList = split('', $Value);
5984 foreach $char (@CharList)
5986 if($char =~ /\s/)
5987 { $output .= "+";}
5988 elsif($char =~ /\w\-/)
5989 { $output .= $char;}
5990 else
5992 $output .= uc(sprintf("%%%2.2x", ord($char)));
5996 $output;
5999 # Extract the value of a CGI variable from the URL-encoded $string
6000 # Also extracts the data blocks from a multipart request. Does NOT
6001 # decode the multipart blocks
6002 sub CGIparseValue # (ValueName [, URL_encoded_QueryString [, \$QueryReturnReference]]) -> Decoded value
6004 my $ValueName = shift;
6005 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6006 my $ReturnReference = shift || undef;
6007 my $output = "";
6009 if($QueryString =~ /(^|\&)$ValueName\=([^\&]*)(\&|$)/)
6011 $output = URLdecode($2);
6012 $$ReturnReference = $' if ref($ReturnReference);
6014 # Get multipart POST or PUT methods
6015 elsif($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
6017 my $MultipartType = $2;
6018 my $BoundaryString = $3;
6019 # Remove the boundary-string
6020 my $temp = $QueryString;
6021 $temp =~ /^\Q--$BoundaryString\E/m;
6022 $temp = $';
6024 # Identify the newline character(s), this is the first character in $temp
6025 my $NewLine = "\r\n"; # Actually, this IS the correct one
6026 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
6028 # Is this correct??? I have to check.
6029 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
6030 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
6031 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
6032 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
6035 # search through all data blocks
6036 while($temp =~ /^\Q--$BoundaryString\E/m)
6038 my $DataBlock = $`;
6039 $temp = $';
6040 # Get the empty line after the header
6041 $DataBlock =~ /$NewLine$NewLine/;
6042 $Header = $`;
6043 $output = $';
6044 my $Header = $`;
6045 $output = $';
6047 # Remove newlines from the header
6048 $Header =~ s/$NewLine/ /g;
6050 # Look whether this block is the one you are looking for
6051 # Require the quotes!
6052 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
6054 my $i;
6055 for($i=length($NewLine); $i; --$i)
6057 chop($output);
6059 # OK, get out
6060 last;
6062 # reinitialize the output
6063 $output = "";
6065 $$ReturnReference = $temp if ref($ReturnReference);
6067 elsif($QueryString !~ /(^|\&)$ValueName\=/) # The value simply isn't there
6069 return undef;
6070 $$ReturnReference = undef if ref($ReturnReference);
6072 else
6074 print "ERROR: $ValueName $main::ENV{'CONTENT_TYPE'}\n";
6076 return $output;
6080 # Get a list of values for the same ValueName. Uses CGIparseValue
6082 sub CGIparseValueList # (ValueName [, URL_encoded_QueryString]) -> List of decoded values
6084 my $ValueName = shift;
6085 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6086 my @output = ();
6087 my $RestQueryString;
6088 my $Value;
6089 while($QueryString &&
6090 (($Value = CGIparseValue($ValueName, $QueryString, \$RestQueryString))
6091 || defined($Value)))
6093 push(@output, $Value);
6094 $QueryString = $RestQueryString; # QueryString is consumed!
6096 # ready, return list with values
6097 return @output;
6100 sub CGIparseValueHash # (ValueName [, URL_encoded_QueryString]) -> Hash table of decoded values
6102 my $ValueName = shift;
6103 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6104 my $RestQueryString;
6105 my %output = ();
6106 while($QueryString && $QueryString =~ /(^|\&)$ValueName([\w]*)\=/)
6108 my $Key = $2;
6109 my $Value = CGIparseValue("$ValueName$Key", $QueryString, \$RestQueryString);
6110 $output{$Key} = $Value;
6111 $QueryString = $RestQueryString; # QueryString is consumed!
6113 # ready, return list with values
6114 return %output;
6117 sub CGIparseForm # ([URL_encoded_QueryString]) -> Decoded Form (NO multipart)
6119 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6120 my $output = "";
6122 $QueryString =~ s/\&/\n/g;
6123 $output = URLdecode($QueryString);
6125 $output;
6128 # Extract the header of a multipart CGI variable from the POST input
6129 sub CGIparseHeader # (ValueName [, URL_encoded_QueryString]) -> Decoded value
6131 my $ValueName = shift;
6132 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6133 my $output = "";
6135 if($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
6137 my $MultipartType = $2;
6138 my $BoundaryString = $3;
6139 # Remove the boundary-string
6140 my $temp = $QueryString;
6141 $temp =~ /^\Q--$BoundaryString\E/m;
6142 $temp = $';
6144 # Identify the newline character(s), this is the first character in $temp
6145 my $NewLine = "\r\n"; # Actually, this IS the correct one
6146 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
6148 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
6149 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
6150 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
6151 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
6154 # search through all data blocks
6155 while($temp =~ /^\Q--$BoundaryString\E/m)
6157 my $DataBlock = $`;
6158 $temp = $';
6159 # Get the empty line after the header
6160 $DataBlock =~ /$NewLine$NewLine/;
6161 $Header = $`;
6162 my $Header = $`;
6164 # Remove newlines from the header
6165 $Header =~ s/$NewLine/ /g;
6167 # Look whether this block is the one you are looking for
6168 # Require the quotes!
6169 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
6171 $output = $Header;
6172 last;
6174 # reinitialize the output
6175 $output = "";
6178 return $output;
6182 # Checking variables for security (e.g., file names and email addresses)
6183 # File names are tested against the $::FileAllowedChars and $::BlockPathAccess variables
6184 sub CGIsafeFileName # FileName -> FileName or ""
6186 my $FileName = shift || "";
6187 return "" if $FileName =~ m?[^$::FileAllowedChars]?;
6188 return "" if $FileName =~ m!(^|/|\:)[\-\.]!;
6189 return "" if $FileName =~ m@\.\.\Q$::DirectorySeparator\E@; # Higher directory not allowed
6190 return "" if $FileName =~ m@\Q$::DirectorySeparator\E\.\.@; # Higher directory not allowed
6191 return "" if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@; # Invisible (blocked) file
6193 return $FileName;
6196 sub CGIsafeEmailAddress # email -> email or ""
6198 my $Email = shift || "";
6199 return "" unless $Email =~ m/^[\w\.\-]+[\@][\w\.\-\:]+$/;
6200 return $Email;
6203 # Get a URL from the web. Needs main::GET_URL($URL) function
6204 # (i.e., curl, snarf, or wget)
6205 sub read_url # ($URL) -> page/file
6207 my $URL = shift || return "";
6209 # Get the commands to read the URL, do NOT add a print command
6210 my $URL_command = main::GET_URL($URL, 1);
6211 # execute the commands, i.e., actually read it
6212 my $URLcontent = CGIexecute->evaluate($URL_command);
6214 # Ready, return the content.
6215 return $URLcontent;
6218 ################################################>>>>>>>>>>Start Remove
6220 # BrowseAllDirs(Directory, indexfile)
6222 # usage:
6223 # <SCRIPT TYPE='text/ssperl'>
6224 # CGIscriptor::BrowseAllDirs('Sounds', 'index.html', '\.wav$')
6225 # </SCRIPT>
6227 # Allows to browse all directories. Stops at '/'. If the directory contains
6228 # an indexfile, eg, index.html, that file will be used instead. Files must match
6229 # the $Pattern, if it is given. Default is
6230 # CGIscriptor::BrowseAllDirs('/', 'index.html', '')
6232 sub BrowseAllDirs # (Directory, indexfile, $Pattern) -> Print HTML code
6234 my $Directory = shift || '/';
6235 my $indexfile = shift || 'index.html';
6236 my $Pattern = shift || '';
6237 $Directory =~ s!/$!!g;
6239 # If the index directory exists, use that one
6240 if(-s "$::CGI_HOME$Directory/$indexfile")
6242 return main::ProcessFile("$::CGI_HOME$Directory/$indexfile");
6245 # No indexfile, continue
6246 my @DirectoryList = glob("$::CGI_HOME$Directory");
6247 $CurrentDirectory = shift(@DirectoryList);
6248 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
6249 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
6250 print "<h1>";
6251 print "$CurrentDirectory" if $CurrentDirectory;
6252 print "</h1>\n";
6254 opendir(BROWSE, "$::CGI_HOME$Directory") || main::dieHandler(31, "$::CGI_HOME$Directory $!");
6255 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
6257 # Print directories
6258 my $file;
6259 print "<pre><ul TYPE='NONE'>\n";
6260 foreach $file (@AllFiles)
6262 next unless -d "$::CGI_HOME$Directory/$file";
6263 # Check whether this file should be visible
6264 next if $::BlockPathAccess &&
6265 "$Directory/$file/" =~ m@$::BlockPathAccess@;
6266 print "<dt><a href='$Directory/$file'>$file</a></dt>\n";
6268 print "</ul></pre>\n";
6270 # Print files
6271 print "<pre><ul TYPE='CIRCLE'>\n";
6272 my $TotalSize = 0;
6273 foreach $file (@AllFiles)
6275 next if $file =~ /^\./;
6276 next if -d "$::CGI_HOME$Directory/$file";
6277 next if -l "$::CGI_HOME$Directory/$file";
6278 # Check whether this file should be visible
6279 next if $::BlockPathAccess &&
6280 "$Directory/$file" =~ m@$::BlockPathAccess@;
6282 if(!$Pattern || $file =~ m@$Pattern@)
6284 my $Date = localtime($^T - (-M "$::CGI_HOME$Directory/$file")*3600*24);
6285 my $Size = -s "$::CGI_HOME$Directory/$file";
6286 $Size = sprintf("%6.0F kB", $Size/1024);
6287 my $Type = `file $::CGI_HOME$Directory/$file`;
6288 $Type =~ s@\s*$::CGI_HOME$Directory/$file\s*\:\s*@@ig;
6289 chomp($Type);
6291 print "<li>";
6292 print "<a href='$Directory/$file'>";
6293 printf("%-40s", "$file</a>");
6294 print "\t$Size\t$Date\t$Type";
6295 print "</li>\n";
6298 print "</ul></pre>";
6300 return 1;
6304 ################################################
6306 # BrowseDirs(RootDirectory [, Pattern, Start])
6308 # usage:
6309 # <SCRIPT TYPE='text/ssperl'>
6310 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', 'Speech', 'DIRECTORY')
6311 # </SCRIPT>
6313 # Allows to browse subdirectories. Start should be relative to the RootDirectory,
6314 # e.g., the full path of the directory 'Speech' is '~/Sounds/Speech'.
6315 # Only files which fit /$Pattern/ and directories are displayed.
6316 # Directories down or up the directory tree are supplied with a
6317 # GET request with the name of the CGI variable in the fourth argument (default
6318 # is 'BROWSEDIRS'). So the correct call for a subdirectory could be:
6319 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', $DIRECTORY, 'DIRECTORY')
6321 sub BrowseDirs # (RootDirectory [, Pattern, Start, CGIvariable, HTTPserver]) -> Print HTML code
6323 my $RootDirectory = shift; # || return 0;
6324 my $Pattern = shift || '\S';
6325 my $Start = shift || "";
6326 my $CGIvariable = shift || "BROWSEDIRS";
6327 my $HTTPserver = shift || '';
6329 $Start = CGIscriptor::URLdecode($Start); # Sometimes, too much has been encoded
6330 $Start =~ s@//+@/@g;
6331 $Start =~ s@[^/]+/\.\.@@ig;
6332 $Start =~ s@^\.\.@@ig;
6333 $Start =~ s@/\.$@@ig;
6334 $Start =~ s!/+$!!g;
6335 $Start .= "/" if $Start;
6337 my @Directory = glob("$::CGI_HOME/$RootDirectory/$Start");
6338 $CurrentDirectory = shift(@Directory);
6339 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
6340 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
6341 print "<h1>";
6342 print "$CurrentDirectory" if $CurrentDirectory;
6343 print "</h1>\n";
6344 opendir(BROWSE, "$::CGI_HOME/$RootDirectory/$Start") || main::dieHandler(31, "$::CGI_HOME/$RootDirectory/$Start $!");
6345 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
6347 # Print directories
6348 my $file;
6349 print "<pre><ul TYPE='NONE'>\n";
6350 foreach $file (@AllFiles)
6352 next unless -d "$::CGI_HOME/$RootDirectory/$Start$file";
6353 # Check whether this file should be visible
6354 next if $::BlockPathAccess &&
6355 "/$RootDirectory/$Start$file/" =~ m@$::BlockPathAccess@;
6357 my $NewURL = $Start ? "$Start$file" : $file;
6358 $NewURL = CGIscriptor::URLencode($NewURL);
6359 print "<dt><a href='";
6360 print "$ENV{SCRIPT_NAME}" if $ENV{SCRIPT_NAME} !~ m@[^\w+\-/]@;
6361 print "$ENV{PATH_INFO}?$CGIvariable=$NewURL'>$file</a></dt>\n";
6363 print "</ul></pre>\n";
6365 # Print files
6366 print "<pre><ul TYPE='CIRCLE'>\n";
6367 my $TotalSize = 0;
6368 foreach $file (@AllFiles)
6370 next if $file =~ /^\./;
6371 next if -d "$::CGI_HOME/$RootDirectory/$Start$file";
6372 next if -l "$::CGI_HOME/$RootDirectory/$Start$file";
6373 # Check whether this file should be visible
6374 next if $::BlockPathAccess &&
6375 "$::CGI_HOME/$RootDirectory/$Start$file" =~ m@$::BlockPathAccess@;
6377 if($file =~ m@$Pattern@)
6379 my $Date = localtime($^T - (-M "$::CGI_HOME/$RootDirectory/$Start$file")*3600*24);
6380 my $Size = -s "$::CGI_HOME/$RootDirectory/$Start$file";
6381 $Size = sprintf("%6.0F kB", $Size/1024);
6382 my $Type = `file $::CGI_HOME/$RootDirectory/$Start$file`;
6383 $Type =~ s@\s*$::CGI_HOME/$RootDirectory/$Start$file\s*\:\s*@@ig;
6384 chomp($Type);
6386 print "<li>";
6387 if($HTTPserver =~ /^\s*[\.\~]\s*$/)
6389 print "<a href='$RootDirectory/$Start$file'>";
6391 elsif($HTTPserver)
6393 print "<a href='$HTTPserver/$RootDirectory/$Start$file'>";
6395 printf("%-40s", "$file</a>") if $HTTPserver;
6396 printf("%-40s", "$file") unless $HTTPserver;
6397 print "\t$Size\t$Date\t$Type";
6398 print "</li>\n";
6401 print "</ul></pre>";
6403 return 1;
6407 # ListDocs(Pattern [,ListType])
6409 # usage:
6410 # <SCRIPT TYPE=text/ssperl>
6411 # CGIscriptor::ListDocs("/*", "dl");
6412 # </SCRIPT>
6414 # This subroutine is very usefull to manage collections of independent
6415 # documents. The resulting list will display the tree-like directory
6416 # structure. If this routine is too slow for online use, you can
6417 # store the result and use a link to that stored file.
6419 # List HTML and Text files with title and first header (HTML)
6420 # or filename and first meaningfull line (general text files).
6421 # The listing starts at the ServerRoot directory. Directories are
6422 # listed recursively.
6424 # You can change the list type (default is dl).
6425 # e.g.,
6426 # <dt><a href=<file.html>>title</a>
6427 # <dd>First Header
6428 # <dt><a href=<file.txt>>file.txt</a>
6429 # <dd>First meaningfull line of text
6431 sub ListDocs # ($Pattern [, prefix]) e.g., ("/Books/*", [, "dl"])
6433 my $Pattern = shift;
6434 $Pattern =~ /\*/;
6435 my $ListType = shift || "dl";
6436 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
6437 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
6438 my @FileList = glob("$::CGI_HOME$Pattern");
6439 my ($FileName, $Path, $Link);
6441 # Print List markers
6442 print "<$ListType>\n";
6444 # Glob all files
6445 File: foreach $FileName (@FileList)
6447 # Check whether this file should be visible
6448 next if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@;
6450 # Recursively list files in all directories
6451 if(-d $FileName)
6453 $FileName =~ m@([^/]*)$@;
6454 my $DirName = $1;
6455 print "<$Prefix>$DirName\n";
6456 $Pattern =~ m@([^/]*)$@;
6457 &ListDocs("$`$DirName/$1", $ListType);
6458 next;
6460 # Use textfiles
6461 elsif(-T "$FileName")
6463 open(TextFile, $FileName) || next;
6465 # Ignore all other file types
6466 else
6467 { next;};
6469 # Get file path for link
6470 $FileName =~ /$::CGI_HOME/;
6471 print "<$Prefix><a href=$URL_root$'>";
6472 # Initialize all variables
6473 my $Line = "";
6474 my $TitleFound = 0;
6475 my $Caption = "";
6476 my $Title = "";
6477 # Read file and step through
6478 while(<TextFile>)
6480 chop $_;
6481 $Line = $_;
6482 # HTML files
6483 if($FileName =~ /\.ht[a-zA-Z]*$/i)
6485 # Catch Title
6486 while(!$Title)
6488 if($Line =~ m@<title>([^<]*)</title>@i)
6490 $Title = $1;
6491 $Line = $';
6493 else
6495 $Line .= <TextFile> || goto Print;
6496 chop $Line;
6499 # Catch First Header
6500 while(!$Caption)
6502 if($Line =~ m@</h1>@i)
6504 $Caption = $`;
6505 $Line = $';
6506 $Caption =~ m@<h1>@i;
6507 $Caption = $';
6508 $Line = $`.$Caption.$Line;
6510 else
6512 $Line .= <TextFile> || goto Print;
6513 chop $Line;
6517 # Other text files
6518 else
6520 # Title equals file name
6521 $FileName =~ /([^\/]+)$/;
6522 $Title = $1;
6523 # Catch equals First Meaningfull line
6524 while(!$Caption)
6526 if($Line =~ /[A-Z]/ &&
6527 ($Line =~ /subject|title/i || $Line =~ /^[\w,\.\s\?\:]+$/)
6528 && $Line !~ /Newsgroup/ && $Line !~ /\:\s*$/)
6530 $Line =~ s/\<[^\>]+\>//g;
6531 $Caption = $Line;
6533 else
6535 $Line = <TextFile> || goto Print;
6539 Print: # Print title and subject
6540 print "$Title</a>\n";
6541 print "<dd>$Caption\n" if $ListType eq "dl";
6542 $TitleFound = 0;
6543 $Caption = "";
6544 close TextFile;
6545 next File;
6548 # Print Closing List Marker
6549 print "</$ListType>\n";
6550 ""; # Empty return value
6554 # HTMLdocTree(Pattern [,ListType])
6556 # usage:
6557 # <SCRIPT TYPE=text/ssperl>
6558 # CGIscriptor::HTMLdocTree("/Welcome.html", "dl");
6559 # </SCRIPT>
6561 # The following subroutine is very usefull for checking large document
6562 # trees. Starting from the root (s), it reads all files and prints out
6563 # a nested list of links to all attached files. Non-existing or misplaced
6564 # files are flagged. This is quite a file-i/o intensive routine
6565 # so you would not like it to be accessible to everyone. If you want to
6566 # use the result, save the whole resulting page to disk and use a link
6567 # to this file.
6569 # HTMLdocTree takes an HTML file or file pattern and constructs nested lists
6570 # with links to *local* files (i.e., only links to the local server are
6571 # followed). The list entries are the document titles.
6572 # If the list type is <dl>, the first <H1> header is used too.
6573 # For each file matching the pattern, a list is made recursively of all
6574 # HTML documents that are linked from it and are stored in the same directory
6575 # or a sub-directory. Warnings are given for missing files.
6576 # The listing starts for the ServerRoot directory.
6577 # You can change the default list type <dl> (<dl>, <ul>, <ol>).
6579 %LinkUsed = ();
6581 sub HTMLdocTree # ($Pattern [, listtype])
6582 # e.g., ("/Welcome.html", [, "ul"])
6584 my $Pattern = shift;
6585 my $ListType = shift || "dl";
6586 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
6587 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
6588 my ($Filename, $Path, $Link);
6589 my %LocalLinks = {};
6591 # Read files (glob them for expansion of wildcards)
6592 my @FileList = glob("$::CGI_HOME$Pattern");
6593 foreach $Path (@FileList)
6595 # Get URL_path
6596 $Path =~ /$::CGI_HOME/;
6597 my $URL_path = $';
6598 # Check whether this file should be visible
6599 next if $::BlockPathAccess && $URL_path =~ m@$::BlockPathAccess@;
6601 my $Title = $URL_path;
6602 my $Caption = "";
6603 # Current file should not be used again
6604 ++$LinkUsed{$URL_path};
6605 # Open HTML doc
6606 unless(open(TextFile, $Path))
6608 print "<$Prefix>$Title <blink>(not found)</blink><br>\n";
6609 next;
6611 while(<TextFile>)
6613 chop $_;
6614 $Line = $_;
6615 # Catch Title
6616 while($Line =~ m@<title>@i)
6618 if($Line =~ m@<title>([^<]*)</title>@i)
6620 $Title = $1;
6621 $Line = $';
6623 else
6625 $Line .= <TextFile>;
6626 chop $Line;
6629 # Catch First Header
6630 while(!$Caption && $Line =~ m@<h1>@i)
6632 if($Line =~ m@</h[1-9]>@i)
6634 $Caption = $`;
6635 $Line = $';
6636 $Caption =~ m@<h1>@i;
6637 $Caption = $';
6638 $Line = $`.$Caption.$Line;
6640 else
6642 $Line .= <TextFile>;
6643 chop $Line;
6646 # Catch and print Links
6647 while($Line =~ m@<a href\=([^>]*)>@i)
6649 $Link = $1;
6650 $Line = $';
6651 # Remove quotes
6652 $Link =~ s/\"//g;
6653 # Remove extras
6654 $Link =~ s/[\#\?].*$//g;
6655 # Remove Servername
6656 if($Link =~ m@(http://|^)@i)
6658 $Link = $';
6659 # Only build tree for current server
6660 next unless $Link =~ m@$::ENV{'SERVER_NAME'}|^/@;
6661 # Remove server name and port
6662 $Link =~ s@^[^\/]*@@g;
6664 # Store the current link
6665 next if $LinkUsed{$Link} || $Link eq $URL_path;
6666 ++$LinkUsed{$Link};
6667 ++$LocalLinks{$Link};
6671 close TextFile;
6672 print "<$Prefix>";
6673 print "<a href=http://";
6674 print "$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}$URL_path>";
6675 print "$Title</a>\n";
6676 print "<br>$Caption\n"
6677 if $Caption && $Caption ne $Title && $ListType =~ /dl/i;
6678 print "<$ListType>\n";
6679 foreach $Link (keys(%LocalLinks))
6681 &HTMLdocTree($Link, $ListType);
6683 print "</$ListType>\n";
6687 ###########################<<<<<<<<<<End Remove
6689 # Make require happy
6692 =head1 NAME
6694 CGIscriptor -
6696 =head1 DESCRIPTION
6698 A flexible HTML 4 compliant script/module for CGI-aware
6699 embeded Perl, shell-scripts, and other scripting languages,
6700 executed at the server side.
6702 =head1 README
6704 Executes embeded Perl code in HTML pages with easy
6705 access to CGI variables. Also processes embeded shell
6706 scripts and scripts in any other language with an
6707 interactive interpreter (e.g., in-line Python, Tcl,
6708 Ruby, Awk, Lisp, Xlispstat, Prolog, M4, R, REBOL, Praat,
6709 sh, bash, csh, ksh).
6711 CGIscriptor is very flexible and hides all the specifics
6712 and idiosyncrasies of correct output and CGI coding and naming.
6713 CGIscriptor complies with the W3C HTML 4.0 recommendations.
6715 This Perl program will run on any WWW server that runs
6716 Perl scripts, just add a line like the following to your
6717 srm.conf file (Apache example):
6719 ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
6721 URL's that refer to http://www.your.address/SHTML/... will
6722 now be handled by CGIscriptor.pl, which can use a private
6723 directory tree (default is the DOCUMENT_ROOT directory tree,
6724 but it can be anywhere).
6726 =head1 PREREQUISITES
6729 =head1 COREQUISITES
6732 =pod OSNAMES
6734 Linux, *BSD, *nix, MS WinXP
6736 =pod SCRIPT CATEGORIES
6738 Servers
6742 =cut