Added REMOTE_HOST to login procedure to protect against MitM attacks
[CGIscriptor.git] / CGIscriptor.pl
blob2d956afae5a0d0bf2ea82960410f50dc9900c120
1 #! /usr/bin/perl
3 # (configure the first line to contain YOUR path to perl 5.000+)
5 # CGIscriptor.pl
6 # Version 2.4
7 # 10 July 2012
9 # YOU NEED:
11 # perl 5.0 or higher (see: "http://www.perl.org/")
13 # Notes:
15 if(grep(/\-\-help/i, @ARGV))
17 print << 'ENDOFPREHELPTEXT1';
18 # CGIscriptor.pl is a Perl program will run on any WWW server that
19 # runs Perl scripts, just add a line like the following to your
20 # httpd.conf file (Apache example):
22 # ScriptAlias /SHTML/ "/real-path/CGIscriptor.pl/"
24 # URL's that refer to http://www.your.address/SHTML/... will now be handled
25 # by CGIscriptor.pl, which can use a private directory tree (default is the
26 # DOCUMENT_ROOT directory tree, but it can be anywhere, see below).
27 # NOTE: if you cannot use a ScriptAlias, there is a way to use .htaccess
28 # instead. See below.
30 # This file contains all documentation as comments. These comments
31 # can be removed to speed up loading (e.g., `egrep -v '^#' CGIscriptor.pl` >
32 # leanScriptor.pl). A bare bones version of CGIscriptor.pl, lacking
33 # documentation, most comments, access control, example functions etc.
34 # (but still with the copyright notice and some minimal documentation)
35 # can be obtained by calling CGIscriptor.pl with the '-slim'
36 # command line argument, e.g.,
37 # >CGIscriptor.pl -slim >slimCGIscriptor.pl
39 # CGIscriptor.pl can be run from the command line as
40 # `CGIscriptor.pl <path> <query>`, inside a perl script with
41 # 'do CGIscriptor.pl' after setting $ENV{PATH_INFO} and $ENV{QUERY_STRING},
42 # or CGIscriptor.pl can be loaded with 'require "/real-path/CGIscriptor.pl"'.
43 # In the latter case, requests are processed by 'Handle_Request();'
44 # (again after setting $ENV{PATH_INFO} and $ENV{QUERY_STRING}).
46 # The --help command line switch will print the manual.
48 # Running demo's and more information can be found at
49 # http://www.fon.hum.uva.nl/rob/OSS/OSS.html
51 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site
52 # or CPAN that can use CGIscriptor.pl as the base of a µWWW server and
53 # demonstrates its use.
55 ENDOFPREHELPTEXT1
58 # Configuration, copyright notice, and user manual follow the next
59 # (Changes) section.
61 ############################################################################
63 # Changes (document ALL changes with date, name and email here):
64 # 05 Apr 2013 - Renamed COOKIE_JAR to HTTP_COOKIE, added support for
65 # CGI::Cookie in case $ENV{HTTP_COOKIE} is undefined (untested)
66 # 31 Mar 2013 - Added support for Digest::SHA
67 # 13 Mar 2013 - Changed password hash
68 # 10 Jul 2012 - Version 2.4
69 # 11 Jun 2012 - Securing CGIvariable setting. Made
70 # 'if($ENV{QUERY_STRING} =~ /$name/)' into elsif in
71 # defineCGIvariable/List/Hash to give precedence to ENV{$name}
72 # This was a very old security bug. Added ProtectCGIvariable($name).
73 # 06 Jun 2012 - Added IP only session types after login.
74 # 31 May 2012 - Session ticket system added for handling login sessions.
75 # 29 May 2012 - CGIsafeFileName does not accept filenames starting with '.'
76 # 29 May 2012 - Added CGIscriptor::BrowseAllDirs to handle browsing directories
77 # correctly.
78 # 22 May 2012 - Added Access control with Session Tickets linked to
79 # IP Address and PATH_INFO.
80 # 21 May 2012 - Corrected the links generated by CGIscriptor::BrowseDirs
81 # Will link to current base URL when the HTTP server is '.' or '~'
82 # 29 Oct 2009 - Adapted David A. Wheeler's suggestion about filenames:
83 # CGIsafeFileName does not accept filenames starting with '-'
84 # (http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html)
85 # 08 Oct 2009 - Some corrections in the README.txt file, eg, new email address
86 # 28 Jan 2005 - Added a file selector to performTranslation.
87 # Changed %TranslationTable to @TranslationTable
88 # and patterns to lists.
89 # 27 Jan 2005 - Added a %TranslationTable with associated
90 # performTranslation(\$text) function to allow
91 # run changes in the web pages. Say, to translate
92 # legacy pages with <%=...%> delimiters to the new
93 # <SCRIPT TYPE=..></SCRIPT> format.
94 # 27 Jan 2005 - Small bug of extra '\n' in output removed from the
95 # Other Languages Code.
96 # 10 May 2004 - Belated upload of latest version (2.3) to CPAN
97 # 07 Oct 2003 - Corrected error '\s' -> '\\s' in rebol scripting
98 # language call
99 # 07 Oct 2003 - Corrected omitted INS tags in <DIV><INS> handling
100 # 20 May 2003 - Added a --help switch to print the manual.
101 # 06 Mar 2003 - Adapted the blurb at the end of the file.
102 # 03 Mar 2003 - Added a user definable dieHandler function to catch all
103 # "die" calls. Also "enhanced" the STDERR printout.
104 # 10 Feb 2003 - Split off the reading of the POST part of a query
105 # from Initialize_output. This was suggested by Gerd Franke
106 # to allow for the catching of the file_path using a
107 # POST based lookup. That is, he needed the POST part
108 # to change the file_path.
109 # 03 Feb 2003 - %{$name}; => %{$name} = (); in defineCGIvariableHash.
110 # 03 Feb 2003 - \1 better written as $1 in
111 # $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
112 # 29 Jan 2003 - This makes "CLASS="ssperl" CSS-compatible Gerd Franke
113 # added:
114 # $ServerScriptContentClass = "ssperl";
115 # changed in ProcessFile():
116 # unless(($CurrentContentType =~
117 # 28 Jan 2003 - Added 'INS' Tag! Gerd Franke
118 # 20 Dec 2002 - Removed useless $Directoryseparator variable.
119 # Update comments and documentation.
120 # 18 Dec 2002 - Corrected bug in Accept/Reject processing.
121 # Files didn't work.
122 # 24 Jul 2002 - Added .htaccess documentation (from Gerd Franke)
123 # Also added a note that RawFilePattern can be a
124 # complete file name.
125 # 19 Mar 2002 - Added SRC pseudo-files PREFIX and POSTFIX. These
126 # switch to prepending or to appending the content
127 # of the SRC attribute. Default is prefixing. You
128 # can add as many of these switches as you like.
129 # 13 Mar 2002 - Do not search for tag content if a tag closes with
130 # />, i.e., <DIV ... /> will be handled the XML/XHTML way.
131 # 25 Jan 2002 - Added 'curl' and 'snarf' to SRC attribute URL handling
132 # (replaces wget).
133 # 25 Jan 2002 - Found a bug in SAFEqx, now executes qx() in a scalar context
134 # (i.o. a list context). This is necessary for binary results.
135 # 24 Jan 2002 - Disambiguated -T $SRCfile to -T "$SRCfile" (and -e) and
136 # changed the order of if/elsif to allow removing these
137 # conditions in systems with broken -T functions.
138 # (I also removed a spurious ')' bracket)
139 # 17 Jan 2002 - Changed DIV tag SRC from <SOURCE> to sysread(SOURCE,...)
140 # to support binary files.
141 # 17 Jan 2002 - Removed WhiteSpace from $FileAllowedCharacters.
142 # 17 Jan 2002 - Allow "file://" prefix in SRC attribute. It is simply
143 # stipped from the path.
144 # 15 Jan 2002 - Version 2.2
145 # 15 Jan 2002 - Debugged and completed URL support (including
146 # CGIscriptor::read_url() function)
147 # 07 Jan 2002 - Added automatic (magic) URL support to the SRC attribute
148 # with the main::GET_URL function. Uses wget -O underlying.
149 # 04 Jan 2002 - Added initialization of $NewDirective in InsertForeignScript
150 # (i.e., my $NewDirective = "";) to clear old output
151 # (this was a realy anoying bug).
152 # 03 Jan 2002 - Added a <DIV CLASS='text/ssperl' ID='varname'></DIV>
153 # tags that assign the body text as-is (literally)
154 # to $varname. Allows standard HTML-tools to handle
155 # Cascading Style Sheet templates. This implements a
156 # design by Gerd Franke (franke@roo.de).
157 # 03 Jan 2002 - I finaly gave in and allowed SRC files to expand ~/.
158 # 12 Oct 2001 - Normalized spelling of "CGIsafFileName" in documentation.
159 # 09 Oct 2001 - Added $ENV{'CGI_BINARY_FILE'} to log files to
160 # detect unwanted indexing of TAR files by webcrawlers.
161 # 10 Sep 2001 - Added $YOUR_SCRIPTS directory to @INC for 'require'.
162 # 22 Aug 2001 - Added .txt (Content-type: text/plain) as a default
163 # processed file type. Was processed via BinaryMapFile.
164 # 31 May 2001 - Changed =~ inside CGIsafeEmailAddress that was buggy.
165 # 29 May 2001 - Updated $CGI_HOME to point to $ENV{DOCUMENT_ROOT} io
166 # the root of PATH_TRANSLATED. DOCUMENT_ROOT can now
167 # be manipulated to achieve a "Sub Root".
168 # NOTE: you can have $YOUR_HTML_FILES != DOCUMENT_ROOT
169 # 28 May 2001 - Changed CGIscriptor::BrowsDirs function for security
170 # and debugging (it now works).
171 # 21 May 2001 - defineCGIvariableHash will ADD values to existing
172 # hashes,instead of replacing existing hashes.
173 # 17 May 2001 - Interjected a '&' when pasting POST to GET data
174 # 24 Apr 2001 - Blocked direct requests for BinaryMapFile.
175 # 16 Aug 2000 - Added hash table extraction for CGI parameters with
176 # CGIparseValueHash (used with structured parameters).
177 # Use: CGI='%<CGI-partial-name>' (fill in your name in <>)
178 # Will collect all <CGI-partial-name><key>=value pairs in
179 # $<CGI-partial-name>{<key>} = value;
180 # 16 Aug 2000 - Adapted SAFEqx to protect @PARAMETER values.
181 # 09 Aug 2000 - Added support for non-filesystem input by way of
182 # the CGI_FILE_CONTENTS and CGI_DATA_ACCESS_CODE
183 # environment variables.
184 # 26 Jul 2000 - On the command-line, file-path '-' indicates STDIN.
185 # This allows CGIscriptor to be used in pipes.
186 # Default, $BLOCK_STDIN_HTTP_REQUEST=1 will block this
187 # in an HTTP request (i.e., in a web server).
188 # 26 Jul 2000 - Blocked 'Content-type: text/html' if the SERVER_PROTOCOL
189 # is not HTTP or another protocol. Changed the default
190 # source directory to DOCUMENT_ROOT (i.o. the incorrect
191 # SERVER_ROOT).
192 # 24 Jul 2000 - -slim Command-line argument added to remove all
193 # comments, security, etc.. Updated documentation.
194 # 05 Jul 2000 - Added IF and UNLESS attributes to make the
195 # execution of all <META> and <SCRIPT> code
196 # conditional.
197 # 05 Jul 2000 - Rewrote and isolated the code for extracting
198 # quoted items from CGI and SRC attributes.
199 # Now all attributes expect the same set of
200 # quotes: '', "", ``, (), {}, [] and the same
201 # preceded by a \, e.g., "\((aap)\)" will be
202 # extracted as "(aap)".
203 # 17 Jun 2000 - Construct @ARGV list directly in CGIexecute
204 # name-space (i.o. by evaluation) from
205 # CGI attributes to prevent interference with
206 # the processing for non perl scripts.
207 # Changed CGIparseValueList to prevent runaway
208 # loops.
209 # 16 Jun 2000 - Added a direct (interpolated) display mode
210 # (text/ssdisplay) and a user log mode
211 # (text/sslogfile).
212 # 06 Jun 2000 - Replace "print $Result" with a syswrite loop to
213 # allow large string output.
214 # 02 Jun 2000 - Corrected shrubCGIparameter($CGI_VALUE) to realy
215 # remove all control characters. Changed Interpreter
216 # initialization to shrub interpolated CGI parameters.
217 # Added 'text/ssmailto' interpreter script.
218 # 22 May 2000 - Changed some of the comments
219 # 09 May 2000 - Added list extraction for CGI parameters with
220 # CGIparseValueList (used with multiple selections).
221 # Use: CGI='@<CGI-parameter>' (fill in your name in <>)
222 # 09 May 2000 - Added a 'Not Present' condition to CGIparseValue.
223 # 27 Apr 2000 - Updated documentation to reflect changes.
224 # 27 Apr 2000 - SRC attribute "cleaned". Supported for external
225 # interpreters.
226 # 27 Apr 2000 - CGI attribute can be used in <SCRIPT> tag.
227 # 27 Apr 2000 - Gprolog, M4 support added.
228 # 26 Apr 2000 - Lisp (rep) support added.
229 # 20 Apr 2000 - Use of external interpreters now functional.
230 # 20 Apr 2000 - Removed bug from extracting Content types (RegExp)
231 # 10 Mar 2000 - Qualified unconditional removal of '#' that preclude
232 # the use of $#foo, i.e., I changed
233 # s/[^\\]\#[^\n\f\r]*([\n\f\r])/\1/g
234 # to
235 # s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/\1/g
236 # 03 Mar 2000 - Added a '$BlockPathAccess' variable to "hide"
237 # things like, e.g., CVS information in CVS subtrees
238 # 10 Feb 2000 - URLencode/URLdecode have been made case-insensitive
239 # 10 Feb 2000 - Added a BrowseDirs function (CGIscriptor package)
240 # 01 Feb 2000 - A BinaryMapFile in the ~/ directory has precedence
241 # over a "burried" BinaryMapFile.
242 # 04 Oct 1999 - Added two functions to check file names and email addresses
243 # (CGIscriptor::CGIsafeFileName and
244 # CGIscriptor::CGIsafeEmailAddress)
245 # 28 Sept 1999 - Corrected bug in sysread call for reading POST method
246 # to allow LONG posts.
247 # 28 Sept 1999 - Changed CGIparseValue to handle multipart/form-data.
248 # 29 July 1999 - Refer to BinaryMapFile from CGIscriptor directory, if
249 # this directory exists.
250 # 07 June 1999 - Limit file-pattern matching to LAST extension
251 # 04 June 1999 - Default text/html content type is printed only once.
252 # 18 May 1999 - Bug in replacement of ~/ and ./ removed.
253 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
254 # 15 May 1999 - Changed the name of the execute package to CGIexecute.
255 # Changed the processing of the Accept and Reject file.
256 # Added a full expression evaluation to Access Control.
257 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
258 # 27 Apr 1999 - Brought CGIscriptor under the GNU GPL. Made CGIscriptor
259 # Version 1.1 a module that can be called with 'require "CGIscriptor.pl"'.
260 # Requests are serviced by "Handle_Request()". CGIscriptor
261 # can still be called as a isolated perl script and a shell
262 # command.
263 # Changed the "factory default setting" so that it will run
264 # from the DOCUMENT_ROOT directory.
265 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
266 # 29 Mar 1999 - Remove second debugging STDERR switch. Moved most code
267 # to subroutines to change CGIscriptor into a module.
268 # Added mapping to process unsupported file types (e.g., binary
269 # pictures). See $BinaryMapFile.
270 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
271 # 24 Sept 1998 - Changed text of license (Rob van Son, R.J.J.H.vanSon@gmail.com)
272 # Removed a double setting of filepatterns and maximum query
273 # size. Changed email address. Removed some typos from the
274 # comments.
275 # 02 June 1998 - Bug fixed in URLdecode. Changing the foreach loop variable
276 # caused quiting CGIscriptor.(Rob van Son, R.J.J.H.vanSon@gmail.com)
277 # 02 June 1998 - $SS_PUB and $SS_SCRIPT inserted an extra /, removed.
278 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
281 # Known Bugs:
283 # 23 Mar 2000
284 # It is not possible to use operators or variables to construct variable names,
285 # e.g., $bar = \@{$foo}; won't work. However, eval('$bar = \@{'.$foo.'};');
286 # will indeed work. If someone could tell me why, I would be obliged.
289 ############################################################################
291 # OBLIGATORY USER CONFIGURATION
293 # Configure the directories where all user files can be found (this
294 # is the equivalent of the server root directory of a WWW-server).
295 # These directories can be located ANYWHERE. For security reasons, it is
296 # better to locate them outside the WWW-tree of your HTTP server, unless
297 # CGIscripter handles ALL requests.
299 # For convenience, the defaults are set to the root of the WWW server.
300 # However, this might not be safe!
302 # ~/ text files
303 # $YOUR_HTML_FILES = "/usr/pub/WWW/SHTML"; # or SS_PUB as environment var
304 # (patch to use the parent directory of CGIscriptor as document root, should be removed)
305 if($ENV{'SCRIPT_FILENAME'}) # && $ENV{'SCRIPT_FILENAME'} !~ /\Q$ENV{'DOCUMENT_ROOT'}\E/)
307 $ENV{'DOCUMENT_ROOT'} = $ENV{'SCRIPT_FILENAME'};
308 $ENV{'DOCUMENT_ROOT'} =~ s@/CGIscriptor.*$@@ig;
311 # Just enter your own directory path here
312 $YOUR_HTML_FILES = $ENV{'DOCUMENT_ROOT'}; # default is the DOCUMENT_ROOT
314 # ./ script files (recommended to be different from the previous)
315 # $YOUR_SCRIPTS = "/usr/pub/WWW/scripts"; # or SS_SCRIPT as environment var
316 $YOUR_SCRIPTS = $YOUR_HTML_FILES; # This might be a SECURITY RISK
318 # End of obligatory user configuration
319 # (note: there is more non-essential user configuration below)
321 ############################################################################
323 # OPTIONAL USER CONFIGURATION (all values are used CASE INSENSITIVE)
325 # Script content-types: TYPE="Content-type" (user defined mime-type)
326 $ServerScriptContentType = "text/ssperl"; # Server Side Perl scripts
327 # CSS require a simple class
328 $ServerScriptContentClass = $ServerScriptContentType =~ m!/! ?
329 $' : "ssperl"; # Server Side Perl CSS classes
331 $ShellScriptContentType = "text/osshell"; # OS shell scripts
332 # # (Server Side perl ``-execution)
334 # Accessible file patterns, block any request that doesn't match.
335 # Matches any file with the extension .(s)htm(l), .txt, or .xmr
336 # (\. is used in regexp)
337 # Note: die unless $PATH_INFO =~ m@($FilePattern)$@is;
338 $FilePattern = ".shtml|.htm|.html|.xml|.xmr|.txt|.js|.css";
340 # The table with the content type MIME types
341 # (allows to differentiate MIME types, if needed)
342 %ContentTypeTable =
344 '.html' => 'text/html',
345 '.shtml' => 'text/html',
346 '.htm' => 'text/html',
347 '.xml' => 'text/xml',
348 '.txt' => 'text/plain',
349 '.js' => 'text/plain',
350 '.css' => 'text/plain'
354 # File pattern post-processing
355 $FilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
357 # SHAsum command needed for Authorization and Login
358 # (note, these have to be accessible in the HTML pages, ie, the CGIexecute environment)
359 my $shasum = "shasum -a 256";
360 if(qx{uname} =~ /Darwin/)
362 $shasum = "shasum-5.12 -a 256" unless `which shasum`;
364 my $SHASUMCMD = $shasum.' |cut -f 1 -d" "';
365 $ENV{"SHASUMCMD"} = $SHASUMCMD;
366 my $RANDOMHASHCMD = 'dd bs=1 count=64 if=/dev/urandom 2>/dev/null | '.$shasum.' -b |cut -f 1 -d" "';
367 $ENV{"RANDOMHASHCMD"} = $RANDOMHASHCMD;
369 # Hash a string, return hex of hash
370 sub hash_string_cmd # ($string) -> hex_hash
372 my $string = shift || "";
373 # Catch nasty \'-quotes, embed them in '..'"'"'..'
374 $string =~ s/\'/\'\"\'\"\'/isg;
375 my $hash = `printf '%s' '$string'| $ENV{"SHASUMCMD"}`;
376 chomp($hash);
377 return $hash;
380 # Note that you CANNOT replace $RANDOMHASHCMD with a call using hash_string_cmd
381 # as the output of /dev/urandom breaks string handling in Perl.
382 # Generate random hex hash
383 sub get_random_hex_cmd # () -> hex
385 # Create Random Hash Salt
386 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $RANDOMHASHCMD | $!\n";
387 my $RANDOMSALT= <URANDOM>;
388 close(URANDOM);
389 chomp($RANDOMSALT);
391 return $RANDOMSALT;
395 # You can use Digest::SHA (SHA.pm), you need sha256_hex
396 # See http://search.cpan.org/~mshelor/Digest-SHA-5.84/lib/Digest/SHA.pm
397 # > sudo CPAN -i Digest
399 # The following code will check whether Digest::SHA is available and then
400 # use the appropriate function calls.
402 $shaDigestLoaded = (eval("require Digest::SHA;1;") eq "1") ? 1 : 0;
404 sub hash_string_Digest # ($string) -> hex_hash
406 my $string = shift || "";
407 my $digest = Digest::SHA::sha256_hex($string);
408 $string = $digest;
409 return $digest;
412 sub get_random_hex_Digest # () -> hex
414 my $randomstring = "";
415 # Create Random Hash Salt
416 open(URANDOM, "</dev/urandom") || die "/dev/urandom: $!\n";
417 read URANDOM, $randomstring, 64 || die "No random bytes read: $!\n";
418 close(URANDOM);
419 my $RANDOMSALT= hash_string_Digest($randomstring);
421 return $RANDOMSALT;
424 # The final functions
425 sub hash_string # ($string) -> hex_hash
427 if($shaDigestLoaded)
428 { return hash_string_Digest (@_) }
429 else
430 { return hash_string_cmd(@_);};
433 sub get_random_hex # () -> hex
435 if($shaDigestLoaded)
436 { return get_random_hex_Digest () }
437 else
438 { return get_random_hex_cmd();};
441 ######################################################################
443 # File patterns of files which are handled by session tickets.
444 %TicketRequiredPatterns = (
445 '^/Private(/|$)' => "Private/.Sessions\tPrivate/.Passwords\t/Private/Login.html\t+36000"
447 # Used to set cookies, only session cookies supported
448 my %SETCOOKIELIST = ();
449 my %CGI_Cookies = ();
450 # Parse the cookies if $ENV{'HTTP_COOKIE'} is defined, else use CGI::Cookie
451 # if it is available
452 sub Get_All_Cookies
454 $ENV{'HTTP_COOKIE'} = $ENV{'Cookie'} if defined($ENV{'Cookie'}) && !defined($ENV{'HTTP_COOKIE'});
456 if(defined($ENV{'HTTP_COOKIE'}))
458 my @CookieList = split(/[\;\s]+/, $ENV{'HTTP_COOKIE'});
459 foreach my $CookieEntry (@CookieList)
461 my ($k, $v) = split(/\=/, $CookieEntry);
462 # Add new cookie only if it does not already exist
463 $CGI_Cookies{$k} = $v unless exists($CGI_Cookies{$k}) && ($v eq "" || $v eq "-");
464 ($k, $v, $CookieEntry) = (0, 0, 0);
466 @CookieList = ();
467 $ENV{'Cookie'} = "" if defined($ENV{'Cookie'})
469 else
471 my $cookiesLoaded = (eval("require CGI::Cookie;1;") eq "1") ? 1 : 0;
472 if($cookiesLoaded)
474 %CGI_Cookies = fetch CGI::Cookie;
480 # Session Ticket Directory: Private/.Sessions
481 # Password Directory: Private/.Passwords
482 # Login page (url path): /Private/Login.html
483 # Expiration time (s): +3600
484 # +<seconds> = relative time <seconds> is absolute date-time
486 # Manage login
487 # Set up a valid ticket from a given text file
488 # Use from command line. DO NOT USE ONLINE
489 # Watch out for passwords that get stored in the history file
491 # perl CGIscriptor.pl --managelogin [options] [files]
492 # Options:
493 # salt={file or saltvalue}
494 # masterkey={file or plaintext}
495 # newmasterkey={file or plaintext}
496 # password={file or palintext}
498 # Followed by one or more file names.
499 # Options can be interspersed between filenames,
500 # e.g., password='plaintext'
501 # Note that passwords are only used once!
503 if($ARGV[0] =~ /^\-\-managelogin/i)
505 my @arguments = @ARGV;
506 shift(@arguments);
507 setup_ticket_file(@arguments);
508 # Should be run on the command line
509 exit;
514 # Raw files must contain their own Content-type (xmr <- x-multipart-replace).
515 # THIS IS A SUBSET OF THE FILES DEFINED IN $FilePattern
516 $RawFilePattern = ".xmr";
517 # (In principle, this could contain a full file specification, e.g.,
518 # ".xmr|relocated.html")
520 # Raw File pattern post-processing
521 $RawFilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
523 # Server protocols for which "Content-type: text/html\n\n" should be printed
524 # (you should not bother with these, except for HTTP, they are mostly imaginary)
525 $ContentTypeServerProtocols = 'HTTP|MAIL|MIME';
527 # Block access to all (sub-) paths and directories that match the
528 # following (URL) path (is used as:
529 # 'die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;' )
530 $BlockPathAccess = '/(CVS|\.git)/'; # Protect CVS and .git information
532 # All (blocked) other file-types can be mapped to a single "binary-file"
533 # processor (a kind of pseudo-file path). This can either be an error
534 # message (e.g., "illegal file") or contain a script that serves binary
535 # files.
536 # Note: the real file path wil be stored in $ENV{CGI_BINARY_FILE}.
537 $BinaryMapFile = "/BinaryMapFile.xmr";
538 # Allow for the addition of a CGIscriptor directory
539 # Note that a BinaryMapFile in the root "~/" directory has precedence
540 $BinaryMapFile = "/CGIscriptor".$BinaryMapFile
541 if ! -e "$YOUR_HTML_FILES".$BinaryMapFile
542 && -e "$YOUR_HTML_FILES/CGIscriptor".$BinaryMapFile;
545 # List of all characters that are allowed in file names and paths.
546 # All requests containing illegal characters are blocked. This
547 # blocks most tricks (e.g., adding "\000", "\n", or other control
548 # characters, also blocks URI's using %FF)
549 # THIS IS A SECURITY FEATURE
550 # (this is also used to parse filenames in SRC= features, note the
551 # '-quotes, they are essential)
552 $FileAllowedChars = '\w\.\~\/\:\*\?\-'; # Covers Unix and Mac, but NO spaces
554 # Maximum size of the Query (number of characters clients can send
555 # covers both GET & POST combined)
556 $MaximumQuerySize = 2**20 - 1; # = 2**14 - 1
559 # Embeded URL get function used in SRC attributes and CGIscriptor::read_url
560 # (returns a string with the PERL code to transfer the URL contents, e.g.,
561 # "SAFEqx(\'curl \"http://www.fon.hum.uva.nl\"\')")
562 # "SAFEqx(\'wget --quiet --output-document=- \"http://www.fon.hum.uva.nl\"\')")
563 # Be sure to handle <BASE HREF='URL'> and allow BOTH
564 # direct printing GET_URL($URL [, 0]) and extracting the content of
565 # the $URL for post-processing GET_URL($URL, 1).
566 # You get the WHOLE file, including HTML header.
567 # The shell command Use $URL where the URL should go
568 # ('wget', 'snarf' or 'curl', uncomment the one you would like to use)
569 my $GET_URL_shell_command = 'wget --quiet --output-document=- $URL';
570 #my $GET_URL_shell_command = 'snarf $URL -';
571 #my $GET_URL_shell_command = 'curl $URL';
573 sub GET_URL # ($URL, $ValueNotPrint) -> content_of_url
575 my $URL = shift || return;
576 my $ValueNotPrint = shift || 0;
578 # Check URL for illegal characters
579 return "print '<h1>Illegal URL<h1>'\"\n\";" if $URL =~ /[^$FileAllowedChars\%]/;
581 # Include URL in final command
582 my $CurrentCommand = $GET_URL_shell_command;
583 $CurrentCommand =~ s/\$URL/$URL/g;
585 # Print to STDOUT or return a value
586 my $BlockPrint = "print STDOUT ";
587 $BlockPrint = "" if $ValueNotPrint;
589 my $Commands = <<"GETURLCODE";
590 # Get URL
592 my \$Page = "";
594 # Simple, using shell command
595 \$Page = SAFEqx('$CurrentCommand');
597 # Add a BASE tage to the header
598 \$Page =~ s!\\</head!\\<base href='$URL'\\>\\</head!ig unless \$Page =~ m!\\<base!;
600 # Print the URL value, or return it as a value
601 $BlockPrint\$Page;
603 GETURLCODE
604 return $Commands;
607 # As files can get rather large (and binary), you might want to use
608 # some more intelligent reading procedure, e.g.,
609 # Direct Perl
610 # # open(URLHANDLE, '/usr/bin/wget --quiet --output-document=- "$URL"|') || die "wget: \$!";
611 # #open(URLHANDLE, '/usr/bin/snarf "$URL" -|') || die "snarf: \$!";
612 # open(URLHANDLE, '/usr/bin/curl "$URL"|') || die "curl: \$!";
613 # my \$text = "";
614 # while(sysread(URLHANDLE,\$text, 1024) > 0)
616 # \$Page .= \$text;
617 # };
618 # close(URLHANDLE) || die "\$!";
619 # However, this doesn't work with the CGIexecute->evaluate() function.
620 # You get an error: 'No child processes at (eval 16) line 15, <file0> line 8.'
622 # You can forget the next two variables, they are only needed when
623 # you don't want to use a regular file system (i.e., with open)
624 # but use some kind of database/RAM image for accessing (generating)
625 # the data.
627 # Name of the environment variable that contains the file contents
628 # when reading directly from Database/RAM. When this environment variable,
629 # $ENV{$CGI_FILE_CONTENTS}, is not false, no real file will be read.
630 $CGI_FILE_CONTENTS = 'CGI_FILE_CONTENTS';
631 # Uncomment the following if you want to force the use of the data access code
632 # $ENV{$CGI_FILE_CONTENTS} = '-'; # Force use of $ENV{$CGI_DATA_ACCESS_CODE}
634 # Name of the environment variable that contains the RAM access perl
635 # code needed to read additional "files", i.e.,
636 # $ENV{$CGI_FILE_CONTENTS} = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
637 # When $ENV{$CGI_FILE_CONTENTS} eq '-', this code is executed to generate the data.
638 $CGI_DATA_ACCESS_CODE = 'CGI_DATA_ACCESS_CODE';
640 # You can, of course, fill this yourself, e.g.,
641 # $ENV{$CGI_DATA_ACCESS_CODE} =
642 # 'open(INPUT, "<$_[0]"); while(<INPUT>){print;};close(INPUT);'
645 # DEBUGGING
647 # Suppress error messages, this can be changed for debugging or error-logging
648 #open(STDERR, "/dev/null"); # (comment out for use in debugging)
650 # SPECIAL: Remove Comments, security, etc. if the command line is
651 # '>CGIscriptor.pl -slim >slimCGIscriptor.pl'
652 $TrimDownCGIscriptor = 1 if $ARGV[0] =~ /^\-slim/i;
654 # If CGIscriptor is used from the command line, the command line
655 # arguments are interpreted as the file (1st) and the Query String (rest).
656 # Get the arguments
657 $ENV{'PATH_INFO'} = shift(@ARGV) unless exists($ENV{'PATH_INFO'}) || grep(/\-\-help/i, @ARGV);
658 $ENV{'QUERY_STRING'} = join("&", @ARGV) unless exists($ENV{'QUERY_STRING'});
661 # Handle bail-outs in a user definable way.
662 # Catch Die and replace it with your own function.
663 # Ends with a call to "die $_[0];"
665 sub dieHandler # ($ErrorCode, "Message", @_) -> DEAD
667 my $ErrorCode = shift;
668 my $ErrorMessage = shift;
670 # Place your own reporting functions here
672 # Now, kill everything (default)
673 print STDERR "$ErrorCode: $ErrorMessage\n";
674 die $ErrorMessage;
678 # End of optional user configuration
679 # (note: there is more non-essential user configuration below)
681 if(grep(/\-\-help/i, @ARGV))
683 print << 'ENDOFPREHELPTEXT2';
685 ###############################################################################
687 # Author and Copyright (c):
688 # Rob van Son, © 1995,1996,1997,1998,1999,2000,2001,2002-2012
689 # NKI-AVL Amsterdam
690 # r.v.son@nki.nl
691 # Institute of Phonetic Sciences & IFOTT/ACLS
692 # University of Amsterdam
693 # Email: R.J.J.H.vanSon@gmail.com
694 # Email: R.J.J.H.vanSon@gmail.com
695 # WWW : http://www.fon.hum.uva.nl/rob/
697 # License for use and disclaimers
699 # CGIscriptor merges plain ASCII HTML files transparantly
700 # with CGI variables, in-line PERL code, shell commands,
701 # and executable scripts in other scripting languages.
703 # This program is free software; you can redistribute it and/or
704 # modify it under the terms of the GNU General Public License
705 # as published by the Free Software Foundation; either version 2
706 # of the License, or (at your option) any later version.
708 # This program is distributed in the hope that it will be useful,
709 # but WITHOUT ANY WARRANTY; without even the implied warranty of
710 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
711 # GNU General Public License for more details.
713 # You should have received a copy of the GNU General Public License
714 # along with this program; if not, write to the Free Software
715 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
718 # Contributors:
719 # Rob van Son (R.J.J.H.vanSon@gmail.com)
720 # Gerd Franke franke@roo.de (designed the <DIV> behaviour)
722 #######################################################
723 ENDOFPREHELPTEXT2
725 #######################################################>>>>>>>>>>Start Remove
727 # You can skip the following code, it is an auto-splice
728 # procedure.
730 # Construct a slimmed down version of CGIscriptor
731 # (i.e., CGIscriptor.pl -slim > slimCGIscriptor.pl)
733 if($TrimDownCGIscriptor)
735 open(CGISCRIPTOR, "<CGIscriptor.pl")
736 || dieHandler(1, "<CGIscriptor.pl not slimmed down: $!\n");
737 my $SKIPtext = 0;
738 my $SKIPComments = 0;
740 while(<CGISCRIPTOR>)
742 my $SKIPline = 0;
744 ++$LineCount;
746 # Start of SKIP text
747 $SKIPtext = 1 if /[\>]{10}Start Remove/;
748 $SKIPComments = 1 if $SKIPtext == 1;
750 # Skip this line?
751 $SKIPline = 1 if $SKIPtext || ($SKIPComments && /^\s*\#/);
753 ++$PrintCount unless $SKIPline;
755 print STDOUT $_ unless $SKIPline;
757 # End of SKIP text ?
758 $SKIPtext = 0 if /[\<]{10}End Remove/;
760 # Ready!
761 print STDERR "\# Printed $PrintCount out of $LineCount lines\n";
762 exit;
765 #######################################################
767 if(grep(/\-\-help/i, @ARGV))
769 print << 'ENDOFHELPTEXT';
771 # HYPE
773 # CGIscriptor merges plain ASCII HTML files transparantly and safely
774 # with CGI variables, in-line PERL code, shell commands, and executable
775 # scripts in many languages (on-line and real-time). It combines the
776 # "ease of use" of HTML files with the versatillity of specialized
777 # scripts and PERL programs. It hides all the specifics and
778 # idiosyncrasies of correct output and CGI coding and naming. Scripts
779 # do not have to be aware of HTML, HTTP, or CGI conventions just as HTML
780 # files can be ignorant of scripts and the associated values. CGIscriptor
781 # complies with the W3C HTML 4.0 recommendations.
782 # In addition to its use as a WWW embeded CGI processor, it can
783 # be used as a command-line document preprocessor (text-filter).
785 # THIS IS HOW IT WORKS
787 # The aim of CGIscriptor is to execute "plain" scripts inside a text file
788 # using any required CGIparameters and environment variables. It
789 # is optimized to transparantly process HTML files inside a WWW server.
790 # The native language is Perl, but many other scripting languages
791 # can be used.
793 # CGIscriptor reads text files from the requested input file (i.e., from
794 # $YOUR_HTML_FILES$PATH_INFO) and writes them to <STDOUT> (i.e., the
795 # client requesting the service) preceded by the obligatory
796 # "Content-type: text/html\n\n" or "Content-type: text/plain\n\n" string
797 # (except for "raw" files which supply their own Content-type message
798 # and only if the SERVER_PROTOCOL supports HTTP, MAIL, or MIME).
800 # When CGIscriptor encounters an embedded script, indicated by an HTML4 tag
802 # <SCRIPT TYPE="text/ssperl" [CGI="$VAR='default value'"] [SRC="ScriptSource"]>
803 # PERL script
804 # </SCRIPT>
806 # or
808 # <SCRIPT TYPE="text/osshell" [CGI="$name='default value'"] [SRC="ScriptSource"]>
809 # OS Shell script
810 # </SCRIPT>
812 # construct (anything between []-brackets is optional, other MIME-types
813 # and scripting languages are supported), the embedded script is removed
814 # and both the contents of the source file (i.e., "do 'ScriptSource'")
815 # AND the script are evaluated as a PERL program (i.e., by eval()),
816 # shell script (i.e., by a "safe" version of `Command`, qx) or an external
817 # interpreter. The output of the eval() function takes the place of the
818 # original <SCRIPT></SCRIPT> construct in the output string. Any CGI
819 # parameters declared by the CGI attribute are available as simple perl
820 # variables, and can subsequently be made available as variables to other
821 # scripting languages (e.g., bash, python, or lisp).
823 # Example: printing "Hello World"
824 # <HTML><HEAD><TITLE>Hello World</TITLE>
825 # <BODY>
826 # <H1><SCRIPT TYPE="text/ssperl">"Hello World"</SCRIPT></H1>
827 # </BODY></HTML>
829 # Save this in a file, hello.html, in the directory you indicated with
830 # $YOUR_HTML_FILES and access http://your_server/SHTML/hello.html
831 # (or to whatever name you use as an alias for CGIscriptor.pl).
832 # This is realy ALL you need to do to get going.
834 # You can use any values that are delivered in CGI-compliant form (i.e.,
835 # the "?name=value" type URL additions) transparently as "$name" variables
836 # in your scripts IFF you have declared them in the CGI attribute of
837 # a META or SCRIPT tag before e.g.:
838 # <META CONTENT="text/ssperl; CGI='$name = `default value`'
839 # [SRC='ScriptSource']">
840 # or
841 # <SCRIPT TYPE="text/ssperl" CGI="$name = 'default value'"
842 # [SRC='ScriptSource']>
843 # After such a 'CGI' attribute, you can use $name as an ordinary PERL variable
844 # (the ScriptSource file is immediately evaluated with "do 'ScriptSource'").
845 # The CGIscriptor script allows you to write ordinary HTML files which will
846 # include dynamic CGI aware (run time) features, such as on-line answers
847 # to specific CGI requests, queries, or the results of calculations.
849 # For example, if you wanted to answer questions of clients, you could write
850 # a Perl program called "Answer.pl" with a function "AnswerQuestion()"
851 # that prints out the answer to requests given as arguments. You then write
852 # an HTML page "Respond.html" containing the following fragment:
854 # <center>
855 # The Answer to your question
856 # <META CONTENT="text/ssperl; CGI='$Question'">
857 # <h3><SCRIPT TYPE="text/ssperl">$Question</SCRIPT></h3>
858 # is
859 # <h3><SCRIPT TYPE="text/ssperl" SRC="./PATH/Answer.pl">
860 # AnswerQuestion($Question);
861 # </SCRIPT></h3>
862 # </center>
863 # <FORM ACTION=Respond.html METHOD=GET>
864 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
865 # <INPUT TYPE=SUBMIT VALUE="Ask">
866 # </FORM>
868 # The output could look like the following (in HTML-speak):
870 # <CENTER>
871 # The Answer to your question
872 # <h3>What is the capital of the Netherlands?</h3>
873 # is
874 # <h3>Amsterdam</h3>
875 # </CENTER>
876 # <FORM ACTION=Respond.html METHOD=GET>
877 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
878 # <INPUT TYPE=SUBMIT VALUE="Ask">
880 # Note that the function "Answer.pl" does know nothing about CGI or HTML,
881 # it just prints out answers to arguments. Likewise, the text has no
882 # provisions for scripts or CGI like constructs. Also, it is completely
883 # trivial to extend this "program" to use the "Answer" later in the page
884 # to call up other information or pictures/sounds. The final text never
885 # shows any cue as to what the original "source" looked like, i.e.,
886 # where you store your scripts and how they are called.
888 # There are some extra's. The argument of the files called in a SRC= tag
889 # can access the CGI variables declared in the preceding META tag from
890 # the @ARGV array. Executable files are called as:
891 # `file '$ARGV[0]' ... ` (e.g., `Answer.pl \'$Question\'`;)
892 # The files called from SRC can even be (CGIscriptor) html files which are
893 # processed in-line. Furthermore, the SRC= tag can contain a perl block
894 # that is evaluated. That is,
895 # <META CONTENT="text/ssperl; CGI='$Question' SRC='{$Question}'">
896 # will result in the evaluation of "print do {$Question};" and the VALUE
897 # of $Question will be printed. Note that these "SRC-blocks" can be
898 # preceded and followed by other file names, but only a single block is
899 # allowed in a SRC= tag.
901 # One of the major hassles of dynamic WWW pages is the fact that several
902 # mutually incompatible browsers and platforms must be supported. For example,
903 # the way sound is played automatically is different for Netscape and
904 # Internet Explorer, and for each browser it is different again on
905 # Unix, MacOS, and Windows. Realy dangerous is processing user-supplied
906 # (form-) values to construct email addresses, file names, or database
907 # queries. All Apache WWW-server exploits reported in the media are
908 # based on faulty CGI-scripts that didn't check their user-data properly.
910 # There is no panacee for these problems, but a lot of work and problems
911 # can be saved by allowing easy and transparent control over which
912 # <SCRIPT></SCRIPT> blocks are executed on what CGI-data. CGIscriptor
913 # supplies such a method in the form of a pair of attributes:
914 # IF='...condition..' and UNLESS='...condition...'. When added to a
915 # script tag, the whole block (including the SRC attribute) will be
916 # ignored if the condition is false (IF) or true (UNLESS).
917 # For example, the following block will NOT be evaluated if the value
918 # of the CGI variable FILENAME is NOT a valid filename:
920 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
921 # IF='CGIscriptor::CGIsafeFileName($FILENAME)'>
922 # .....
923 # </SCRIPT>
925 # (the function CGIsafeFileName(String) returns an empty string ("")
926 # if the String argument is not a valid filename).
927 # The UNLESS attribute is the mirror image of IF.
929 # A user manual follows the HTML 4 and security paragraphs below.
931 ##########################################################################
933 # HTML 4 compliance
935 # In general, CGIscriptor.pl complies with the HTML 4 recommendations of
936 # the W3C. This means that any software to manage Web sites will be able
937 # to handle CGIscriptor files, as will web agents.
939 # All script code should be placed between <SCRIPT></SCRIPT> tags, the
940 # script type is indicated with TYPE="mime-type", the LANGUAGE
941 # feature is ignored, and a SRC feature is implemented. All CGI specific
942 # features are delegated to the CGI attribute.
944 # However, the behavior deviates from the W3C recommendations at some
945 # points. Most notably:
946 # 0- The scripts are executed at the server side, invissible to the
947 # client (i.e., the browser)
948 # 1- The mime-types are personal and idiosyncratic, but can be adapted.
949 # 2- Code in the body of a <SCRIPT></SCRIPT> tag-pair is still evaluated
950 # when a SRC feature is present.
951 # 3- The SRC attribute reads a list of files.
952 # 4- The files in a SRC attribute are processed according to file type.
953 # 5- The SRC attribute evaluates inline Perl code.
954 # 6- Processed META, DIV, INS tags are removed from the output
955 # document.
956 # 7- All attributes of the processed META tags, except CONTENT, are ignored
957 # (i.e., deleted from the output).
958 # 8- META tags can be placed ANYWHERE in the document.
959 # 9- Through the SRC feature, META tags can have visible output in the
960 # document.
961 # 10- The CGI attribute that declares CGI parameters, can be used
962 # inside the <SCRIPT> tag.
963 # 11- Use of an extended quote set, i.e., '', "", ``, (), {}, []
964 # and their \-slashed combinations: \'\', \"\", \`\`, \(\),
965 # \{\}, \[\].
966 # 12- IF and UNLESS attributes to <SCRIPT>, <META>, <DIV>, <INS> tags.
967 # 13- <DIV> tags cannot be nested, DIV tags are not
968 # rendered with new-lines.
969 # 14- The XML style <TAG .... /> is recognized and handled correctly.
970 # (i.e., no content is processed)
972 # The reasons for these choices are:
973 # You can still write completely HTML4 compliant documents. CGIscriptor
974 # will not force you to write "deviant" code. However, it allows you to
975 # do so (which is, in fact, just as bad). The prime design principle
976 # was to allow users to include plain Perl code. The code itself should
977 # be "enhancement free". Therefore, extra features were needed to
978 # supply easy access to CGI and Web site components. For security
979 # reasons these have to be declared explicitly. The SRC feature
980 # transparently manages access to external files, especially the safe
981 # use of executable files.
982 # The CGI attribute handles the declarations of external (CGI) variables
983 # in the SCRIPT and META tag's.
984 # EVERYTHING THE CGI ATTRIBUTE AND THE META TAG DO CAN BE DONE INSIDE
985 # A <SCRIPT></SCRIPT> TAG CONSTRUCT.
987 # The reason for the IF, UNLESS, and SRC attributes (and their Perl code
988 # evaluation) were build into the META and SCRIPT tags is part laziness,
989 # part security. The SRC blocks allows more compact documents and easier
990 # debugging. The values of the CGI variables can be immediately screened
991 # for security by IF or UNLESS conditions, and even SRC attributes (e.g.,
992 # email addresses and file names), and a few commands can be called
993 # without having to add another Perl TAG pair. This is especially important
994 # for documents that require the use of other (more restricted) "scripting"
995 # languages and facilities that lag transparent control structures.
997 ##########################################################################
999 # SECURITY
1001 # Your WWW site is a few keystrokes away from a few hundred million internet
1002 # users. A fair percentage of these users knows more about your computer
1003 # than you do. And some of these just might have bad intentions.
1005 # To ensure uncompromized operation of your server and platform, several
1006 # features are incorporated in CGIscriptor.pl to enhance security.
1007 # First of all, you should check the source of this program. No security
1008 # measures will help you when you download programs from anonymous sources.
1009 # If you want to use THIS file, please make sure that it is uncompromized.
1010 # The best way to do this is to contact the source and try to determine
1011 # whether s/he is reliable (and accountable).
1013 # BE AWARE THAT ANY PROGRAMMER CAN CHANGE THIS PROGRAM IN SUCH A WAY THAT
1014 # IT WILL SET THE DOORS TO YOUR SYSTEM WIDE OPEN
1016 # I would like to ask any user who finds bugs that could compromise
1017 # security to report them to me (and any other bug too,
1018 # Email: R.J.J.H.vanSon@gmail.com or ifa@hum.uva.nl).
1020 # Security features
1022 # 1 Invisibility
1023 # The inner workings of the HTML source files are completely hidden
1024 # from the client. Only the HTTP header and the ever changing content
1025 # of the output distinguish it from the output of a plain, fixed HTML
1026 # file. Names, structures, and arguments of the "embedded" scripts
1027 # are invisible to the client. Error output is suppressed except
1028 # during debugging (user configurable).
1030 # 2 Separate directory trees
1031 # Directories containing Inline text and script files can reside on
1032 # separate trees, distinct from those of the HTTP server. This means
1033 # that NEITHER the text files, NOR the script files can be read by
1034 # clients other than through CGIscriptor.pl, UNLESS they are
1035 # EXPLICITELY made available.
1037 # 3 Requests are NEVER "evaluated"
1038 # All client supplied values are used as literal values (''-quoted).
1039 # Client supplied ''-quotes are ALWAYS removed. Therefore, as long as the
1040 # embedded scripts do NOT themselves evaluate these values, clients CANNOT
1041 # supply executable commands. Be sure to AVOID scripts like:
1043 # <META CONTENT="text/ssperl; CGI='$UserValue'">
1044 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 $UserValue`;</SCRIPT>
1046 # These are a recipe for disaster. However, the following quoted
1047 # form should be save (but is still not adviced):
1049 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 \'$UserValue\'`;</SCRIPT>
1051 # A special function, SAFEqx(), will automatically do exactly this,
1052 # e.g., SAFEqx('ls -1 $UserValue') will execute `ls -1 \'$UserValue\'`
1053 # with $UserValue interpolated. I recommend to use SAFEqx() instead
1054 # of backticks whenever you can. The OS shell scripts inside
1056 # <SCRIPT TYPE="text/osshell">ls -1 $UserValue</SCRIPT>
1058 # are handeld by SAFEqx and automatically ''-quoted.
1060 # 4 Logging of requests
1061 # All requests can be logged separate from the Host server. The level of
1062 # detail is user configurable: Including or excluding the actual queries.
1063 # This allows for the inspection of (im-) proper use.
1065 # 5 Access control: Clients
1066 # The Remote addresses can be checked against a list of authorized
1067 # (i.e., accepted) or non-authorized (i.e., rejected) clients. Both
1068 # REMOTE_HOST and REMOTE_ADDR are tested so clients without a proper
1069 # HOST name can be (in-) excluded by their IP-address. Client patterns
1070 # containing all numbers and dots are considered IP-addresses, all others
1071 # domain names. No wild-cards or regexp's are allowed, only partial
1072 # addresses.
1073 # Matching of names is done from the back to the front (domain first,
1074 # i.e., $REMOTE_HOST =~ /\Q$pattern\E$/is), so including ".edu" will
1075 # accept or reject all clients from the domain EDU. Matching of
1076 # IP-addresses is done from the front to the back (domain first, i.e.,
1077 # $REMOTE_ADDR =~ /^\Q$pattern\E/is), so including "128." will (in-)
1078 # exclude all clients whose IP-address starts with 128.
1079 # There are two special symbols: "-" matches HOSTs with no name and "*"
1080 # matches ALL HOSTS/clients.
1081 # For those needing more expressional power, lines starting with
1082 # "-e" are evaluated by the perl eval() function. E.g.,
1083 # '-e $REMOTE_HOST =~ /\.edu$/is;' will accept/reject clients from the
1084 # domain '.edu'.
1086 # 6 Access control: Files
1087 # In principle, CGIscriptor could read ANY file in the directory
1088 # tree as discussed in 1. However, for security reasons this is
1089 # restricted to text files. It can be made more restricted by entering
1090 # a global file pattern (e.g., ".html"). This is done by default.
1091 # For each client requesting access, the file pattern(s) can be made
1092 # more restrictive than the global pattern by entering client specific
1093 # file patterns in the Access Control files (see 5).
1094 # For example: if the ACCEPT file contained the lines
1095 # * DEMO
1096 # .hum.uva.nl LET
1097 # 145.18.230.
1098 # Then all clients could request paths containing "DEMO" or "demo", e.g.
1099 # "/my/demo/file.html" ($PATH_INFO =~ /\Q$pattern\E/), Clients from
1100 # *.hum.uva.nl could also request paths containing "LET or "let", e.g.
1101 # "/my/let/file.html", and clients from the local cluster
1102 # 145.18.230.[0-9]+ could access ALL files.
1103 # Again, for those needing more expressional power, lines starting with
1104 # "-e" are evaluated. For instance:
1105 # '-e $REMOTE_HOST =~ /\.edu$/is && $PATH_INFO =~ m@/DEMO/@is;'
1106 # will accept/reject requests for files from the directory "/demo/" from
1107 # clients from the domain '.edu'.
1109 # 7 Access control: Server side session tickets
1110 # Specific paths can be controlled by Session Tickets which must be
1111 # present as a SESSIONTICKET=<value> CGI variable in the request. These paths
1112 # are defined in %TicketRequiredPatterns as pairs of:
1113 # ('regexp' => 'SessionPath\tPasswordPath\tLogin.html\tExpiration').
1114 # Session Tickets are stored in a separate directory (SessionPath, e.g.,
1115 # "Private/.Session") as files with the exact same name of the SESSIONTICKET
1116 # CGI. The following is an example:
1117 # Type: SESSION
1118 # IPaddress: 127.0.0.1
1119 # AllowedPaths: ^/Private/Name/
1120 # Expires: 3600
1121 # Username: test
1122 # ...
1123 # Other content can follow.
1125 # It is adviced that Session Tickets should be deleted
1126 # after some (idle) time. The IP address should be the IP number at login, and
1127 # the SESSIONTICKET will be rejected if it is presented from another IP address.
1128 # AllowedPaths and DeniedPaths are perl regexps. Be careful how they match. Make sure to delimit
1129 # the names to prevent access to overlapping names, eg, "^/Private/Rob" will also
1130 # match "^/Private/Robert", however, "^/Private/Rob/" will not. Expires is the
1131 # time the ticket will remain valid after creation (file ctime). Time can be given
1132 # in s[econds] (default), m[inutes], h[hours], or d[ays], eg, "24h" means 24 hours.
1133 # None of these need be present, but the Ticket must have a non-zero size.
1135 # Next to Session Tickets, there are two other type of ticket files:
1136 # - LOGIN tickets store information about a current login request
1137 # - PASSWORD ticket store account information to authorize login requests
1139 # 8 Query length limiting
1140 # The length of the Query string can be limited. If CONTENT_LENGTH is larger
1141 # than this limit, the request is rejected. The combined length of the
1142 # Query string and the POST input is checked before any processing is done.
1143 # This will prevent clients from overloading the scripts.
1144 # The actual, combined, Query Size is accessible as a variable through
1145 # $CGI_Content_Length.
1147 # 9 Illegal filenames, paths, and protected directories
1148 # One of the primary security concerns in handling CGI-scripts is the
1149 # use of "funny" characters in the requests that con scripts in executing
1150 # malicious commands. Examples are inserting ';', null bytes, or <newline>
1151 # characters in URL's and filenames, followed by executable commands. A
1152 # special variable $FileAllowedChars stores a string of all allowed
1153 # characters. Any request that translates to a filename with a character
1154 # OUTSIDE this set will be rejected.
1155 # In general, all (readable files) in the DocumentRoot tree are accessible.
1156 # This might not be what you want. For instance, your DocumentRoot directory
1157 # might be the working directory of a CVS project and contain sensitive
1158 # information (e.g., the password to get to the repository). You can block
1159 # access to these subdirectories by adding the corresponding patterns to
1160 # the $BlockPathAccess variable. For instance, $BlockPathAccess = '/CVS/'
1161 # will block any request that contains '/CVS/' or:
1162 # die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;
1164 #10 The execution of code blocks can be controlled in a transparent way
1165 # by adding IF or UNLESS conditions in the tags themselves. That is,
1166 # a simple check of the validity of filenames or email addresses can
1167 # be done before any code is executed.
1169 ###############################################################################
1171 # USER MANUAL (sort of)
1173 # CGIscriptor removes embedded scripts, indicated by an HTML 4 type
1174 # <SCRIPT TYPE='text/ssperl'> </SCRIPT> or <SCRIPT TYPE='text/osshell'>
1175 # </SCRIPT> constructs. CGIscriptor also recognizes XML-type
1176 # <SCRIPT TYPE='text/ssperl'/> constructs. These are usefull when
1177 # the necessary code is already available in the TAG itself (e.g.,
1178 # using external files). The contents of the directive are executed by
1179 # the PERL eval() and `` functions (in a separate name space). The
1180 # result of the eval() function replaces the <SCRIPT> </SCRIPT> construct
1181 # in the output file. You can use the values that are delivered in
1182 # CGI-compliant form (i.e., the "?name=value&.." type URL additions)
1183 # transparently as "$name" variables in your directives after they are
1184 # defined in a <META> or <SCRIPT> tag.
1185 # If you define the variable "$CGIscriptorResults" in a CGI attribute, all
1186 # subsequent <SCRIPT> and <META> results (including the defining
1187 # tag) will also be pushed onto a stack: @CGIscriptorResults. This list
1188 # behaves like any other, ordinary list and can be manipulated.
1190 # Both GET and POST requests are accepted. These two methods are treated
1191 # equal. Variables, i.e., those values that are determined when a file is
1192 # processed, are indicated in the CGI attribute by $<name> or $<name>=<default>
1193 # in which <name> is the name of the variable and <default> is the value
1194 # used when there is NO current CGI value for <name> (you can use
1195 # white-spaces in $<name>=<default> but really DO make sure that the
1196 # default value is followed by white space or is quoted). Names can contain
1197 # any alphanumeric characters and _ (i.e., names match /[\w]+/).
1198 # If the Content-type: is 'multipart/*', the input is treated as a
1199 # MIME multipart message and automatically delimited. CGI variables get
1200 # the "raw" (i.e., undecoded) body of the corresponding message part.
1202 # Variables can be CGI variables, i.e., those from the QUERY_STRING,
1203 # environment variables, e.g., REMOTE_USER, REMOTE_HOST, or REMOTE_ADDR,
1204 # or predefined values, e.g., CGI_Decoded_QS (The complete, decoded,
1205 # query string), CGI_Content_Length (the length of the decoded query
1206 # string), CGI_Year, CGI_Month, CGI_Time, and CGI_Hour (the current
1207 # date and time).
1209 # All these are available when defined in a CGI attribute. All environment
1210 # variables are accessible as $ENV{'name'}. So, to access the REMOTE_HOST
1211 # and the REMOTE_USER, use, e.g.:
1213 # <SCRIPT TYPE='text/ssperl'>
1214 # ($ENV{'REMOTE_HOST'}||"-")." $ENV{'REMOTE_USER'}"
1215 # </SCRIPT>
1217 # (This will print a "-" if REMOTE_HOST is not known)
1218 # Another way to do this is:
1220 # <META CONTENT="text/ssperl; CGI='$REMOTE_HOST = - $REMOTE_USER'">
1221 # <SCRIPT TYPE='text/ssperl'>"$REMOTE_HOST $REMOTE_USER"</SCRIPT>
1222 # or
1223 # <META CONTENT='text/ssperl; CGI="$REMOTE_HOST = - $REMOTE_USER"
1224 # SRC={"$REMOTE_HOST $REMOTE_USER\n"}'>
1226 # This is possible because ALL environment variables are available as
1227 # CGI variables. The environment variables take precedence over CGI
1228 # names in case of a "name clash". For instance:
1229 # <META CONTENT="text/ssperl; CGI='$HOME' SRC={$HOME}">
1230 # Will print the current HOME directory (environment) irrespective whether
1231 # there is a CGI variable from the query
1232 # (e.g., Where do you live? <INPUT TYPE="TEXT" NAME="HOME">)
1233 # THIS IS A SECURITY FEATURE. It prevents clients from changing
1234 # the values of defined environment variables (e.g., by supplying
1235 # a bogus $REMOTE_ADDR). Although $ENV{} is not changed by the META tags,
1236 # it would make the use of declared variables insecure. You can still
1237 # access CGI variables after a name clash with
1238 # CGIscriptor::CGIparseValue(<name>).
1240 # Some CGI variables are present several times in the query string
1241 # (e.g., from multiple selections). These should be defined as
1242 # @VARIABLENAME=default in the CGI attribute. The list @VARIABLENAME
1243 # will contain ALL VARIABLENAME values from the query, or a single
1244 # default value. If there is an ENVIRONMENT variable of the
1245 # same name, it will be used instead of the default AND the query
1246 # values. The corresponding function is
1247 # CGIscriptor::CGIparseValueList(<name>)
1249 # CGI variables collected in a @VARIABLENAME list are unordered.
1250 # When more structured variables are needed, a hash table can be used.
1251 # A variable defined as %VARIABLE=default will collect all
1252 # CGI-parameters whose name start with 'VARIABLE' in a hash table with
1253 # the remainder of the name as a key. For instance, %PERSON will
1254 # collect PERSONname='John Doe', PERSONbirthdate='01 Jan 00', and
1255 # PERSONspouse='Alice' into a hash table %PERSON such that $PERSON{'spouse'}
1256 # equals 'Alice'. Any default value or environment value will be stored
1257 # under the "" key. If there is an ENVIRONMENT variable of the same name,
1258 # it will be used instead of the default AND the query values. The
1259 # corresponding function is CGIscriptor::CGIparseValueHash(<name>)
1261 # This method of first declaring your environment and CGI variables
1262 # before being able to use them in the scripts might seem somewhat
1263 # clumsy, but it protects you from inadvertedly printing out the values of
1264 # system environment variables when their names coincide with those used
1265 # in the CGI forms. It also prevents "clients" from supplying CGI
1266 # parameter values for your private variables.
1267 # THIS IS A SECURITY FEATURE!
1270 # NON-HTML CONTENT TYPES
1272 # Normally, CGIscriptor prints the standard "Content-type: text/html\n\n"
1273 # message before anything is printed. This has been extended to include
1274 # plain text (.txt) files, for which the Content-type (MIME type)
1275 # 'text/plain' is printed. In all other respects, text files are treated
1276 # as HTML files (this can be switched off by removing '.txt' from the
1277 # $FilePattern variable) . When the content type should be something else,
1278 # e.g., with multipart files, use the $RawFilePattern (.xmr, see also next
1279 # item). CGIscriptor will not print a Content-type message for this file
1280 # type (which must supply its OWN Content-type message). Raw files must
1281 # still conform to the <SCRIPT></SCRIPT> and <META> tag specifications.
1284 # NON-HTML FILES
1286 # CGIscriptor is intended to process HTML and text files only. You can
1287 # create documents of any mime-type on-the-fly using "raw" text files,
1288 # e.g., with the .xmr extension. However, CGIscriptor will not process
1289 # binary files of any type, e.g., pictures or sounds. Given the sheer
1290 # number of formats, I do not have any intention to do so. However,
1291 # an escape route has been provided. You can construct a genuine raw
1292 # (.xmr) text file that contains the perl code to service any file type
1293 # you want. If the global $BinaryMapFile variable contains the path to
1294 # this file (e.g., /BinaryMapFile.xmr), this file will be called
1295 # whenever an unsupported (non-HTML) file type is requested. The path
1296 # to the requested binary file is stored in $ENV('CGI_BINARY_FILE')
1297 # and can be used like any other CGI-variable. Servicing binary files
1298 # then becomes supplying the correct Content-type (e.g., print
1299 # "Content-type: image/jpeg\n\n";) and reading the file and writing it
1300 # to STDOUT (e.g., using sysread() and syswrite()).
1303 # THE META TAG
1305 # All attributes of a META tag are ignored, except the
1306 # CONTENT='text/ssperl; CGI=" ... " [SRC=" ... "]' attribute. The string
1307 # inside the quotes following the CONTENT= indication (white-space is
1308 # ignored, "" '' `` (){}[]-quote pairs are allowed, plus their \ versions)
1309 # MUST start with any of the CGIscriptor mime-types (e.g.: text/ssperl or
1310 # text/osshell) and a comma or semicolon.
1311 # The quoted string following CGI= contains a white-space separated list
1312 # of declarations of the CGI (and Environment) values and default values
1313 # used when no CGI values are supplied by the query string.
1315 # If the default value is a longer string containing special characters,
1316 # possibly spanning several lines, the string must be enclosed in quotes.
1317 # You may use any pair of quotes or brackets from the list '', "", ``, (),
1318 # [], or {} to distinguish default values (or preceded by \, e.g., \(...\)
1319 # is different from (...)). The outermost pair will always be used and any
1320 # other quotes inside the string are considered to be part of the string
1321 # value, e.g.,
1323 # $Value = {['this'
1324 # "and" (this)]}
1325 # will result in $Value getting the default value: ['this'
1326 # "and" (this)]
1327 # (NOTE that the newline is part of the default value!).
1329 # Internally, for defining and initializing CGI (ENV) values, the META
1330 # and SCRIPT tags use the functions "defineCGIvariable($name, $default)"
1331 # (scalars) and "defineCGIvariableList($name, $default)" (lists).
1332 # These functions can be used inside scripts as
1333 # "CGIscriptor::defineCGIvariable($name, $default)" and
1334 # "CGIscriptor::defineCGIvariableList($name, $default)".
1335 # "CGIscriptor::defineCGIvariableHash($name, $default)".
1337 # The CGI attribute will be processed exactly identical when used inside
1338 # the <SCRIPT> tag. However, this use is not according to the
1339 # HTML 4.0 specifications of the W3C.
1342 # THE DIV/INS TAGS
1344 # There is a problem when constructing html files containing
1345 # server-side perl scripts with standard HTML tools. These
1346 # tools will refuse to process any text between <SCRIPT></SCRIPT>
1347 # tags. This is quite annoying when you want to use large
1348 # HTML templates where you will fill in values.
1350 # For this purpose, CGIscriptor will read the neutral
1351 # <DIV CLASS="ssperl" ID="varname"></DIV> or
1352 # <INS CLASS="ssperl" ID="varname"></INS>
1353 # tag (in Cascading Style Sheet manner) Note that
1354 # "varname" has NO '$' before it, it is a bare name.
1355 # Any text between these <DIV ...></DIV> or
1356 # <INS ...></INS>tags will be assigned to '$varname'
1357 # as is (e.g., as a literal).
1358 # No processing or interpolation will be performed.
1359 # There is also NO nesting possible. Do NOT nest a
1360 # </DIV> inside a <DIV></DIV>! Moreover, neither INS nor
1361 # DIV tags do ensure a block structure in the final
1362 # rendering (i.e., no empty lines).
1364 # Note that <DIV CLASS="ssperl" ID="varname"/>
1365 # is handled the XML way. No content is processed,
1366 # but varname is defined, and any SRC directives are
1367 # processed.
1369 # You can use $varname like any other variable name.
1370 # However, $varname is NOT a CGI variable and will be
1371 # completely internal to your script. There is NO
1372 # interaction between $varname and the outside world.
1374 # To interpolate a DIV derived text, you can use:
1375 # $varname =~ s/([\]])/\\\1/g; # Mark ']'-quotes
1376 # $varname = eval("qq[$varname]"); # Interpolate all values
1378 # The DIV tags will process IF, UNLESS, CGI and
1379 # SRC attributes. The SRC files will be pre-pended to the
1380 # body text of the tag. SRC blocks are NOT executed.
1382 # CONDITIONAL PROCESSING: THE 'IF' AND 'UNLESS' ATTRIBUTES
1384 # It is often necessary to include code-blocks that should be executed
1385 # conditionally, e.g., only for certain browsers or operating system.
1386 # Furthermore, quite often sanity and security checks are necessary
1387 # before user (form) data can be processed, e.g., with respect to
1388 # email addresses and filenames.
1390 # Checks added to the code are often difficult to find, interpret or
1391 # maintain and in general mess up the code flow. This kind of confussion
1392 # is dangerous.
1393 # Also, for many of the supported "foreign" scripting languages, adding
1394 # these checks is cumbersome or even impossible.
1396 # As a uniform method for asserting the correctness of "context", two
1397 # attributes are added to all supported tags: IF and UNLESS.
1398 # They both evaluate their value and block execution when the
1399 # result is <FALSE> (IF) or <TRUE> (UNLESS) in Perl, e.g.,
1400 # UNLESS='$NUMBER \> 100;' blocks execution if $NUMBER <= 100. Note that
1401 # the backslash in the '\>' is removed and only used to differentiate
1402 # this conditional '>' from the tag-closing '>'. For symmetry, the
1403 # backslash in '\<' is also removed. Inside these conditionals,
1404 # ~/ and ./ are expanded to their respective directory root paths.
1406 # For example, the following tag will be ignored when the filename is
1407 # invalid:
1409 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
1410 # IF='CGIscriptor::CGIsafeFileName($FILENAME);'>
1411 # ...
1412 # </SCRIPT>
1414 # The IF and UNLESS values must be quoted. The same quotes are supported
1415 # as with the other attributes. The SRC attribute is ignored when IF and
1416 # UNLESS block execution.
1418 # NOTE: 'IF' and 'UNLESS' always evaluate perl code.
1421 # THE MAGIC SOURCE ATTRIBUTE (SRC=)
1423 # The SRC attribute inside tags accepts a list of filenames and URL's
1424 # separated by "," comma's (or ";" semicolons).
1425 # ALL the variable values defined in the CGI attribute are available
1426 # in @ARGV as if the file or block was executed from the command line,
1427 # in the exact order in which they were declared in the preceding CGI
1428 # attribute.
1430 # First, a SRC={}-block will be evaluated as if the code inside the
1431 # block was part of a <SCRIPT></SCRIPT> construct, i.e.,
1432 # "print do { code };'';" or `code` (i.e., SAFEqx('code)).
1433 # Only a single block is evaluated. Note that this is processed less
1434 # efficiently than <SCRIPT> </SCRIPT> blocks. Type of evaluation
1435 # depends on the content-type: Perl for text/ssperl and OS shell for
1436 # text/osshell. For other mime types (scripting languages), anything in
1437 # the source block is put in front of the code block "inside" the tag.
1439 # Second, executable files (i.e., -x filename != 0) are evaluated as:
1440 # print `filename \'$ARGV[0]\' \'$ARGV[1]\' ...`
1441 # That is, you can actually call executables savely from the SRC tag.
1443 # Third, text files that match the file pattern, used by CGIscriptor to
1444 # check whether files should be processed ($FilePattern), are
1445 # processed in-line (i.e., recursively) by CGIscriptor as if the code
1446 # was inserted in the original source file. Recursions, i.e., calling
1447 # a file inside itself, are blocked. If you need them, you have to code
1448 # them explicitely using "main::ProcessFile($file_path)".
1450 # Fourth, Perl text files (i.e., -T filename != 0) are evaluated as:
1451 # "do FileName;'';".
1453 # Last, URL's (i.e., starting with 'HTTP://', 'FTP://', 'GOPHER://',
1454 # 'TELNET://', 'WHOIS://' etc.) are loaded
1455 # and printed. The loading and handling of <BASE> and document header
1456 # is done by a command generated by main::GET_URL($URL [, 0]). You can enter your
1457 # own code (default is curl, wget, or snarf and some post-processing to add a <BASE> tag).
1459 # There are two pseudo-file names: PREFIX and POSTFIX. These implement
1460 # a switch from prefixing the SRC code/files (PREFIX, default) before the
1461 # content of the tag to appending the code after the content of the tag
1462 # (POSTFIX). The switches are done in the order in which the PREFIX and
1463 # POSTFIX labels are encountered. You can mix PREFIX and POSTFIX labels
1464 # in any order with the SRC files. Note that the ORDER of file execution
1465 # is determined for prefixed and postfixed files seperately.
1467 # File paths can be preceded by the URL protocol prefix "file://". This
1468 # is simply STRIPPED from the name.
1470 # Example:
1471 # The request
1472 # "http://cgi-bin/Action_Forms.pl/Statistics/Sign_Test.html?positive=8&negative=22
1473 # will result in printing "${SS_PUB}/Statistics/Sign_Test.html"
1474 # With QUERY_STRING = "positive=8&negative=22"
1476 # on encountering the lines:
1477 # <META CONTENT="text/osshell; CGI='$positive=11 $negative=3'">
1478 # <b><SCRIPT LANGUAGE=PERL TYPE="text/ssperl" SRC="./Statistics/SignTest.pl">
1479 # </SCRIPT></b><p>"
1481 # This line will be processed as:
1482 # "<b>`${SS_SCRIPT}/Statistics/SignTest.pl '8' '22'`</b><p>"
1484 # In which "${SS_SCRIPT}/Statistics/SignTest.pl" is an executable script,
1485 # This line will end up printed as:
1486 # "<b>p <= 0.0161</b><p>"
1488 # Note that the META tag itself will never be printed, and is invisible to
1489 # the outside world.
1491 # The SRC files in a DIV or INS tag will be added (pre-pended) to the body
1492 # of the <DIV></DIV> tag. Blocks are NOT executed! If you do not
1493 # need any content, you can use the <DIV...../> format.
1496 # THE CGISCRIPTOR ROOT DIRECTORIES ~/ AND ./
1498 # Inside <SCRIPT></SCRIPT> tags, filepaths starting
1499 # with "~/" are replaced by "$YOUR_HTML_FILES/", this way files in the
1500 # public directories can be accessed without direct reference to the
1501 # actual paths. Filepaths starting with "./" are replaced by
1502 # "$YOUR_SCRIPTS/" and this should only be used for scripts.
1504 # Note: this replacement can seriously affect Perl scripts. Watch
1505 # out for constructs like $a =~ s/aap\./noot./g, use
1506 # $a =~ s@aap\.@noot.@g instead.
1508 # CGIscriptor.pl will assign the values of $SS_PUB and $SS_SCRIPT
1509 # (i.e., $YOUR_HTML_FILES and $YOUR_SCRIPTS) to the environment variables
1510 # $SS_PUB and $SS_SCRIPT. These can be accessed by the scripts that are
1511 # executed.
1512 # Values not preceded by $, ~/, or ./ are used as literals
1515 # OS SHELL SCRIPT EVALUATION (CONTENT-TYPE=TEXT/OSSHELL)
1517 # OS scripts are executed by a "safe" version of the `` operator (i.e.,
1518 # SAFEqx(), see also below) and any output is printed. CGIscriptor will
1519 # interpolate the script and replace all user-supplied CGI-variables by
1520 # their ''-quoted values (actually, all variables defined in CGI attributes
1521 # are quoted). Other Perl variables are interpolated in a simple fasion,
1522 # i.e., $scalar by their value, @list by join(' ', @list), and %hash by
1523 # their name=value pairs. Complex references, e.g., @$variable, are all
1524 # evaluated in a scalar context. Quotes should be used with care.
1525 # NOTE: the results of the shell script evaluation will appear in the
1526 # @CGIscriptorResults stack just as any other result.
1527 # All occurrences of $@% that should NOT be interpolated must be
1528 # preceeded by a "\". Interpolation can be switched off completely by
1529 # setting $CGIscriptor::NoShellScriptInterpolation = 1
1530 # (set to 0 or undef to switch interpolation on again)
1531 # i.e.,
1532 # <SCRIPT TYPE="text/ssperl">
1533 # $CGIscriptor::NoShellScriptInterpolation = 1;
1534 # </SCRIPT>
1537 # RUN TIME TRANSLATION OF INPUT FILES
1539 # Allows general and global conversions of files using Regular Expressions.
1540 # Very handy (but costly) to rewrite legacy pages to a new format.
1541 # Select files to use it on with
1542 # my $TranslationPaths = 'filepattern';
1543 # This is costly. For efficiency, define:
1544 # $TranslationPaths = ''; when not using translations.
1545 # Accepts general regular expressions: [$pattern, $replacement]
1547 # Define:
1548 # my $TranslationPaths = 'filepattern'; # Pattern matching PATH_INFO
1550 # push(@TranslationTable, ['pattern', 'replacement']);
1551 # e.g. (for Ruby Rails):
1552 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
1553 # push(@TranslationTable, ['%>', '</SCRIPT>']);
1555 # Runs:
1556 # my $currentRegExp;
1557 # foreach $currentRegExp (@TranslationTable)
1559 # my ($pattern, $replacement) = @$currentRegExp;
1560 # $$text =~ s!$pattern!$replacement!msg;
1561 # };
1564 # EVALUATION OF OTHER SCRIPTING LANGUAGES
1566 # Adding a MIME-type and an interpreter command to
1567 # %ScriptingLanguages automatically will catch any other
1568 # scripting language in the standard
1569 # <SCRIPT TYPE="[mime]"></SCRIPT> manner.
1570 # E.g., adding: $ScriptingLanguages{'text/sspython'} = 'python';
1571 # will actually execute the folowing code in an HTML page
1572 # (ignore 'REMOTE_HOST' for the moment):
1573 # <SCRIPT TYPE="text/sspython">
1574 # # A Python script
1575 # x = ["A","real","python","script","Hello","World","and", REMOTE_HOST]
1576 # print x[4:8] # Prints the list ["Hello","World","and", REMOTE_HOST]
1577 # </SCRIPT>
1579 # The script code is NOT interpolated by perl, EXCEPT for those
1580 # interpreters that cannot handle variables themselves.
1581 # Currently, several interpreters are pre-installed:
1583 # Perl test - "text/testperl" => 'perl',
1584 # Python - "text/sspython" => 'python',
1585 # Ruby - "text/ssruby" => 'ruby',
1586 # Tcl - "text/sstcl" => 'tcl',
1587 # Awk - "text/ssawk" => 'awk -f-',
1588 # Gnu Lisp - "text/sslisp" => 'rep | tail +5 '.
1589 # "| egrep -v '> |^rep. |^nil\\\$'",
1590 # XLispstat - "text/xlispstat" => 'xlispstat | tail +7 '.
1591 # "| egrep -v '> \\\$|^NIL'",
1592 # Gnu Prolog- "text/ssprolog" => 'gprolog',
1593 # M4 macro's- "text/ssm4" => 'm4',
1594 # Born shell- "text/sh" => 'sh',
1595 # Bash - "text/bash" => 'bash',
1596 # C-shell - "text/csh" => 'csh',
1597 # Korn shell- "text/ksh" => 'ksh',
1598 # Praat - "text/sspraat" => "praat - | sed 's/Praat > //g'",
1599 # R - "text/ssr" => "R --vanilla --slave | sed 's/^[\[0-9\]*] //g'",
1600 # REBOL - "text/ssrebol" =>
1601 # "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\s*\[> \]* //g'",
1602 # PostgreSQL- "text/postgresql" => 'psql 2>/dev/null',
1603 # (psql)
1605 # Note that the "value" of $ScriptingLanguages{mime} must be a command
1606 # that reads Standard Input and writes to standard output. Any extra
1607 # output of interactive interpreters (banners, echo's, prompts)
1608 # should be removed by piping the output through 'tail', 'grep',
1609 # 'sed', or even 'awk' or 'perl'.
1611 # For access to CGI variables there is a special hashtable:
1612 # %ScriptingCGIvariables.
1613 # CGI variables can be accessed in three ways.
1614 # 1. If the mime type is not present in %ScriptingCGIvariables,
1615 # nothing is done and the script itself should parse the relevant
1616 # environment variables.
1617 # 2. If the mime type IS present in %ScriptingCGIvariables, but it's
1618 # value is empty, e.g., $ScriptingCGIvariables{"text/sspraat"} = '';,
1619 # the script text is interpolated by perl. That is, all $var, @array,
1620 # %hash, and \-slashes are replaced by their respective values.
1621 # 3. In all other cases, the CGI and environment variables are added
1622 # in front of the script according to the format stored in
1623 # %ScriptingCGIvariables. That is, the following (pseudo-)code is
1624 # executed for each CGI- or Environment variable defined in the CGI-tag:
1625 # printf(INTERPRETER, $ScriptingCGIvariables{$mime}, $CGI_NAME, $CGI_VALUE);
1627 # For instance, "text/testperl" => '$%s = "%s";' defines variable
1628 # definitions for Perl, and "text/sspython" => '%s = "%s"' for Python
1629 # (note that these definitions are not save, the real ones contain '-quotes).
1631 # THIS WILL NOT WORK FOR @VARIABLES, the (empty) $VARIABLES will be used
1632 # instead.
1634 # The $CGI_VALUE parameters are "shrubed" of all control characters
1635 # and quotes (by &shrubCGIparameter($CGI_VALUE)) for the options 2 and 3.
1636 # Control characters are replaced by \0<octal ascii value> (the exception
1637 # is \015, the newline, which is replaced by \n) and quotes
1638 # and backslashes by their HTML character
1639 # value (' -> &#39; ` -> &#96; " -> &quot; \ -> &#92; & -> &amper;).
1640 # For example:
1641 # if a client would supply the string value (in standard perl, e.g.,
1642 # \n means <newline>)
1643 # "/dev/null';\nrm -rf *;\necho '"
1644 # it would be processed as
1645 # '/dev/null&#39;;\nrm -rf *;\necho &#39;'
1646 # (e.g., sh or bash would process the latter more according to your
1647 # intentions).
1648 # If your intepreter requires different protection measures, you will
1649 # have to supply these in %main::SHRUBcharacterTR (string => translation),
1650 # e.g., $SHRUBcharacterTR{"\'"} = "&#39;";
1652 # Currently, the following definitions are used:
1653 # %ScriptingCGIvariables = (
1654 # "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value' (for testing)
1655 # "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
1656 # "text/ssruby" => '@%s = "%s"', # Ruby @VAR = "value"
1657 # "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
1658 # "text/ssawk" => '%s = "%s";', # Awk VAR = "value";
1659 # "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
1660 # "text/xlispstat" => '(setq %s "%s")', # Xlispstat (setq VAR "value")
1661 # "text/ssprolog" => '', # Gnu prolog (interpolated)
1662 # "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
1663 # "text/sh" => "\%s='\%s';", # Born shell VAR='value';
1664 # "text/bash" => "\%s='\%s';", # Born again shell VAR='value';
1665 # "text/csh" => "\$\%s = '\%s';", # C shell $VAR = 'value';
1666 # "text/ksh" => "\$\%s = '\%s';", # Korn shell $VAR = 'value';
1667 # "text/sspraat" => '', # Praat (interpolation)
1668 # "text/ssr" => '%s <- "%s";', # R VAR <- "value";
1669 # "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
1670 # "text/postgresql" => '', # PostgreSQL (interpolation)
1671 # "" => ""
1672 # );
1674 # Four tables allow fine-tuning of interpreter with code that should be
1675 # added before and after each code block:
1677 # Code added before each script block
1678 # %ScriptingPrefix = (
1679 # "text/testperl" => "\# Prefix Code;", # Perl script testing
1680 # "text/ssm4" => 'divert(0)' # M4 macro's (open STDOUT)
1681 # );
1682 # Code added at the end of each script block
1683 # %ScriptingPostfix = (
1684 # "text/testperl" => "\# Postfix Code;", # Perl script testing
1685 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1686 # );
1687 # Initialization code, inserted directly after opening (NEVER interpolated)
1688 # %ScriptingInitialization = (
1689 # "text/testperl" => "\# Initialization Code;", # Perl script testing
1690 # "text/ssawk" => 'BEGIN {', # Server Side awk scripts
1691 # "text/sslisp" => '(prog1 nil ', # Lisp (rep)
1692 # "text/xlispstat" => '(prog1 nil ', # xlispstat
1693 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1694 # );
1695 # Cleanup code, inserted before closing (NEVER interpolated)
1696 # %ScriptingCleanup = (
1697 # "text/testperl" => "\# Cleanup Code;", # Perl script testing
1698 # "text/sspraat" => 'Quit',
1699 # "text/ssawk" => '};', # Server Side awk scripts
1700 # "text/sslisp" => '(princ "\n" standard-output)).' # Closing print to rep
1701 # "text/xlispstat" => '(print "" *standard-output*)).' # Closing print to xlispstat
1702 # "text/postgresql" => '\q',
1703 # );
1706 # The SRC attribute is NOT magical for these interpreters. In short,
1707 # all code inside a source file or {} block is written verbattim
1708 # to the interpreter. No (pre-)processing or executional magic is done.
1710 # A serious shortcomming of the described mechanism for handling other
1711 # (scripting) languages, with respect to standard perl scripts
1712 # (i.e., 'text/ssperl'), is that the code is only executed when
1713 # the pipe to the interpreter is closed. So the pipe has to be
1714 # closed at the end of each block. This means that the state of the
1715 # interpreter (e.g., all variable values) is lost after the closing of
1716 # the next </SCRIPT> tag. The standard 'text/ssperl' scripts retain
1717 # all values and definitions.
1719 # APPLICATION MIME TYPES
1721 # To ease some important auxilliary functions from within the
1722 # html pages I have added them as MIME types. This uses
1723 # the mechanism that is also used for the evaluation of
1724 # other scripting languages, with interpolation of CGI
1725 # parameters (and perl-variables). Actually, these are
1726 # defined exactly like any other "scripting language".
1728 # text/ssdisplay: display some (HTML) text with interpolated
1729 # variables (uses `cat`).
1730 # text/sslogfile: write (append) the interpolated block to the file
1731 # mentioned on the first, non-empty line
1732 # (the filename can be preceded by 'File: ',
1733 # note the space after the ':',
1734 # uses `awk .... >> <filename>`).
1735 # text/ssmailto: send email directly from within the script block.
1736 # The first line of the body must contain
1737 # To:Name@Valid.Email.Address
1738 # (note: NO space between 'To:' and the email adres)
1739 # For other options see the mailto man pages.
1740 # It works by directly sending the (interpolated)
1741 # content of the text block to a pipe into the
1742 # Linux program 'mailto'.
1744 # In these script blocks, all Perl variables will be
1745 # replaced by their values. All CGI variables are cleaned before
1746 # they are used. These CGI variables must be redefined with a
1747 # CGI attribute to restore their original values.
1748 # In general, this will be more secure than constructing
1749 # e.g., your own email command lines. For instance, Mailto will
1750 # not execute any odd (forged) email addres, but just stops
1751 # when the email address is invalid and awk will construct
1752 # any filename you give it (e.g. '<File;rm\\\040-f' would end up
1753 # as a "valid" UNIX filename). Note that it will also gladly
1754 # store this file anywhere (/../../../etc/passwd will work!).
1755 # Use the CGIscriptor::CGIsafeFileName() function to clean the
1756 # filename.
1758 # SHELL SCRIPT PIPING
1760 # If a shell script starts with the UNIX style "#! <shell command> \n"
1761 # line, the rest of the shell script is piped into the indicated command,
1762 # i.e.,
1763 # open(COMMAND, "| command");print COMMAND $RestOfScript;
1765 # In many ways this is equivalent to the MIME-type profiling for
1766 # evaluating other scripting languages as discussed above. The
1767 # difference breaks down to convenience. Shell script piping is a
1768 # "raw" implementation. It allows you to control all aspects of
1769 # execution. Using the MIME-type profiling is easier, but has a
1770 # lot of defaults built in that might get in the way. Another
1771 # difference is that shell script piping uses the SAFEqx() function,
1772 # and MIME-type profiling does not.
1774 # Execution of shell scripts is under the control of the Perl Script blocks
1775 # in the document. The MIME-type triggered execution of <SCRIPT></SCRIPT>
1776 # blocks can be simulated easily. You can switch to a different shell,
1777 # e.g. tcl, completely by executing the following Perl commands inside
1778 # your document:
1780 # <SCRIPT TYPE="text/ssperl">
1781 # $main::ShellScriptContentType = "text/ssTcl"; # Yes, you can do this
1782 # CGIscriptor::RedirectShellScript('/usr/bin/tcl'); # Pipe to Tcl
1783 # $CGIscriptor::NoShellScriptInterpolation = 1;
1784 # </SCRIPT>
1786 # After this script is executed, CGIscriptor will parse scripts of
1787 # TYPE="text/ssTcl" and pipe their contents into '|/usr/bin/tcl'
1788 # WITHOUT interpolation (i.e., NO substitution of Perl variables).
1789 # The crucial function is :
1790 # CGIscriptor::RedirectShellScript('/usr/bin/tcl')
1791 # After executing this function, all shell scripts AND all
1792 # calls to SAFEqx()) are piped into '|/usr/bin/tcl'. If the argument
1793 # of RedirectShellScript is empty, e.g., '', the original (default)
1794 # value is reset.
1796 # The standard output, STDOUT, of any pipe is send to the client.
1797 # Currently, you should be carefull with quotes in such a piped script.
1798 # The results of a pipe is NOT put on the @CGIscriptorResults stack.
1799 # As a result, you do not have access to the output of any piped (#!)
1800 # process! If you want such access, execute
1801 # <SCRIPT TYPE="text/osshell">echo "script"|command</SCRIPT>
1802 # or
1803 # <SCRIPT TYPE="text/ssperl">
1804 # $resultvar = SAFEqx('echo "script"|command');
1805 # </SCRIPT>.
1807 # Safety is never complete. Although SAFEqx() prevents some of the
1808 # most obvious forms of attacks and security slips, it cannot prevent
1809 # them all. Especially, complex combinations of quotes and intricate
1810 # variable references cannot be handled safely by SAFEqx. So be on
1811 # guard.
1814 # PERL CODE EVALUATION (CONTENT-TYPE=TEXT/SSPERL)
1816 # All PERL scripts are evaluated inside a PERL package. This package
1817 # has a separate name space. This isolated name space protects the
1818 # CGIscriptor.pl program against interference from user code. However,
1819 # some variables, e.g., $_, are global and cannot be protected. You are
1820 # advised NOT to use such global variable names. You CAN write
1821 # directives that directly access the variables in the main program.
1822 # You do so at your own risk (there is definitely enough rope available
1823 # to hang yourself). The behavior of CGIscriptor becomes undefined if
1824 # you change its private variables during run time. The PERL code
1825 # directives are used as in:
1826 # $Result = eval($directive); print $Result;'';
1827 # ($directive contains all text between <SCRIPT></SCRIPT>).
1828 # That is, the <directive> is treated as ''-quoted string and
1829 # the result is treated as a scalar. To prevent the VALUE of the code
1830 # block from appearing on the client's screen, end the directive with
1831 # ';""</SCRIPT>'. Evaluated directives return the last value, just as
1832 # eval(), blocks, and subroutines, but only as a scalar.
1834 # IMPORTANT: All PERL variables defined are persistent. Each <SCRIPT>
1835 # </SCRIPT> construct is evaluated as a {}-block with associated scope
1836 # (e.g., for "my $var;" declarations). This means that values assigned
1837 # to a PERL variable can be used throughout the document unless they
1838 # were declared with "my". The following will actually work as intended
1839 # (note that the ``-quotes in this example are NOT evaluated, but used
1840 # as simple quotes):
1842 # <META CONTENT="text/ssperl; CGI=`$String='abcdefg'`">
1843 # anything ...
1844 # <SCRIPT TYPE=text/ssperl>@List = split('', $String);</SCRIPT>
1845 # anything ...
1846 # <SCRIPT TYPE=text/ssperl>join(", ", @List[1..$#List]);</SCRIPT>
1848 # The first <SCRIPT TYPE=text/ssperl></SCRIPT> construct will return the
1849 # value scalar(@List), the second <SCRIPT TYPE=text/ssperl></SCRIPT>
1850 # construct will print the elements of $String separated by commas, leaving
1851 # out the first element, i.e., $List[0].
1853 # Another warning: './' and '~/' are ALWAYS replaced by the values of
1854 # $YOUR_SCRIPTS and $YOUR_HTML_FILES, respectively . This can interfere
1855 # with pattern matching, e.g., $a =~ s/aap\./noot\./g will result in the
1856 # evaluations of $a =~ s/aap\\${YOUR_SCRIPTS}noot\\${YOUR_SCRIPTS}g. Use
1857 # s@<regexp>.@<replacement>.@g instead.
1860 # SERVER SIDE SESSIONS AND ACCESS CONTROL (LOGIN)
1862 # An infrastructure for user acount authorization and file access control
1863 # is available. Each request is matched against a list of URL path patterns.
1864 # If the request matches, a Session Ticket is required to access the URL.
1865 # This Session Ticket should be present as a CGI parameter or Cookie, eg:
1867 # CGI: SESSIONTICKET=&lt;value&gt;
1868 # Cookie: CGIscriptorSESSION=&lt;value&gt;
1870 # The example implementation stores Session Tickets as files in a local
1871 # directory. To create Session Tickets, a Login request must be given
1872 # with a LOGIN=&lt;value&gt; CGI parameter, a user name and a (doubly hashed)
1873 # password. The user name and (singly hashed) password are stored in a
1874 # PASSWORD ticket with the same name as the user account (name cleaned up
1875 # for security).
1877 # The example session model implements 4 functions:
1878 # - Login
1879 # The password is hashed with the user name and server side salt, and then
1880 # hashed with the REMOTE_HOST and a random salt. Client and Server both
1881 # perform these actions and the Server only grants access if restults are
1882 # the same. The server side only stores the password hashed with the user
1883 # name and server side salt. Neither the plain password, nor the hashed
1884 # password is ever exchanged. Only values hashed with the one-time salt
1885 # are exchanged.
1886 # - Session
1887 # For every access to a restricted URL, the Session Ticket is checked before
1888 # access is granted. There are three session modes. The first uses a fixed
1889 # Session Ticket that is stored as a cookie value in the browser (actually,
1890 # as a sessionStorage value). The second uses only the IP address at login
1891 # to authenticate requests. The third
1892 # is a Challenge mode, where the client has to calculate the value of the
1893 # next one-time Session Ticket from a value derived from the password and
1894 # a random string.
1895 # - Password Change
1896 # A new password is hashed with the user name and server side salt, and
1897 # then encrypted (XORed)
1898 # with the old password hashed with the user name and salt. That value is
1899 # exchanged and XORed with the stored old hashed(password+username+salt).
1900 # Again, the stored password value is never exchanged unencrypted.
1901 # - New Account
1902 # The text of a new account (Type: PASSWORD) file is constructed from
1903 # the new username (CGI: NEWUSERNAME, converted to lowercase) and
1904 # hashed new password (CGI: NEWPASSWORD). The same process is used to encrypt
1905 # the new password as is used for the Password Change function.
1906 # Again, the stored password value is never exchanged unencrypted.
1907 # Some default setting are encoded. For display in the browser, the new password
1908 # is reencrypted (XORed) with a special key, the old password hash
1909 # hashed with a session specific random hex value sent initially with the
1910 # session login ticket ($RANDOMSALT).
1911 # For example for user "NewUser" and password "NewPassword" with filename
1912 # "newuser":
1914 # Type: PASSWORD
1915 # Username: newuser
1916 # Password: 19afeadfba8d5dcd252e157fafd3010859f8762b87682b6b6cdb3e565194fa91
1917 # IPaddress: 127\.0\.0\.1
1918 # AllowedPaths: ^/Private/[\w\-]+\.html?
1919 # AllowedPaths: ^/Private/newuser/
1920 # Salt: e93cf858a1d5626bf095ea5c25df990dfa969ff5a5dc908b22c9a5229b525f65
1921 # Session: SESSION
1922 # Date: Fri Jun 29 12:46:22 2012
1923 # Time: 1340973982
1924 # Signature: 676c35d3aa63540293ea5442f12872bfb0a22665b504f58f804582493b6ef04e
1926 # The password is created with the commands:
1928 # printf '%s' 'NewPasswordnewuser970e68017413fb0ea84d7fe3c463077636dd6d53486910d4a53c693dd4109b1a'|shasum -a 256
1930 # If the CPAN mudule Digest is installed, it is used instead of the commands.
1931 # However, the password account files are protected against unauthorized change.
1932 # To obtain a valid Password account, the following command should be given:
1934 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
1935 # masterkey='Sherlock investigates oleander curry in Bath' \
1936 # password='NewPassword' \
1937 # Private/.Passwords/newuser
1940 # Implementation
1942 # The session authentication mechanism is based on the exchange of ticket
1943 # identifiers. A ticket identifier is just a string of characters, a name
1944 # or a random 64 character hexadecimal string. Authentication is based
1945 # on a (password derived) shared secret and the ability to calculate ticket
1946 # identifiers from this shared secret. Ticket identifiers should be
1947 # "safe" filenames (except user names). There are four types of tickets:
1948 # PASSWORD: User account descriptors, including a user name and password
1949 # LOGIN: Temporary anonymous tickets used during login
1950 # IPADDRESS: Authentication tokens that allow access based on the IP address of the request
1951 # SESSION: Reusable authentication tokens
1952 # CHALLENGE: One-time authentication tokens
1953 # All tickets can have an expiration date in the form of a time duration
1954 # from creation, in seconds, minutes, hours, or days (+duration[smhd]).
1955 # An absolute time can be given in seconds since the epoch of the server host.
1956 # Note that expiration times of CHALLENGE authentication tokens are calculated
1957 # from the last access time. Accounts can include a maximal lifetime
1958 # for session tickets (MaxLifetime).
1960 # A Login page should create a LOGIN ticket file locally and send a
1961 # server specific salt, a Random salt, and a LOGIN ticket
1962 # identifier. The server side compares the username and hashed password,
1963 # actually hashed(hashed(password+serversalt)+Random salt) from the client with
1964 # the values it calculates from the stored Random salt from the LOGIN
1965 # ticket and the hashed(password+serversalt) from the PASSWORD ticket. If
1966 # successful, a new SESSION ticket is generated as a (double) hash sum of the stored
1967 # password and the LOGIN ticket, i.e.
1968 # LoginTicket = hashed(hashed(password+serversalt)+REMOTE_HOST + Random salt) and
1969 # SessionTicket = hashed(hashed(LoginTicket).LoginTicket). This SESSION
1970 # ticket should also be generated by the client and stored as
1971 # sessionStorage and cookie values as needed. The Username, IP address
1972 # and Path are available as $LoginUsername, $LoginIPaddress, and
1973 # $LoginPath, respectively.
1975 # The CHALLENGE protocol stores the single hashed version of the SESSION tickets.
1976 # However, this value is not exchanged, but kept secret in the JavaScript
1977 # sessionStorage object. Instead, every page returned from the
1978 # server will contain a one-time Challenge value ($CHALLENGETICKET) which
1979 # has to be hashed with the stored value to return the current ticket
1980 # id string.
1982 # In the current example implementation, all random values are created as
1983 # full, 256 bit SHA256 hash values (Hex strings) of 64 bytes read from
1984 # /dev/urandom.
1987 # Authorization
1989 # A limited level of authorization tuning is build into the login system.
1990 # Each account file (PASSWORD ticket file) can contain a number of
1991 # Capabilities lines. These control special priveliges. The
1992 # Capabilities can be checked inside the HTML pages as part of the
1993 # ticket information. Two privileges are handled internally:
1994 # CreateUser and VariableREMOTE_ADDR.
1995 # CreateUser allows the logged in user to create a new user account.
1996 # With VariableREMOTE_ADDR, the session of the logged in user is
1997 # not limited to the Remote IP address from which the inital log-in took
1998 # place. Sessions can hop from one apparant (proxy) IP address to another,
1999 # e.g., when using Tor. Any IPaddress patterns given in the PASSWORD
2000 # ticket file remain in effect during the session. For security reasons,
2001 # the VariableREMOTE_ADDR capability is only effective if the session
2002 # type is CHALLENGE.
2005 # Security considerations with Session tickets
2007 # For strong security, please use end-to-end encryption. This can be
2008 # achieved using a VPN (Virtual Private Network), SSH tunnel, or a HTTPS
2009 # capable server with OpenSSL. The session ticket system of CGIscriptor.pl
2010 # is intended to be used as a simple authentication mechanism WITHOUT
2011 # END-TO-END ENCRYPTION. The authenticating mechanism tries to use some
2012 # simple means to protect the authentication process from eavesdropping.
2013 # For this it uses a secure hash function, SHA256. For all practial purposes,
2014 # it is impossible to "decrypt" a SHA256 sum. But this login scheme is
2015 # only as secure as your browser. Which, in general, is not very secure.
2017 # One fundamental weakness of the implemented procedure is that the Client
2018 # obtains the code to encrypt the passwords from the server. It is the JavaScript
2019 # code in the HTML pages. An attacker who could place himself between Server
2020 # and Client, a man in the middle attack (MITM), could change the code to
2021 # reveal the plaintext password and other information. There is no
2022 # real protection against this attack without end-to-end encryption and
2023 # authentication. A simple, but rather cumbersome, way to check for such
2024 # attacks would be to store known good copys of the pages (downloaded
2025 # with a browser or automatically with curl or wget) and
2026 # then use other tools to download new pages at random intervals and compare
2027 # them to the old pages. For instance, the following line would remove
2028 # the variable ticket codes and give a fixed SHA256 sum for the original
2029 # Login.html page+code:
2030 # curl http://localhost:8080/Private/index.html | \
2031 # sed 's/=\"[a-z0-9]\{64\}\"/=""/g' | shasum -a 256
2032 # A simple diff command between old and new files should give only
2033 # differences in half a dozen lines, where only hexadecimal salt values
2034 # will actually differ.
2036 # A sort of solution for the MITM attack problem that might protect at
2037 # least the plaintext password would be to run a trusted web
2038 # page from local storage to handle password input. The solution would be
2039 # to add a hidden iFrame tag loading the untrusted page from the URL and
2040 # extract the needed ticket and salt values. Then run the stored, trusted,
2041 # code with these values. It is not (yet) possible to set the
2042 # required session storage inside the browser, so this method only works
2043 # for IPADDRESS sessions and plain SESSION tickets. There are many
2044 # security problems with this "solution".
2046 # If you are able to ascertain the integrity of the login page using any
2047 # of the above methods, you can check whether the IP address seen by the
2048 # login server is indeed the IP address of your computer. The IP address
2049 # of the REMOTE_HOST (your visible IP address) is part of the login
2050 # "password". It is stored in the login page as a CLIENTIPADDRESS. It can
2051 # can be inspected by clicking the "Check IP address" box. Provided the
2052 # MitM attacker cannot spoof your IP address, you can ensure that the login
2053 # server sees your IP address and not that of an attacker.
2055 # Humans tend to reuse passwords. A compromise of a site running
2056 # CGIscriptor.pl could therefore lead to a compromise of user accounts at
2057 # other sites. Therefore, plain text passwords are never stored, used, or
2058 # exchanged. Instead, the plain password and user name are "encrypted" with
2059 # a server site salt value. Actually, all are concatenated and hashed
2060 # with a one-way secure hash function (SHA256) into a single string.
2061 # Whenever the word "password" is used, this hash sum is meant. Note that
2062 # the salts are generated from /dev/urandom. You should check whether the
2063 # implementation of /dev/urandom on your platform is secure before
2064 # relying on it. This might be a problem when running CGIscriptor under
2065 # Cygwin on MS Windows.
2066 # Note: no attempt is made to slow down the password hash, so bad
2067 # passwords can be cracked by brute force
2069 # As the (hashed) passwords are all that is needed to identify at the site,
2070 # these should not be stored in this form. A site specific passphrase
2071 # can be entered as an environment variable ($ENV{'CGIMasterKey'}). This
2072 # phrase is hashed with the server site salt and the result is hashed with
2073 # the user name and then XORed with the password when it is stored. Also, to
2074 # detect changes to the account (PASSWORD) and session tickets, a
2075 # (HMAC) hash of some of the contents of the ticket with the server salt and
2076 # CGIMasterKey is stored in each ticket.
2078 # Creating a valid (hashed) password, encrypt it with the CGIMasterKey and
2079 # construct a signature of the ticket are non-trivial. This has to be redone
2080 # with every change of the ticket file or CGIMasterKey change. CGIscriptor
2081 # can do this from the command line with the command:
2083 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
2084 # masterkey='Sherlock investigates oleander curry in Bath' \
2085 # password='There is no password like more password' \
2086 # admin
2088 # CGIscriptor will exit after this command with the first option being
2089 # --managelogin. Options have the form:
2091 # salt=[file or string]
2092 # Server salt value to use io the value
2093 # stored in the ticket file. Will replace the stored value if a new
2094 # password is given. If you change the server salt, you have to
2095 # reset all the passwords. There is absolutely no procedure known
2096 # to recover plaintext passwords, except asking the account holders.
2097 # You are strongly adviced to make a backup before you apply such a change
2098 # masterkey=[file or string]
2099 # CGIMasterKey used to read and decrypt the ticket
2100 # newmasterkey=[file or string]
2101 # CGIMasterKey used to encrypt, sign,
2102 # and write the ticket. Defaults to the masterkey. If you change
2103 # the masterkey, you will have to reset all the accounts. You are strongly
2104 # adviced to make a backup before you apply such a change
2105 # password=[file or string]
2106 # New plaintext password
2108 # When the value of an option is a existing file path, the first line of
2109 # that file is used. Options are followed by one or more paths plus names
2110 # of existing ticket files. Each password option is only used for a single
2111 # ticket file. It is most definitely a bad idea to use a password that is
2112 # identical to an existing filepath, as the file will be read instead. Be
2113 # aware that the name of the file should be a cleaned up version of the
2114 # Username. This will not be checked.
2116 # For the authentication and a change of password, the (old) password
2117 # is used to "encrypt" a random one-time token or the new password,
2118 # respectively. For authentication, decryption is not needed, so a secure
2119 # hash function (SHA256) is used to create a one-way hash sum "encryption".
2120 # A new password must be decrypted. New passwords are encryped by XORing
2121 # them with the old password.
2123 # Strong Passwords: It is so easy
2124 # If you only could see what you are typing
2126 # Your password might be vulnerable to brute force guessing
2127 # (https://en.wikipedia.org/wiki/Brute_force_attack).
2128 # Protections against such attacks are costly in terms of code
2129 # complexity, bugs, and execution time. However, there is a very
2130 # simple and secure counter measure. See the XKCD comic
2131 # (http://xkcd.com/936/). The phrase, "There is no password like more
2132 # password" would be both much easier to remember, and still stronger
2133 # than "h4]D%@m:49", at least before this phrase was pasted as an
2134 # example on the Internet.
2136 # For the procedures used at this site, a basic computer setup can
2137 # check in the order of a billion passwords per second. You need a
2138 # password (or phrase) strength in the order of 56 bits to be a
2139 # little secure (one year on a single computer). Please be so kind
2140 # and add the name of your favorite flower, dish, fictional
2141 # character, or small town to your password. Say, Oleander, Curry,
2142 # Sherlock, or Bath, UK (each adds ~12 bits) or even the phrase "Sherlock
2143 # investigates oleander curry in Bath" (adds > 56 bits, note that
2144 # oleander is poisonous, so do not try this curry at home). That
2145 # would be more effective than adding a thousand rounds of encryption.
2146 # Typing long passwords without seeing what you are typing is
2147 # problematic. So a button should be included to make password
2148 # visible.
2151 # Technical matters
2153 # Client side JavaScript code definitions. Variable names starting with '$'
2154 # are CGIscriptor CGI variables. Some of the hashes could be strengthened
2155 # by switching to HMAC signatures. However, the security issues of
2156 # maintaining parallel functions for HMAC in both Perl and Javascript seem
2157 # to be more serious than the attack vectors against the hashes. But HMAC
2158 # is indeed used for the ticket signatures.
2160 # // On Login
2161 # HashPlaintextPassword() {
2162 # var plaintextpassword = document.getElementById('PASSWORD');
2163 # var serversalt = document.getElementById('SERVERSALT');
2164 # var username = document.getElementById('CGIUSERNAME');
2165 # return hex_sha256(plaintextpassword.value+username.value.toLowerCase()+serversalt.value);
2167 # var randomsalt = $RANDOMSALT; // From CGIscriptor
2168 # var loginticket = $LOGINTICKET; // From CGIscriptor
2169 # // Hash plaintext password
2170 # var password = HashPlaintextPassword();
2171 # // Authorize login
2172 # var hashedpassword = hex_sha256(randomsalt+password);
2173 # // Sessionticket
2174 # var sessionticket = hex_sha256(loginticket+password);
2175 # sessionStorage.setItem("CGIscriptorPRIVATE", sessionticket);
2176 # // Secretkey for encrypting new passwords, acts like a one-time pad
2177 # // Is set anew with every login, ie, also whith password changes
2178 # // and for each create new user request
2179 # var secretkey = hex_sha256(password+loginticket+randomsalt);
2180 # sessionStorage.setItem("CGIscriptorSECRET", secretkey);
2182 # // For a SESSION type request
2183 # sessionticket = hex_sha256(sessionStorage.getItem("CGIscriptorPRIVATE"));
2184 # createCookie("CGIscriptorSESSION",sessionticket, 0, "");
2186 // For a CHALLENGE type request
2187 # var sessionset = "$CHALLENGETICKET"; // From CGIscriptor
2188 # var sessionkey = sessionStorage.getItem("CGIscriptorPRIVATE");
2189 # sessionticket = hex_sha256(sessionset+sessionkey);
2190 # createCookie("CGIscriptorCHALLENGE",sessionticket, 0, "");
2192 # // For transmitting a new password
2193 # HashPlaintextNewPassword() {
2194 # var plaintextpassword = document.getElementById('NEWPASSWORD');
2195 # var serversalt = document.getElementById('SERVERSALT');
2196 # var username = document.getElementById('NEWUSERNAME');
2197 # return hex_sha256(plaintextpassword.value+username.value.toLowerCase()+serversalt.value);
2200 # var newpassword = document.getElementById('NEWPASSWORD');
2201 # var newpasswordrep = document.getElementById('NEWPASSWORDREP');
2202 # // Hash plaintext password
2203 # newpassword.value = HashPlaintextNewPassword();
2204 # var secretkey = sessionStorage.getItem("CGIscriptorSECRET");
2206 # var encrypted = XOR_hex_strings(secretkey, newpassword.value);
2207 # newpassword.value = encrypted;
2208 # newpasswordrep.value = encrypted;
2210 # // XOR of hexadecimal strings of equal length
2211 # function XOR_hex_strings(hex1, hex2) {
2212 # var resultHex = "";
2213 # var maxlength = Math.max(hex1.length, hex2.length);
2215 # for(var i=0; i &lt; maxlength; ++i) {
2216 # var h1 = hex1.charAt(i);
2217 # if(! h1) h1='0';
2218 # var h2 = hex2.charAt(i);
2219 # if(! h2) h2 ='0';
2220 # var d1 = parseInt(h1,16);
2221 # var d2 = parseInt(h2,16);
2222 # var resultD = d1^d2;
2223 # resultHex = resultHex+resultD.toString(16);
2224 # };
2225 # return resultHex;
2226 # };
2228 # Password encryption based on $ENV{'CGIMasterKey'}.
2229 # Server side Perl code:
2231 # # Password encryption
2232 # my $masterkey = $ENV{'CGIMasterKey'}
2233 # my $hash1 = hash_string($masterkey.$serversalt);
2234 # my $CryptKey = hash_string($username.$hash1);
2235 # $password = XOR_hex_strings($CryptKey,$password);
2237 # # Key for HMAC signing
2238 # my $hash1 = hash_string($masterkey.$serversalt);
2239 # my $HMACKey = hash_string($username.$hash1);
2243 # USER EXTENSIONS
2245 # A CGIscriptor package is attached to the bottom of this file. With
2246 # this package you can personalize your version of CGIscriptor by
2247 # including often used perl routines. These subroutines can be
2248 # accessed by prefixing their names with CGIscriptor::, e.g.,
2249 # <SCRIPT LANGUAGE=PERL TYPE=text/ssperl>
2250 # CGIscriptor::ListDocs("/Books/*") # List all documents in /Books
2251 # </SCRIPT>
2252 # It already contains some useful subroutines for Document Management.
2253 # As it is a separate package, it has its own namespace, isolated from
2254 # both the evaluator and the main program. To access variables from
2255 # the document <SCRIPT></SCRIPT> blocks, use $CGIexecute::<var>.
2257 # Currently, the following functions are implemented
2258 # (precede them with CGIscriptor::, see below for more information)
2259 # - SAFEqx ('String') -> result of qx/"String"/ # Safe application of ``-quotes
2260 # Is used by text/osshell Shell scripts. Protects all CGI
2261 # (client-supplied) values with single quotes before executing the
2262 # commands (one of the few functions that also works WITHOUT CGIscriptor::
2263 # in front)
2264 # - defineCGIvariable ($name[, $default) -> 0/1 (i.e., failure/success)
2265 # Is used by the META tag to define and initialize CGI and ENV
2266 # name/value pairs. Tries to obtain an initializing value from (in order):
2267 # $ENV{$name}
2268 # The Query string
2269 # The default value given (if any)
2270 # (one of the few functions that also works WITHOUT CGIscriptor::
2271 # in front)
2272 # - CGIsafeFileName (FileName) -> FileName or ""
2273 # Check a string against the Allowed File Characters (and ../ /..).
2274 # Returns an empty string for unsafe filenames.
2275 # - CGIsafeEmailAddress (Email) -> Email or ""
2276 # Check a string against correct email address pattern.
2277 # Returns an empty string for unsafe addresses.
2278 # - RedirectShellScript ('CommandString') -> FILEHANDLER or undef
2279 # Open a named PIPE for SAFEqx to receive ALL shell scripts
2280 # - URLdecode (URL encoded string) -> plain string # Decode URL encoded argument
2281 # - URLencode (plain string) -> URL encoded string # Encode argument as URL code
2282 # - CGIparseValue (ValueName [, URL_encoded_QueryString]) -> Decoded value
2283 # Extract the value of a CGI variable from the global or a private
2284 # URL-encoded query (multipart POST raw, NOT decoded)
2285 # - CGIparseValueList (ValueName [, URL_encoded_QueryString])
2286 # -> List of decoded values
2287 # As CGIparseValue, but now assembles ALL values of ValueName into a list.
2288 # - CGIparseHeader (ValueName [, URL_encoded_QueryString]) -> Header
2289 # Extract the header of a multipart CGI variable from the global or a private
2290 # URL-encoded query ("" when not a multipart variable or absent)
2291 # - CGIparseForm ([URL_encoded_QueryString]) -> Decoded Form
2292 # Decode the complete global URL-encoded query or a private
2293 # URL-encoded query
2294 # - read_url(URL) # Returns the page from URL (with added base tag, both FTP and HTTP)
2295 # Uses main::GET_URL(URL, 1) to get at the command to read the URL.
2296 # - BrowseDirs(RootDirectory [, Pattern, Startdir, CGIname]) # print browsable directories
2297 # - ListDocs(Pattern [,ListType]) # Prints a nested HTML directory listing of
2298 # all documents, e.g., ListDocs("/*", "dl");.
2299 # - HTMLdocTree(Pattern [,ListType]) # Prints a nested HTML listing of all
2300 # local links starting from a given document, e.g.,
2301 # HTMLdocTree("/Welcome.html", "dl");
2304 # THE RESULTS STACK: @CGISCRIPTORRESULTS
2306 # If the pseudo-variable "$CGIscriptorResults" has been defined in a
2307 # META tag, all subsequent SCRIPT and META results are pushed
2308 # on the @CGIscriptorResults stack. This list is just another
2309 # Perl variable and can be used and manipulated like any other list.
2310 # $CGIscriptorResults[-1] is always the last result.
2311 # This is only of limited use, e.g., to use the results of an OS shell
2312 # script inside a Perl script. Will NOT contain the results of Pipes
2313 # or code from MIME-profiling.
2316 # USEFULL CGI PREDEFINED VARIABLES (DO NOT ASSIGN TO THESE)
2318 # $CGI_HOME - The DocumentRoot directory
2319 # $CGI_Decoded_QS - The complete decoded Query String
2320 # $CGI_Content_Length - The ACTUAL length of the Query String
2321 # $CGI_Date - Current date and time
2322 # $CGI_Year $CGI_Month $CGI_Day $CGI_WeekDay - Current Date
2323 # $CGI_Time - Current Time
2324 # $CGI_Hour $CGI_Minutes $CGI_Seconds - Current Time, split
2325 # GMT Date/Time:
2326 # $CGI_GMTYear $CGI_GMTMonth $CGI_GMTDay $CGI_GMTWeekDay $CGI_GMTYearDay
2327 # $CGI_GMTHour $CGI_GMTMinutes $CGI_GMTSeconds $CGI_GMTisdst
2330 # USEFULL CGI ENVIRONMENT VARIABLES
2332 # Variables accessible (in APACHE) as $ENV{<name>}
2333 # (see: "http://hoohoo.ncsa.uiuc.edu/cgi/env.html"):
2335 # QUERY_STRING - The query part of URL, that is, everything that follows the
2336 # question mark.
2337 # PATH_INFO - Extra path information given after the script name
2338 # PATH_TRANSLATED - Extra pathinfo translated through the rule system.
2339 # (This doesn't always make sense.)
2340 # REMOTE_USER - If the server supports user authentication, and the script is
2341 # protected, this is the username they have authenticated as.
2342 # REMOTE_HOST - The hostname making the request. If the server does not have
2343 # this information, it should set REMOTE_ADDR and leave this unset
2344 # REMOTE_ADDR - The IP address of the remote host making the request.
2345 # REMOTE_IDENT - If the HTTP server supports RFC 931 identification, then this
2346 # variable will be set to the remote user name retrieved from
2347 # the server. Usage of this variable should be limited to logging
2348 # only.
2349 # AUTH_TYPE - If the server supports user authentication, and the script
2350 # is protected, this is the protocol-specific authentication
2351 # method used to validate the user.
2352 # CONTENT_TYPE - For queries which have attached information, such as HTTP
2353 # POST and PUT, this is the content type of the data.
2354 # CONTENT_LENGTH - The length of the said content as given by the client.
2355 # SERVER_SOFTWARE - The name and version of the information server software
2356 # answering the request (and running the gateway).
2357 # Format: name/version
2358 # SERVER_NAME - The server's hostname, DNS alias, or IP address as it
2359 # would appear in self-referencing URLs
2360 # GATEWAY_INTERFACE - The revision of the CGI specification to which this
2361 # server complies. Format: CGI/revision
2362 # SERVER_PROTOCOL - The name and revision of the information protocol this
2363 # request came in with. Format: protocol/revision
2364 # SERVER_PORT - The port number to which the request was sent.
2365 # REQUEST_METHOD - The method with which the request was made. For HTTP,
2366 # this is "GET", "HEAD", "POST", etc.
2367 # SCRIPT_NAME - A virtual path to the script being executed, used for
2368 # self-referencing URLs.
2369 # HTTP_ACCEPT - The MIME types which the client will accept, as given by
2370 # HTTP headers. Other protocols may need to get this
2371 # information from elsewhere. Each item in this list should
2372 # be separated by commas as per the HTTP spec.
2373 # Format: type/subtype, type/subtype
2374 # HTTP_USER_AGENT - The browser the client is using to send the request.
2375 # General format: software/version library/version.
2378 # INSTRUCTIONS FOR RUNNING CGIscriptor ON UNIX
2380 # CGIscriptor.pl will run on any WWW server that runs Perl scripts, just add
2381 # a line like the following to your srm.conf file (Apache example):
2383 # ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
2385 # URL's that refer to http://www.your.address/SHTML/... will now be handled
2386 # by CGIscriptor.pl, which can use a private directory tree (default is the
2387 # DOCUMENT_ROOT directory tree, but it can be anywhere, see manual).
2389 # If your hosting ISP won't let you add ScriptAlias lines you can use
2390 # the following "rewrite"-based "scriptalias" in .htaccess
2391 # (from Gerd Franke)
2393 # RewriteEngine On
2394 # RewriteBase /
2395 # RewriteCond %{REQUEST_FILENAME} .html$
2396 # RewriteCond %{SCRIPT_FILENAME} !cgiscriptor.pl$
2397 # RewriteCond %{REQUEST_FILENAME} -f
2398 # RewriteRule ^(.*)$ /cgi-bin/cgiscriptor.pl/$1?&%{QUERY_STRING}
2400 # Everthing with the extension ".html" and not including "cgiscriptor.pl"
2401 # in the url and where the file "path/filename.html" exists is redirected
2402 # to "/cgi.bin/cgiscriptor.pl/path/filename.html?query".
2403 # The user configuration should get the same path-level as the
2404 # .htaccess-file:
2406 # # Just enter your own directory path here
2407 # $YOUR_HTML_FILES = "$ENV{'DOCUMENT_ROOT'}";
2408 # # use DOCUMENT_ROOT only, if .htaccess lies in the root-directory.
2410 # If this .htaccess goes in a specific directory, the path to this
2411 # directory must be added to $ENV{'DOCUMENT_ROOT'}.
2413 # The CGIscriptor file contains all documentation as comments. These
2414 # comments can be removed to speed up loading (e.g., `egrep -v '^#'
2415 # CGIscriptor.pl` > leanScriptor.pl). A bare bones version of
2416 # CGIscriptor.pl, lacking documentation, most comments, access control,
2417 # example functions etc. (but still with the copyright notice and some
2418 # minimal documentation) can be obtained by calling CGIscriptor.pl on the
2419 # command line with the '-slim' command line argument, e.g.,
2421 # >CGIscriptor.pl -slim > slimCGIscriptor.pl
2423 # CGIscriptor.pl can be run from the command line with <path> and <query> as
2424 # arguments, as `CGIscriptor.pl <path> <query>`, inside a perl script
2425 # with 'do CGIscriptor.pl' after setting $ENV{PATH_INFO}
2426 # and $ENV{QUERY_STRING}, or CGIscriptor.pl can be loaded with 'require
2427 # "/real-path/CGIscriptor.pl"'. In the latter case, requests are processed
2428 # by 'Handle_Request();' (again after setting $ENV{PATH_INFO} and
2429 # $ENV{QUERY_STRING}).
2431 # Using the command line execution option, CGIscriptor.pl can be used as a
2432 # document (meta-)preprocessor. If the first argument is '-', STDIN will be read.
2433 # For example:
2435 # > cat MyDynamicDocument.html | CGIscriptor.pl - '[QueryString]' > MyStaticFile.html
2437 # This command line will produce a STATIC file with the DYNAMIC content of
2438 # MyDocument.html "interpolated".
2440 # This option would be very dangerous when available over the internet.
2441 # If someone could sneak a 'http://www.your.domain/-' URL past your
2442 # server, CGIscriptor could EXECUTE any POSTED contend.
2443 # Therefore, for security reasons, STDIN will NOT be read
2444 # if ANY of the HTTP server environment variables is set (e.g.,
2445 # SERVER_PORT, SERVER_PROTOCOL, SERVER_NAME, SERVER_SOFTWARE,
2446 # HTTP_USER_AGENT, REMOTE_ADDR).
2447 # This block on processing STDIN on HTTP requests can be lifted by setting
2448 # $BLOCK_STDIN_HTTP_REQUEST = 0;
2449 # In the security configuration. Butbe carefull when doing this.
2450 # It can be very dangerous.
2452 # Running demo's and more information can be found at
2453 # http://www.fon.hum.uva.nl/~rob/OSS/OSS.html
2455 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site or
2456 # CPAN that can use CGIscriptor.pl as the base of a µWWW server and
2457 # demonstrates its use.
2460 # PROCESSING NON-FILESYSTEM DATA
2462 # Normally, HTTP (WWW) requests map onto file that can be accessed
2463 # using the perl open() function. That is, the web server runs on top of
2464 # some directory structure. However, we can envission (and put to good
2465 # use) other systems that do not use a normal file system. The whole CGI
2466 # was developed to make dynamic document generation possible.
2468 # A special case is where we want to have it both: A normal web server
2469 # with normal "file data", but not a normal files system. For instance,
2470 # we want or normal Web Site to run directly from a RAM hash table or
2471 # other database, instead of from disk. But we do NOT want to code the
2472 # whole site structure in CGI.
2474 # CGIscriptor can do this. If the web server fills an environment variable
2475 # $ENV{'CGI_FILE_CONTENT'} with the content of the "file", then the content
2476 # of this variable is processed instead of opening a file. If this environment
2477 # variable has the value '-', the content of another environment variable,
2478 # $ENV{'CGI_DATA_ACCESS_CODE'} is executed as:
2479 # eval("\@_ = ($file_path); do {$ENV{'CGI_DATA_ACCESS_CODE'}};")
2480 # and the result is processed as if it was the content of the requested
2481 # file.
2482 # (actually, the names of the environment variables are user configurable,
2483 # they are stored in the local variables $CGI_FILE_CONTENT and
2484 # $CGI_DATA_ACCESS_CODE)
2486 # When using this mechanism, the SRC attribute mechanism will only partially work.
2487 # Only the "recursive" calls to CGIscriptor (the ProcessFile() function)
2488 # will work, the automagical execution of SRC files won't. (In this case,
2489 # the SRC attribute won't work either for other scripting languages)
2492 # NON-UNIX PLATFORMS
2494 # CGIscriptor.pl was mainly developed and tested on UNIX. However, as I
2495 # coded part of the time on an Apple Macintosh under MacPerl, I made sure
2496 # CGIscriptor did run under MacPerl (with command line options). But only
2497 # as an independend script, not as part of a HTTP server. I have used it
2498 # under Apache in Windows XP.
2500 ENDOFHELPTEXT
2501 exit;
2503 ###############################################################################
2505 # SECURITY CONFIGURATION
2507 # Special configurations related to SECURITY
2508 # (i.e., optional, see also environment variables below)
2510 # LOGGING
2511 # Log Clients and the requested paths (Redundant when loging Queries)
2513 $ClientLog = "./Client.log"; # (uncomment for use)
2515 # Format: Localtime | REMOTE_USER REMOTE_IDENT REMOTE_HOST REMOTE_ADDRESS \
2516 # PATH_INFO CONTENT_LENGTH (actually, the real query+post length)
2518 # Log Clients and the queries, the CGIQUERYDECODE is required if you want
2519 # to log queries. If you log Queries, the loging of Clients is redundant
2520 # (note that queries can be quite long, so this might not be a good idea)
2522 #$QueryLog = "./Query.log"; # (uncomment for use)
2524 # ACCESS CONTROL
2525 # the Access files should contain Hostnames or IP addresses,
2526 # i.e. REMOTE_HOST or REMOTE_ADDR, each on a separate line
2527 # optionally followed by one ore more file patterns, e.g., "edu /DEMO".
2528 # Matching is done "domain first". For example ".edu" matches all
2529 # clients whose "name" ends in ".edu" or ".EDU". The file pattern
2530 # "/DEMO" matches all paths that contain the strings "/DEMO" or "/demo"
2531 # (both matchings are done case-insensitive).
2532 # The name special symbol "-" matches ALL clients who do not supply a
2533 # REMOTE_HOST name, "*" matches all clients.
2534 # Lines starting with '-e' are evaluated. A non-zero return value indicates
2535 # a match. You can use $REMOTE_HOST, $REMOTE_ADDR, and $PATH_INFO. These
2536 # lines are evaluated in the program's own name-space. So DO NOT assign to
2537 # variables.
2539 # Accept the following users (remove comment # and adapt filename)
2540 $CGI_Accept = -s "$YOUR_SCRIPTS/ACCEPT.lis" ? "$YOUR_SCRIPTS/ACCEPT.lis" : ''; # (uncomment for use)
2542 # Reject requests from the following users (remove comment # and
2543 # adapt filename, this is only of limited use)
2544 $CGI_Reject = -s "$YOUR_SCRIPTS/REJECT.lis" ? "$YOUR_SCRIPTS/REJECT.lis" : ''; # (uncomment for use)
2546 # Empty lines or comment lines starting with '#' are ignored in both
2547 # $CGI_Accept and $CGI_Reject.
2549 # Block STDIN (i.e., '-') requests when servicing an HTTP request
2550 # Comment this out if you realy want to use STDIN in an on-line web server
2551 $BLOCK_STDIN_HTTP_REQUEST = 1;
2554 # End of security configuration
2556 ##################################################<<<<<<<<<<End Remove
2558 # PARSING CGI VALUES FROM THE QUERY STRING (USER CONFIGURABLE)
2560 # The CGI parse commands. These commands extract the values of the
2561 # CGI variables from the URL encoded Query String.
2562 # If you want to use your own CGI decoders, you can call them here
2563 # instead, using your own PATH and commenting/uncommenting the
2564 # appropriate lines
2566 # CGI parse command for individual values
2567 # (if $List > 0, returns a list value, if $List < 0, a hash table, this is optional)
2568 sub YOUR_CGIPARSE # ($Name [, $List]) -> Decoded value
2570 my $Name = shift;
2571 my $List = shift || 0;
2572 # Use one of the following by uncommenting
2573 if(!$List) # Simple value
2575 return CGIscriptor::CGIparseValue($Name) ;
2577 elsif($List < 0) # Hash tables
2579 return CGIscriptor::CGIparseValueHash($Name); # Defined in CGIscriptor below
2581 else # Lists
2583 return CGIscriptor::CGIparseValueList($Name); # Defined in CGIscriptor below
2586 # return `/PATH/cgiparse -value $Name`; # Shell commands
2587 # require "/PATH/cgiparse.pl"; return cgivalue($Name); # Library
2589 # Complete queries
2590 sub YOUR_CGIQUERYDECODE
2592 # Use one of the following by uncommenting
2593 return CGIscriptor::CGIparseForm(); # Defined in CGIscriptor below
2594 # return `/PATH/cgiparse -form`; # Shell commands
2595 # require "/PATH/cgiparse.pl"; return cgiform(); # Library
2598 # End of configuration
2600 #######################################################################
2602 # Translating input files.
2603 # Allows general and global conversions of files using Regular Expressions
2604 # Translations are applied in the order of definition.
2606 # Define:
2607 # my $TranslationPaths = 'pattern'; # Pattern matching PATH_INFO
2609 # push(@TranslationTable, ['pattern', 'replacement']);
2610 # e.g. (for Ruby Rails):
2611 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2612 # push(@TranslationTable, ['%>', '</SCRIPT>']);
2614 # Runs:
2615 # my $currentRegExp;
2616 # foreach $currentRegExp (keys(%TranslationTable))
2618 # my $currentRegExp;
2619 # foreach $currentRegExp (@TranslationTable)
2621 # my ($pattern, $replacement) = @$currentRegExp;
2622 # $$text =~ s!$pattern!$replacement!msg;
2623 # };
2624 # };
2626 # Configuration section
2628 #######################################################################
2630 # The file paths on which to apply the translation
2631 my $TranslationPaths = ''; # NO files
2632 #$TranslationPaths = '.'; # ANY file
2633 # $TranslationPaths = '\.html'; # HTML files
2635 my @TranslationTable = ();
2636 # Some legacy code
2637 push(@TranslationTable, ['\<\s*CGI\s+([^\>])*\>', '\<SCRIPT TYPE=\"text/ssperl\"\>$1\<\/SCRIPT>']);
2638 # Ruby Rails?
2639 push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2640 push(@TranslationTable, ['%>', '</SCRIPT>']);
2642 sub performTranslation # (\$text)
2644 my $text = shift || return;
2645 if(@TranslationTable && $TranslationPaths && $ENV{'PATH_INFO'} =~ m!$TranslationPaths!)
2647 my $currentRegExp;
2648 foreach $currentRegExp (@TranslationTable)
2650 my ($pattern, $replacement) = @$currentRegExp;
2651 $$text =~ s!$pattern!$replacement!msg;
2656 #######################################################################
2658 # Seamless access to other (Scripting) Languages
2659 # TYPE='text/ss<interpreter>'
2661 # Configuration section
2663 #######################################################################
2665 # OTHER SCRIPTING LANGUAGES AT THE SERVER SIDE (MIME => OScommand)
2666 # Yes, it realy is this simple! (unbelievable, isn't it)
2667 # NOTE: Some interpreters require some filtering to obtain "clean" output
2669 %ScriptingLanguages = (
2670 "text/testperl" => 'perl', # Perl for testing
2671 "text/sspython" => 'python', # Python
2672 "text/ssruby" => 'ruby', # Ruby
2673 "text/sstcl" => 'tcl', # TCL
2674 "text/ssawk" => 'awk -f-', # Awk
2675 "text/sslisp" => # lisp (rep, GNU)
2676 'rep | tail +4 '."| egrep -v '> |^rep. |^nil\\\$'",
2677 "text/xlispstat" => # xlispstat
2678 'xlispstat | tail +7 ' ."| egrep -v '> \\\$|^NIL'",
2679 "text/ssprolog" => # Prolog (GNU)
2680 "gprolog | tail +4 | sed 's/^| ?- //'",
2681 "text/ssm4" => 'm4', # M4 macro's
2682 "text/sh" => 'sh', # Born shell
2683 "text/bash" => 'bash', # Born again shell
2684 "text/csh" => 'csh', # C shell
2685 "text/ksh" => 'ksh', # Korn shell
2686 "text/sspraat" => # Praat (sound/speech analysis)
2687 "praat - | sed 's/Praat > //g'",
2688 "text/ssr" => # R
2689 "R --vanilla --slave | sed 's/^[\[0-9\]*] //'",
2690 "text/ssrebol" => # REBOL
2691 "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\\s*\[> \]* //'",
2692 "text/postgresql" => 'psql 2>/dev/null',
2694 # Not real scripting, but the use of other applications
2695 "text/ssmailto" => "awk 'NF||F{F=1;print \\\$0;}'|mailto >/dev/null", # Send mail from server
2696 "text/ssdisplay" => 'cat', # Display, (interpolation)
2697 "text/sslogfile" => # Log to file, (interpolation)
2698 "awk 'NF||L {if(!L){L=tolower(\\\$1)~/^file:\\\$/ ? \\\$2 : \\\$1;}else{print \\\$0 >> L;};}'",
2700 "" => ""
2703 # To be able to access the CGI variables in your script, they
2704 # should be passed to the scripting language in a readable form
2705 # Here you can enter how they should be printed (the first %s
2706 # is replaced by the NAME of the CGI variable as it apears in the
2707 # META tag, the second by its VALUE).
2708 # For Perl this would be:
2709 # "text/testperl" => '$%s = "%s";',
2710 # which would be executed as
2711 # printf('$%s = "%s";', $CGI_NAME, $CGI_VALUE);
2713 # If the hash table value doesn't exist, nothing is done
2714 # (you have to parse the Environment variables yourself).
2715 # If it DOES exist but is empty (e.g., "text/sspraat" => '',)
2716 # Perl string interpolation of variables (i.e., $var, @array,
2717 # %hash) is performed. This means that $@%\ must be protected
2718 # with a \.
2720 %ScriptingCGIvariables = (
2721 "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value'; (for testing)
2722 "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
2723 "text/ssruby" => '@%s = "%s"', # Ruby @VAR = 'value'
2724 "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
2725 "text/ssawk" => '%s = "%s";', # Awk VAR = 'value';
2726 "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
2727 "text/xlispstat" => '(setq %s "%s")', # xlispstat (setq VAR "value")
2728 "text/ssprolog" => '', # Gnu prolog (interpolated)
2729 "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
2730 "text/sh" => "\%s='\%s'", # Born shell VAR='value'
2731 "text/bash" => "\%s='\%s'", # Born again shell VAR='value'
2732 "text/csh" => "\$\%s='\%s';", # C shell $VAR = 'value';
2733 "text/ksh" => "\$\%s='\%s';", # Korn shell $VAR = 'value';
2735 "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
2736 "text/sspraat" => '', # Praat (interpolation)
2737 "text/ssr" => '%s <- "%s";', # R VAR <- "value";
2738 "text/postgresql" => '', # PostgreSQL (interpolation)
2740 # Not real scripting, but the use of other applications
2741 "text/ssmailto" => '', # MAILTO, (interpolation)
2742 "text/ssdisplay" => '', # Display, (interpolation)
2743 "text/sslogfile" => '', # Log to file, (interpolation)
2745 "" => ""
2748 # If you want something added in front or at the back of each script
2749 # block as send to the interpreter add it here.
2750 # mime => "string", e.g., "text/sspython" => "python commands"
2751 %ScriptingPrefix = (
2752 "text/testperl" => "\# Prefix Code;", # Perl script testing
2753 "text/ssm4" => 'divert(0)', # M4 macro's (open STDOUT)
2755 "" => ""
2757 # If you want something added at the end of each script block
2758 %ScriptingPostfix = (
2759 "text/testperl" => "\# Postfix Code;", # Perl script testing
2760 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2762 "" => ""
2764 # If you need initialization code, directly after opening
2765 %ScriptingInitialization = (
2766 "text/testperl" => "\# Initialization Code;", # Perl script testing
2767 "text/ssawk" => 'BEGIN {', # Server Side awk scripts (VAR = "value")
2768 "text/sslisp" => '(prog1 nil ', # Lisp (rep)
2769 "text/xlispstat" => '(prog1 nil ', # xlispstat
2770 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2772 "" => ""
2774 # If you need cleanup code before closing
2775 %ScriptingCleanup = (
2776 "text/testperl" => "\# Cleanup Code;", # Perl script testing
2777 "text/sspraat" => 'Quit',
2778 "text/ssawk" => '};', # Server Side awk scripts (VAR = "value")
2779 "text/sslisp" => '(princ "\n" standard-output)).', # Closing print to rep
2780 "text/xlispstat" => '(print ""))', # Closing print to xlispstat
2781 "text/postgresql" => '\q', # quit psql
2782 "text/ssdisplay" => "", # close cat
2784 "" => ""
2787 # End of configuration for foreign scripting languages
2789 ###############################################################################
2791 # Initialization Code
2794 sub Initialize_Request
2796 ###############################################################################
2798 # ENVIRONMENT VARIABLES
2800 # Use environment variables to configure CGIscriptor on a temporary basis.
2801 # If you define any of the configurable variables as environment variables,
2802 # these are used instead of the "hard coded" values above.
2804 $SS_PUB = $ENV{'SS_PUB'} || $YOUR_HTML_FILES;
2805 $SS_SCRIPT = $ENV{'SS_SCRIPT'} || $YOUR_SCRIPTS;
2808 # Substitution strings, these are used internally to handle the
2809 # directory separator strings, e.g., '~/' -> 'SS_PUB:' (Mac)
2810 $HOME_SUB = $SS_PUB;
2811 $SCRIPT_SUB = $SS_SCRIPT;
2814 # Make sure all script are reliably loaded
2815 push(@INC, $SS_SCRIPT);
2818 # Add the directory separator to the "home" directories.
2819 # (This is required for ~/ and ./ substitution)
2820 $HOME_SUB .= '/' if $HOME_SUB;
2821 $SCRIPT_SUB .= '/' if $SCRIPT_SUB;
2823 $CGI_HOME = $ENV{'DOCUMENT_ROOT'};
2824 $ENV{'PATH_TRANSLATED'} =~ /$ENV{'PATH_INFO'}/is;
2825 $CGI_HOME = $` unless $ENV{'DOCUMENT_ROOT'}; # Get the DOCUMENT_ROOT directory
2826 $default_values{'CGI_HOME'} = $CGI_HOME;
2827 $ENV{'HOME'} = $CGI_HOME;
2828 # Set SS_PUB and SS_SCRIPT as Environment variables (make them available
2829 # to the scripts)
2830 $ENV{'SS_PUB'} = $SS_PUB unless $ENV{'SS_PUB'};
2831 $ENV{'SS_SCRIPT'} = $SS_SCRIPT unless $ENV{'SS_SCRIPT'};
2833 $FilePattern = $ENV{'FilePattern'} || $FilePattern;
2834 $MaximumQuerySize = $ENV{'MaximumQuerySize'} || $MaximumQuerySize;
2835 $ClientLog = $ENV{'ClientLog'} || $ClientLog;
2836 $QueryLog = $ENV{'QueryLog'} || $QueryLog;
2837 $CGI_Accept = $ENV{'CGI_Accept'} || $CGI_Accept;
2838 $CGI_Reject = $ENV{'CGI_Reject'} || $CGI_Reject;
2840 # Parse file names
2841 $CGI_Accept =~ s@^\~/@$HOME_SUB@g if $CGI_Accept;
2842 $CGI_Reject =~ s@^\~/@$HOME_SUB@g if $CGI_Reject;
2843 $ClientLog =~ s@^\~/@$HOME_SUB@g if $ClientLog;
2844 $QueryLog =~ s@^\~/@$HOME_SUB@g if $QueryLog;
2846 $CGI_Accept =~ s@^\./@$SCRIPT_SUB@g if $CGI_Accept;
2847 $CGI_Reject =~ s@^\./@$SCRIPT_SUB@g if $CGI_Reject;
2848 $ClientLog =~ s@^\./@$SCRIPT_SUB@g if $ClientLog;
2849 $QueryLog =~ s@^\./@$SCRIPT_SUB@g if $QueryLog;
2851 @CGIscriptorResults = (); # A stack of results
2853 # end of Environment variables
2855 #############################################################################
2857 # Define and Store "standard" values
2859 # BEFORE doing ANYTHING check the size of Query String
2860 length($ENV{'QUERY_STRING'}) <= $MaximumQuerySize || dieHandler(2, "QUERY TOO LONG\n");
2862 # The Translated Query String and the Actual length of the (decoded)
2863 # Query String
2864 if($ENV{'QUERY_STRING'})
2866 # If this can contain '`"-quotes, be carefull to use it QUOTED
2867 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2868 $default_values{CGI_Content_Length} = length($default_values{CGI_Decoded_QS});
2871 # Get the current Date and time and store them as default variables
2873 # Get Local Time
2874 $LocalTime = localtime;
2876 # CGI_Year CGI_Month CGI_Day CGI_WeekDay CGI_Time
2877 # CGI_Hour CGI_Minutes CGI_Seconds
2879 $default_values{CGI_Date} = $LocalTime;
2880 ($default_values{CGI_WeekDay},
2881 $default_values{CGI_Month},
2882 $default_values{CGI_Day},
2883 $default_values{CGI_Time},
2884 $default_values{CGI_Year}) = split(' ', $LocalTime);
2885 ($default_values{CGI_Hour},
2886 $default_values{CGI_Minutes},
2887 $default_values{CGI_Seconds}) = split(':', $default_values{CGI_Time});
2889 # GMT:
2890 # CGI_GMTYear CGI_GMTMonth CGI_GMTDay CGI_GMTWeekDay CGI_GMTYearDay
2891 # CGI_GMTHour CGI_GMTMinutes CGI_GMTSeconds CGI_GMTisdst
2893 ($default_values{CGI_GMTSeconds},
2894 $default_values{CGI_GMTMinutes},
2895 $default_values{CGI_GMTHour},
2896 $default_values{CGI_GMTDay},
2897 $default_values{CGI_GMTMonth},
2898 $default_values{CGI_GMTYear},
2899 $default_values{CGI_GMTWeekDay},
2900 $default_values{CGI_GMTYearDay},
2901 $default_values{CGI_GMTisdst}) = gmtime;
2905 # End of Initialize Request
2907 ###################################################################
2909 # SECURITY: ACCESS CONTROL
2911 # Check the credentials of each client (use pattern matching, domain first).
2912 # This subroutine will kill-off (die) the current process whenever access
2913 # is denied.
2915 sub Access_Control
2917 # >>>>>>>>>>Start Remove
2919 # ACCEPTED CLIENTS
2921 # Only accept clients which are authorized, reject all unnamed clients
2922 # if REMOTE_HOST is given.
2923 # If file patterns are given, check whether the user is authorized for
2924 # THIS file.
2925 if($CGI_Accept)
2927 # Use local variables, REMOTE_HOST becomes '-' if undefined
2928 my $REMOTE_HOST = $ENV{REMOTE_HOST} || '-';
2929 my $REMOTE_ADDR = $ENV{REMOTE_ADDR};
2930 my $PATH_INFO = $ENV{'PATH_INFO'};
2932 open(CGI_Accept, "<$CGI_Accept") || dieHandler(3, "$CGI_Accept: $!\n");
2933 $NoAccess = 1;
2934 while(<CGI_Accept>)
2936 next unless /\S/; # Skip empty lines
2937 next if /^\s*\#/; # Skip comments
2939 # Full expressions
2940 if(/^\s*-e\s/is)
2942 my $Accept = $'; # Get the expression
2943 $NoAccess &&= eval($Accept); # evaluate the expresion
2945 else
2947 my ($Accept, @FilePatternList) = split;
2948 if($Accept eq '*' # Always match
2949 ||$REMOTE_HOST =~ /\Q$Accept\E$/is # REMOTE_HOST matches
2950 || (
2951 $Accept =~ /^[0-9\.]+$/
2952 && $REMOTE_ADDR =~ /^\Q$Accept\E/ # IP address matches
2956 if($FilePatternList[0])
2958 foreach $Pattern (@FilePatternList)
2960 # Check whether this patterns is accepted
2961 $NoAccess &&= ($PATH_INFO !~ m@\Q$Pattern\E@is);
2964 else
2966 $NoAccess = 0; # No file patterns -> Accepted
2970 # Blocked
2971 last unless $NoAccess;
2973 close(CGI_Accept);
2974 if($NoAccess){ dieHandler(4, "No Access: $PATH_INFO\n");};
2978 # REJECTED CLIENTS
2980 # Reject named clients, accept all unnamed clients
2981 if($CGI_Reject)
2983 # Use local variables, REMOTE_HOST becomes '-' if undefined
2984 my $REMOTE_HOST = $ENV{'REMOTE_HOST'} || '-';
2985 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
2986 my $PATH_INFO = $ENV{'PATH_INFO'};
2988 open(CGI_Reject, "<$CGI_Reject") || dieHandler(5, "$CGI_Reject: $!\n");
2989 $NoAccess = 0;
2990 while(<CGI_Reject>)
2992 next unless /\S/; # Skip empty lines
2993 next if /^\s*\#/; # Skip comments
2995 # Full expressions
2996 if(/^-e\s/is)
2998 my $Reject = $'; # Get the expression
2999 $NoAccess ||= eval($Reject); # evaluate the expresion
3001 else
3003 my ($Reject, @FilePatternList) = split;
3004 if($Reject eq '*' # Always match
3005 ||$REMOTE_HOST =~ /\Q$Reject\E$/is # REMOTE_HOST matches
3006 ||($Reject =~ /^[0-9\.]+$/
3007 && $REMOTE_ADDR =~ /^\Q$Reject\E/is # IP address matches
3011 if($FilePatternList[0])
3013 foreach $Pattern (@FilePatternList)
3015 $NoAccess ||= ($PATH_INFO =~ m@\Q$Pattern\E@is);
3018 else
3020 $NoAccess = 1; # No file patterns -> Rejected
3024 last if $NoAccess;
3026 close(CGI_Reject);
3027 if($NoAccess){ dieHandler(6, "Request rejected: $PATH_INFO\n");};
3030 ##########################################################<<<<<<<<<<End Remove
3033 # Get the filename
3035 # Does the filename contain any illegal characters (e.g., |, >, or <)
3036 dieHandler(7, "Illegal request: $ENV{'PATH_INFO'}\n") if $ENV{'PATH_INFO'} =~ /[^$FileAllowedChars]/;
3037 # Does the pathname contain an illegal (blocked) "directory"
3038 dieHandler(8, "Illegal request: $ENV{'PATH_INFO'}\n") if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@; # Access is blocked
3039 # Does the pathname contain a direct referencer to BinaryMapFile
3040 dieHandler(9, "Illegal request: $ENV{'PATH_INFO'}\n") if $BinaryMapFile && $ENV{'PATH_INFO'} =~ m@\Q$BinaryMapFile\E@; # Access is blocked
3042 # SECURITY: Is PATH_INFO allowed?
3043 if($FilePattern && $ENV{'PATH_INFO'} && $ENV{'PATH_INFO'} ne '-' &&
3044 ($ENV{'PATH_INFO'} !~ m@($FilePattern)$@is))
3046 # Unsupported file types can be processed by a special raw-file
3047 if($BinaryMapFile)
3049 $ENV{'CGI_BINARY_FILE'} = $ENV{'PATH_INFO'};
3050 $ENV{'PATH_INFO'} = $BinaryMapFile;
3052 else
3054 dieHandler(10, "Illegal file\n");
3060 # End of Security Access Control
3063 ############################################################################
3065 # Get the POST part of the query and add it to the QUERY_STRING.
3068 sub Get_POST_part_of_query
3071 # If POST, Read data from stdin to QUERY_STRING
3072 if($ENV{'REQUEST_METHOD'} =~ /POST/is)
3074 # SECURITY: Check size of Query String
3075 $ENV{'CONTENT_LENGTH'} <= $MaximumQuerySize || dieHandler(11, "Query too long: $ENV{'CONTENT_LENGTH'}\n"); # Query too long
3076 my $QueryRead = 0;
3077 my $SystemRead = $ENV{'CONTENT_LENGTH'};
3078 $ENV{'QUERY_STRING'} .= '&' if length($ENV{'QUERY_STRING'}) > 0;
3079 while($SystemRead > 0)
3081 $QueryRead = sysread(STDIN, $Post, $SystemRead); # Limit length
3082 $ENV{'QUERY_STRING'} .= $Post;
3083 $SystemRead -= $QueryRead;
3085 # Update decoded Query String
3086 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
3087 $default_values{CGI_Content_Length} =
3088 length($default_values{CGI_Decoded_QS});
3092 # End of getting POST part of query
3095 ############################################################################
3097 # Start (HTML) output and logging
3098 # (if there are irregularities, it can kill the current process)
3101 sub Initialize_output
3103 # Construct the REAL file path (except for STDIN on the command line)
3104 my $file_path = $ENV{'PATH_INFO'} ne '-' ? $SS_PUB . $ENV{'PATH_INFO'} : '-';
3105 $file_path =~ s/\?.*$//; # Remove query
3106 # This is only necessary if your server does not catch ../ directives
3107 $file_path !~ m@\.\./@ || dieHandler(12, "Illegal ../ Construct\n"); # SECURITY: Do not allow ../ constructs
3109 # Block STDIN use (-) if CGIscriptor is servicing a HTTP request
3110 if($file_path eq '-')
3112 dieHandler(13, "STDIN request in On Line system\n") if $BLOCK_STDIN_HTTP_REQUEST
3113 && ($ENV{'SERVER_SOFTWARE'}
3114 || $ENV{'SERVER_NAME'}
3115 || $ENV{'GATEWAY_INTERFACE'}
3116 || $ENV{'SERVER_PROTOCOL'}
3117 || $ENV{'SERVER_PORT'}
3118 || $ENV{'REMOTE_ADDR'}
3119 || $ENV{'HTTP_USER_AGENT'});
3124 if($ClientLog)
3126 open(ClientLog, ">>$ClientLog");
3127 print ClientLog "$LocalTime | ",
3128 ($ENV{REMOTE_USER} || "-"), " ",
3129 ($ENV{REMOTE_IDENT} || "-"), " ",
3130 ($ENV{REMOTE_HOST} || "-"), " ",
3131 $ENV{REMOTE_ADDR}, " ",
3132 $ENV{PATH_INFO}, " ",
3133 $ENV{'CGI_BINARY_FILE'}, " ",
3134 ($default_values{CGI_Content_Length} || "-"),
3135 "\n";
3136 close(ClientLog);
3138 if($QueryLog)
3140 open(QueryLog, ">>$QueryLog");
3141 print QueryLog "$LocalTime\n",
3142 ($ENV{REMOTE_USER} || "-"), " ",
3143 ($ENV{REMOTE_IDENT} || "-"), " ",
3144 ($ENV{REMOTE_HOST} || "-"), " ",
3145 $ENV{REMOTE_ADDR}, ": ",
3146 $ENV{PATH_INFO}, " ",
3147 $ENV{'CGI_BINARY_FILE'}, "\n";
3149 # Write Query to Log file
3150 print QueryLog $default_values{CGI_Decoded_QS}, "\n\n";
3151 close(QueryLog);
3154 # Return the file path
3155 return $file_path;
3158 # End of Initialize output
3161 ############################################################################
3163 # Handle login access
3165 # Access is based on a valid session ticket.
3166 # Session tickets should be dependend on user name
3167 # and IP address. The patterns of URLs for which a
3168 # session ticket is needed and the login URL are stored in
3169 # %TicketRequiredPatterns as:
3170 # 'RegEx pattern' -> 'SessionPath\tPasswordPath\tLogin URL\tExpiration'
3173 sub Log_In_Access # () -> 0 = Access Allowed, Login page if access is not allowed
3175 # No patterns, no login
3176 goto Return unless %TicketRequiredPatterns;
3178 # Get and initialize values (watch out for stuff processed by BinaryMap files)
3179 my ($SessionPath, $PasswordsPath, $Login, $valid_duration) = ("", "", "", 0);
3180 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3181 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
3182 goto Return if $REMOTE_ADDR =~ /[^0-9\.]/;
3183 # Extract TICKETs, starting with returned cookies
3184 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3185 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3186 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3187 Get_All_Cookies();
3188 if(length(keys(%CGI_Cookies)) > 0)
3190 ${"CGIexecute::LOGINTICKET"} = $CGI_Cookies{'CGIscriptorLOGIN'}
3191 if $CGI_Cookies{'CGIscriptorLOGIN'} && $CGI_Cookies{'CGIscriptorLOGIN'} ne "-";
3192 $CGI_Cookies{'CGIscriptorLOGIN'} = "-";
3193 ${"CGIexecute::CHALLENGETICKET"} = $CGI_Cookies{'CGIscriptorCHALLENGE'}
3194 if $CGI_Cookies{'CGIscriptorCHALLENGE'} && $CGI_Cookies{'CGIscriptorCHALLENGE'} ne "-";
3195 $CGI_Cookies{'CGIscriptorCHALLENGE'} = "-";
3196 ${"CGIexecute::SESSIONTICKET"} = $CGI_Cookies{'CGIscriptorSESSION'}
3197 if $CGI_Cookies{'CGIscriptorSESSION'} && $CGI_Cookies{'CGIscriptorSESSION'} ne "-";
3198 $CGI_Cookies{'CGIscriptorSESSION'} = "-";
3200 # Get and check the tickets. Tickets are restricted to word-characters (alphanumeric+_+.)
3201 my $LOGINTICKET = ${"CGIexecute::LOGINTICKET"};
3202 goto Return if ($LOGINTICKET && $LOGINTICKET =~ /[^\w\.]/isg);
3203 my $SESSIONTICKET = ${"CGIexecute::SESSIONTICKET"};
3204 goto Return if ($SESSIONTICKET && $SESSIONTICKET =~ /[^\w\.]/isg);
3205 my $CHALLENGETICKET = ${"CGIexecute::CHALLENGETICKET"};
3206 goto Return if ($CHALLENGETICKET && $CHALLENGETICKET =~ /[^\w\.]/isg);
3207 # Look for a LOGOUT message
3208 my $LOGOUT = $ENV{QUERY_STRING} =~ /(^|\&)LOGOUT([\=\&]|$)/;
3209 # Username and password
3210 CGIexecute::defineCGIvariable('CGIUSERNAME', "");
3211 my $username = lc(${"CGIexecute::CGIUSERNAME"});
3212 goto Return if $username =~ m!^[^\w]!isg || $username =~ m![^\w \-]!isg;
3213 my $userfile = lc($username);
3214 $userfile =~ s/[^\w]/_/isg;
3215 CGIexecute::defineCGIvariable('PASSWORD', "");
3216 my $password = ${"CGIexecute::PASSWORD"};
3217 CGIexecute::defineCGIvariable('NEWUSERNAME', "");
3218 my $newuser = lc(${"CGIexecute::NEWUSERNAME"});
3219 CGIexecute::defineCGIvariable('NEWPASSWORD', "");
3220 my $newpassword = ${"CGIexecute::NEWPASSWORD"};
3222 foreach my $pattern (keys(%TicketRequiredPatterns))
3224 # Check BOTH the real PATH_INFO and the CGI_BINARY_FILE variable
3225 if($ENV{'PATH_INFO'} =~ m#$pattern# || $ENV{'CGI_BINARY_FILE'} =~ m#$pattern#)
3227 # Fall through a sieve of requirements
3228 ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3230 # Is there a change password request?
3231 if($newuser && $LOGINTICKET && $username)
3233 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3234 goto Login unless (-s "$PasswordsPath/$userfile");
3235 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3236 goto Login unless $ticket_valid;
3237 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".", 1);
3238 goto Login unless $ticket_valid;
3240 my ($sessiontype, $currentticket) = ("", "");
3241 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
3242 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
3243 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
3245 if($sessiontype)
3247 goto Login unless (-s "$SessionPath/$currentticket");
3248 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3249 goto Login unless $ticket_valid;
3251 # Authorize
3252 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath, $REMOTE_ADDR);
3253 goto Login unless $TMPTICKET;
3255 # Create a new user account
3256 CGIexecute::defineCGIvariable('NEWSESSION', "");
3257 my $newsession = ${"CGIexecute::NEWSESSION"};
3258 my $newaccount = create_newuser("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket",
3259 "$PasswordsPath/$userfile", $password, $newuser, $newpassword, $newsession);
3260 CGIexecute::defineCGIvariable('NEWACCOUNTTEXT', $newaccount);
3261 ${CGIexecute::NEWACCOUNTTEXT} = $newaccount;
3262 # NEWACCOUNTTEXT is NOT to be set by the query
3263 CGIexecute::ProtectCGIvariable('NEWACCOUNTTEXT');
3266 # Ready
3267 goto Return;
3269 # Is there a change password request?
3270 elsif($newpassword && $LOGINTICKET && $username)
3272 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3273 goto Login unless (-s "$PasswordsPath/$userfile");
3274 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3275 goto Login unless $ticket_valid;
3276 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".", 1);
3277 goto Login unless $ticket_valid;
3279 my ($sessiontype, $currentticket) = ("", "");
3280 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
3281 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
3282 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
3284 if($sessiontype)
3286 goto Login unless (-s "$SessionPath/$currentticket");
3287 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3288 goto Login unless $ticket_valid;
3290 # Authorize
3291 change_password("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket", "$PasswordsPath/$userfile", $password, $newpassword);
3292 # After a change of password, you have to login again for a CHALLENGE
3293 if($CHALLENGETICKET){$CHALLENGETICKET = "";};
3294 # Ready
3295 goto Return;
3297 # Is there a login ticket of this name?
3298 elsif($LOGINTICKET)
3300 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3301 goto Login unless (-s "$PasswordsPath/$userfile");
3302 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3303 goto Login unless $ticket_valid;
3304 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".");
3305 goto Login unless $ticket_valid;
3307 # Authorize
3308 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath, $REMOTE_ADDR);
3309 if($TMPTICKET)
3311 my $authorization = read_ticket("$PasswordsPath/$userfile");
3312 goto Login unless $authorization;
3313 # Session type is read from the userfile
3314 if($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "CHALLENGE")
3316 # Create New Random CHALLENGETICKET
3317 $CHALLENGETICKET = $TMPTICKET;
3318 create_session_file("$SessionPath/$CHALLENGETICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3320 elsif($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "IPADDRESS")
3322 create_session_file("$SessionPath/$REMOTE_ADDR", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3324 else
3326 # Extra hash to protect CHALLENGETICKET use
3327 $SESSIONTICKET = hash_string($TMPTICKET);
3328 $SESSIONTICKET = hash_string($SESSIONTICKET.$TMPTICKET);
3329 create_session_file("$SessionPath/$SESSIONTICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3330 $SETCOOKIELIST{"CGIscriptorSESSION"} = "-";
3331 $TMPTICKET = $SESSIONTICKET;
3334 # Login ticket file has been used, remove it
3335 unlink($loginfile);
3337 # Is there a session ticket of this name?
3338 # CHALLENGE
3339 if($CHALLENGETICKET)
3341 # Do not log into a CHALLENGE account if the SESSION cookie is present
3342 # Uncomment when $SESSIONTICKET does not receive an extra hash
3343 #goto Login if $SESSIONTICKET =~ /\S/;
3344 goto Login unless (-s "$SessionPath/$CHALLENGETICKET");
3345 my $ticket_valid = check_ticket_validity("CHALLENGE", "$SessionPath/$CHALLENGETICKET", $REMOTE_ADDR, $PATH_INFO);
3346 goto Login unless $ticket_valid;
3348 my $oldchallenge = read_ticket("$SessionPath/$CHALLENGETICKET");
3349 goto Login unless $oldchallenge;
3350 # Check whether the login still exists
3351 my $userfile = lc($oldchallenge->{"Username"}->[0]);
3352 $userfile =~ s/[^\w]/_/isg;
3353 goto Login unless (-s "$PasswordsPath/$userfile");
3355 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3356 goto Login unless $ticket_valid;
3358 # This is a LOGOUT request, clean up (Access has already been validated)
3359 if($LOGOUT)
3361 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
3362 $CHALLENGETICKET = "";
3363 goto Login;
3366 my $NEWCHALLENGETICKET = "";
3367 $NEWCHALLENGETICKET = copy_challenge_file("$SessionPath/$CHALLENGETICKET", "$PasswordsPath/$userfile", $SessionPath);
3368 # Sessionticket is available to scripts, do NOT set the cookie
3369 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3370 goto Return;
3372 # IPADDRESS
3373 elsif(-s "$SessionPath/$REMOTE_ADDR")
3375 my $ticket_valid = check_ticket_validity("IPADDRESS", "$SessionPath/$REMOTE_ADDR", $REMOTE_ADDR, $PATH_INFO);
3376 goto Login unless $ticket_valid;
3377 # Check whether the login still exists
3378 my $currentsessionticket = read_ticket("$SessionPath/$REMOTE_ADDR");
3379 my $userfile = lc($currentsessionticket->{"Username"}->[0]);
3380 $userfile =~ s/[^\w]/_/isg;
3381 goto Login unless (-s "$PasswordsPath/$userfile");
3383 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3384 goto Login unless $ticket_valid;
3386 # This is a LOGOUT request, clean up (Access has already been validated)
3387 if($LOGOUT)
3389 unlink "$SessionPath/$REMOTE_ADDR" if (-s "$SessionPath/$REMOTE_ADDR");
3390 goto Login;
3393 goto Return;
3395 # SESSION
3396 elsif($SESSIONTICKET)
3398 goto Login unless (-s "$SessionPath/$SESSIONTICKET");
3399 my $ticket_valid = check_ticket_validity("SESSION", "$SessionPath/$SESSIONTICKET", $REMOTE_ADDR, $PATH_INFO);
3400 goto Login unless $ticket_valid;
3402 # Check whether the login still exists
3403 my $currentsessionticket = read_ticket("$SessionPath/$SESSIONTICKET");
3404 my $userfile = lc($currentsessionticket->{"Username"}->[0]);
3405 $userfile =~ s/[^\w]/_/isg;
3406 goto Login unless (-s "$PasswordsPath/$userfile");
3408 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3409 goto Login unless $ticket_valid;
3411 # This is a LOGOUT request, clean up (Access has already been validated)
3412 if($LOGOUT)
3414 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
3415 $SESSIONTICKET = "";
3416 goto Login;
3419 # Sessionticket is available to scripts
3420 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3421 goto Return;
3424 goto Login;
3425 goto Return;
3428 Return:
3429 # The Masterkey should NOT be accessible by the parsed files
3430 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3431 return 0;
3433 Login:
3434 # To deter DOS attacks, do not remove valid session tickets unless the
3435 # "owner" has accredited herself
3436 my $tickets_removed = remove_expired_tickets($SessionPath);
3437 create_login_file($PasswordsPath, $SessionPath, $REMOTE_ADDR);
3438 # Note, cookies are set only ONCE
3439 $SETCOOKIELIST{"CGIscriptorLOGIN"} = "-";
3440 # The Masterkey should NOT be accessible by the parsed files
3441 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3442 return "$YOUR_HTML_FILES/$Login";
3445 sub authorize_login # ($loginfile, $authorizationfile, $password, $SessionPath, $IPaddress) => SESSIONTICKET First two arguments are file paths
3447 my $loginfile = shift || "";
3448 my $authorizationfile = shift || "";
3449 my $password = shift || "";
3450 my $SessionPath = shift || "";
3451 my $RemoteIPaddress = shift || "";
3453 # Get Login session ticket
3454 my $loginticket = read_ticket($loginfile);
3455 return 0 unless $loginticket;
3456 # Get User credentials for authorization
3457 my $authorization = read_ticket($authorizationfile);
3458 return 0 unless $authorization;
3460 # Get Randomsalt
3461 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3462 return "" unless $Randomsalt;
3464 my $storedpassword = $authorization->{'Password'}->[0];
3465 return "" unless $storedpassword;
3466 my $Hashedpassword = hash_string($storedpassword.$RemoteIPaddress.$Randomsalt);
3467 return "" unless $password eq $Hashedpassword;
3469 # Extract Session Ticket
3470 my $loginsession = $loginticket->{'Session'}->[0];
3471 my $sessionticket = hash_string($storedpassword.$loginsession);
3472 chomp($sessionticket);
3473 $sessionticket = "" if -x "$SessionPath/$sessionticket";
3475 # No lingering password variables
3476 $Hashedpassword = $Randomsalt;
3477 $password = $Randomsalt;
3478 $authorization->{'Password'}->[0] = $Randomsalt;
3480 return $sessionticket;
3483 sub change_password # ($loginfile, $sessionfile, $authorizationfile, $password, $newpassword) First three arguments are file paths
3485 my $loginfile = shift || "";
3486 my $sessionfile = shift || "";
3487 my $authorizationfile = shift || "";
3488 my $password = shift || "";
3489 my $newpassword = shift || "";
3490 # Get Login session ticket
3491 my $loginticket = read_ticket($loginfile);
3492 return "" unless $loginticket;
3493 # Login ticket file has been used, remove it
3494 unlink($loginfile);
3495 # Get Randomsalt
3496 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3497 return "" unless $Randomsalt;
3498 my $LoginID = $loginticket->{'Session'}->[0];
3499 return "" unless $LoginID;
3501 # Get session ticket
3502 my $sessionticket = read_ticket($sessionfile);
3503 return "" unless $sessionticket;
3505 # Get User credentials for authorization
3506 my $authorization = read_ticket($authorizationfile);
3507 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3509 my $storedpassword = $authorization->{'Password'}->[0];
3510 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3511 return "" unless $password eq $Hashedpassword;
3512 my $secretkey = hash_string($storedpassword.$LoginID.$Randomsalt);
3514 # Decrypt the $newpassword
3515 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3516 return "" unless $decryptedPassword;
3517 # Authorization succeeded, change password
3518 $authorization->{'Password'}->[0] = $decryptedPassword;
3519 # Write out
3520 write_ticket($authorizationfile, $authorization, $authorization->{'Salt'}->[0]);
3522 # No lingering password variables
3523 $decryptedPassword = $Randomsalt;
3524 $secretkey = $Randomsalt;
3525 $storedpassword = $Randomsalt;
3526 $Hashedpassword = $Randomsalt;
3527 $authorization->{'Password'}->[0] = $Randomsalt;
3529 return $newpassword;
3531 # First three arguments are file paths
3532 sub create_newuser # ($loginfile, $sessionfile, $authorizationfile, $password, $newuser, $newpassword, $newsession) -> account text
3534 my $loginfile = shift || "";
3535 my $sessionfile = shift || "";
3536 my $authorizationfile = shift || "";
3537 my $password = shift || "";
3538 my $newuser = shift || "";
3539 my $newpassword = shift || "";
3540 my $newsession = shift || "";
3542 # Get Login session ticket
3543 my $loginticket = read_ticket($loginfile);
3544 return "" unless $loginticket;
3545 # Login ticket file has been used, remove it
3546 unlink($loginfile);
3547 # Get Randomsalt
3548 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3549 return "" unless $Randomsalt;
3550 my $LoginID = $loginticket->{'Session'}->[0];
3551 return "" unless $LoginID;
3553 # Get session ticket
3554 my $sessionticket = read_ticket($sessionfile);
3555 return "" unless $sessionticket;
3556 # Get User credentials for authorization
3557 my $authorization = read_ticket($authorizationfile);
3558 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3559 my $sessionkey = $sessionticket->{'Key'}->[0];
3560 my $serversalt = $authorization->{'Salt'}->[0];
3561 return "" unless $serversalt;
3563 my $storedpassword = $authorization->{'Password'}->[0];
3564 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3565 return "" unless $password eq $Hashedpassword;
3566 my $secretkey = hash_string($storedpassword.$LoginID.$Randomsalt);
3568 # Decrypt the $newpassword
3569 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3570 return "" unless $decryptedPassword;
3572 # Authorization succeeded, create new account
3573 my $newaccount = {};
3574 $newaccount->{'Type'} = ['PASSWORD'];
3575 $newaccount->{'Username'} = [$newuser];
3576 $newaccount->{'Password'} = [$decryptedPassword];
3577 $newaccount->{'Salt'} = [$serversalt];
3578 $newaccount->{'Session'} = ['SESSION'];
3579 if($newsession eq 'IPADDRESS'){$newaccount->{'Session'} = ['IPADDRESS'];};
3580 if($newsession eq 'CHALLENGE'){$newaccount->{'Session'} = ['CHALLENGE'];};
3581 my $timesec = time();
3582 my $gmt_date = gmtime();
3583 $newaccount->{'Time'} = [$timesec];
3584 $newaccount->{'Date'} = [$gmt_date];
3586 # AllowedPaths
3587 my $NewAllowedPaths = "";
3588 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3589 my $currentRoot = "";
3590 $currentRoot = $1 if $PATH_INFO =~ m!^([\w\-\. /]+)!isg;
3591 $currentRoot =~ s![^/]+$!!isg;
3592 if($currentRoot)
3594 $currentRoot .= '/' unless $currentRoot =~ m!/$!;
3595 my $newpath = "^".${currentRoot}.'[\w\-]+\.html?';
3596 $NewAllowedPaths .= 'AllowedPaths: ^'.${currentRoot}.'[\w\-]+\.html?'."\n";
3597 $newaccount->{'AllowedPaths'} = [$newpath];
3599 else
3601 # Tricky PATH_INFO, deny all
3602 $NewAllowedPaths .= "DeniedPaths: ^/\n";
3603 $newaccount->{'DeniedPaths'} = ["DeniedPaths: ^/\n"];
3606 # Construct home directory path
3607 my $FullHomeDirectoryPath = "";
3608 my $currentHome = lc($newuser);
3609 if($currentHome && $currentHome !~ /^\s*\#/)
3611 $currentHome =~ s![^\w]!_!isg;
3612 my $newpath = "^${currentRoot}$currentHome/";
3613 push(@{$newaccount->{'AllowedPaths'}}, $newpath);
3614 # Create home directory
3615 $FullHomeDirectoryPath = $ENV{'HOME'}.${currentRoot}.$currentHome;
3618 # Allowed Paths
3619 CGIexecute::defineCGIvariable('ALLOWEDPATHS', "");
3620 my $allowedpaths = ${"CGIexecute::ALLOWEDPATHS"};
3621 if($allowedpaths && $allowedpaths !~ /^\s*\#/)
3623 $allowedpaths =~ s!\#.*$!!isg;
3624 $allowedpaths =~ s![^\^\w\./\;\+\*\?\[\]\$]!!isg;
3625 my @pathlist = split(/\;/, $allowedpaths);
3626 foreach my $entry (@pathlist)
3628 push(@{$newaccount->{'AllowedPaths'}}, "^".${currentRoot}.$entry);
3632 # Allowed IP addresses
3633 CGIexecute::defineCGIvariable('IPADDRESS', "");
3634 my $ipaddress = ${"CGIexecute::IPADDRESS"};
3635 if($ipaddress && $ipaddress !~ /^\s*\#/)
3637 $ipaddress =~ s!\#.*$!!isg;
3638 $ipaddress =~ s![^\d\.\;]!!isg;
3639 my @iplist = split(/\;/, $ipaddress);
3640 foreach my $entry (@iplist)
3642 next unless $entry =~ /\d/;
3643 next if $entry =~ /^\s*\#/;
3644 $entry =~ s/\./\\./g;
3645 push(@{$newaccount->{'IPaddress'}}, $entry);
3649 # Capabilities
3650 CGIexecute::defineCGIvariable('NEWCAPABILITIES', "");
3651 my $capabilities = ${"CGIexecute::NEWCAPABILITIES"};
3652 if($capabilities && $capabilities !~ /^\W*\#/)
3654 $capabilities =~ s!\#.*$!!isg;
3655 $capabilities =~ s![^\w\s]!!isg;
3656 my @caplist = split(/\s/, $capabilities);
3657 foreach my $entry (@caplist)
3659 next unless $entry =~ /\w/;
3660 next if $entry =~ /^\s*\#/;
3661 push(@{$newaccount->{'Capabilities'}}, $entry);
3665 # Sign the new ticket
3666 my $Signature = SignTicketWithMasterkey($newaccount, $newaccount->{'Salt'}->[0]);
3668 # Write
3669 my $datetime = gmtime();
3670 my $newuserfile = "";
3671 if(grep(/^CreateUser$/, @{$authorization->{'Capabilities'}}))
3673 my $newuserfilename = lc($newuser);
3674 $newuserfilename =~ s/[^\w]/_/isg;
3675 $newuserfile = $authorizationfile;
3676 $newuserfile =~ s![^/]*$!!isg;
3677 $newuserfile .= $newuserfilename;
3678 if(-s $newuserfile)
3680 $newuserfile = "";
3682 elsif($FullHomeDirectoryPath && !(-d $FullHomeDirectoryPath || -s $FullHomeDirectoryPath))
3684 if(-d "$ENV{'HOME'}${currentRoot}.SkeletonDir")
3686 `cp -r '$ENV{'HOME'}${currentRoot}.SkeletonDir' '$FullHomeDirectoryPath'`;
3688 elsif(-d "$ENV{'HOME'}${currentRoot}SkeletonDir")
3690 `cp -r '$ENV{'HOME'}${currentRoot}SkeletonDir' '$FullHomeDirectoryPath'`;
3692 elsif(-s "$ENV{'HOME'}${currentRoot}UserIndex.html")
3694 mkdir $FullHomeDirectoryPath;
3695 `cp '$ENV{'HOME'}${currentRoot}UserIndex.html' '$FullHomeDirectoryPath/index.html'`;
3697 elsif(-s "$ENV{'HOME'}${currentRoot}index.html")
3699 mkdir $FullHomeDirectoryPath;
3700 `cp '$ENV{'HOME'}${currentRoot}index.html' '$FullHomeDirectoryPath/index.html'`;
3706 my $newaccounttext = write_ticket($newuserfile, $newaccount, $serversalt);
3708 # Re-encrypt the new password for transmission
3709 if($newaccounttext =~ /^(Password\:\s+)(\S+)\s*$/)
3711 my $passwordvalue = $1;
3712 my $reencryptedpassword = XOR_hex_strings($secretkey, $passwordvalue);
3713 my $encryptedpasswordline = "<span id='newaccount'>$reencryptedpassword</span>";
3714 $newaccounttext =~ s/^(Password\:\s+)(\S+)\s*$/\1$encryptedpasswordline/gim;
3716 # No lingering passwords
3717 $passwordvalue = $serversalt;
3719 return $newaccounttext;
3722 # Copy a Challenge ticket file to a new name which is the hash of the new $CHALLENGETICKET and the password
3723 sub copy_challenge_file #($oldchallengefile, $authorizationfile, $sessionpath) -> $CHALLENGETICKET
3725 my $oldchallengefile = shift || "";
3726 my $authorizationfile = shift || "";
3727 my $sessionpath = shift || "";
3728 $sessionpath =~ s!/+$!!g;
3730 # Get Login session ticket
3731 my $oldchallenge = read_ticket($oldchallengefile);
3732 return "" unless $oldchallenge;
3734 # Get Authorization (user) session file
3735 my $authorization = read_ticket($authorizationfile);
3736 return "" unless $authorization;
3737 my $storedpassword = $authorization->{'Password'}->[0];
3738 return "" unless $storedpassword;
3739 my $challengekey = $oldchallenge->{'Key'}->[0];
3740 return "" unless $challengekey;
3742 # Create Random Hash Salt
3743 my $NEWCHALLENGETICKET = get_random_hex();;
3744 my $newchallengefile = hash_string($challengekey.$NEWCHALLENGETICKET);
3745 return "" unless $newchallengefile;
3747 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3748 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3749 ${"CGIexecute::CHALLENGETICKET"} = $NEWCHALLENGETICKET;
3751 # Write Session Ticket
3752 open(OLDCHALLENGE, "<$oldchallengefile") || die "<$oldchallengefile: $!\n";
3753 my @OldChallengeLines = <OLDCHALLENGE>;
3754 close(OLDCHALLENGE);
3755 # Old file should now be removed
3756 unlink($oldchallengefile);
3758 open(SESSION, ">$sessionpath/$newchallengefile") || die "$sessionpath/$newchallengefile: $!\n";
3759 foreach $line (@OldChallengeLines)
3761 print SESSION $line;
3763 close(SESSION);
3765 # No lingering passwords
3766 $storedpassword = $oldchallenge;
3768 return $NEWCHALLENGETICKET;
3771 sub create_login_file #($PasswordDir, $SessionDir, $IPaddress)
3773 my $PasswordDir = shift || "";
3774 my $SessionDir = shift || "";
3775 my $IPaddress = shift || "";
3777 # Create Login Ticket
3778 my $LOGINTICKET= get_random_hex ();
3780 # Create Random Hash Salt
3781 my $RANDOMSALT= get_random_hex();
3783 # Create SALT file if it does not exist
3784 # Remove this, including test account for life system
3785 unless(-d "$SessionDir")
3787 `mkdir -p "$SessionDir"`;
3789 unless(-d "$PasswordDir")
3791 `mkdir -p "$PasswordDir"`;
3793 # Create SERVERSALT and default test account
3794 my $SERVERSALT = "";
3795 unless(-s "$PasswordDir/SALT")
3797 $SERVERSALT= get_random_hex();
3798 open(SALTFILE, ">$PasswordDir/SALT") || die ">$PasswordDir/SALT: $!\n";
3799 print SALTFILE "$SERVERSALT\n";
3800 close(SALTFILE);
3802 # Update test account (should be removed in live system)
3803 my @alltestusers = ("test", "testip", "testchallenge", "admin");
3804 foreach my $testuser (@alltestusers)
3806 if(-s "$PasswordDir/$testuser")
3808 my $plainpassword = $testuser eq 'admin' ? "There is no password like more password" : "testing";
3810 my $storedpassword = hash_string(${plainpassword}.${testuser}.${SERVERSALT});
3811 # Encrypt the new password with the MasterKey
3812 my $authorization = read_ticket("$PasswordDir/$testuser") || return "";
3813 $authorization->{'Salt'} = [$SERVERSALT];
3814 $authorization->{'Type'} = ['INACTIVE PASSWORD'] if $testuser eq 'admin';
3815 set_password($authorization, $SERVERSALT, $plainpassword);
3816 write_ticket("$PasswordDir/$testuser", $authorization, $SERVERSALT);
3817 # No lingering passwords
3818 $storedpassword = $SERVERSALT;
3819 $plainpassword = $SERVERSALT;
3824 # Read in site Salt
3825 open(SALTFILE, "<$PasswordDir/SALT") || die "$PasswordDir/SALT: $!\n";
3826 $SERVERSALT=<SALTFILE>;
3827 close(SALTFILE);
3828 chomp($SERVERSALT);
3830 # Create login session ticket
3831 my $datetime = gmtime();
3832 my $timesec = time();
3833 my $loginticket = {};
3834 $loginticket->{Type} = ['LOGIN'];
3835 $loginticket->{IPaddress} = [$IPaddress];
3836 $loginticket->{Salt} = [$SERVERSALT];
3837 $loginticket->{Session} = [$LOGINTICKET];
3838 $loginticket->{Randomsalt} = [$RANDOMSALT];
3839 $loginticket->{Expires} = ['+600s'];
3840 $loginticket->{Date} = ["$datetime UTC"];
3841 $loginticket->{Time} = [$timesec];
3842 write_ticket("$SessionDir/$LOGINTICKET", $loginticket, $SERVERSALT);
3844 # Set global variables
3845 # $SERVERSALT
3846 $ENV{'SERVERSALT'} = $SERVERSALT;
3847 CGIexecute::defineCGIvariable('SERVERSALT', "");
3848 ${"CGIexecute::SERVERSALT"} = $SERVERSALT;
3850 # $SESSIONTICKET
3851 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3852 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3853 ${"CGIexecute::SESSIONTICKET"} = $SESSIONTICKET;
3855 # $RANDOMSALT
3856 $ENV{'RANDOMSALT'} = $RANDOMSALT;
3857 CGIexecute::defineCGIvariable('RANDOMSALT', "");
3858 ${"CGIexecute::RANDOMSALT"} = $RANDOMSALT;
3860 # $LOGINTICKET
3861 $ENV{'LOGINTICKET'} = $LOGINTICKET;
3862 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3863 ${"CGIexecute::LOGINTICKET"} = $LOGINTICKET;
3865 return $ENV{'LOGINTICKET'};
3868 sub create_session_file #($sessionfile, $loginfile, $authorizationfile, $path) -> Is $loginfile deleted? 0/1
3870 my $sessionfile = shift || "";
3871 my $loginfile = shift || "";
3872 my $authorizationfile = shift || "";
3873 my $path = shift || "";
3875 # Get Login session ticket
3876 my $loginticket = read_ticket($loginfile);
3877 return unlink($loginfile) unless $loginticket;
3879 # Get Authorization (user) session file
3880 my $authorization = read_ticket($authorizationfile);
3881 return unlink($loginfile) unless $authorization;
3883 # For a Session or a Challenge, we need a stored key
3884 my $sessionkey = "";
3885 my $secretkey = "";
3886 if($authorization->{'Session'} && $authorization->{'Session'}->[0] ne 'IPADDRESS')
3888 my $storedpassword = $authorization->{'Password'}->[0];
3889 my $loginticketid = $loginticket->{'Session'}->[0];
3890 my $randomsalt = $loginticket->{'Randomsalt'}->[0];
3891 $sessionkey = hash_string($storedpassword.$loginticketid);
3892 $secretkey = hash_string($storedpassword.$loginticketid.$randomsalt);
3893 # No lingering passwords
3894 $storedpassword = $loginticketid;
3896 # Get Session id
3897 my $sessionid = "";
3898 if($sessionfile =~ m!([^/]+)$!)
3900 $sessionid = $1;
3903 # Convert Authorization content to Session content
3904 my $sessionContent = {};
3905 my $SessionType = $authorization->{'Session'}->[0] ? $authorization->{'Session'}->[0] : "SESSION";
3906 $sessionContent->{Type} = [$SessionType];
3907 $sessionContent->{Username} = [lc($authorization->{'Username'}->[0])];
3908 $sessionContent->{Session} = [$sessionid];
3909 $sessionContent->{Time} = [time];
3910 # Limit communication to the login IP address, except for Tor like situations with VariableREMOTE_ADDR
3911 $sessionContent->{IPaddress} = ['.'];
3912 if($sessionContent->{Type}->[0] eq 'CHALLENGE' && grep(/^VariableREMOTE_ADDR$/, @{$authorization->{'Capabilities'}}))
3914 $sessionContent->{IPaddress} = $authorization->{'IPaddress'} if $authorization->{'IPaddress'};
3916 else
3918 $sessionContent->{IPaddress} = $loginticket->{'IPaddress'};
3920 $sessionContent->{Salt} = $authorization->{'Salt'};
3921 $sessionContent->{Randomsalt} = $loginticket->{'Randomsalt'};
3922 $sessionContent->{AllowedPaths} = $authorization->{'AllowedPaths'};
3923 $sessionContent->{DeniedPaths} = $authorization->{'DeniedPaths'};
3924 $sessionContent->{Expires} = $authorization->{'MaxLifetime'};
3925 $sessionContent->{Capabilities} = $authorization->{'Capabilities'};
3926 foreach my $pattern (keys(%TicketRequiredPatterns))
3928 if($path =~ m#$pattern#)
3930 my ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3931 push(@{$sessionContent->{Expires}}, $validtime);
3934 $sessionContent->{Key} = [$sessionkey] if $sessionkey;
3935 $sessionContent->{Secretkey} = [$secretkey] if $secretkey;
3936 $sessionContent->{Date} = [gmtime()." UTC"];
3938 # Write Session Ticket
3939 write_ticket($sessionfile, $sessionContent, $authorization->{'Salt'}->[0]);
3941 # Login file should now be removed
3942 return unlink($loginfile);
3945 sub check_ticket_validity # ($type, $ticketfile, $address, $path [, $unsigned])
3947 my $type = shift || "SESSION";
3948 my $ticketfile = shift || "";
3949 my $address = shift || "";
3950 my $path = shift || "";
3951 my $unsigned = shift || 0;
3953 # Is there a session ticket of this name?
3954 return 0 unless -s "$ticketfile";
3956 # There is a session ticket, is it linked to this IP address?
3957 my $ticket = read_ticket($ticketfile);
3958 unless($ticket)
3960 print STDERR "Ticket expired or empty: $ticketfile\n";
3961 return;
3964 # Is this the right type of ticket
3965 unless($ticket && $ticket->{'Type'}->[0] eq $type)
3967 print STDERR "Wrong ticket type: $ticket->{'Type'}->[0] eq $type\n";
3968 return;
3971 # Does the IP address match?
3972 my $IPmatches = @{$ticket->{"IPaddress"}} ? 0 : 1;
3973 for $IPpattern (@{$ticket->{"IPaddress"}})
3975 ++$IPmatches if $address =~ m#^$IPpattern#ig;
3977 if($address && ! $IPmatches)
3979 print STDERR "Wrong REMOTE ADDR for $ticket->{'Username'}->[0]: $ticket->{'IPaddress'}->[0] vs $address\n";
3980 return 0;
3983 # Is the path denied
3984 my $Pathmatches = 0;
3985 foreach $Pathpattern (@{$ticket->{"DeniedPaths"}})
3987 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3989 return 0 if @{$ticket->{"DeniedPaths"}} && $Pathmatches;
3991 # Is the path allowed
3992 $Pathmatches = 0;
3993 foreach $Pathpattern (@{$ticket->{"AllowedPaths"}})
3995 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3997 return 0 unless !@{$ticket->{"AllowedPaths"}} || $Pathmatches;
3999 # Check signature if not told to use an unsigned ticket (dangerous)
4000 my $Signature = TicketSignature($ticket, $ticket->{'Salt'}->[0]);
4001 if((! $unsigned) && $Signature && $Signature ne $ticket->{'Signature'}->[0])
4003 print STDERR "Invalid signature for $ticket->{'Type'}: $ticket->{'Username'}\n$ticketfile\n";
4004 return 0;
4007 # Make login values available (will also protect against resetting by query)
4008 $ENV{"LOGINUSERNAME"} = lc($ticket->{'Username'}->[0]);
4009 $ENV{"LOGINIPADDRESS"} = $address;
4010 $ENV{"LOGINPATH"} = $path;
4011 $ENV{"SESSIONTYPE"} = $type unless $type eq "PASSWORD";
4013 # Set Capabilities, if present
4014 if($ticket->{'Username'}->[0] && @{$ticket->{'Capabilities'}})
4016 $ENV{'CAPABILITIES'} = $ticket->{'Username'}->[0];
4017 CGIexecute::defineCGIvariableList('CAPABILITIES', "");
4018 @{"CGIexecute::CAPABILITIES"} = @{$ticket->{'Capabilities'}};
4019 # Capabilities should not be changed anymore by CGI query!
4021 # Capabilities are NOT to be set by the query
4022 CGIexecute::ProtectCGIvariable('CAPABILITIES');
4024 return 1;
4028 # This might be run in a fork()?
4029 sub remove_expired_tickets # ($path) -> number of tickets removed
4031 my $path = shift || "";
4032 return 0 unless $path;
4033 $path =~ s!/+$!!g;
4034 my $removed_tickets = 0;
4035 my @ticketlist = glob("$path/*");
4036 foreach my $ticketfile (@ticketlist)
4038 my $ticket = read_ticket($ticketfile);
4039 unless($ticket)
4041 unlink $ticketfile;
4042 ++$removed_tickets;
4045 return $removed_tickets;
4048 sub set_password # ($ticket, $salt, $plainpassword) -> $password
4050 my $ticket = shift || "";
4051 my $salt = shift || "";
4052 my $plainpassword = shift || "";
4054 my $user = lc($ticket->{'Username'}->[0]);
4055 return "" unless $user;
4056 $salt = $ticket->{'Salt'}->[0] unless $salt;
4058 my $storedpassword = hash_string(${plainpassword}.${user}.${salt});
4059 $ticket->{'Password'} = [$storedpassword];
4060 $ticket->{'Salt'} = [$salt];
4061 # No lingering passwords
4062 $storedpassword = $salt;
4063 $plainpassword = $salt;
4065 return $ticket->{'Password'}->[0];
4068 sub write_ticket # ($ticketfile, $ticket, $salt [, $masterkey]) -> &%ticket
4070 my $ticketfile = shift || "";
4071 my $ticket = shift || "";
4072 my $salt = shift || "";
4073 my $masterkey = shift || $ENV{'CGIMasterKey'};
4075 # Encrypt password
4076 EncryptTicketWithMasterKey($ticket, $salt, $masterkey);
4078 # Sign the new ticket
4079 my $signature = SignTicketWithMasterkey($ticket, $salt, $masterkey);
4081 # Create ordered list with labels
4082 my @orderlist = ('Type', 'Username', 'Password', 'IPaddress', 'AllowedPaths', 'DeniedPaths',
4083 'Expires', 'Capabilities', 'Salt', 'Session', 'Randomsalt',
4084 'Date', 'Time', 'Signature', 'Key', 'Secretkey');
4085 my @labellist = keys(%{$ticket});
4086 foreach my $label (@orderlist)
4088 @labellist = grep(!/\b$label\b/, @labellist);
4091 # Create ticket in text
4092 my $TicketText = "";
4093 foreach my $label (@orderlist, @labellist)
4095 next unless exists($ticket->{$label}) && $ticket->{$label}->[0];
4096 foreach my $value (@{$ticket->{$label}})
4098 $TicketText .= "$label: $value\n";
4101 if($ticketfile)
4103 open(TICKET, ">$ticketfile") || die "$ticketfile: $!\n";
4104 print TICKET $TicketText;
4105 close(TICKET);
4108 return $TicketText;
4111 # Note, read_ticket will return 0 if the ticket has expired!
4112 sub read_ticket # ($ticketfile [, $salt, $masterkey]) -> &%ticket
4114 my $ticketfile = shift || "";
4115 my $serversalt = shift || "";
4116 my $masterkey = shift || $ENV{'CGIMasterKey'};
4118 my $ticket = {};
4119 if($ticketfile && -s $ticketfile)
4121 open(TICKETFILE, "<$ticketfile") || die "$ticketfile: $!\n";
4122 my @alllines = <TICKETFILE>;
4123 close(TICKETFILE);
4124 foreach my $currentline (@alllines)
4126 # Skip empty lines and comments
4127 next unless $currentline =~ /\S/;
4128 next if $currentline =~ /^\s*\#/;
4130 if($currentline =~ /^\s*(\S[^\:]+)\:\s+(.*)\s*$/)
4132 my $Label = $1;
4133 my $Value = $2;
4134 $ticket->{$Label} = () unless exists($ticket->{$Label});
4135 push(@{$ticket->{$Label}}, $Value);
4139 elsif(-z $ticketfile)
4141 return 0;
4143 if($masterkey && exists($ticket->{'Password'}) && $ticket->{'Password'}->[0])
4145 # Use the ServerSalt stored in the ticket, if present
4146 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4148 $serversalt = $ticket->{Salt}->[0];
4150 # Decrypt all passwords
4151 DecryptTicketWithMasterKey($ticket, $serversalt, $masterkey) ||
4152 die "Decryption failed: DecryptTicketWithMasterKey ($ticket, $serversalt)\n";
4155 # Check whether the ticket has expired
4156 if(exists($ticket->{Expires}))
4158 my $StartTime = 0;
4159 if(exists($ticket->{Time}) && $ticket->{Time}->[0] > 0)
4161 $StartTime = [(sort(@{$ticket->{Time}}))]->[0];
4163 else
4165 # Get SessionTicket file stats
4166 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)
4167 = stat($ticketfile);
4168 $StartTime = $ctime;
4170 foreach my $Value (@{$ticket->{'Expires'}})
4172 # Recalculate expire date from relative time
4173 if($Value =~ /^\+/)
4175 if($Value =~ /^\+(\d+)\s*d(ays)?\s*$/)
4177 $ExpireTime = 24*3600*$1;
4179 elsif($Value =~ /^\+(\d+)\s*m(inutes)?\s*$/)
4181 $ExpireTime = 60*$1;
4183 elsif($Value =~ /^\+(\d+)\s*h(ours)?\s*$/)
4185 $ExpireTime = 3600*$1;
4187 elsif($Value =~ /^\+(\d+)\s*s(econds)?\s*$/)
4189 $ExpireTime = $1;
4191 elsif($Value =~ /^\+(\d+)\s*$/)
4193 $ExpireTime = $1;
4196 my $absoluteTime = $Value =~ /^\+/ ? $StartTime + $ExpireTime : $Value;
4197 return 0 unless $absoluteTime > time;
4199 @{$ticket->{Expires}} = sort(@{$ticket->{Expires}});
4201 return $ticket;
4204 # Set up a valid ticket from a given text file
4205 # Use from command line. DO NOT USE ONLINE
4206 # Watch out for passwords that get stored in the history file
4208 # perl CGIscriptor.pl --managelogin [options] [files]
4209 # Options:
4210 # salt={file or saltvalue}
4211 # masterkey={file or plaintext}
4212 # newmasterkey={file or plaintext}
4213 # password={file or palintext}
4215 # Followed by one or more file names.
4216 # Options can be interspersed between filenames,
4217 # e.g., password='plaintext'
4218 # Note that passwords are only used once!
4220 sub setup_ticket_file # (@ARGV)
4222 # Stop when run on-line
4223 return if $ENV{'PATH_INFO'} || $ENV{'QUERY_STRING'};
4225 my %Settings = ();
4226 foreach my $input (@_)
4228 if($input =~ /^([\w]+)\=/)
4230 my $name = lc($1);
4231 my $value = $';
4232 chomp($value);
4234 if($value !~ m![^\w\.\~\/\:\-]! && $value !~ /^[\-\.]/ && -s "$value" && ! -d "$value")
4236 # Warn about reading a value from file
4237 print STDERR "Read '$name' from: '$value'\n";
4238 open(INPUTVALUE, "<$value") || die "$value: $!\n";
4239 $value = <INPUTVALUE>;
4240 chomp($value);
4243 $value =~ s/(^\'([^\']*)\'$)/\1/g;
4244 $value =~ s/(^\"([^\"]*)\"$)/\1/g;
4245 $Settings{$name} = $value;
4247 elsif($input !~ m![^\w\.\~\/\:\-]!i && $input !~ /^[\-\.]/i && -s $input)
4249 # We MUST have a salt
4250 $Settings{'salt'} = $ticket->{'Salt'}->[0] unless $Settings{'salt'};
4252 # Set the new masterkey to the old masterkey if there is no new masterkey
4253 $Settings{'newmasterkey'} = $Settings{'masterkey'} unless exists($Settings{'newmasterkey'});
4255 # Get the ticket
4256 my $ticket = read_ticket($input, $Settings{'salt'}, $Settings{'masterkey'});
4258 # Set a new password from plaintext
4259 $ticket->{'Salt'}->[0] = $Settings{'salt'} if $Settings{'salt'} && $Settings{'password'};
4260 set_password ($ticket, $Settings{'salt'}, $Settings{'password'}) if $Settings{'password'};
4261 # Write the ticket back to file
4262 write_ticket($input, $ticket, $Settings{'salt'}, $Settings{'newmasterkey'});
4264 # A password is only used once
4265 $Settings{'password'} = "";
4270 # Add a signature from $masterkey to a ticket in the label $signlabel
4271 sub SignTicketWithMasterkey # ($ticket, $serversalt [, $masterkey, $signlabel]) -> $Signature
4273 my $ticket = shift || return 0;
4274 my $serversalt = shift || "";
4275 my $masterkey = shift || $ENV{'CGIMasterKey'};
4276 my $signlabel = shift || 'Signature';
4278 my $Signature = TicketSignature($ticket, $serversalt, $masterkey);
4280 $ticket->{$signlabel} = [$Signature] if $Signature;
4282 return $Signature;
4285 # Determine ticket signature
4286 sub TicketSignature # ($ticket, $serversalt [, $masterkey]) -> $Signature
4288 my $ticket = shift || return 0;
4289 my $serversalt = shift || "";
4290 my $masterkey = shift || $ENV{'CGIMasterKey'};
4291 my $Signature = "";
4293 if($masterkey)
4295 # If the ServerSalt is not stored in the ticket, the SALT file has to be found
4296 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4298 $serversalt = $ticket->{Salt}->[0];
4300 # Sign
4301 if($serversalt)
4303 my $username = lc($ticket->{'Username'}->[0]);
4304 my $hash1 = hash_string(${masterkey}.${serversalt});
4305 # The order of $username.$hash1 should be different than in DecryptTicketWithMasterKey
4306 my $CryptKey = hash_string($username.${'hash1'});
4307 my $SignText = "Type: ".$ticket->{'Type'}->[0]."\n";
4308 my @tmp = sort(@{$ticket->{'Username'}});
4309 $SignText .= "Username: @tmp\n";
4310 @tmp = sort(@{$ticket->{'IPaddress'}});
4311 $SignText .= "IPaddress: @tmp\n";
4312 @tmp = sort(@{$ticket->{'AllowedPaths'}});
4313 $SignText .= "AllowedPaths: @tmp\n";
4314 @tmp = sort(@{$ticket->{'DeniedPaths'}});
4315 $SignText .= "DeniedPaths: @tmp\n";
4316 @tmp = sort(@{$ticket->{'Session'}});
4317 $SignText .= "Session: @tmp\n";
4318 @tmp = sort(@{$ticket->{'Time'}});
4319 $SignText .= "Time: @tmp\n";
4320 @tmp = sort(@{$ticket->{'Expires'}});
4321 $SignText .= "Expires: @tmp\n";
4322 @tmp = sort(@{$ticket->{'Capabilities'}});
4323 $SignText .= "Capabilities: @tmp\n";
4324 @tmp = sort(@{$ticket->{'MaxLifetime'}});
4325 $SignText .= "MaxLifetime: @tmp\n";
4326 $Signature = HMAC_hex($CryptKey, $SignText);
4329 return $Signature;
4332 # Decrypts a password list IN PLACE
4333 sub DecryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4335 my $ticket = shift || return 0;
4336 my $serversalt = shift || "";
4337 my $masterkey = shift || $ENV{'CGIMasterKey'};
4339 if($masterkey && exists($ticket->{Password}) && $ticket->{Password}->[0])
4341 # If the ServerSalt is not given, read it from the the ticket
4342 if(! $serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4344 $serversalt = $ticket->{Salt}->[0];
4346 # Decrypt password(s)
4347 if($serversalt)
4349 my $hash1 = hash_string(${masterkey}.${serversalt});
4350 my $username = lc($ticket->{'Username'}->[0]);
4351 # The order of $hash1.$username should be different than in TicketSignature
4352 my $CryptKey = hash_string(${'hash1'}.$username);
4353 foreach my $password (@{$ticket->{Password}})
4355 $password = XOR_hex_strings($CryptKey,$password);
4359 return $ticket->{'Password'};
4361 sub EncryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4363 DecryptTicketWithMasterKey(@_);
4366 # Implement HMAC signature hash.
4367 # Blocksize is length in HEX characters, NOT bytes
4368 sub HMAC_hex # ($key, $message [, $blocksizehex]) -> $hex
4370 my $key = shift || "";
4371 my $message = shift || "";
4372 my $blocksizehex = shift || length($key);
4373 $key = hash_string($key) if length($key) > $blocksizehex;
4375 my $innerkey = XOR_hex_byte ($key, "36");
4376 my $outerkey = XOR_hex_byte ($key, "5c");
4377 my $innerhash = hash_string($innerkey.$message);
4378 my $outerhash = hash_string($outerkey.$innerhash);
4380 return $outerhash;
4383 # XOR input with equally long string of repeated 2 hex character (byte)
4384 # string. Input must have even number of hex characters
4385 sub XOR_hex_byte # ($hex1, $hexbyte) -> $hex
4387 my $hex1 = shift || "";
4388 my $hexbyte = shift || "";
4389 my $bytelength = length($hexbyte);
4390 my $hex2 = $hex1;
4391 $hex2 =~ s/.{$bytelength}/$hexbyte/ig;
4392 return XOR_hex_strings($hex1, $hex2);
4395 sub XOR_hex_strings # ($hex1, $hex2) -> $hex
4397 my $hex1 = shift || "";
4398 my $hex2 = shift || "";
4399 my @hex1list = split('', $hex1);
4400 my @hex2list = split('', $hex2);
4401 my @hexresultlist = ();
4402 for(my $i; $i < scalar(@hex1list); ++$i)
4404 my $d1 = hex($hex1list[$i]);
4405 my $d2 = hex($hex2list[$i]);
4406 my $dresult = ($d1 ^ $d2);
4407 $hexresultlist[$i] = sprintf("%x", $dresult);
4409 $hexresult = join('', @hexresultlist);
4410 return $hexresult;
4413 # End of Handle login access
4416 ############################################################################
4418 # Handle foreign interpreters (i.e., scripting languages)
4420 # Insert perl code to execute scripts in foreign scripting languages.
4421 # Actually, the scripts inside the <SCRIPT></SCRIPT> blocks are piped
4422 # into an interpreter.
4423 # The code presented here is fairly confusing because it
4424 # actually writes perl code code to the output.
4426 # A table with the file handles
4427 %SCRIPTINGINPUT = ();
4429 # A function to clean up Client delivered CGI parameter values
4430 # (i.e., quote all odd characters)
4431 %SHRUBcharacterTR =
4433 "\'" => '&#39;',
4434 "\`" => '&#96;',
4435 "\"" => '&quot;',
4436 '&' => '&amper;',
4437 "\\" => '&#92;'
4440 sub shrubCGIparameter # ($String) -> Cleaned string
4442 my $String = shift || "";
4444 # Change all quotes [`'"] into HTML character entities
4445 my ($Char, $Transcript) = ('&', $SHRUBcharacterTR{'&'});
4447 # Protect &
4448 $String =~ s/\Q$Char\E/$Transcript/isg if $Transcript;
4450 while( ($Char, $Transcript) = each %SHRUBcharacterTR)
4452 next if $Char eq '&';
4453 $String =~ s/\Q$Char\E/$Transcript/isg;
4456 # Replace newlines
4457 $String =~ s/[\n]/\\n/g;
4458 # Replace control characters with their backslashed octal ordinal numbers
4459 $String =~ s/([^\S \t])/(sprintf("\\0%o", ord($1)))/eisg; #
4460 $String =~ s/([\x00-\x08\x0A-\x1F])/(sprintf("\\0%o", ord($1)))/eisg; #
4462 return $String;
4466 # The initial open statements: Open a pipe to the foreign script interpreter
4467 sub OpenForeignScript # ($ContentType) -> $DirectivePrefix
4469 my $ContentType = lc(shift) || return "";
4470 my $NewDirective = "";
4472 return $NewDirective if($SCRIPTINGINPUT{$ContentType});
4474 # Construct a unique file handle name
4475 $SCRIPTINGFILEHANDLE = uc($ContentType);
4476 $SCRIPTINGFILEHANDLE =~ s/\W/\_/isg;
4477 $SCRIPTINGINPUT{$ContentType} = $SCRIPTINGFILEHANDLE
4478 unless $SCRIPTINGINPUT{$ContentType};
4480 # Create the relevant script: Open the pipe to the interpreter
4481 $NewDirective .= <<"BLOCKCGISCRIPTOROPEN";
4482 # Open interpreter for '$ContentType'
4483 # Open pipe to interpreter (if it isn't open already)
4484 open($SCRIPTINGINPUT{$ContentType}, "|$ScriptingLanguages{$ContentType}") || main::dieHandler(14, "$ContentType: \$!\\n");
4485 BLOCKCGISCRIPTOROPEN
4487 # Insert Initialization code and CGI variables
4488 $NewDirective .= InitializeForeignScript($ContentType);
4490 # Ready
4491 return $NewDirective;
4495 # The final closing code to stop the interpreter
4496 sub CloseForeignScript # ($ContentType) -> $DirectivePrefix
4498 my $ContentType = lc(shift) || return "";
4499 my $NewDirective = "";
4501 # Do nothing unless the pipe realy IS open
4502 return "" unless $SCRIPTINGINPUT{$ContentType};
4504 # Initial comment
4505 $NewDirective .= "\# Close interpreter for '$ContentType'\n";
4508 # Write the Postfix code
4509 $NewDirective .= CleanupForeignScript($ContentType);
4511 # Create the relevant script: Close the pipe to the interpreter
4512 $NewDirective .= <<"BLOCKCGISCRIPTORCLOSE";
4513 close($SCRIPTINGINPUT{$ContentType}) || main::dieHandler(15, \"$ContentType: \$!\\n\");
4514 select(STDOUT); \$|=1;
4516 BLOCKCGISCRIPTORCLOSE
4518 # Remove the file handler of the foreign script
4519 delete($SCRIPTINGINPUT{$ContentType});
4521 return $NewDirective;
4525 # The initialization code for the foreign script interpreter
4526 sub InitializeForeignScript # ($ContentType) -> $DirectivePrefix
4528 my $ContentType = lc(shift) || return "";
4529 my $NewDirective = "";
4531 # Add initialization code
4532 if($ScriptingInitialization{$ContentType})
4534 $NewDirective .= <<"BLOCKCGISCRIPTORINIT";
4535 # Initialization Code for '$ContentType'
4536 # Select relevant output filehandle
4537 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4539 # The Initialization code (if any)
4540 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}INITIALIZATIONCODE';
4541 $ScriptingInitialization{$ContentType}
4542 ${ContentType}INITIALIZATIONCODE
4544 BLOCKCGISCRIPTORINIT
4547 # Add all CGI variables defined
4548 if(exists($ScriptingCGIvariables{$ContentType}))
4550 # Start writing variable definitions to the Interpreter
4551 if($ScriptingCGIvariables{$ContentType})
4553 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEF";
4554 # CGI variables (from the %default_values table)
4555 print $SCRIPTINGINPUT{$ContentType} << '${ContentType}CGIVARIABLES';
4556 BLOCKCGISCRIPTORVARDEF
4559 my ($N, $V);
4560 foreach $N (keys(%default_values))
4562 # Determine whether the parameter has been defined
4563 # (the eval is a workaround to get at the variable value)
4564 next unless eval("defined(\$CGIexecute::$N)");
4566 # Get the value from the EXECUTION environment
4567 $V = eval("\$CGIexecute::$N");
4568 # protect control characters (i.e., convert them to \0.. form)
4569 $V = shrubCGIparameter($V);
4571 # Protect interpolated variables
4572 eval("\$CGIexecute::$N = '$V';") unless $ScriptingCGIvariables{$ContentType};
4574 # Print the actual declaration for this scripting language
4575 if($ScriptingCGIvariables{$ContentType})
4577 $NewDirective .= sprintf($ScriptingCGIvariables{$ContentType}, $N, $V);
4578 $NewDirective .= "\n";
4582 # Stop writing variable definitions to the Interpreter
4583 if($ScriptingCGIvariables{$ContentType})
4585 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEFEND";
4586 ${ContentType}CGIVARIABLES
4587 BLOCKCGISCRIPTORVARDEFEND
4592 $NewDirective .= << "BLOCKCGISCRIPTOREND";
4594 # Select STDOUT filehandle
4595 select(STDOUT); \$|=1;
4597 BLOCKCGISCRIPTOREND
4599 return $NewDirective;
4603 # The cleanup code for the foreign script interpreter
4604 sub CleanupForeignScript # ($ContentType) -> $DirectivePrefix
4606 my $ContentType = lc(shift) || return "";
4607 my $NewDirective = "";
4609 # Return if not needed
4610 return $NewDirective unless $ScriptingCleanup{$ContentType};
4612 # Create the relevant script: Open the pipe to the interpreter
4613 $NewDirective .= <<"BLOCKCGISCRIPTORSTOP";
4614 # Cleanup Code for '$ContentType'
4615 # Select relevant output filehandle
4616 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4617 # Print Cleanup code to foreign script
4618 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}SCRIPTSTOP';
4619 $ScriptingCleanup{$ContentType}
4620 ${ContentType}SCRIPTSTOP
4622 # Select STDOUT filehandle
4623 select(STDOUT); \$|=1;
4624 BLOCKCGISCRIPTORSTOP
4626 return $NewDirective;
4630 # The prefix code for each <script></script> block
4631 sub PrefixForeignScript # ($ContentType) -> $DirectivePrefix
4633 my $ContentType = lc(shift) || return "";
4634 my $NewDirective = "";
4636 # Return if not needed
4637 return $NewDirective unless $ScriptingPrefix{$ContentType};
4639 my $Quote = "\'";
4640 # If the CGIvariables parameter is defined, but empty, interpolate
4641 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4642 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4643 !$ScriptingCGIvariables{$ContentType};
4645 # Add initialization code
4646 $NewDirective .= <<"BLOCKCGISCRIPTORPREFIX";
4647 # Prefix Code for '$ContentType'
4648 # Select relevant output filehandle
4649 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4651 # The block Prefix code (if any)
4652 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}PREFIXCODE$Quote;
4653 $ScriptingPrefix{$ContentType}
4654 ${ContentType}PREFIXCODE
4655 # Select STDOUT filehandle
4656 select(STDOUT); \$|=1;
4657 BLOCKCGISCRIPTORPREFIX
4659 return $NewDirective;
4663 # The postfix code for each <script></script> block
4664 sub PostfixForeignScript # ($ContentType) -> $DirectivePrefix
4666 my $ContentType = lc(shift) || return "";
4667 my $NewDirective = "";
4669 # Return if not needed
4670 return $NewDirective unless $ScriptingPostfix{$ContentType};
4672 my $Quote = "\'";
4673 # If the CGIvariables parameter is defined, but empty, interpolate
4674 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4675 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4676 !$ScriptingCGIvariables{$ContentType};
4678 # Create the relevant script: Open the pipe to the interpreter
4679 $NewDirective .= <<"BLOCKCGISCRIPTORPOSTFIX";
4680 # Postfix Code for '$ContentType'
4681 # Select filehandle to interpreter
4682 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4683 # Print postfix code to foreign script
4684 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SCRIPTPOSTFIX$Quote;
4685 $ScriptingPostfix{$ContentType}
4686 ${ContentType}SCRIPTPOSTFIX
4687 # Select STDOUT filehandle
4688 select(STDOUT); \$|=1;
4689 BLOCKCGISCRIPTORPOSTFIX
4691 return $NewDirective;
4694 sub InsertForeignScript # ($ContentType, $directive, @SRCfile) -> $NewDirective
4696 my $ContentType = lc(shift) || return "";
4697 my $directive = shift || return "";
4698 my @SRCfile = @_;
4699 my $NewDirective = "";
4701 my $Quote = "\'";
4702 # If the CGIvariables parameter is defined, but empty, interpolate
4703 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4704 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4705 !$ScriptingCGIvariables{$ContentType};
4707 # Create the relevant script
4708 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4709 # Insert Code for '$ContentType'
4710 # Select filehandle to interpreter
4711 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4712 BLOCKCGISCRIPTORINSERT
4714 # Use SRC feature files
4715 my $ThisSRCfile;
4716 while($ThisSRCfile = shift(@_))
4718 # Handle blocks
4719 if($ThisSRCfile =~ /^\s*\{\s*/)
4721 my $Block = $';
4722 $Block = $` if $Block =~ /\s*\}\s*$/;
4723 $NewDirective .= <<"BLOCKCGISCRIPTORSRCBLOCK";
4724 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SRCBLOCKCODE$Quote;
4725 $Block
4726 ${ContentType}SRCBLOCKCODE
4727 BLOCKCGISCRIPTORSRCBLOCK
4729 next;
4732 # Handle files
4733 $NewDirective .= <<"BLOCKCGISCRIPTORSRCFILES";
4734 # Read $ThisSRCfile
4735 open(SCRIPTINGSOURCE, "<$ThisSRCfile") || main::dieHandler(16, "$ThisSRCfILE: \$!");
4736 while(<SCRIPTINGSOURCE>)
4738 print $SCRIPTINGINPUT{$ContentType} \$_;
4740 close(SCRIPTINGSOURCE);
4742 BLOCKCGISCRIPTORSRCFILES
4746 # Add the directive
4747 if($directive)
4749 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4750 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}DIRECTIVECODE$Quote;
4751 $directive
4752 ${ContentType}DIRECTIVECODE
4753 BLOCKCGISCRIPTORINSERT
4757 $NewDirective .= <<"BLOCKCGISCRIPTORSELECT";
4758 # Select STDOUT filehandle
4759 select(STDOUT); \$|=1;
4760 BLOCKCGISCRIPTORSELECT
4762 # Ready
4763 return $NewDirective;
4766 sub CloseAllForeignScripts # Call CloseForeignScript on all open scripts
4768 my $ContentType;
4769 foreach $ContentType (keys(%SCRIPTINGINPUT))
4771 my $directive = CloseForeignScript($ContentType);
4772 print STDERR "\nDirective $CGI_Date: ", $directive;
4773 CGIexecute->evaluate($directive);
4778 # End of handling foreign (external) scripting languages.
4780 ############################################################################
4782 # A subroutine to handle "nested" quotes, it cuts off the leading
4783 # item or quoted substring
4784 # E.g.,
4785 # ' A_word and more words' -> @('A_word', ' and more words')
4786 # '"quoted string" The rest' -> @('quoted string', ' The rest')
4787 # (this is needed for parsing the <TAGS> and their attributes)
4788 my $SupportedQuotes = "\'\"\`\(\{\[";
4789 my %QuotePairs = ('('=>')','['=>']','{'=>'}'); # Brackets
4790 sub ExtractQuotedItem # ($String) -> @($QuotedString, $RestOfString)
4792 my @Result = ();
4793 my $String = shift || return @Result;
4795 if($String =~ /^\s*([\w\/\-\.]+)/is)
4797 push(@Result, $1, $');
4799 elsif($String =~ /^\s*(\\?)([\Q$SupportedQuotes\E])/is)
4801 my $BackSlash = $1 || "";
4802 my $OpenQuote = $2;
4803 my $CloseQuote = $OpenQuote;
4804 $CloseQuote = $QuotePairs{$OpenQuote} if $QuotePairs{$OpenQuote};
4806 if($BackSlash)
4808 $String =~ /^\s*\\\Q$OpenQuote\E/i;
4809 my $Onset = $';
4810 $Onset =~ /\\\Q$CloseQuote\E/i;
4811 my $Rest = $';
4812 my $Item = $`;
4813 push(@Result, $Item, $Rest);
4816 else
4818 $String =~ /^\s*\Q$OpenQuote\E([^\Q$CloseQuote\E]*)\Q$CloseQuote\E/i;
4819 push(@Result, $1, $');
4822 else
4824 push(@Result, "", $String);
4826 return @Result;
4829 # Now, start with the real work
4831 # Control the output of the Content-type: text/html\n\n message
4832 my $SupressContentType = 0;
4834 # Process a file
4835 sub ProcessFile # ($file_path)
4837 my $file_path = shift || return 0;
4840 # Generate a unique file handle (for recursions)
4841 my @SRClist = ();
4842 my $FileHandle = "file";
4843 my $n = 0;
4844 while(!eof($FileHandle.$n)) {++$n;};
4845 $FileHandle .= $n;
4847 # Start HTML output
4848 # Use the default Content-type if this is NOT a raw file
4849 unless(($RawFilePattern && $ENV{'PATH_INFO'} =~ m@($RawFilePattern)$@i)
4850 || $SupressContentType)
4852 $ENV{'PATH_INFO'} =~ m@($FilePattern)$@i;
4853 my $ContentType = $ContentTypeTable{$1};
4854 print "Content-type: $ContentType\n";
4855 if(%SETCOOKIELIST && keys(%SETCOOKIELIST))
4857 foreach my $name (keys(%SETCOOKIELIST))
4859 my $value = $SETCOOKIELIST{$name};
4860 print "Set-Cookie: $name=$value\n";
4862 # Cookies are set only ONCE
4863 %SETCOOKIELIST = ();
4865 print "\n";
4866 $SupressContentType = 1; # Content type has been printed
4870 # Get access to the actual data. This can be from RAM (by way of an
4871 # environment variable) or by opening a file.
4873 # Handle the use of RAM images (file-data is stored in the
4874 # $CGI_FILE_CONTENTS environment variable)
4875 # Note that this environment variable will be cleared, i.e., it is strictly for
4876 # single-use only!
4877 if($ENV{$CGI_FILE_CONTENTS})
4879 # File has been read already
4880 $_ = $ENV{$CGI_FILE_CONTENTS};
4881 # Sorry, you have to do the reading yourself (dynamic document creation?)
4882 # NOTE: you must read the whole document at once
4883 if($_ eq '-')
4885 $_ = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
4887 else # Clear environment variable
4889 $ENV{$CGI_FILE_CONTENTS} = '-';
4892 # Open Only PLAIN TEXT files (or STDIN) and NO executable files (i.e., scripts).
4893 # THIS IS A SECURITY FEATURE!
4894 elsif($file_path eq '-' || (-e "$file_path" && -r _ && -T _ && -f _ && ! (-x _ || -X _) ))
4896 open($FileHandle, $file_path) || dieHandler(17, "<h2>File not found</h2>\n");
4897 push(@OpenFiles, $file_path);
4898 $_ = <$FileHandle>; # Read first line
4900 else
4902 print "<h2>File not found</h2>\n";
4903 dieHandler(18, "$file_path\n");
4906 $| = 1; # Flush output buffers
4908 # Initialize variables
4909 my $METAarguments = ""; # The CGI arguments from the latest META tag
4910 my @METAvalues = (); # The ''-quoted CGI values from the latest META tag
4911 my $ClosedTag = 0; # <TAG> </TAG> versus <TAG/>
4914 # Send document to output
4915 # Process the requested document.
4916 # Do a loop BEFORE reading input again (this catches the RAM/Database
4917 # type of documents).
4918 do {
4921 # Handle translations if needed
4923 performTranslation(\$_) if $TranslationPaths;
4925 # Catch <SCRIPT LANGUAGE="PERL" TYPE="text/ssperl" > directives in $_
4926 # There can be more than 1 <SCRIPT> or META tags on a line
4927 while(/\<\s*(SCRIPT|META|DIV|INS)\s/is)
4929 my $directive = "";
4930 # Store rest of line
4931 my $Before = $`;
4932 my $ScriptTag = $&;
4933 my $After = $';
4934 my $TagType = uc($1);
4935 # The before part can be send to the output
4936 print $Before;
4938 # Read complete Tag from after and/or file
4939 until($After =~ /([^\\])\>/)
4941 $After .= <$FileHandle>;
4942 performTranslation(\$After) if $TranslationPaths;
4945 if($After =~ /([^\\])\>/)
4947 $ScriptTag .= $`.$&; # Keep the Script Tag intact
4948 $After = $';
4950 else
4952 dieHandler(19, "Closing > not found\n");
4955 # The tag could be closed by />, we handle this in the XML way
4956 # and don't process any content (we ignore whitespace)
4957 $ClosedTag = ($ScriptTag =~ m@[^\\]/\s*\>\s*$@) ? 1 : 0;
4960 # TYPE or CLASS?
4961 my $TypeName = ($TagType =~ /META/is) ? "CONTENT" : "TYPE";
4962 $TypeName = "CLASS" if $TagType eq 'DIV' || $TagType eq 'INS';
4964 # Parse <SCRIPT> or <META> directive
4965 # If NOT (TYPE|CONTENT)="text/ssperl" (i.e., $ServerScriptContentType),
4966 # send the line to the output and go to the next loop
4967 my $CurrentContentType = "";
4968 if($ScriptTag =~ /(^|\s)$TypeName\s*=\s*/is)
4970 my ($Type) = ExtractQuotedItem($');
4971 $Type =~ /^\s*([\w\/\-]+)\s*[\,\;]?/;
4972 $CurrentContentType = lc($1); # Note: mime-types are "case-less"
4973 # CSS classes are aliases of $ServerScriptContentType
4974 if($TypeName eq "CLASS" && $CurrentContentType eq $ServerScriptContentClass)
4976 $CurrentContentType = $ServerScriptContentType;
4981 # Not a known server-side content type, print and continue
4982 unless(($CurrentContentType =~
4983 /$ServerScriptContentType|$ShellScriptContentType/is) ||
4984 $ScriptingLanguages{$CurrentContentType})
4986 print $ScriptTag;
4987 $_ = $After;
4988 next;
4992 # A known server-side content type, evaluate
4994 # First, handle \> and \<
4995 $ScriptTag =~ s/\\\>/\>/isg;
4996 $ScriptTag =~ s/\\\</\</isg;
4998 # Extract the CGI, SRC, ID, IF and UNLESS attributes
4999 my %ScriptTagAttributes = ();
5000 while($ScriptTag =~ /(^|\s)(CGI|IF|UNLESS|SRC|ID)\s*=\s*/is)
5002 my $Attribute = $2;
5003 my $Rest = $';
5004 my $Value = "";
5005 ($Value, $ScriptTag) = ExtractQuotedItem($Rest);
5006 $ScriptTagAttributes{uc($Attribute)} = $Value;
5010 # The attribute used to define the CGI variables
5011 # Extract CGI-variables from
5012 # <META CONTENT="text/ssperl; CGI='' SRC=''">
5013 # <SCRIPT TYPE='text/ssperl' CGI='' SRC=''>
5014 # <DIV CLASS='ssperl' CGI='' SRC='' ID=""> tags
5015 # <INS CLASS='ssperl' CGI='' SRC='' ID=""> tags
5016 if($ScriptTagAttributes{'CGI'})
5018 @ARGV = (); # Reset ARGV
5019 $ARGC = 0;
5020 $METAarguments = ""; # Reset the META CGI arguments
5021 @METAvalues = ();
5022 my $Meta_CGI = $ScriptTagAttributes{'CGI'};
5024 # Process default values of variables ($<name> = 'default value')
5025 # Allowed quotes are '', "", ``, (), [], and {}
5026 while($Meta_CGI =~ /(^\s*|[^\\])([\$\@\%]?)([\w\-]+)\s*/is)
5028 my $varType = $2 || '$'; # Variable or list
5029 my $name = $3; # The Name
5030 my $default = "";
5031 $Meta_CGI = $';
5033 if($Meta_CGI =~ /^\s*\=\s*/is)
5035 # Locate (any) default value
5036 ($default, $Meta_CGI) = ExtractQuotedItem($'); # Cut the parameter from the CGI
5038 $RemainingTag = $Meta_CGI;
5041 # Define CGI (or ENV) variable, initalize it from the
5042 # Query string or the default value
5044 # Also construct the @ARGV and @_ arrays. This allows other (SRC=) Perl
5045 # scripts to access the CGI arguments defined in the META tag
5046 # (Not for CGI inside <SCRIPT> tags)
5047 if($varType eq '$')
5049 CGIexecute::defineCGIvariable($name, $default)
5050 || dieHandler(20, "INVALID CGI name/value pair ($name, $default)\n");
5051 push(@METAvalues, "'".${"CGIexecute::$name"}."'");
5052 # Add value to the @ARGV list
5053 push(@ARGV, ${"CGIexecute::$name"});
5054 ++$ARGC;
5056 elsif($varType eq '@')
5058 CGIexecute::defineCGIvariableList($name, $default)
5059 || dieHandler(21, "INVALID CGI name/value list pair ($name, $default)\n");
5060 push(@METAvalues, "'".join("'", @{"CGIexecute::$name"})."'");
5061 # Add value to the @ARGV list
5062 push(@ARGV, @{"CGIexecute::$name"});
5063 $ARGC = scalar(@CGIexecute::ARGV);
5065 elsif($varType eq '%')
5067 CGIexecute::defineCGIvariableHash($name, $default)
5068 || dieHandler(22, "INVALID CGI name/value hash pair ($name, $default)\n");
5069 my @PairList = map {"$_ => ".${"CGIexecute::$name"}{$_}} keys(%{"CGIexecute::$name"});
5070 push(@METAvalues, "'".join("'", @PairList)."'");
5071 # Add value to the @ARGV list
5072 push(@ARGV, %{"CGIexecute::$name"});
5073 $ARGC = scalar(@CGIexecute::ARGV);
5076 # Store the values for internal and later use
5077 $METAarguments .= "$varType".$name.","; # A string of CGI variable names
5079 push(@METAvalues, "\'".eval("\"$varType\{CGIexecute::$name\}\"")."\'"); # ALWAYS add '-quotes around values
5084 # The IF (conditional execution) Attribute
5085 # Evaluate the condition and stop unless it evaluates to true
5086 if($ScriptTagAttributes{'IF'})
5088 my $IFcondition = $ScriptTagAttributes{'IF'};
5090 # Convert SCRIPT calls, ./<script>
5091 $IFcondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5093 # Convert FILE calls, ~/<file>
5094 $IFcondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5096 # Block execution if necessary
5097 unless(CGIexecute->evaluate($IFcondition))
5099 %ScriptTagAttributes = ();
5100 $CurrentContentType = "";
5104 # The UNLESS (conditional execution) Attribute
5105 # Evaluate the condition and stop if it evaluates to true
5106 if($ScriptTagAttributes{'UNLESS'})
5108 my $UNLESScondition = $ScriptTagAttributes{'UNLESS'};
5110 # Convert SCRIPT calls, ./<script>
5111 $UNLESScondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5113 # Convert FILE calls, ~/<file>
5114 $UNLESScondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5116 # Block execution if necessary
5117 if(CGIexecute->evaluate($UNLESScondition))
5119 %ScriptTagAttributes = ();
5120 $CurrentContentType = "";
5124 # The SRC (Source File) Attribute
5125 # Extract any source script files and add them in
5126 # front of the directive
5127 # The SRC list should be emptied
5128 @SRClist = ();
5129 my $SRCtag = "";
5130 my $Prefix = 1;
5131 my $PrefixDirective = "";
5132 my $PostfixDirective = "";
5133 # There is a SRC attribute
5134 if($ScriptTagAttributes{'SRC'})
5136 $SRCtag = $ScriptTagAttributes{'SRC'};
5137 # Remove "file://" prefixes
5138 $SRCtag =~ s@([^\w\/\\]|^)file\://([^\s\/\@\=])@$1$2@gis;
5139 # Expand script filenames "./Script"
5140 $SRCtag =~ s@([^\w\/\\]|^)\./([^\s\/\@\=])@$1$SCRIPT_SUB/$2@gis;
5141 # Expand script filenames "~/Script"
5142 $SRCtag =~ s@([^\w\/\\]|^)\~/([^\s\/\@\=])@$1$HOME_SUB/$2@gis;
5145 # File source tags
5146 while($SRCtag =~ /\S/is)
5148 my $SRCdirective = "";
5150 # Pseudo file, just a switch to go from PREFIXING to POSTFIXING
5151 # SRC files
5152 if($SRCtag =~ /^[\s\;\,]*(POSTFIX|PREFIX)([^$FileAllowedChars]|$)/is)
5154 my $InsertionPlace = $1;
5155 $SRCtag = $2.$';
5157 $Prefix = $InsertionPlace =~ /POSTFIX/i ? 0 : 1;
5158 # Go to next round
5159 next;
5161 # {}-blocks are just evaluated by "do"
5162 elsif($SRCtag =~ /^[\s\;\,]*\{/is)
5164 my $SRCblock = $';
5165 if($SRCblock =~ /\}[\s\;\,]*([^\}]*)$/is)
5167 $SRCblock = $`;
5168 $SRCtag = $1.$';
5169 # SAFEqx shell script blocks
5170 if($CurrentContentType =~ /$ShellScriptContentType/is)
5172 # Handle ''-quotes inside the script
5173 $SRCblock =~ s/[\']/\\$&/gis;
5175 $SRCblock = "print do { SAFEqx(\'".$SRCblock."\'); };'';";
5176 $SRCdirective .= $SRCblock."\n";
5178 # do { SRCblocks }
5179 elsif($CurrentContentType =~ /$ServerScriptContentType/is)
5181 $SRCblock = "print do { $SRCblock };'';";
5182 $SRCdirective .= $SRCblock."\n";
5184 else # The interpreter should handle this
5186 push(@SRClist, "{ $SRCblock }");
5190 else
5191 { dieHandler(23, "Closing \} missing\n");};
5193 # Files are processed as Text or Executable files
5194 elsif($SRCtag =~ /[\s\;\,]*([$FileAllowedChars]+)[\;\,\s]*/is)
5196 my $SrcFile = $1;
5197 $SRCtag = $';
5199 # We are handling one of the external interpreters
5200 if($ScriptingLanguages{$CurrentContentType})
5202 push(@SRClist, $SrcFile);
5204 # We are at the start of a DIV tag, just load all SRC files and/or URL's
5205 elsif($TagType eq 'DIV' || $TagType eq 'INS') # All files are prepended in DIV's
5207 # $SrcFile is a URL pointing to an HTTP or FTP server
5208 if($SrcFile =~ m!^([a-z]+)\://!)
5210 my $URLoutput = CGIscriptor::read_url($SrcFile);
5211 $SRCdirective .= $URLoutput;
5213 # SRC file is an existing file
5214 elsif(-e "$SrcFile")
5216 open(DIVSOURCE, "<$SrcFile") || dieHandler(24, "<$SrcFile: $!\n");
5217 my $Content;
5218 while(sysread(DIVSOURCE, $Content, 1024) > 0)
5220 $SRCdirective .= $Content;
5222 close(DIVSOURCE);
5225 # Executable files are executed as
5226 # `$SrcFile 'ARGV[0]' 'ARGV[1]'`
5227 elsif(-x "$SrcFile")
5229 $SRCdirective .= "print \`$SrcFile @METAvalues\`;'';\n";
5231 # Handle 'standard' files, using ProcessFile
5232 elsif((-T "$SrcFile" || $ENV{$CGI_FILE_CONTENTS})
5233 && $SrcFile =~ m@($FilePattern)$@) # A recursion
5236 # Do not process still open files because it can lead
5237 # to endless recursions
5238 if(grep(/^$SrcFile$/, @OpenFiles))
5239 { dieHandler(25, "$SrcFile allready opened (endless recursion)\n")};
5240 # Prepare meta arguments
5241 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5242 # Process the file
5243 $SRCdirective .= "main::ProcessFile(\'$SrcFile\');'';\n";
5245 elsif($SrcFile =~ m!^([a-z]+)\://!) # URL's are loaded and printed
5247 $SRCdirective .= GET_URL($SrcFile);
5249 elsif(-T "$SrcFile") # Textfiles are "do"-ed (Perl execution)
5251 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5252 $SRCdirective .= "do \'$SrcFile\';'';\n";
5254 else # This one could not be resolved (should be handled by BinaryMapFile)
5256 $SRCdirective .= 'print "'.$SrcFile.' cannot be used"'."\n";
5261 # Postfix or Prefix
5262 if($Prefix)
5264 $PrefixDirective .= $SRCdirective;
5266 else
5268 $PostfixDirective .= $SRCdirective;
5271 # The prefix should be handled immediately
5272 $directive .= $PrefixDirective;
5273 $PrefixDirective = "";
5277 # Handle the content of the <SCRIPT></SCRIPT> tags
5278 # Do not process the content of <SCRIPT/>
5279 if($TagType =~ /SCRIPT/is && !$ClosedTag) # The <SCRIPT> TAG
5281 my $EndScriptTag = "";
5283 # Execute SHELL scripts with SAFEqx()
5284 if($CurrentContentType =~ /$ShellScriptContentType/is)
5286 $directive .= "SAFEqx(\'";
5289 # Extract Program
5290 while($After !~ /\<\s*\/SCRIPT[^\>]*\>/is && !eof($FileHandle))
5292 $After .= <$FileHandle>;
5293 performTranslation(\$After) if $TranslationPaths;
5296 if($After =~ /\<\s*\/SCRIPT[^\>]*\>/is)
5298 $directive .= $`;
5299 $EndScriptTag = $&;
5300 $After = $';
5302 else
5304 dieHandler(26, "Missing </SCRIPT> end tag in $ENV{'PATH_INFO'}\n");
5307 # Process only when content should be executed
5308 if($CurrentContentType)
5311 # Remove all comments from Perl scripts
5312 # (NOT from OS shell scripts)
5313 $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
5314 if $CurrentContentType =~ /$ServerScriptContentType/i;
5316 # Convert SCRIPT calls, ./<script>
5317 $directive =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5319 # Convert FILE calls, ~/<file>
5320 $directive =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5322 # Execute SHELL scripts with SAFEqx(), closing bracket
5323 if($CurrentContentType =~ /$ShellScriptContentType/i)
5325 # Handle ''-quotes inside the script
5326 $directive =~ /SAFEqx\(\'/;
5327 $directive = $`.$&;
5328 my $Executable = $';
5329 $Executable =~ s/[\']/\\$&/gs;
5331 $directive .= $Executable."\');"; # Closing bracket
5334 else
5336 $directive = "";
5339 # Handle the content of the <DIV></DIV> tags
5340 # Do not process the content of <DIV/>
5341 elsif(($TagType eq 'DIV' || $TagType eq 'INS') && !$ClosedTag) # The <DIV> TAGs
5343 my $EndScriptTag = "";
5345 # Extract Text
5346 while($After !~ /\<\s*\/$TagType[^\>]*\>/is && !eof($FileHandle))
5348 $After .= <$FileHandle>;
5349 performTranslation(\$After) if $TranslationPaths;
5352 if($After =~ /\<\s*\/$TagType[^\>]*\>/is)
5354 $directive .= $`;
5355 $EndScriptTag = $&;
5356 $After = $';
5358 else
5360 dieHandler(27, "Missing </$TagType> end tag in $ENV{'PATH_INFO'}\n");
5363 # Add the Postfixed directives (but only when it contains something printable)
5364 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5365 $PostfixDirective = "";
5368 # Process only when content should be handled
5369 if($CurrentContentType)
5372 # Get the name (ID), and clean it (i.e., remove anything that is NOT part of
5373 # a valid Perl name). Names should not contain $, but we can handle it.
5374 my $name = $ScriptTagAttributes{'ID'};
5375 $name =~ /^\s*[\$\@\%]?([\w\-]+)/;
5376 $name = $1;
5378 # Assign DIV contents to $NAME value OUTSIDE the CGI values!
5379 CGIexecute::defineCGIexecuteVariable($name, $directive);
5380 $directive = "";
5383 # Nothing to execute
5384 $directive = "";
5388 # Handle Foreign scripting languages
5389 if($ScriptingLanguages{$CurrentContentType})
5391 my $newDirective = "";
5392 $newDirective .= OpenForeignScript($CurrentContentType); # Only if not already done
5393 $newDirective .= PrefixForeignScript($CurrentContentType);
5394 $newDirective .= InsertForeignScript($CurrentContentType, $directive, @SRClist);
5395 $newDirective .= PostfixForeignScript($CurrentContentType);
5396 $newDirective .= CloseForeignScript($CurrentContentType); # This shouldn't be necessary
5398 $newDirective .= '"";';
5400 $directive = $newDirective;
5404 # Add the Postfixed directives (but only when it contains something printable)
5405 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5406 $PostfixDirective = "";
5409 # EXECUTE the script and print the results
5411 # Use this to debug the program
5412 # print STDERR "Directive $CGI_Date: \n", $directive, "\n\n";
5414 my $Result = CGIexecute->evaluate($directive) if $directive; # Evaluate as PERL code
5415 $Result =~ s/\n$//g; # Remove final newline
5417 # Print the Result of evaluating the directive
5418 # (this will handle LARGE, >64 kB output)
5419 my $BytesWritten = 1;
5420 while($Result && $BytesWritten)
5422 $BytesWritten = syswrite(STDOUT, $Result, 64);
5423 $Result = substr($Result, $BytesWritten);
5425 # print $Result; # Could be used instead of above code
5427 # Store result if wanted, i.e., if $CGIscriptorResults has been
5428 # defined in a <META> tag.
5429 push(@CGIexecute::CGIscriptorResults, $Result)
5430 if exists($default_values{'CGIscriptorResults'});
5432 # Process the rest of the input line (this could contain
5433 # another directive)
5434 $_ = $After;
5436 print $_;
5437 } while(<$FileHandle>); # Read and Test AFTER first loop!
5439 close ($FileHandle);
5440 dieHandler(28, "Error in recursion\n") unless pop(@OpenFiles) == $file_path;
5444 ###############################################################################
5446 # Call the whole package
5448 sub Handle_Request
5450 my $file_path = "";
5452 # Initialization Code
5453 Initialize_Request();
5455 # SECURITY: ACCESS CONTROL
5456 Access_Control();
5458 # Read the POST part of the query, if there is one
5459 Get_POST_part_of_query();
5461 # Start (HTML) output and logging
5462 $file_path = Initialize_output();
5464 # Check login access or divert to login procedure
5465 $Use_Login = Log_In_Access();
5466 $file_path = $Use_Login if $Use_Login;
5468 # Record which files are still open (to avoid endless recursions)
5469 my @OpenFiles = ();
5471 # Record whether the default HTML ContentType has already been printed
5472 # but only if the SERVER uses HTTP or some other protocol that might interpret
5473 # a content MIME type.
5475 $SupressContentType = !("$ENV{'SERVER_PROTOCOL'}" =~ /($ContentTypeServerProtocols)/i);
5477 # Process the specified file
5478 ProcessFile($file_path) if $file_path ne $SS_PUB;
5480 # Cleanup all open external (foreign) interpreters
5481 CloseAllForeignScripts();
5484 "" # SUCCESS
5487 # Make a single call to handle an (empty) request
5488 Handle_Request();
5491 # END OF PACKAGE MAIN
5494 ####################################################################################
5496 # The CGIEXECUTE PACKAGE
5498 ####################################################################################
5500 # Isolate the evaluation of directives as PERL code from the rest of the program.
5501 # Remember that each package has its own name space.
5502 # Note that only the FIRST argument of execute->evaluate is actually evaluated,
5503 # all other arguments are accessible inside the first argument as $_[0] to $_[$#_].
5505 package CGIexecute;
5507 sub evaluate
5509 my $self = shift;
5510 my $directive = shift;
5511 $directive = eval($directive);
5512 warn $@ if $@; # Write an error message to STDERR
5513 $directive; # Return value of directive
5517 # defineCGIexecuteVariable($name [, $value]) -> 0/1
5519 # Define and intialize variables inside CGIexecute
5520 # Does no sanity checking, for internal use only
5522 sub defineCGIexecuteVariable # ($name [, $value]) -> 0/1
5524 my $name = shift || return 0; # The Name
5525 my $value = shift || ""; # The value
5527 ${$name} = $value;
5529 return 1;
5532 # Protect certain CGI variables values when set internally
5533 # If not defined internally, there will be no variable set AT ALL
5534 my %CGIprotectedVariable = ();
5535 sub ProtectCGIvariable # ($name) -> 0/1
5537 my $name = shift || "";
5538 return 0 unless $name && $name =~ /\w/;
5540 ++$CGIprotectedVariable{$name};
5542 return $CGIprotectedVariable{$name};
5545 # defineCGIvariable($name [, $default]) -> 0/1
5547 # Define and intialize CGI variables
5548 # Tries (in order) $ENV{$name}, the Query string and the
5549 # default value.
5550 # Removes all '-quotes etc.
5552 sub defineCGIvariable # ($name [, $default]) -> 0/1
5554 my $name = shift || return 0; # The Name
5555 my $default = shift || ""; # The default value
5557 # Protect variables set internally
5558 return 1 if !$name || exists($CGIprotectedVariable{$name});
5560 # Remove \-quoted characters
5561 $default =~ s/\\(.)/$1/g;
5562 # Store default values
5563 $::default_values{$name} = $default if $default;
5565 # Process variables
5566 my $temp = undef;
5567 # If there is a user supplied value, it replaces the
5568 # default value.
5570 # Environment values have precedence
5571 if(exists($ENV{$name}))
5573 $temp = $ENV{$name};
5575 # Get name and its value from the query string
5576 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5578 $temp = ::YOUR_CGIPARSE($name);
5580 # Defined values must exist for security
5581 elsif(!exists($::default_values{$name}))
5583 $::default_values{$name} = undef;
5586 # SECURITY, do not allow '- and `-quotes in
5587 # client values.
5588 # Remove all existing '-quotes
5589 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5590 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5591 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5592 # If $temp is empty, use the default value (if it exists)
5593 unless($temp =~ /\S/ || length($temp) > 0) # I.e., $temp is empty
5595 $temp = $::default_values{$name};
5596 # Remove all existing '-quotes
5597 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5598 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5599 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5601 else # Store current CGI values and remove defaults
5603 $::default_values{$name} = $temp;
5605 # Define the CGI variable and its value (in the execute package)
5606 ${$name} = $temp;
5608 # return SUCCES
5609 return 1;
5612 sub defineCGIvariableList # ($name [, $default]) -> 0/1)
5614 my $name = shift || return 0; # The Name
5615 my $default = shift || ""; # The default value
5617 # Protect variables set internally
5618 return 1 if !$name || exists($CGIprotectedVariable{$name});
5620 # Defined values must exist for security
5621 if(!exists($::default_values{$name}))
5623 $::default_values{$name} = $default;
5626 my @temp = ();
5629 # For security:
5630 # Environment values have precedence
5631 if(exists($ENV{$name}))
5633 push(@temp, $ENV{$name});
5635 # Get name and its values from the query string
5636 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5638 push(@temp, ::YOUR_CGIPARSE($name, 1)); # Extract LIST
5640 else
5642 push(@temp, $::default_values{$name});
5646 # SECURITY, do not allow '- and `-quotes in
5647 # client values.
5648 # Remove all existing '-quotes
5649 @temp = map {s/([\r\f]+\n)/\n/g; $_} @temp; # Only \n is allowed
5650 @temp = map {s/[\']/&#8217;/igs; $_} @temp; # Remove all single quotes
5651 @temp = map {s/[\`]/&#8216;/igs; $_} @temp; # Remove all backtick quotes
5653 # Store current CGI values and remove defaults
5654 $::default_values{$name} = $temp[0];
5656 # Define the CGI variable and its value (in the execute package)
5657 @{$name} = @temp;
5659 # return SUCCES
5660 return 1;
5663 sub defineCGIvariableHash # ($name [, $default]) -> 0/1) Note: '$name{""} = $default';
5665 my $name = shift || return 0; # The Name
5666 my $default = shift || ""; # The default value
5668 # Protect variables set internally
5669 return 1 if !$name || exists($CGIprotectedVariable{$name});
5671 # Defined values must exist for security
5672 if(!exists($::default_values{$name}))
5674 $::default_values{$name} = $default;
5677 my %temp = ();
5680 # For security:
5681 # Environment values have precedence
5682 if(exists($ENV{$name}))
5684 $temp{""} = $ENV{$name};
5686 # Get name and its values from the query string
5687 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5689 %temp = ::YOUR_CGIPARSE($name, -1); # Extract HASH table
5691 elsif($::default_values{$name} ne "")
5693 $temp{""} = $::default_values{$name};
5697 # SECURITY, do not allow '- and `-quotes in
5698 # client values.
5699 # Remove all existing '-quotes
5700 my $Key;
5701 foreach $Key (keys(%temp))
5703 $temp{$Key} =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5704 $temp{$Key} =~ s/[\']/&#8217;/igs; # Remove all single quotes
5705 $temp{$Key} =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5708 # Store current CGI values and remove defaults
5709 $::default_values{$name} = $temp{""};
5711 # Define the CGI variable and its value (in the execute package)
5712 %{$name} = ();
5713 my $tempKey;
5714 foreach $tempKey (keys(%temp))
5716 ${$name}{$tempKey} = $temp{$tempKey};
5719 # return SUCCES
5720 return 1;
5724 # SAFEqx('CommandString')
5726 # A special function that is a safe alternative to backtick quotes (and qx//)
5727 # with client-supplied CGI values. All CGI variables are surrounded by
5728 # single ''-quotes (except between existing \'\'-quotes, don't try to be
5729 # too smart). All variables are then interpolated. Simple (@) lists are
5730 # expanded with join(' ', @List), and simple (%) hash tables expanded
5731 # as a list of "key=value" pairs. Complex variables, e.g., @$var, are
5732 # evaluated in a scalar context (e.g., as scalar(@$var)). All occurrences of
5733 # $@% that should NOT be interpolated must be preceeded by a "\".
5734 # If the first line of the String starts with "#! interpreter", the
5735 # remainder of the string is piped into interpreter (after interpolation), i.e.,
5736 # open(INTERPRETER, "|interpreter");print INTERPRETER remainder;
5737 # just like in UNIX. There are some problems with quotes. Be carefull in
5738 # using them. You do not have access to the output of any piped (#!)
5739 # process! If you want such access, execute
5740 # <SCRIPT TYPE="text/osshell">echo "script"|interpreter</SCRIPT> or
5741 # <SCRIPT TYPE="text/ssperl">$resultvar = SAFEqx('echo "script"|interpreter');
5742 # </SCRIPT>.
5744 # SAFEqx ONLY WORKS WHEN THE STRING ITSELF IS SURROUNDED BY SINGLE QUOTES
5745 # (SO THAT IT IS NOT INTERPOLATED BEFORE IT CAN BE PROTECTED)
5746 sub SAFEqx # ('String') -> result of executing qx/"String"/
5748 my $CommandString = shift;
5749 my $NewCommandString = "";
5751 # Only interpolate when required (check the On/Off switch)
5752 unless($CGIscriptor::NoShellScriptInterpolation)
5755 # Handle existing single quotes around CGI values
5756 while($CommandString =~ /\'[^\']+\'/s)
5758 my $CurrentQuotedString = $&;
5759 $NewCommandString .= $`;
5760 $CommandString = $'; # The remaining string
5761 # Interpolate CGI variables between quotes
5762 # (e.g., '$CGIscriptorResults[-1]')
5763 $CurrentQuotedString =~
5764 s/(^|[^\\])([\$\@])((\w*)([\{\[][\$\@\%]?[\:\w\-]+[\}\]])*)/if(exists($main::default_values{$4})){
5765 "$1".eval("$2$3")}else{"$&"}/egs;
5767 # Combine result with previous result
5768 $NewCommandString .= $CurrentQuotedString;
5770 $CommandString = $NewCommandString.$CommandString;
5772 # Select known CGI variables and surround them with single quotes,
5773 # then interpolate all variables
5774 $CommandString =~
5775 s/(^|[^\\])([\$\@\%]+)((\w*)([\{\[][\w\:\$\"\-]+[\}\]])*)/
5776 if($2 eq '$' && exists($main::default_values{$4}))
5777 {"$1\'".eval("\$$3")."\'";}
5778 elsif($2 eq '@'){$1.join(' ', @{"$3"});}
5779 elsif($2 eq '%'){my $t=$1;map {$t.=" $_=".${"$3"}{$_}}
5780 keys(%{"$3"});$t}
5781 else{$1.eval("${2}$3");
5782 }/egs;
5784 # Remove backslashed [$@%]
5785 $CommandString =~ s/\\([\$\@\%])/$1/gs;
5788 # Debugging
5789 # return $CommandString;
5791 # Handle UNIX style "#! shell command\n" constructs as
5792 # a pipe into the shell command. The output cannot be tapped.
5793 my $ReturnValue = "";
5794 if($CommandString =~ /^\s*\#\!([^\f\n\r]+)[\f\n\r]/is)
5796 my $ShellScripts = $';
5797 my $ShellCommand = $1;
5798 open(INTERPRETER, "|$ShellCommand") || dieHandler(29, "\'$ShellCommand\' PIPE not opened: &!\n");
5799 select(INTERPRETER);$| = 1;
5800 print INTERPRETER $ShellScripts;
5801 close(INTERPRETER);
5802 select(STDOUT);$| = 1;
5804 # Shell scripts which are redirected to an existing named pipe.
5805 # The output cannot be tapped.
5806 elsif($CGIscriptor::ShellScriptPIPE)
5808 CGIscriptor::printSAFEqxPIPE($CommandString);
5810 else # Plain ``-backtick execution
5812 # Execute the commands
5813 $ReturnValue = qx/$CommandString/;
5815 return $ReturnValue;
5818 ####################################################################################
5820 # The CGIscriptor PACKAGE
5822 ####################################################################################
5824 # Isolate the evaluation of CGIscriptor functions, i.e., those prefixed with
5825 # "CGIscriptor::"
5827 package CGIscriptor;
5830 # The Interpolation On/Off switch
5831 my $NoShellScriptInterpolation = undef;
5832 # The ShellScript redirection pipe
5833 my $ShellScriptPIPE = undef;
5835 # Open a named PIPE for SAFEqx to receive ALL shell scripts
5836 sub RedirectShellScript # ('CommandString')
5838 my $CommandString = shift || undef;
5840 if($CommandString)
5842 $ShellScriptPIPE = "ShellScriptNamedPipe";
5843 open($ShellScriptPIPE, "|$CommandString")
5844 || main::dieHandler(30, "\'|$CommandString\' PIPE open failed: $!\n");
5846 else
5848 close($ShellScriptPIPE);
5849 $ShellScriptPIPE = undef;
5851 return $ShellScriptPIPE;
5854 # Print to redirected shell script pipe
5855 sub printSAFEqxPIPE # ("String") -> print return value
5857 my $String = shift || undef;
5859 select($ShellScriptPIPE); $| = 1;
5860 my $returnvalue = print $ShellScriptPIPE ($String);
5861 select(STDOUT); $| = 1;
5863 return $returnvalue;
5866 # a pointer to CGIexecute::SAFEqx
5867 sub SAFEqx # ('String') -> result of qx/"String"/
5869 my $CommandString = shift;
5870 return CGIexecute::SAFEqx($CommandString);
5874 # a pointer to CGIexecute::defineCGIvariable
5875 sub defineCGIvariable # ($name[, $default]) ->0/1
5877 my $name = shift;
5878 my $default = shift;
5879 return CGIexecute::defineCGIvariable($name, $default);
5883 # a pointer to CGIexecute::defineCGIvariable
5884 sub defineCGIvariableList # ($name[, $default]) ->0/1
5886 my $name = shift;
5887 my $default = shift;
5888 return CGIexecute::defineCGIvariableList($name, $default);
5892 # a pointer to CGIexecute::defineCGIvariable
5893 sub defineCGIvariableHash # ($name[, $default]) ->0/1
5895 my $name = shift;
5896 my $default = shift;
5897 return CGIexecute::defineCGIvariableHash($name, $default);
5901 # Decode URL encoded arguments
5902 sub URLdecode # (URL encoded input) -> string
5904 my $output = "";
5905 my $char;
5906 my $Value;
5907 foreach $Value (@_)
5909 my $EncodedValue = $Value; # Do not change the loop variable
5910 # Convert all "+" to " "
5911 $EncodedValue =~ s/\+/ /g;
5912 # Convert all hexadecimal codes (%FF) to their byte values
5913 while($EncodedValue =~ /\%([0-9A-F]{2})/i)
5915 $output .= $`.chr(hex($1));
5916 $EncodedValue = $';
5918 $output .= $EncodedValue; # The remaining part of $Value
5920 $output;
5923 # Encode arguments as URL codes.
5924 sub URLencode # (input) -> URL encoded string
5926 my $output = "";
5927 my $char;
5928 my $Value;
5929 foreach $Value (@_)
5931 my @CharList = split('', $Value);
5932 foreach $char (@CharList)
5934 if($char =~ /\s/)
5935 { $output .= "+";}
5936 elsif($char =~ /\w\-/)
5937 { $output .= $char;}
5938 else
5940 $output .= uc(sprintf("%%%2.2x", ord($char)));
5944 $output;
5947 # Extract the value of a CGI variable from the URL-encoded $string
5948 # Also extracts the data blocks from a multipart request. Does NOT
5949 # decode the multipart blocks
5950 sub CGIparseValue # (ValueName [, URL_encoded_QueryString [, \$QueryReturnReference]]) -> Decoded value
5952 my $ValueName = shift;
5953 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5954 my $ReturnReference = shift || undef;
5955 my $output = "";
5957 if($QueryString =~ /(^|\&)$ValueName\=([^\&]*)(\&|$)/)
5959 $output = URLdecode($2);
5960 $$ReturnReference = $' if ref($ReturnReference);
5962 # Get multipart POST or PUT methods
5963 elsif($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
5965 my $MultipartType = $2;
5966 my $BoundaryString = $3;
5967 # Remove the boundary-string
5968 my $temp = $QueryString;
5969 $temp =~ /^\Q--$BoundaryString\E/m;
5970 $temp = $';
5972 # Identify the newline character(s), this is the first character in $temp
5973 my $NewLine = "\r\n"; # Actually, this IS the correct one
5974 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
5976 # Is this correct??? I have to check.
5977 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
5978 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
5979 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
5980 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
5983 # search through all data blocks
5984 while($temp =~ /^\Q--$BoundaryString\E/m)
5986 my $DataBlock = $`;
5987 $temp = $';
5988 # Get the empty line after the header
5989 $DataBlock =~ /$NewLine$NewLine/;
5990 $Header = $`;
5991 $output = $';
5992 my $Header = $`;
5993 $output = $';
5995 # Remove newlines from the header
5996 $Header =~ s/$NewLine/ /g;
5998 # Look whether this block is the one you are looking for
5999 # Require the quotes!
6000 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
6002 my $i;
6003 for($i=length($NewLine); $i; --$i)
6005 chop($output);
6007 # OK, get out
6008 last;
6010 # reinitialize the output
6011 $output = "";
6013 $$ReturnReference = $temp if ref($ReturnReference);
6015 elsif($QueryString !~ /(^|\&)$ValueName\=/) # The value simply isn't there
6017 return undef;
6018 $$ReturnReference = undef if ref($ReturnReference);
6020 else
6022 print "ERROR: $ValueName $main::ENV{'CONTENT_TYPE'}\n";
6024 return $output;
6028 # Get a list of values for the same ValueName. Uses CGIparseValue
6030 sub CGIparseValueList # (ValueName [, URL_encoded_QueryString]) -> List of decoded values
6032 my $ValueName = shift;
6033 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6034 my @output = ();
6035 my $RestQueryString;
6036 my $Value;
6037 while($QueryString &&
6038 (($Value = CGIparseValue($ValueName, $QueryString, \$RestQueryString))
6039 || defined($Value)))
6041 push(@output, $Value);
6042 $QueryString = $RestQueryString; # QueryString is consumed!
6044 # ready, return list with values
6045 return @output;
6048 sub CGIparseValueHash # (ValueName [, URL_encoded_QueryString]) -> Hash table of decoded values
6050 my $ValueName = shift;
6051 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6052 my $RestQueryString;
6053 my %output = ();
6054 while($QueryString && $QueryString =~ /(^|\&)$ValueName([\w]*)\=/)
6056 my $Key = $2;
6057 my $Value = CGIparseValue("$ValueName$Key", $QueryString, \$RestQueryString);
6058 $output{$Key} = $Value;
6059 $QueryString = $RestQueryString; # QueryString is consumed!
6061 # ready, return list with values
6062 return %output;
6065 sub CGIparseForm # ([URL_encoded_QueryString]) -> Decoded Form (NO multipart)
6067 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6068 my $output = "";
6070 $QueryString =~ s/\&/\n/g;
6071 $output = URLdecode($QueryString);
6073 $output;
6076 # Extract the header of a multipart CGI variable from the POST input
6077 sub CGIparseHeader # (ValueName [, URL_encoded_QueryString]) -> Decoded value
6079 my $ValueName = shift;
6080 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
6081 my $output = "";
6083 if($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
6085 my $MultipartType = $2;
6086 my $BoundaryString = $3;
6087 # Remove the boundary-string
6088 my $temp = $QueryString;
6089 $temp =~ /^\Q--$BoundaryString\E/m;
6090 $temp = $';
6092 # Identify the newline character(s), this is the first character in $temp
6093 my $NewLine = "\r\n"; # Actually, this IS the correct one
6094 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
6096 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
6097 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
6098 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
6099 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
6102 # search through all data blocks
6103 while($temp =~ /^\Q--$BoundaryString\E/m)
6105 my $DataBlock = $`;
6106 $temp = $';
6107 # Get the empty line after the header
6108 $DataBlock =~ /$NewLine$NewLine/;
6109 $Header = $`;
6110 my $Header = $`;
6112 # Remove newlines from the header
6113 $Header =~ s/$NewLine/ /g;
6115 # Look whether this block is the one you are looking for
6116 # Require the quotes!
6117 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
6119 $output = $Header;
6120 last;
6122 # reinitialize the output
6123 $output = "";
6126 return $output;
6130 # Checking variables for security (e.g., file names and email addresses)
6131 # File names are tested against the $::FileAllowedChars and $::BlockPathAccess variables
6132 sub CGIsafeFileName # FileName -> FileName or ""
6134 my $FileName = shift || "";
6135 return "" if $FileName =~ m?[^$::FileAllowedChars]?;
6136 return "" if $FileName =~ m!(^|/|\:)[\-\.]!;
6137 return "" if $FileName =~ m@\.\.\Q$::DirectorySeparator\E@; # Higher directory not allowed
6138 return "" if $FileName =~ m@\Q$::DirectorySeparator\E\.\.@; # Higher directory not allowed
6139 return "" if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@; # Invisible (blocked) file
6141 return $FileName;
6144 sub CGIsafeEmailAddress # email -> email or ""
6146 my $Email = shift || "";
6147 return "" unless $Email =~ m/^[\w\.\-]+[\@][\w\.\-\:]+$/;
6148 return $Email;
6151 # Get a URL from the web. Needs main::GET_URL($URL) function
6152 # (i.e., curl, snarf, or wget)
6153 sub read_url # ($URL) -> page/file
6155 my $URL = shift || return "";
6157 # Get the commands to read the URL, do NOT add a print command
6158 my $URL_command = main::GET_URL($URL, 1);
6159 # execute the commands, i.e., actually read it
6160 my $URLcontent = CGIexecute->evaluate($URL_command);
6162 # Ready, return the content.
6163 return $URLcontent;
6166 ################################################>>>>>>>>>>Start Remove
6168 # BrowseAllDirs(Directory, indexfile)
6170 # usage:
6171 # <SCRIPT TYPE='text/ssperl'>
6172 # CGIscriptor::BrowseAllDirs('Sounds', 'index.html', '\.wav$')
6173 # </SCRIPT>
6175 # Allows to browse all directories. Stops at '/'. If the directory contains
6176 # an indexfile, eg, index.html, that file will be used instead. Files must match
6177 # the $Pattern, if it is given. Default is
6178 # CGIscriptor::BrowseAllDirs('/', 'index.html', '')
6180 sub BrowseAllDirs # (Directory, indexfile, $Pattern) -> Print HTML code
6182 my $Directory = shift || '/';
6183 my $indexfile = shift || 'index.html';
6184 my $Pattern = shift || '';
6185 $Directory =~ s!/$!!g;
6187 # If the index directory exists, use that one
6188 if(-s "$::CGI_HOME$Directory/$indexfile")
6190 return main::ProcessFile("$::CGI_HOME$Directory/$indexfile");
6193 # No indexfile, continue
6194 my @DirectoryList = glob("$::CGI_HOME$Directory");
6195 $CurrentDirectory = shift(@DirectoryList);
6196 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
6197 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
6198 print "<h1>";
6199 print "$CurrentDirectory" if $CurrentDirectory;
6200 print "</h1>\n";
6202 opendir(BROWSE, "$::CGI_HOME$Directory") || main::dieHandler(31, "$::CGI_HOME$Directory $!");
6203 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
6205 # Print directories
6206 my $file;
6207 print "<pre><ul TYPE='NONE'>\n";
6208 foreach $file (@AllFiles)
6210 next unless -d "$::CGI_HOME$Directory/$file";
6211 # Check whether this file should be visible
6212 next if $::BlockPathAccess &&
6213 "$Directory/$file/" =~ m@$::BlockPathAccess@;
6214 print "<dt><a href='$Directory/$file'>$file</a></dt>\n";
6216 print "</ul></pre>\n";
6218 # Print files
6219 print "<pre><ul TYPE='CIRCLE'>\n";
6220 my $TotalSize = 0;
6221 foreach $file (@AllFiles)
6223 next if $file =~ /^\./;
6224 next if -d "$::CGI_HOME$Directory/$file";
6225 next if -l "$::CGI_HOME$Directory/$file";
6226 # Check whether this file should be visible
6227 next if $::BlockPathAccess &&
6228 "$Directory/$file" =~ m@$::BlockPathAccess@;
6230 if(!$Pattern || $file =~ m@$Pattern@)
6232 my $Date = localtime($^T - (-M "$::CGI_HOME$Directory/$file")*3600*24);
6233 my $Size = -s "$::CGI_HOME$Directory/$file";
6234 $Size = sprintf("%6.0F kB", $Size/1024);
6235 my $Type = `file $::CGI_HOME$Directory/$file`;
6236 $Type =~ s@\s*$::CGI_HOME$Directory/$file\s*\:\s*@@ig;
6237 chomp($Type);
6239 print "<li>";
6240 print "<a href='$Directory/$file'>";
6241 printf("%-40s", "$file</a>");
6242 print "\t$Size\t$Date\t$Type";
6243 print "</li>\n";
6246 print "</ul></pre>";
6248 return 1;
6252 ################################################
6254 # BrowseDirs(RootDirectory [, Pattern, Start])
6256 # usage:
6257 # <SCRIPT TYPE='text/ssperl'>
6258 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', 'Speech', 'DIRECTORY')
6259 # </SCRIPT>
6261 # Allows to browse subdirectories. Start should be relative to the RootDirectory,
6262 # e.g., the full path of the directory 'Speech' is '~/Sounds/Speech'.
6263 # Only files which fit /$Pattern/ and directories are displayed.
6264 # Directories down or up the directory tree are supplied with a
6265 # GET request with the name of the CGI variable in the fourth argument (default
6266 # is 'BROWSEDIRS'). So the correct call for a subdirectory could be:
6267 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', $DIRECTORY, 'DIRECTORY')
6269 sub BrowseDirs # (RootDirectory [, Pattern, Start, CGIvariable, HTTPserver]) -> Print HTML code
6271 my $RootDirectory = shift; # || return 0;
6272 my $Pattern = shift || '\S';
6273 my $Start = shift || "";
6274 my $CGIvariable = shift || "BROWSEDIRS";
6275 my $HTTPserver = shift || '';
6277 $Start = CGIscriptor::URLdecode($Start); # Sometimes, too much has been encoded
6278 $Start =~ s@//+@/@g;
6279 $Start =~ s@[^/]+/\.\.@@ig;
6280 $Start =~ s@^\.\.@@ig;
6281 $Start =~ s@/\.$@@ig;
6282 $Start =~ s!/+$!!g;
6283 $Start .= "/" if $Start;
6285 my @Directory = glob("$::CGI_HOME/$RootDirectory/$Start");
6286 $CurrentDirectory = shift(@Directory);
6287 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
6288 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
6289 print "<h1>";
6290 print "$CurrentDirectory" if $CurrentDirectory;
6291 print "</h1>\n";
6292 opendir(BROWSE, "$::CGI_HOME/$RootDirectory/$Start") || main::dieHandler(31, "$::CGI_HOME/$RootDirectory/$Start $!");
6293 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
6295 # Print directories
6296 my $file;
6297 print "<pre><ul TYPE='NONE'>\n";
6298 foreach $file (@AllFiles)
6300 next unless -d "$::CGI_HOME/$RootDirectory/$Start$file";
6301 # Check whether this file should be visible
6302 next if $::BlockPathAccess &&
6303 "/$RootDirectory/$Start$file/" =~ m@$::BlockPathAccess@;
6305 my $NewURL = $Start ? "$Start$file" : $file;
6306 $NewURL = CGIscriptor::URLencode($NewURL);
6307 print "<dt><a href='";
6308 print "$ENV{SCRIPT_NAME}" if $ENV{SCRIPT_NAME} !~ m@[^\w+\-/]@;
6309 print "$ENV{PATH_INFO}?$CGIvariable=$NewURL'>$file</a></dt>\n";
6311 print "</ul></pre>\n";
6313 # Print files
6314 print "<pre><ul TYPE='CIRCLE'>\n";
6315 my $TotalSize = 0;
6316 foreach $file (@AllFiles)
6318 next if $file =~ /^\./;
6319 next if -d "$::CGI_HOME/$RootDirectory/$Start$file";
6320 next if -l "$::CGI_HOME/$RootDirectory/$Start$file";
6321 # Check whether this file should be visible
6322 next if $::BlockPathAccess &&
6323 "$::CGI_HOME/$RootDirectory/$Start$file" =~ m@$::BlockPathAccess@;
6325 if($file =~ m@$Pattern@)
6327 my $Date = localtime($^T - (-M "$::CGI_HOME/$RootDirectory/$Start$file")*3600*24);
6328 my $Size = -s "$::CGI_HOME/$RootDirectory/$Start$file";
6329 $Size = sprintf("%6.0F kB", $Size/1024);
6330 my $Type = `file $::CGI_HOME/$RootDirectory/$Start$file`;
6331 $Type =~ s@\s*$::CGI_HOME/$RootDirectory/$Start$file\s*\:\s*@@ig;
6332 chomp($Type);
6334 print "<li>";
6335 if($HTTPserver =~ /^\s*[\.\~]\s*$/)
6337 print "<a href='$RootDirectory/$Start$file'>";
6339 elsif($HTTPserver)
6341 print "<a href='$HTTPserver/$RootDirectory/$Start$file'>";
6343 printf("%-40s", "$file</a>") if $HTTPserver;
6344 printf("%-40s", "$file") unless $HTTPserver;
6345 print "\t$Size\t$Date\t$Type";
6346 print "</li>\n";
6349 print "</ul></pre>";
6351 return 1;
6355 # ListDocs(Pattern [,ListType])
6357 # usage:
6358 # <SCRIPT TYPE=text/ssperl>
6359 # CGIscriptor::ListDocs("/*", "dl");
6360 # </SCRIPT>
6362 # This subroutine is very usefull to manage collections of independent
6363 # documents. The resulting list will display the tree-like directory
6364 # structure. If this routine is too slow for online use, you can
6365 # store the result and use a link to that stored file.
6367 # List HTML and Text files with title and first header (HTML)
6368 # or filename and first meaningfull line (general text files).
6369 # The listing starts at the ServerRoot directory. Directories are
6370 # listed recursively.
6372 # You can change the list type (default is dl).
6373 # e.g.,
6374 # <dt><a href=<file.html>>title</a>
6375 # <dd>First Header
6376 # <dt><a href=<file.txt>>file.txt</a>
6377 # <dd>First meaningfull line of text
6379 sub ListDocs # ($Pattern [, prefix]) e.g., ("/Books/*", [, "dl"])
6381 my $Pattern = shift;
6382 $Pattern =~ /\*/;
6383 my $ListType = shift || "dl";
6384 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
6385 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
6386 my @FileList = glob("$::CGI_HOME$Pattern");
6387 my ($FileName, $Path, $Link);
6389 # Print List markers
6390 print "<$ListType>\n";
6392 # Glob all files
6393 File: foreach $FileName (@FileList)
6395 # Check whether this file should be visible
6396 next if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@;
6398 # Recursively list files in all directories
6399 if(-d $FileName)
6401 $FileName =~ m@([^/]*)$@;
6402 my $DirName = $1;
6403 print "<$Prefix>$DirName\n";
6404 $Pattern =~ m@([^/]*)$@;
6405 &ListDocs("$`$DirName/$1", $ListType);
6406 next;
6408 # Use textfiles
6409 elsif(-T "$FileName")
6411 open(TextFile, $FileName) || next;
6413 # Ignore all other file types
6414 else
6415 { next;};
6417 # Get file path for link
6418 $FileName =~ /$::CGI_HOME/;
6419 print "<$Prefix><a href=$URL_root$'>";
6420 # Initialize all variables
6421 my $Line = "";
6422 my $TitleFound = 0;
6423 my $Caption = "";
6424 my $Title = "";
6425 # Read file and step through
6426 while(<TextFile>)
6428 chop $_;
6429 $Line = $_;
6430 # HTML files
6431 if($FileName =~ /\.ht[a-zA-Z]*$/i)
6433 # Catch Title
6434 while(!$Title)
6436 if($Line =~ m@<title>([^<]*)</title>@i)
6438 $Title = $1;
6439 $Line = $';
6441 else
6443 $Line .= <TextFile> || goto Print;
6444 chop $Line;
6447 # Catch First Header
6448 while(!$Caption)
6450 if($Line =~ m@</h1>@i)
6452 $Caption = $`;
6453 $Line = $';
6454 $Caption =~ m@<h1>@i;
6455 $Caption = $';
6456 $Line = $`.$Caption.$Line;
6458 else
6460 $Line .= <TextFile> || goto Print;
6461 chop $Line;
6465 # Other text files
6466 else
6468 # Title equals file name
6469 $FileName =~ /([^\/]+)$/;
6470 $Title = $1;
6471 # Catch equals First Meaningfull line
6472 while(!$Caption)
6474 if($Line =~ /[A-Z]/ &&
6475 ($Line =~ /subject|title/i || $Line =~ /^[\w,\.\s\?\:]+$/)
6476 && $Line !~ /Newsgroup/ && $Line !~ /\:\s*$/)
6478 $Line =~ s/\<[^\>]+\>//g;
6479 $Caption = $Line;
6481 else
6483 $Line = <TextFile> || goto Print;
6487 Print: # Print title and subject
6488 print "$Title</a>\n";
6489 print "<dd>$Caption\n" if $ListType eq "dl";
6490 $TitleFound = 0;
6491 $Caption = "";
6492 close TextFile;
6493 next File;
6496 # Print Closing List Marker
6497 print "</$ListType>\n";
6498 ""; # Empty return value
6502 # HTMLdocTree(Pattern [,ListType])
6504 # usage:
6505 # <SCRIPT TYPE=text/ssperl>
6506 # CGIscriptor::HTMLdocTree("/Welcome.html", "dl");
6507 # </SCRIPT>
6509 # The following subroutine is very usefull for checking large document
6510 # trees. Starting from the root (s), it reads all files and prints out
6511 # a nested list of links to all attached files. Non-existing or misplaced
6512 # files are flagged. This is quite a file-i/o intensive routine
6513 # so you would not like it to be accessible to everyone. If you want to
6514 # use the result, save the whole resulting page to disk and use a link
6515 # to this file.
6517 # HTMLdocTree takes an HTML file or file pattern and constructs nested lists
6518 # with links to *local* files (i.e., only links to the local server are
6519 # followed). The list entries are the document titles.
6520 # If the list type is <dl>, the first <H1> header is used too.
6521 # For each file matching the pattern, a list is made recursively of all
6522 # HTML documents that are linked from it and are stored in the same directory
6523 # or a sub-directory. Warnings are given for missing files.
6524 # The listing starts for the ServerRoot directory.
6525 # You can change the default list type <dl> (<dl>, <ul>, <ol>).
6527 %LinkUsed = ();
6529 sub HTMLdocTree # ($Pattern [, listtype])
6530 # e.g., ("/Welcome.html", [, "ul"])
6532 my $Pattern = shift;
6533 my $ListType = shift || "dl";
6534 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
6535 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
6536 my ($Filename, $Path, $Link);
6537 my %LocalLinks = {};
6539 # Read files (glob them for expansion of wildcards)
6540 my @FileList = glob("$::CGI_HOME$Pattern");
6541 foreach $Path (@FileList)
6543 # Get URL_path
6544 $Path =~ /$::CGI_HOME/;
6545 my $URL_path = $';
6546 # Check whether this file should be visible
6547 next if $::BlockPathAccess && $URL_path =~ m@$::BlockPathAccess@;
6549 my $Title = $URL_path;
6550 my $Caption = "";
6551 # Current file should not be used again
6552 ++$LinkUsed{$URL_path};
6553 # Open HTML doc
6554 unless(open(TextFile, $Path))
6556 print "<$Prefix>$Title <blink>(not found)</blink><br>\n";
6557 next;
6559 while(<TextFile>)
6561 chop $_;
6562 $Line = $_;
6563 # Catch Title
6564 while($Line =~ m@<title>@i)
6566 if($Line =~ m@<title>([^<]*)</title>@i)
6568 $Title = $1;
6569 $Line = $';
6571 else
6573 $Line .= <TextFile>;
6574 chop $Line;
6577 # Catch First Header
6578 while(!$Caption && $Line =~ m@<h1>@i)
6580 if($Line =~ m@</h[1-9]>@i)
6582 $Caption = $`;
6583 $Line = $';
6584 $Caption =~ m@<h1>@i;
6585 $Caption = $';
6586 $Line = $`.$Caption.$Line;
6588 else
6590 $Line .= <TextFile>;
6591 chop $Line;
6594 # Catch and print Links
6595 while($Line =~ m@<a href\=([^>]*)>@i)
6597 $Link = $1;
6598 $Line = $';
6599 # Remove quotes
6600 $Link =~ s/\"//g;
6601 # Remove extras
6602 $Link =~ s/[\#\?].*$//g;
6603 # Remove Servername
6604 if($Link =~ m@(http://|^)@i)
6606 $Link = $';
6607 # Only build tree for current server
6608 next unless $Link =~ m@$::ENV{'SERVER_NAME'}|^/@;
6609 # Remove server name and port
6610 $Link =~ s@^[^\/]*@@g;
6612 # Store the current link
6613 next if $LinkUsed{$Link} || $Link eq $URL_path;
6614 ++$LinkUsed{$Link};
6615 ++$LocalLinks{$Link};
6619 close TextFile;
6620 print "<$Prefix>";
6621 print "<a href=http://";
6622 print "$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}$URL_path>";
6623 print "$Title</a>\n";
6624 print "<br>$Caption\n"
6625 if $Caption && $Caption ne $Title && $ListType =~ /dl/i;
6626 print "<$ListType>\n";
6627 foreach $Link (keys(%LocalLinks))
6629 &HTMLdocTree($Link, $ListType);
6631 print "</$ListType>\n";
6635 ###########################<<<<<<<<<<End Remove
6637 # Make require happy
6640 =head1 NAME
6642 CGIscriptor -
6644 =head1 DESCRIPTION
6646 A flexible HTML 4 compliant script/module for CGI-aware
6647 embeded Perl, shell-scripts, and other scripting languages,
6648 executed at the server side.
6650 =head1 README
6652 Executes embeded Perl code in HTML pages with easy
6653 access to CGI variables. Also processes embeded shell
6654 scripts and scripts in any other language with an
6655 interactive interpreter (e.g., in-line Python, Tcl,
6656 Ruby, Awk, Lisp, Xlispstat, Prolog, M4, R, REBOL, Praat,
6657 sh, bash, csh, ksh).
6659 CGIscriptor is very flexible and hides all the specifics
6660 and idiosyncrasies of correct output and CGI coding and naming.
6661 CGIscriptor complies with the W3C HTML 4.0 recommendations.
6663 This Perl program will run on any WWW server that runs
6664 Perl scripts, just add a line like the following to your
6665 srm.conf file (Apache example):
6667 ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
6669 URL's that refer to http://www.your.address/SHTML/... will
6670 now be handled by CGIscriptor.pl, which can use a private
6671 directory tree (default is the DOCUMENT_ROOT directory tree,
6672 but it can be anywhere).
6674 =head1 PREREQUISITES
6677 =head1 COREQUISITES
6680 =pod OSNAMES
6682 Linux, *BSD, *nix, MS WinXP
6684 =pod SCRIPT CATEGORIES
6686 Servers
6690 =cut