Started to clean up passwords variables
[CGIscriptor.git] / CGIscriptor.pl
blob960b352b5e69b045e1ce84a46314877677f815ec
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 # 13 Mar 2013 - Changed password hash
65 # 10 Jul 2012 - Version 2.4
66 # 11 Jun 2012 - Securing CGIvariable setting. Made
67 # 'if($ENV{QUERY_STRING} =~ /$name/)' into elsif in
68 # defineCGIvariable/List/Hash to give precedence to ENV{$name}
69 # This was a very old security bug. Added ProtectCGIvariable($name).
70 # 06 Jun 2012 - Added IP only session types after login.
71 # 31 May 2012 - Session ticket system added for handling login sessions.
72 # 29 May 2012 - CGIsafeFileName does not accept filenames starting with '.'
73 # 29 May 2012 - Added CGIscriptor::BrowseAllDirs to handle browsing directories
74 # correctly.
75 # 22 May 2012 - Added Access control with Session Tickets linked to
76 # IP Address and PATH_INFO.
77 # 21 May 2012 - Corrected the links generated by CGIscriptor::BrowseDirs
78 # Will link to current base URL when the HTTP server is '.' or '~'
79 # 29 Oct 2009 - Adapted David A. Wheeler's suggestion about filenames:
80 # CGIsafeFileName does not accept filenames starting with '-'
81 # (http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html)
82 # 08 Oct 2009 - Some corrections in the README.txt file, eg, new email address
83 # 28 Jan 2005 - Added a file selector to performTranslation.
84 # Changed %TranslationTable to @TranslationTable
85 # and patterns to lists.
86 # 27 Jan 2005 - Added a %TranslationTable with associated
87 # performTranslation(\$text) function to allow
88 # run changes in the web pages. Say, to translate
89 # legacy pages with <%=...%> delimiters to the new
90 # <SCRIPT TYPE=..></SCRIPT> format.
91 # 27 Jan 2005 - Small bug of extra '\n' in output removed from the
92 # Other Languages Code.
93 # 10 May 2004 - Belated upload of latest version (2.3) to CPAN
94 # 07 Oct 2003 - Corrected error '\s' -> '\\s' in rebol scripting
95 # language call
96 # 07 Oct 2003 - Corrected omitted INS tags in <DIV><INS> handling
97 # 20 May 2003 - Added a --help switch to print the manual.
98 # 06 Mar 2003 - Adapted the blurb at the end of the file.
99 # 03 Mar 2003 - Added a user definable dieHandler function to catch all
100 # "die" calls. Also "enhanced" the STDERR printout.
101 # 10 Feb 2003 - Split off the reading of the POST part of a query
102 # from Initialize_output. This was suggested by Gerd Franke
103 # to allow for the catching of the file_path using a
104 # POST based lookup. That is, he needed the POST part
105 # to change the file_path.
106 # 03 Feb 2003 - %{$name}; => %{$name} = (); in defineCGIvariableHash.
107 # 03 Feb 2003 - \1 better written as $1 in
108 # $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
109 # 29 Jan 2003 - This makes "CLASS="ssperl" CSS-compatible Gerd Franke
110 # added:
111 # $ServerScriptContentClass = "ssperl";
112 # changed in ProcessFile():
113 # unless(($CurrentContentType =~
114 # 28 Jan 2003 - Added 'INS' Tag! Gerd Franke
115 # 20 Dec 2002 - Removed useless $Directoryseparator variable.
116 # Update comments and documentation.
117 # 18 Dec 2002 - Corrected bug in Accept/Reject processing.
118 # Files didn't work.
119 # 24 Jul 2002 - Added .htaccess documentation (from Gerd Franke)
120 # Also added a note that RawFilePattern can be a
121 # complete file name.
122 # 19 Mar 2002 - Added SRC pseudo-files PREFIX and POSTFIX. These
123 # switch to prepending or to appending the content
124 # of the SRC attribute. Default is prefixing. You
125 # can add as many of these switches as you like.
126 # 13 Mar 2002 - Do not search for tag content if a tag closes with
127 # />, i.e., <DIV ... /> will be handled the XML/XHTML way.
128 # 25 Jan 2002 - Added 'curl' and 'snarf' to SRC attribute URL handling
129 # (replaces wget).
130 # 25 Jan 2002 - Found a bug in SAFEqx, now executes qx() in a scalar context
131 # (i.o. a list context). This is necessary for binary results.
132 # 24 Jan 2002 - Disambiguated -T $SRCfile to -T "$SRCfile" (and -e) and
133 # changed the order of if/elsif to allow removing these
134 # conditions in systems with broken -T functions.
135 # (I also removed a spurious ')' bracket)
136 # 17 Jan 2002 - Changed DIV tag SRC from <SOURCE> to sysread(SOURCE,...)
137 # to support binary files.
138 # 17 Jan 2002 - Removed WhiteSpace from $FileAllowedCharacters.
139 # 17 Jan 2002 - Allow "file://" prefix in SRC attribute. It is simply
140 # stipped from the path.
141 # 15 Jan 2002 - Version 2.2
142 # 15 Jan 2002 - Debugged and completed URL support (including
143 # CGIscriptor::read_url() function)
144 # 07 Jan 2002 - Added automatic (magic) URL support to the SRC attribute
145 # with the main::GET_URL function. Uses wget -O underlying.
146 # 04 Jan 2002 - Added initialization of $NewDirective in InsertForeignScript
147 # (i.e., my $NewDirective = "";) to clear old output
148 # (this was a realy anoying bug).
149 # 03 Jan 2002 - Added a <DIV CLASS='text/ssperl' ID='varname'></DIV>
150 # tags that assign the body text as-is (literally)
151 # to $varname. Allows standard HTML-tools to handle
152 # Cascading Style Sheet templates. This implements a
153 # design by Gerd Franke (franke@roo.de).
154 # 03 Jan 2002 - I finaly gave in and allowed SRC files to expand ~/.
155 # 12 Oct 2001 - Normalized spelling of "CGIsafFileName" in documentation.
156 # 09 Oct 2001 - Added $ENV{'CGI_BINARY_FILE'} to log files to
157 # detect unwanted indexing of TAR files by webcrawlers.
158 # 10 Sep 2001 - Added $YOUR_SCRIPTS directory to @INC for 'require'.
159 # 22 Aug 2001 - Added .txt (Content-type: text/plain) as a default
160 # processed file type. Was processed via BinaryMapFile.
161 # 31 May 2001 - Changed =~ inside CGIsafeEmailAddress that was buggy.
162 # 29 May 2001 - Updated $CGI_HOME to point to $ENV{DOCUMENT_ROOT} io
163 # the root of PATH_TRANSLATED. DOCUMENT_ROOT can now
164 # be manipulated to achieve a "Sub Root".
165 # NOTE: you can have $YOUR_HTML_FILES != DOCUMENT_ROOT
166 # 28 May 2001 - Changed CGIscriptor::BrowsDirs function for security
167 # and debugging (it now works).
168 # 21 May 2001 - defineCGIvariableHash will ADD values to existing
169 # hashes,instead of replacing existing hashes.
170 # 17 May 2001 - Interjected a '&' when pasting POST to GET data
171 # 24 Apr 2001 - Blocked direct requests for BinaryMapFile.
172 # 16 Aug 2000 - Added hash table extraction for CGI parameters with
173 # CGIparseValueHash (used with structured parameters).
174 # Use: CGI='%<CGI-partial-name>' (fill in your name in <>)
175 # Will collect all <CGI-partial-name><key>=value pairs in
176 # $<CGI-partial-name>{<key>} = value;
177 # 16 Aug 2000 - Adapted SAFEqx to protect @PARAMETER values.
178 # 09 Aug 2000 - Added support for non-filesystem input by way of
179 # the CGI_FILE_CONTENTS and CGI_DATA_ACCESS_CODE
180 # environment variables.
181 # 26 Jul 2000 - On the command-line, file-path '-' indicates STDIN.
182 # This allows CGIscriptor to be used in pipes.
183 # Default, $BLOCK_STDIN_HTTP_REQUEST=1 will block this
184 # in an HTTP request (i.e., in a web server).
185 # 26 Jul 2000 - Blocked 'Content-type: text/html' if the SERVER_PROTOCOL
186 # is not HTTP or another protocol. Changed the default
187 # source directory to DOCUMENT_ROOT (i.o. the incorrect
188 # SERVER_ROOT).
189 # 24 Jul 2000 - -slim Command-line argument added to remove all
190 # comments, security, etc.. Updated documentation.
191 # 05 Jul 2000 - Added IF and UNLESS attributes to make the
192 # execution of all <META> and <SCRIPT> code
193 # conditional.
194 # 05 Jul 2000 - Rewrote and isolated the code for extracting
195 # quoted items from CGI and SRC attributes.
196 # Now all attributes expect the same set of
197 # quotes: '', "", ``, (), {}, [] and the same
198 # preceded by a \, e.g., "\((aap)\)" will be
199 # extracted as "(aap)".
200 # 17 Jun 2000 - Construct @ARGV list directly in CGIexecute
201 # name-space (i.o. by evaluation) from
202 # CGI attributes to prevent interference with
203 # the processing for non perl scripts.
204 # Changed CGIparseValueList to prevent runaway
205 # loops.
206 # 16 Jun 2000 - Added a direct (interpolated) display mode
207 # (text/ssdisplay) and a user log mode
208 # (text/sslogfile).
209 # 06 Jun 2000 - Replace "print $Result" with a syswrite loop to
210 # allow large string output.
211 # 02 Jun 2000 - Corrected shrubCGIparameter($CGI_VALUE) to realy
212 # remove all control characters. Changed Interpreter
213 # initialization to shrub interpolated CGI parameters.
214 # Added 'text/ssmailto' interpreter script.
215 # 22 May 2000 - Changed some of the comments
216 # 09 May 2000 - Added list extraction for CGI parameters with
217 # CGIparseValueList (used with multiple selections).
218 # Use: CGI='@<CGI-parameter>' (fill in your name in <>)
219 # 09 May 2000 - Added a 'Not Present' condition to CGIparseValue.
220 # 27 Apr 2000 - Updated documentation to reflect changes.
221 # 27 Apr 2000 - SRC attribute "cleaned". Supported for external
222 # interpreters.
223 # 27 Apr 2000 - CGI attribute can be used in <SCRIPT> tag.
224 # 27 Apr 2000 - Gprolog, M4 support added.
225 # 26 Apr 2000 - Lisp (rep) support added.
226 # 20 Apr 2000 - Use of external interpreters now functional.
227 # 20 Apr 2000 - Removed bug from extracting Content types (RegExp)
228 # 10 Mar 2000 - Qualified unconditional removal of '#' that preclude
229 # the use of $#foo, i.e., I changed
230 # s/[^\\]\#[^\n\f\r]*([\n\f\r])/\1/g
231 # to
232 # s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/\1/g
233 # 03 Mar 2000 - Added a '$BlockPathAccess' variable to "hide"
234 # things like, e.g., CVS information in CVS subtrees
235 # 10 Feb 2000 - URLencode/URLdecode have been made case-insensitive
236 # 10 Feb 2000 - Added a BrowseDirs function (CGIscriptor package)
237 # 01 Feb 2000 - A BinaryMapFile in the ~/ directory has precedence
238 # over a "burried" BinaryMapFile.
239 # 04 Oct 1999 - Added two functions to check file names and email addresses
240 # (CGIscriptor::CGIsafeFileName and
241 # CGIscriptor::CGIsafeEmailAddress)
242 # 28 Sept 1999 - Corrected bug in sysread call for reading POST method
243 # to allow LONG posts.
244 # 28 Sept 1999 - Changed CGIparseValue to handle multipart/form-data.
245 # 29 July 1999 - Refer to BinaryMapFile from CGIscriptor directory, if
246 # this directory exists.
247 # 07 June 1999 - Limit file-pattern matching to LAST extension
248 # 04 June 1999 - Default text/html content type is printed only once.
249 # 18 May 1999 - Bug in replacement of ~/ and ./ removed.
250 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
251 # 15 May 1999 - Changed the name of the execute package to CGIexecute.
252 # Changed the processing of the Accept and Reject file.
253 # Added a full expression evaluation to Access Control.
254 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
255 # 27 Apr 1999 - Brought CGIscriptor under the GNU GPL. Made CGIscriptor
256 # Version 1.1 a module that can be called with 'require "CGIscriptor.pl"'.
257 # Requests are serviced by "Handle_Request()". CGIscriptor
258 # can still be called as a isolated perl script and a shell
259 # command.
260 # Changed the "factory default setting" so that it will run
261 # from the DOCUMENT_ROOT directory.
262 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
263 # 29 Mar 1999 - Remove second debugging STDERR switch. Moved most code
264 # to subroutines to change CGIscriptor into a module.
265 # Added mapping to process unsupported file types (e.g., binary
266 # pictures). See $BinaryMapFile.
267 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
268 # 24 Sept 1998 - Changed text of license (Rob van Son, R.J.J.H.vanSon@gmail.com)
269 # Removed a double setting of filepatterns and maximum query
270 # size. Changed email address. Removed some typos from the
271 # comments.
272 # 02 June 1998 - Bug fixed in URLdecode. Changing the foreach loop variable
273 # caused quiting CGIscriptor.(Rob van Son, R.J.J.H.vanSon@gmail.com)
274 # 02 June 1998 - $SS_PUB and $SS_SCRIPT inserted an extra /, removed.
275 # (Rob van Son, R.J.J.H.vanSon@gmail.com)
278 # Known Bugs:
280 # 23 Mar 2000
281 # It is not possible to use operators or variables to construct variable names,
282 # e.g., $bar = \@{$foo}; won't work. However, eval('$bar = \@{'.$foo.'};');
283 # will indeed work. If someone could tell me why, I would be obliged.
286 ############################################################################
288 # OBLIGATORY USER CONFIGURATION
290 # Configure the directories where all user files can be found (this
291 # is the equivalent of the server root directory of a WWW-server).
292 # These directories can be located ANYWHERE. For security reasons, it is
293 # better to locate them outside the WWW-tree of your HTTP server, unless
294 # CGIscripter handles ALL requests.
296 # For convenience, the defaults are set to the root of the WWW server.
297 # However, this might not be safe!
299 # ~/ text files
300 # $YOUR_HTML_FILES = "/usr/pub/WWW/SHTML"; # or SS_PUB as environment var
301 # (patch to use the parent directory of CGIscriptor as document root, should be removed)
302 if($ENV{'SCRIPT_FILENAME'}) # && $ENV{'SCRIPT_FILENAME'} !~ /\Q$ENV{'DOCUMENT_ROOT'}\E/)
304 $ENV{'DOCUMENT_ROOT'} = $ENV{'SCRIPT_FILENAME'};
305 $ENV{'DOCUMENT_ROOT'} =~ s@/CGIscriptor.*$@@ig;
308 # Just enter your own directory path here
309 $YOUR_HTML_FILES = $ENV{'DOCUMENT_ROOT'}; # default is the DOCUMENT_ROOT
311 # ./ script files (recommended to be different from the previous)
312 # $YOUR_SCRIPTS = "/usr/pub/WWW/scripts"; # or SS_SCRIPT as environment var
313 $YOUR_SCRIPTS = $YOUR_HTML_FILES; # This might be a SECURITY RISK
315 # End of obligatory user configuration
316 # (note: there is more non-essential user configuration below)
318 ############################################################################
320 # OPTIONAL USER CONFIGURATION (all values are used CASE INSENSITIVE)
322 # Script content-types: TYPE="Content-type" (user defined mime-type)
323 $ServerScriptContentType = "text/ssperl"; # Server Side Perl scripts
324 # CSS require a simple class
325 $ServerScriptContentClass = $ServerScriptContentType =~ m!/! ?
326 $' : "ssperl"; # Server Side Perl CSS classes
328 $ShellScriptContentType = "text/osshell"; # OS shell scripts
329 # # (Server Side perl ``-execution)
331 # Accessible file patterns, block any request that doesn't match.
332 # Matches any file with the extension .(s)htm(l), .txt, or .xmr
333 # (\. is used in regexp)
334 # Note: die unless $PATH_INFO =~ m@($FilePattern)$@is;
335 $FilePattern = ".shtml|.htm|.html|.xml|.xmr|.txt|.js|.css";
337 # The table with the content type MIME types
338 # (allows to differentiate MIME types, if needed)
339 %ContentTypeTable =
341 '.html' => 'text/html',
342 '.shtml' => 'text/html',
343 '.htm' => 'text/html',
344 '.xml' => 'text/xml',
345 '.txt' => 'text/plain',
346 '.js' => 'text/plain',
347 '.css' => 'text/plain'
351 # File pattern post-processing
352 $FilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
354 # SHAsum command needed for Authorization and Login
355 # (note, these have to be accessible in the HTML pages, ie, the CGIexecute environment)
356 my $shasum = "shasum -a 256";
357 if(qx{uname} =~ /Darwin/)
359 $shasum = "shasum-5.12 -a 256" unless `which shasum`;
361 my $SHASUMCMD = $shasum.' |cut -f 1 -d" "';
362 $ENV{"SHASUMCMD"} = $SHASUMCMD;
363 my $RANDOMHASHCMD = 'dd bs=1 count=64 if=/dev/urandom 2>/dev/null | '.$shasum.' -b |cut -f 1 -d" "';
364 $ENV{"RANDOMHASHCMD"} = $RANDOMHASHCMD;
366 # Hash a string, return hex of hash
367 sub hash_string # ($string) -> hex_hash
369 my $string = shift || "";
370 # Catch nasty \'-quotes, embed them in '..'"'"'..'
371 $string =~ s/\'/\'\"\'\"\'/isg;
372 my $hash = `printf '%s' '$string'| $ENV{"SHASUMCMD"}`;
373 chomp($hash);
374 return $hash;
377 # Generate random hex hash
378 sub get_random_hex # () -> hex
380 # Create Random Hash Salt
381 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $RANDOMHASHCMD | $!\n";
382 my $RANDOMSALT= <URANDOM>;
383 close(URANDOM);
384 chomp($RANDOMSALT);
386 return $RANDOMSALT;
390 # File patterns of files which are handled by session tickets.
391 %TicketRequiredPatterns = (
392 '^/Private(/|$)' => "Private/.Sessions\tPrivate/.Passwords\t/Private/Login.html\t+36000"
394 # Used to set cookies, only session cookies supported
395 my %SETCOOKIELIST = ();
397 # Session Ticket Directory: Private/.Sessions
398 # Password Directory: Private/.Passwords
399 # Login page (url path): /Private/Login.html
400 # Expiration time (s): +3600
401 # +<seconds> = relative time <seconds> is absolute date-time
403 # Manage login
404 # Set up a valid ticket from a given text file
405 # Use from command line. DO NOT USE ONLINE
406 # Watch out for passwords that get stored in the history file
408 # perl CGIscriptor.pl --managelogin [options] [files]
409 # Options:
410 # salt={file or saltvalue}
411 # masterkey={file or plaintext}
412 # newmasterkey={file or plaintext}
413 # password={file or palintext}
415 # Followed by one or more file names.
416 # Options can be interspersed between filenames,
417 # e.g., password='plaintext'
418 # Note that passwords are only used once!
420 if($ARGV[0] =~ /^\-\-managelogin/i)
422 my @arguments = @ARGV;
423 shift(@arguments);
424 setup_ticket_file(@arguments);
425 # Should be run on the command line
426 exit;
431 # Raw files must contain their own Content-type (xmr <- x-multipart-replace).
432 # THIS IS A SUBSET OF THE FILES DEFINED IN $FilePattern
433 $RawFilePattern = ".xmr";
434 # (In principle, this could contain a full file specification, e.g.,
435 # ".xmr|relocated.html")
437 # Raw File pattern post-processing
438 $RawFilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
440 # Server protocols for which "Content-type: text/html\n\n" should be printed
441 # (you should not bother with these, except for HTTP, they are mostly imaginary)
442 $ContentTypeServerProtocols = 'HTTP|MAIL|MIME';
444 # Block access to all (sub-) paths and directories that match the
445 # following (URL) path (is used as:
446 # 'die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;' )
447 $BlockPathAccess = '/(CVS|\.git)/'; # Protect CVS and .git information
449 # All (blocked) other file-types can be mapped to a single "binary-file"
450 # processor (a kind of pseudo-file path). This can either be an error
451 # message (e.g., "illegal file") or contain a script that serves binary
452 # files.
453 # Note: the real file path wil be stored in $ENV{CGI_BINARY_FILE}.
454 $BinaryMapFile = "/BinaryMapFile.xmr";
455 # Allow for the addition of a CGIscriptor directory
456 # Note that a BinaryMapFile in the root "~/" directory has precedence
457 $BinaryMapFile = "/CGIscriptor".$BinaryMapFile
458 if ! -e "$YOUR_HTML_FILES".$BinaryMapFile
459 && -e "$YOUR_HTML_FILES/CGIscriptor".$BinaryMapFile;
462 # List of all characters that are allowed in file names and paths.
463 # All requests containing illegal characters are blocked. This
464 # blocks most tricks (e.g., adding "\000", "\n", or other control
465 # characters, also blocks URI's using %FF)
466 # THIS IS A SECURITY FEATURE
467 # (this is also used to parse filenames in SRC= features, note the
468 # '-quotes, they are essential)
469 $FileAllowedChars = '\w\.\~\/\:\*\?\-'; # Covers Unix and Mac, but NO spaces
471 # Maximum size of the Query (number of characters clients can send
472 # covers both GET & POST combined)
473 $MaximumQuerySize = 2**20 - 1; # = 2**14 - 1
476 # Embeded URL get function used in SRC attributes and CGIscriptor::read_url
477 # (returns a string with the PERL code to transfer the URL contents, e.g.,
478 # "SAFEqx(\'curl \"http://www.fon.hum.uva.nl\"\')")
479 # "SAFEqx(\'wget --quiet --output-document=- \"http://www.fon.hum.uva.nl\"\')")
480 # Be sure to handle <BASE HREF='URL'> and allow BOTH
481 # direct printing GET_URL($URL [, 0]) and extracting the content of
482 # the $URL for post-processing GET_URL($URL, 1).
483 # You get the WHOLE file, including HTML header.
484 # The shell command Use $URL where the URL should go
485 # ('wget', 'snarf' or 'curl', uncomment the one you would like to use)
486 my $GET_URL_shell_command = 'wget --quiet --output-document=- $URL';
487 #my $GET_URL_shell_command = 'snarf $URL -';
488 #my $GET_URL_shell_command = 'curl $URL';
490 sub GET_URL # ($URL, $ValueNotPrint) -> content_of_url
492 my $URL = shift || return;
493 my $ValueNotPrint = shift || 0;
495 # Check URL for illegal characters
496 return "print '<h1>Illegal URL<h1>'\"\n\";" if $URL =~ /[^$FileAllowedChars\%]/;
498 # Include URL in final command
499 my $CurrentCommand = $GET_URL_shell_command;
500 $CurrentCommand =~ s/\$URL/$URL/g;
502 # Print to STDOUT or return a value
503 my $BlockPrint = "print STDOUT ";
504 $BlockPrint = "" if $ValueNotPrint;
506 my $Commands = <<"GETURLCODE";
507 # Get URL
509 my \$Page = "";
511 # Simple, using shell command
512 \$Page = SAFEqx('$CurrentCommand');
514 # Add a BASE tage to the header
515 \$Page =~ s!\\</head!\\<base href='$URL'\\>\\</head!ig unless \$Page =~ m!\\<base!;
517 # Print the URL value, or return it as a value
518 $BlockPrint\$Page;
520 GETURLCODE
521 return $Commands;
524 # As files can get rather large (and binary), you might want to use
525 # some more intelligent reading procedure, e.g.,
526 # Direct Perl
527 # # open(URLHANDLE, '/usr/bin/wget --quiet --output-document=- "$URL"|') || die "wget: \$!";
528 # #open(URLHANDLE, '/usr/bin/snarf "$URL" -|') || die "snarf: \$!";
529 # open(URLHANDLE, '/usr/bin/curl "$URL"|') || die "curl: \$!";
530 # my \$text = "";
531 # while(sysread(URLHANDLE,\$text, 1024) > 0)
533 # \$Page .= \$text;
534 # };
535 # close(URLHANDLE) || die "\$!";
536 # However, this doesn't work with the CGIexecute->evaluate() function.
537 # You get an error: 'No child processes at (eval 16) line 15, <file0> line 8.'
539 # You can forget the next two variables, they are only needed when
540 # you don't want to use a regular file system (i.e., with open)
541 # but use some kind of database/RAM image for accessing (generating)
542 # the data.
544 # Name of the environment variable that contains the file contents
545 # when reading directly from Database/RAM. When this environment variable,
546 # $ENV{$CGI_FILE_CONTENTS}, is not false, no real file will be read.
547 $CGI_FILE_CONTENTS = 'CGI_FILE_CONTENTS';
548 # Uncomment the following if you want to force the use of the data access code
549 # $ENV{$CGI_FILE_CONTENTS} = '-'; # Force use of $ENV{$CGI_DATA_ACCESS_CODE}
551 # Name of the environment variable that contains the RAM access perl
552 # code needed to read additional "files", i.e.,
553 # $ENV{$CGI_FILE_CONTENTS} = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
554 # When $ENV{$CGI_FILE_CONTENTS} eq '-', this code is executed to generate the data.
555 $CGI_DATA_ACCESS_CODE = 'CGI_DATA_ACCESS_CODE';
557 # You can, of course, fill this yourself, e.g.,
558 # $ENV{$CGI_DATA_ACCESS_CODE} =
559 # 'open(INPUT, "<$_[0]"); while(<INPUT>){print;};close(INPUT);'
562 # DEBUGGING
564 # Suppress error messages, this can be changed for debugging or error-logging
565 #open(STDERR, "/dev/null"); # (comment out for use in debugging)
567 # SPECIAL: Remove Comments, security, etc. if the command line is
568 # '>CGIscriptor.pl -slim >slimCGIscriptor.pl'
569 $TrimDownCGIscriptor = 1 if $ARGV[0] =~ /^\-slim/i;
571 # If CGIscriptor is used from the command line, the command line
572 # arguments are interpreted as the file (1st) and the Query String (rest).
573 # Get the arguments
574 $ENV{'PATH_INFO'} = shift(@ARGV) unless exists($ENV{'PATH_INFO'}) || grep(/\-\-help/i, @ARGV);
575 $ENV{'QUERY_STRING'} = join("&", @ARGV) unless exists($ENV{'QUERY_STRING'});
578 # Handle bail-outs in a user definable way.
579 # Catch Die and replace it with your own function.
580 # Ends with a call to "die $_[0];"
582 sub dieHandler # ($ErrorCode, "Message", @_) -> DEAD
584 my $ErrorCode = shift;
585 my $ErrorMessage = shift;
587 # Place your own reporting functions here
589 # Now, kill everything (default)
590 print STDERR "$ErrorCode: $ErrorMessage\n";
591 die $ErrorMessage;
595 # End of optional user configuration
596 # (note: there is more non-essential user configuration below)
598 if(grep(/\-\-help/i, @ARGV))
600 print << 'ENDOFPREHELPTEXT2';
602 ###############################################################################
604 # Author and Copyright (c):
605 # Rob van Son, © 1995,1996,1997,1998,1999,2000,2001,2002-2012
606 # NKI-AVL Amsterdam
607 # r.v.son@nki.nl
608 # Institute of Phonetic Sciences & IFOTT/ACLS
609 # University of Amsterdam
610 # Email: R.J.J.H.vanSon@gmail.com
611 # Email: R.J.J.H.vanSon@gmail.com
612 # WWW : http://www.fon.hum.uva.nl/rob/
614 # License for use and disclaimers
616 # CGIscriptor merges plain ASCII HTML files transparantly
617 # with CGI variables, in-line PERL code, shell commands,
618 # and executable scripts in other scripting languages.
620 # This program is free software; you can redistribute it and/or
621 # modify it under the terms of the GNU General Public License
622 # as published by the Free Software Foundation; either version 2
623 # of the License, or (at your option) any later version.
625 # This program is distributed in the hope that it will be useful,
626 # but WITHOUT ANY WARRANTY; without even the implied warranty of
627 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
628 # GNU General Public License for more details.
630 # You should have received a copy of the GNU General Public License
631 # along with this program; if not, write to the Free Software
632 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
635 # Contributors:
636 # Rob van Son (R.J.J.H.vanSon@gmail.com)
637 # Gerd Franke franke@roo.de (designed the <DIV> behaviour)
639 #######################################################
640 ENDOFPREHELPTEXT2
642 #######################################################>>>>>>>>>>Start Remove
644 # You can skip the following code, it is an auto-splice
645 # procedure.
647 # Construct a slimmed down version of CGIscriptor
648 # (i.e., CGIscriptor.pl -slim > slimCGIscriptor.pl)
650 if($TrimDownCGIscriptor)
652 open(CGISCRIPTOR, "<CGIscriptor.pl")
653 || dieHandler(1, "<CGIscriptor.pl not slimmed down: $!\n");
654 my $SKIPtext = 0;
655 my $SKIPComments = 0;
657 while(<CGISCRIPTOR>)
659 my $SKIPline = 0;
661 ++$LineCount;
663 # Start of SKIP text
664 $SKIPtext = 1 if /[\>]{10}Start Remove/;
665 $SKIPComments = 1 if $SKIPtext == 1;
667 # Skip this line?
668 $SKIPline = 1 if $SKIPtext || ($SKIPComments && /^\s*\#/);
670 ++$PrintCount unless $SKIPline;
672 print STDOUT $_ unless $SKIPline;
674 # End of SKIP text ?
675 $SKIPtext = 0 if /[\<]{10}End Remove/;
677 # Ready!
678 print STDERR "\# Printed $PrintCount out of $LineCount lines\n";
679 exit;
682 #######################################################
684 if(grep(/\-\-help/i, @ARGV))
686 print << 'ENDOFHELPTEXT';
688 # HYPE
690 # CGIscriptor merges plain ASCII HTML files transparantly and safely
691 # with CGI variables, in-line PERL code, shell commands, and executable
692 # scripts in many languages (on-line and real-time). It combines the
693 # "ease of use" of HTML files with the versatillity of specialized
694 # scripts and PERL programs. It hides all the specifics and
695 # idiosyncrasies of correct output and CGI coding and naming. Scripts
696 # do not have to be aware of HTML, HTTP, or CGI conventions just as HTML
697 # files can be ignorant of scripts and the associated values. CGIscriptor
698 # complies with the W3C HTML 4.0 recommendations.
699 # In addition to its use as a WWW embeded CGI processor, it can
700 # be used as a command-line document preprocessor (text-filter).
702 # THIS IS HOW IT WORKS
704 # The aim of CGIscriptor is to execute "plain" scripts inside a text file
705 # using any required CGIparameters and environment variables. It
706 # is optimized to transparantly process HTML files inside a WWW server.
707 # The native language is Perl, but many other scripting languages
708 # can be used.
710 # CGIscriptor reads text files from the requested input file (i.e., from
711 # $YOUR_HTML_FILES$PATH_INFO) and writes them to <STDOUT> (i.e., the
712 # client requesting the service) preceded by the obligatory
713 # "Content-type: text/html\n\n" or "Content-type: text/plain\n\n" string
714 # (except for "raw" files which supply their own Content-type message
715 # and only if the SERVER_PROTOCOL supports HTTP, MAIL, or MIME).
717 # When CGIscriptor encounters an embedded script, indicated by an HTML4 tag
719 # <SCRIPT TYPE="text/ssperl" [CGI="$VAR='default value'"] [SRC="ScriptSource"]>
720 # PERL script
721 # </SCRIPT>
723 # or
725 # <SCRIPT TYPE="text/osshell" [CGI="$name='default value'"] [SRC="ScriptSource"]>
726 # OS Shell script
727 # </SCRIPT>
729 # construct (anything between []-brackets is optional, other MIME-types
730 # and scripting languages are supported), the embedded script is removed
731 # and both the contents of the source file (i.e., "do 'ScriptSource'")
732 # AND the script are evaluated as a PERL program (i.e., by eval()),
733 # shell script (i.e., by a "safe" version of `Command`, qx) or an external
734 # interpreter. The output of the eval() function takes the place of the
735 # original <SCRIPT></SCRIPT> construct in the output string. Any CGI
736 # parameters declared by the CGI attribute are available as simple perl
737 # variables, and can subsequently be made available as variables to other
738 # scripting languages (e.g., bash, python, or lisp).
740 # Example: printing "Hello World"
741 # <HTML><HEAD><TITLE>Hello World</TITLE>
742 # <BODY>
743 # <H1><SCRIPT TYPE="text/ssperl">"Hello World"</SCRIPT></H1>
744 # </BODY></HTML>
746 # Save this in a file, hello.html, in the directory you indicated with
747 # $YOUR_HTML_FILES and access http://your_server/SHTML/hello.html
748 # (or to whatever name you use as an alias for CGIscriptor.pl).
749 # This is realy ALL you need to do to get going.
751 # You can use any values that are delivered in CGI-compliant form (i.e.,
752 # the "?name=value" type URL additions) transparently as "$name" variables
753 # in your scripts IFF you have declared them in the CGI attribute of
754 # a META or SCRIPT tag before e.g.:
755 # <META CONTENT="text/ssperl; CGI='$name = `default value`'
756 # [SRC='ScriptSource']">
757 # or
758 # <SCRIPT TYPE="text/ssperl" CGI="$name = 'default value'"
759 # [SRC='ScriptSource']>
760 # After such a 'CGI' attribute, you can use $name as an ordinary PERL variable
761 # (the ScriptSource file is immediately evaluated with "do 'ScriptSource'").
762 # The CGIscriptor script allows you to write ordinary HTML files which will
763 # include dynamic CGI aware (run time) features, such as on-line answers
764 # to specific CGI requests, queries, or the results of calculations.
766 # For example, if you wanted to answer questions of clients, you could write
767 # a Perl program called "Answer.pl" with a function "AnswerQuestion()"
768 # that prints out the answer to requests given as arguments. You then write
769 # an HTML page "Respond.html" containing the following fragment:
771 # <center>
772 # The Answer to your question
773 # <META CONTENT="text/ssperl; CGI='$Question'">
774 # <h3><SCRIPT TYPE="text/ssperl">$Question</SCRIPT></h3>
775 # is
776 # <h3><SCRIPT TYPE="text/ssperl" SRC="./PATH/Answer.pl">
777 # AnswerQuestion($Question);
778 # </SCRIPT></h3>
779 # </center>
780 # <FORM ACTION=Respond.html METHOD=GET>
781 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
782 # <INPUT TYPE=SUBMIT VALUE="Ask">
783 # </FORM>
785 # The output could look like the following (in HTML-speak):
787 # <CENTER>
788 # The Answer to your question
789 # <h3>What is the capital of the Netherlands?</h3>
790 # is
791 # <h3>Amsterdam</h3>
792 # </CENTER>
793 # <FORM ACTION=Respond.html METHOD=GET>
794 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
795 # <INPUT TYPE=SUBMIT VALUE="Ask">
797 # Note that the function "Answer.pl" does know nothing about CGI or HTML,
798 # it just prints out answers to arguments. Likewise, the text has no
799 # provisions for scripts or CGI like constructs. Also, it is completely
800 # trivial to extend this "program" to use the "Answer" later in the page
801 # to call up other information or pictures/sounds. The final text never
802 # shows any cue as to what the original "source" looked like, i.e.,
803 # where you store your scripts and how they are called.
805 # There are some extra's. The argument of the files called in a SRC= tag
806 # can access the CGI variables declared in the preceding META tag from
807 # the @ARGV array. Executable files are called as:
808 # `file '$ARGV[0]' ... ` (e.g., `Answer.pl \'$Question\'`;)
809 # The files called from SRC can even be (CGIscriptor) html files which are
810 # processed in-line. Furthermore, the SRC= tag can contain a perl block
811 # that is evaluated. That is,
812 # <META CONTENT="text/ssperl; CGI='$Question' SRC='{$Question}'">
813 # will result in the evaluation of "print do {$Question};" and the VALUE
814 # of $Question will be printed. Note that these "SRC-blocks" can be
815 # preceded and followed by other file names, but only a single block is
816 # allowed in a SRC= tag.
818 # One of the major hassles of dynamic WWW pages is the fact that several
819 # mutually incompatible browsers and platforms must be supported. For example,
820 # the way sound is played automatically is different for Netscape and
821 # Internet Explorer, and for each browser it is different again on
822 # Unix, MacOS, and Windows. Realy dangerous is processing user-supplied
823 # (form-) values to construct email addresses, file names, or database
824 # queries. All Apache WWW-server exploits reported in the media are
825 # based on faulty CGI-scripts that didn't check their user-data properly.
827 # There is no panacee for these problems, but a lot of work and problems
828 # can be saved by allowing easy and transparent control over which
829 # <SCRIPT></SCRIPT> blocks are executed on what CGI-data. CGIscriptor
830 # supplies such a method in the form of a pair of attributes:
831 # IF='...condition..' and UNLESS='...condition...'. When added to a
832 # script tag, the whole block (including the SRC attribute) will be
833 # ignored if the condition is false (IF) or true (UNLESS).
834 # For example, the following block will NOT be evaluated if the value
835 # of the CGI variable FILENAME is NOT a valid filename:
837 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
838 # IF='CGIscriptor::CGIsafeFileName($FILENAME)'>
839 # .....
840 # </SCRIPT>
842 # (the function CGIsafeFileName(String) returns an empty string ("")
843 # if the String argument is not a valid filename).
844 # The UNLESS attribute is the mirror image of IF.
846 # A user manual follows the HTML 4 and security paragraphs below.
848 ##########################################################################
850 # HTML 4 compliance
852 # In general, CGIscriptor.pl complies with the HTML 4 recommendations of
853 # the W3C. This means that any software to manage Web sites will be able
854 # to handle CGIscriptor files, as will web agents.
856 # All script code should be placed between <SCRIPT></SCRIPT> tags, the
857 # script type is indicated with TYPE="mime-type", the LANGUAGE
858 # feature is ignored, and a SRC feature is implemented. All CGI specific
859 # features are delegated to the CGI attribute.
861 # However, the behavior deviates from the W3C recommendations at some
862 # points. Most notably:
863 # 0- The scripts are executed at the server side, invissible to the
864 # client (i.e., the browser)
865 # 1- The mime-types are personal and idiosyncratic, but can be adapted.
866 # 2- Code in the body of a <SCRIPT></SCRIPT> tag-pair is still evaluated
867 # when a SRC feature is present.
868 # 3- The SRC attribute reads a list of files.
869 # 4- The files in a SRC attribute are processed according to file type.
870 # 5- The SRC attribute evaluates inline Perl code.
871 # 6- Processed META, DIV, INS tags are removed from the output
872 # document.
873 # 7- All attributes of the processed META tags, except CONTENT, are ignored
874 # (i.e., deleted from the output).
875 # 8- META tags can be placed ANYWHERE in the document.
876 # 9- Through the SRC feature, META tags can have visible output in the
877 # document.
878 # 10- The CGI attribute that declares CGI parameters, can be used
879 # inside the <SCRIPT> tag.
880 # 11- Use of an extended quote set, i.e., '', "", ``, (), {}, []
881 # and their \-slashed combinations: \'\', \"\", \`\`, \(\),
882 # \{\}, \[\].
883 # 12- IF and UNLESS attributes to <SCRIPT>, <META>, <DIV>, <INS> tags.
884 # 13- <DIV> tags cannot be nested, DIV tags are not
885 # rendered with new-lines.
886 # 14- The XML style <TAG .... /> is recognized and handled correctly.
887 # (i.e., no content is processed)
889 # The reasons for these choices are:
890 # You can still write completely HTML4 compliant documents. CGIscriptor
891 # will not force you to write "deviant" code. However, it allows you to
892 # do so (which is, in fact, just as bad). The prime design principle
893 # was to allow users to include plain Perl code. The code itself should
894 # be "enhancement free". Therefore, extra features were needed to
895 # supply easy access to CGI and Web site components. For security
896 # reasons these have to be declared explicitly. The SRC feature
897 # transparently manages access to external files, especially the safe
898 # use of executable files.
899 # The CGI attribute handles the declarations of external (CGI) variables
900 # in the SCRIPT and META tag's.
901 # EVERYTHING THE CGI ATTRIBUTE AND THE META TAG DO CAN BE DONE INSIDE
902 # A <SCRIPT></SCRIPT> TAG CONSTRUCT.
904 # The reason for the IF, UNLESS, and SRC attributes (and their Perl code
905 # evaluation) were build into the META and SCRIPT tags is part laziness,
906 # part security. The SRC blocks allows more compact documents and easier
907 # debugging. The values of the CGI variables can be immediately screened
908 # for security by IF or UNLESS conditions, and even SRC attributes (e.g.,
909 # email addresses and file names), and a few commands can be called
910 # without having to add another Perl TAG pair. This is especially important
911 # for documents that require the use of other (more restricted) "scripting"
912 # languages and facilities that lag transparent control structures.
914 ##########################################################################
916 # SECURITY
918 # Your WWW site is a few keystrokes away from a few hundred million internet
919 # users. A fair percentage of these users knows more about your computer
920 # than you do. And some of these just might have bad intentions.
922 # To ensure uncompromized operation of your server and platform, several
923 # features are incorporated in CGIscriptor.pl to enhance security.
924 # First of all, you should check the source of this program. No security
925 # measures will help you when you download programs from anonymous sources.
926 # If you want to use THIS file, please make sure that it is uncompromized.
927 # The best way to do this is to contact the source and try to determine
928 # whether s/he is reliable (and accountable).
930 # BE AWARE THAT ANY PROGRAMMER CAN CHANGE THIS PROGRAM IN SUCH A WAY THAT
931 # IT WILL SET THE DOORS TO YOUR SYSTEM WIDE OPEN
933 # I would like to ask any user who finds bugs that could compromise
934 # security to report them to me (and any other bug too,
935 # Email: R.J.J.H.vanSon@gmail.com or ifa@hum.uva.nl).
937 # Security features
939 # 1 Invisibility
940 # The inner workings of the HTML source files are completely hidden
941 # from the client. Only the HTTP header and the ever changing content
942 # of the output distinguish it from the output of a plain, fixed HTML
943 # file. Names, structures, and arguments of the "embedded" scripts
944 # are invisible to the client. Error output is suppressed except
945 # during debugging (user configurable).
947 # 2 Separate directory trees
948 # Directories containing Inline text and script files can reside on
949 # separate trees, distinct from those of the HTTP server. This means
950 # that NEITHER the text files, NOR the script files can be read by
951 # clients other than through CGIscriptor.pl, UNLESS they are
952 # EXPLICITELY made available.
954 # 3 Requests are NEVER "evaluated"
955 # All client supplied values are used as literal values (''-quoted).
956 # Client supplied ''-quotes are ALWAYS removed. Therefore, as long as the
957 # embedded scripts do NOT themselves evaluate these values, clients CANNOT
958 # supply executable commands. Be sure to AVOID scripts like:
960 # <META CONTENT="text/ssperl; CGI='$UserValue'">
961 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 $UserValue`;</SCRIPT>
963 # These are a recipe for disaster. However, the following quoted
964 # form should be save (but is still not adviced):
966 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 \'$UserValue\'`;</SCRIPT>
968 # A special function, SAFEqx(), will automatically do exactly this,
969 # e.g., SAFEqx('ls -1 $UserValue') will execute `ls -1 \'$UserValue\'`
970 # with $UserValue interpolated. I recommend to use SAFEqx() instead
971 # of backticks whenever you can. The OS shell scripts inside
973 # <SCRIPT TYPE="text/osshell">ls -1 $UserValue</SCRIPT>
975 # are handeld by SAFEqx and automatically ''-quoted.
977 # 4 Logging of requests
978 # All requests can be logged separate from the Host server. The level of
979 # detail is user configurable: Including or excluding the actual queries.
980 # This allows for the inspection of (im-) proper use.
982 # 5 Access control: Clients
983 # The Remote addresses can be checked against a list of authorized
984 # (i.e., accepted) or non-authorized (i.e., rejected) clients. Both
985 # REMOTE_HOST and REMOTE_ADDR are tested so clients without a proper
986 # HOST name can be (in-) excluded by their IP-address. Client patterns
987 # containing all numbers and dots are considered IP-addresses, all others
988 # domain names. No wild-cards or regexp's are allowed, only partial
989 # addresses.
990 # Matching of names is done from the back to the front (domain first,
991 # i.e., $REMOTE_HOST =~ /\Q$pattern\E$/is), so including ".edu" will
992 # accept or reject all clients from the domain EDU. Matching of
993 # IP-addresses is done from the front to the back (domain first, i.e.,
994 # $REMOTE_ADDR =~ /^\Q$pattern\E/is), so including "128." will (in-)
995 # exclude all clients whose IP-address starts with 128.
996 # There are two special symbols: "-" matches HOSTs with no name and "*"
997 # matches ALL HOSTS/clients.
998 # For those needing more expressional power, lines starting with
999 # "-e" are evaluated by the perl eval() function. E.g.,
1000 # '-e $REMOTE_HOST =~ /\.edu$/is;' will accept/reject clients from the
1001 # domain '.edu'.
1003 # 6 Access control: Files
1004 # In principle, CGIscriptor could read ANY file in the directory
1005 # tree as discussed in 1. However, for security reasons this is
1006 # restricted to text files. It can be made more restricted by entering
1007 # a global file pattern (e.g., ".html"). This is done by default.
1008 # For each client requesting access, the file pattern(s) can be made
1009 # more restrictive than the global pattern by entering client specific
1010 # file patterns in the Access Control files (see 5).
1011 # For example: if the ACCEPT file contained the lines
1012 # * DEMO
1013 # .hum.uva.nl LET
1014 # 145.18.230.
1015 # Then all clients could request paths containing "DEMO" or "demo", e.g.
1016 # "/my/demo/file.html" ($PATH_INFO =~ /\Q$pattern\E/), Clients from
1017 # *.hum.uva.nl could also request paths containing "LET or "let", e.g.
1018 # "/my/let/file.html", and clients from the local cluster
1019 # 145.18.230.[0-9]+ could access ALL files.
1020 # Again, for those needing more expressional power, lines starting with
1021 # "-e" are evaluated. For instance:
1022 # '-e $REMOTE_HOST =~ /\.edu$/is && $PATH_INFO =~ m@/DEMO/@is;'
1023 # will accept/reject requests for files from the directory "/demo/" from
1024 # clients from the domain '.edu'.
1026 # 7 Access control: Server side session tickets
1027 # Specific paths can be controlled by Session Tickets which must be
1028 # present as a SESSIONTICKET=<value> CGI variable in the request. These paths
1029 # are defined in %TicketRequiredPatterns as pairs of:
1030 # ('regexp' => 'SessionPath\tPasswordPath\tLogin.html\tExpiration').
1031 # Session Tickets are stored in a separate directory (SessionPath, e.g.,
1032 # "Private/.Session") as files with the exact same name of the SESSIONTICKET
1033 # CGI. The following is an example:
1034 # Type: SESSION
1035 # IPaddress: 127.0.0.1
1036 # AllowedPaths: ^/Private/Name/
1037 # Expires: 3600
1038 # Username: test
1039 # ...
1040 # Other content can follow.
1042 # It is adviced that Session Tickets should be deleted
1043 # after some (idle) time. The IP address should be the IP number at login, and
1044 # the SESSIONTICKET will be rejected if it is presented from another IP address.
1045 # AllowedPaths and DeniedPaths are perl regexps. Be careful how they match. Make sure to delimit
1046 # the names to prevent access to overlapping names, eg, "^/Private/Rob" will also
1047 # match "^/Private/Robert", however, "^/Private/Rob/" will not. Expires is the
1048 # time the ticket will remain valid after creation (file ctime). Time can be given
1049 # in s[econds] (default), m[inutes], h[hours], or d[ays], eg, "24h" means 24 hours.
1050 # None of these need be present, but the Ticket must have a non-zero size.
1052 # Next to Session Tickets, there are two other type of ticket files:
1053 # - LOGIN tickets store information about a current login request
1054 # - PASSWORD ticket store account information to authorize login requests
1056 # 8 Query length limiting
1057 # The length of the Query string can be limited. If CONTENT_LENGTH is larger
1058 # than this limit, the request is rejected. The combined length of the
1059 # Query string and the POST input is checked before any processing is done.
1060 # This will prevent clients from overloading the scripts.
1061 # The actual, combined, Query Size is accessible as a variable through
1062 # $CGI_Content_Length.
1064 # 9 Illegal filenames, paths, and protected directories
1065 # One of the primary security concerns in handling CGI-scripts is the
1066 # use of "funny" characters in the requests that con scripts in executing
1067 # malicious commands. Examples are inserting ';', null bytes, or <newline>
1068 # characters in URL's and filenames, followed by executable commands. A
1069 # special variable $FileAllowedChars stores a string of all allowed
1070 # characters. Any request that translates to a filename with a character
1071 # OUTSIDE this set will be rejected.
1072 # In general, all (readable files) in the DocumentRoot tree are accessible.
1073 # This might not be what you want. For instance, your DocumentRoot directory
1074 # might be the working directory of a CVS project and contain sensitive
1075 # information (e.g., the password to get to the repository). You can block
1076 # access to these subdirectories by adding the corresponding patterns to
1077 # the $BlockPathAccess variable. For instance, $BlockPathAccess = '/CVS/'
1078 # will block any request that contains '/CVS/' or:
1079 # die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;
1081 #10 The execution of code blocks can be controlled in a transparent way
1082 # by adding IF or UNLESS conditions in the tags themselves. That is,
1083 # a simple check of the validity of filenames or email addresses can
1084 # be done before any code is executed.
1086 ###############################################################################
1088 # USER MANUAL (sort of)
1090 # CGIscriptor removes embedded scripts, indicated by an HTML 4 type
1091 # <SCRIPT TYPE='text/ssperl'> </SCRIPT> or <SCRIPT TYPE='text/osshell'>
1092 # </SCRIPT> constructs. CGIscriptor also recognizes XML-type
1093 # <SCRIPT TYPE='text/ssperl'/> constructs. These are usefull when
1094 # the necessary code is already available in the TAG itself (e.g.,
1095 # using external files). The contents of the directive are executed by
1096 # the PERL eval() and `` functions (in a separate name space). The
1097 # result of the eval() function replaces the <SCRIPT> </SCRIPT> construct
1098 # in the output file. You can use the values that are delivered in
1099 # CGI-compliant form (i.e., the "?name=value&.." type URL additions)
1100 # transparently as "$name" variables in your directives after they are
1101 # defined in a <META> or <SCRIPT> tag.
1102 # If you define the variable "$CGIscriptorResults" in a CGI attribute, all
1103 # subsequent <SCRIPT> and <META> results (including the defining
1104 # tag) will also be pushed onto a stack: @CGIscriptorResults. This list
1105 # behaves like any other, ordinary list and can be manipulated.
1107 # Both GET and POST requests are accepted. These two methods are treated
1108 # equal. Variables, i.e., those values that are determined when a file is
1109 # processed, are indicated in the CGI attribute by $<name> or $<name>=<default>
1110 # in which <name> is the name of the variable and <default> is the value
1111 # used when there is NO current CGI value for <name> (you can use
1112 # white-spaces in $<name>=<default> but really DO make sure that the
1113 # default value is followed by white space or is quoted). Names can contain
1114 # any alphanumeric characters and _ (i.e., names match /[\w]+/).
1115 # If the Content-type: is 'multipart/*', the input is treated as a
1116 # MIME multipart message and automatically delimited. CGI variables get
1117 # the "raw" (i.e., undecoded) body of the corresponding message part.
1119 # Variables can be CGI variables, i.e., those from the QUERY_STRING,
1120 # environment variables, e.g., REMOTE_USER, REMOTE_HOST, or REMOTE_ADDR,
1121 # or predefined values, e.g., CGI_Decoded_QS (The complete, decoded,
1122 # query string), CGI_Content_Length (the length of the decoded query
1123 # string), CGI_Year, CGI_Month, CGI_Time, and CGI_Hour (the current
1124 # date and time).
1126 # All these are available when defined in a CGI attribute. All environment
1127 # variables are accessible as $ENV{'name'}. So, to access the REMOTE_HOST
1128 # and the REMOTE_USER, use, e.g.:
1130 # <SCRIPT TYPE='text/ssperl'>
1131 # ($ENV{'REMOTE_HOST'}||"-")." $ENV{'REMOTE_USER'}"
1132 # </SCRIPT>
1134 # (This will print a "-" if REMOTE_HOST is not known)
1135 # Another way to do this is:
1137 # <META CONTENT="text/ssperl; CGI='$REMOTE_HOST = - $REMOTE_USER'">
1138 # <SCRIPT TYPE='text/ssperl'>"$REMOTE_HOST $REMOTE_USER"</SCRIPT>
1139 # or
1140 # <META CONTENT='text/ssperl; CGI="$REMOTE_HOST = - $REMOTE_USER"
1141 # SRC={"$REMOTE_HOST $REMOTE_USER\n"}'>
1143 # This is possible because ALL environment variables are available as
1144 # CGI variables. The environment variables take precedence over CGI
1145 # names in case of a "name clash". For instance:
1146 # <META CONTENT="text/ssperl; CGI='$HOME' SRC={$HOME}">
1147 # Will print the current HOME directory (environment) irrespective whether
1148 # there is a CGI variable from the query
1149 # (e.g., Where do you live? <INPUT TYPE="TEXT" NAME="HOME">)
1150 # THIS IS A SECURITY FEATURE. It prevents clients from changing
1151 # the values of defined environment variables (e.g., by supplying
1152 # a bogus $REMOTE_ADDR). Although $ENV{} is not changed by the META tags,
1153 # it would make the use of declared variables insecure. You can still
1154 # access CGI variables after a name clash with
1155 # CGIscriptor::CGIparseValue(<name>).
1157 # Some CGI variables are present several times in the query string
1158 # (e.g., from multiple selections). These should be defined as
1159 # @VARIABLENAME=default in the CGI attribute. The list @VARIABLENAME
1160 # will contain ALL VARIABLENAME values from the query, or a single
1161 # default value. If there is an ENVIRONMENT variable of the
1162 # same name, it will be used instead of the default AND the query
1163 # values. The corresponding function is
1164 # CGIscriptor::CGIparseValueList(<name>)
1166 # CGI variables collected in a @VARIABLENAME list are unordered.
1167 # When more structured variables are needed, a hash table can be used.
1168 # A variable defined as %VARIABLE=default will collect all
1169 # CGI-parameters whose name start with 'VARIABLE' in a hash table with
1170 # the remainder of the name as a key. For instance, %PERSON will
1171 # collect PERSONname='John Doe', PERSONbirthdate='01 Jan 00', and
1172 # PERSONspouse='Alice' into a hash table %PERSON such that $PERSON{'spouse'}
1173 # equals 'Alice'. Any default value or environment value will be stored
1174 # under the "" key. If there is an ENVIRONMENT variable of the same name,
1175 # it will be used instead of the default AND the query values. The
1176 # corresponding function is CGIscriptor::CGIparseValueHash(<name>)
1178 # This method of first declaring your environment and CGI variables
1179 # before being able to use them in the scripts might seem somewhat
1180 # clumsy, but it protects you from inadvertedly printing out the values of
1181 # system environment variables when their names coincide with those used
1182 # in the CGI forms. It also prevents "clients" from supplying CGI
1183 # parameter values for your private variables.
1184 # THIS IS A SECURITY FEATURE!
1187 # NON-HTML CONTENT TYPES
1189 # Normally, CGIscriptor prints the standard "Content-type: text/html\n\n"
1190 # message before anything is printed. This has been extended to include
1191 # plain text (.txt) files, for which the Content-type (MIME type)
1192 # 'text/plain' is printed. In all other respects, text files are treated
1193 # as HTML files (this can be switched off by removing '.txt' from the
1194 # $FilePattern variable) . When the content type should be something else,
1195 # e.g., with multipart files, use the $RawFilePattern (.xmr, see also next
1196 # item). CGIscriptor will not print a Content-type message for this file
1197 # type (which must supply its OWN Content-type message). Raw files must
1198 # still conform to the <SCRIPT></SCRIPT> and <META> tag specifications.
1201 # NON-HTML FILES
1203 # CGIscriptor is intended to process HTML and text files only. You can
1204 # create documents of any mime-type on-the-fly using "raw" text files,
1205 # e.g., with the .xmr extension. However, CGIscriptor will not process
1206 # binary files of any type, e.g., pictures or sounds. Given the sheer
1207 # number of formats, I do not have any intention to do so. However,
1208 # an escape route has been provided. You can construct a genuine raw
1209 # (.xmr) text file that contains the perl code to service any file type
1210 # you want. If the global $BinaryMapFile variable contains the path to
1211 # this file (e.g., /BinaryMapFile.xmr), this file will be called
1212 # whenever an unsupported (non-HTML) file type is requested. The path
1213 # to the requested binary file is stored in $ENV('CGI_BINARY_FILE')
1214 # and can be used like any other CGI-variable. Servicing binary files
1215 # then becomes supplying the correct Content-type (e.g., print
1216 # "Content-type: image/jpeg\n\n";) and reading the file and writing it
1217 # to STDOUT (e.g., using sysread() and syswrite()).
1220 # THE META TAG
1222 # All attributes of a META tag are ignored, except the
1223 # CONTENT='text/ssperl; CGI=" ... " [SRC=" ... "]' attribute. The string
1224 # inside the quotes following the CONTENT= indication (white-space is
1225 # ignored, "" '' `` (){}[]-quote pairs are allowed, plus their \ versions)
1226 # MUST start with any of the CGIscriptor mime-types (e.g.: text/ssperl or
1227 # text/osshell) and a comma or semicolon.
1228 # The quoted string following CGI= contains a white-space separated list
1229 # of declarations of the CGI (and Environment) values and default values
1230 # used when no CGI values are supplied by the query string.
1232 # If the default value is a longer string containing special characters,
1233 # possibly spanning several lines, the string must be enclosed in quotes.
1234 # You may use any pair of quotes or brackets from the list '', "", ``, (),
1235 # [], or {} to distinguish default values (or preceded by \, e.g., \(...\)
1236 # is different from (...)). The outermost pair will always be used and any
1237 # other quotes inside the string are considered to be part of the string
1238 # value, e.g.,
1240 # $Value = {['this'
1241 # "and" (this)]}
1242 # will result in $Value getting the default value: ['this'
1243 # "and" (this)]
1244 # (NOTE that the newline is part of the default value!).
1246 # Internally, for defining and initializing CGI (ENV) values, the META
1247 # and SCRIPT tags use the functions "defineCGIvariable($name, $default)"
1248 # (scalars) and "defineCGIvariableList($name, $default)" (lists).
1249 # These functions can be used inside scripts as
1250 # "CGIscriptor::defineCGIvariable($name, $default)" and
1251 # "CGIscriptor::defineCGIvariableList($name, $default)".
1252 # "CGIscriptor::defineCGIvariableHash($name, $default)".
1254 # The CGI attribute will be processed exactly identical when used inside
1255 # the <SCRIPT> tag. However, this use is not according to the
1256 # HTML 4.0 specifications of the W3C.
1259 # THE DIV/INS TAGS
1261 # There is a problem when constructing html files containing
1262 # server-side perl scripts with standard HTML tools. These
1263 # tools will refuse to process any text between <SCRIPT></SCRIPT>
1264 # tags. This is quite annoying when you want to use large
1265 # HTML templates where you will fill in values.
1267 # For this purpose, CGIscriptor will read the neutral
1268 # <DIV CLASS="ssperl" ID="varname"></DIV> or
1269 # <INS CLASS="ssperl" ID="varname"></INS>
1270 # tag (in Cascading Style Sheet manner) Note that
1271 # "varname" has NO '$' before it, it is a bare name.
1272 # Any text between these <DIV ...></DIV> or
1273 # <INS ...></INS>tags will be assigned to '$varname'
1274 # as is (e.g., as a literal).
1275 # No processing or interpolation will be performed.
1276 # There is also NO nesting possible. Do NOT nest a
1277 # </DIV> inside a <DIV></DIV>! Moreover, neither INS nor
1278 # DIV tags do ensure a block structure in the final
1279 # rendering (i.e., no empty lines).
1281 # Note that <DIV CLASS="ssperl" ID="varname"/>
1282 # is handled the XML way. No content is processed,
1283 # but varname is defined, and any SRC directives are
1284 # processed.
1286 # You can use $varname like any other variable name.
1287 # However, $varname is NOT a CGI variable and will be
1288 # completely internal to your script. There is NO
1289 # interaction between $varname and the outside world.
1291 # To interpolate a DIV derived text, you can use:
1292 # $varname =~ s/([\]])/\\\1/g; # Mark ']'-quotes
1293 # $varname = eval("qq[$varname]"); # Interpolate all values
1295 # The DIV tags will process IF, UNLESS, CGI and
1296 # SRC attributes. The SRC files will be pre-pended to the
1297 # body text of the tag. SRC blocks are NOT executed.
1299 # CONDITIONAL PROCESSING: THE 'IF' AND 'UNLESS' ATTRIBUTES
1301 # It is often necessary to include code-blocks that should be executed
1302 # conditionally, e.g., only for certain browsers or operating system.
1303 # Furthermore, quite often sanity and security checks are necessary
1304 # before user (form) data can be processed, e.g., with respect to
1305 # email addresses and filenames.
1307 # Checks added to the code are often difficult to find, interpret or
1308 # maintain and in general mess up the code flow. This kind of confussion
1309 # is dangerous.
1310 # Also, for many of the supported "foreign" scripting languages, adding
1311 # these checks is cumbersome or even impossible.
1313 # As a uniform method for asserting the correctness of "context", two
1314 # attributes are added to all supported tags: IF and UNLESS.
1315 # They both evaluate their value and block execution when the
1316 # result is <FALSE> (IF) or <TRUE> (UNLESS) in Perl, e.g.,
1317 # UNLESS='$NUMBER \> 100;' blocks execution if $NUMBER <= 100. Note that
1318 # the backslash in the '\>' is removed and only used to differentiate
1319 # this conditional '>' from the tag-closing '>'. For symmetry, the
1320 # backslash in '\<' is also removed. Inside these conditionals,
1321 # ~/ and ./ are expanded to their respective directory root paths.
1323 # For example, the following tag will be ignored when the filename is
1324 # invalid:
1326 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
1327 # IF='CGIscriptor::CGIsafeFileName($FILENAME);'>
1328 # ...
1329 # </SCRIPT>
1331 # The IF and UNLESS values must be quoted. The same quotes are supported
1332 # as with the other attributes. The SRC attribute is ignored when IF and
1333 # UNLESS block execution.
1335 # NOTE: 'IF' and 'UNLESS' always evaluate perl code.
1338 # THE MAGIC SOURCE ATTRIBUTE (SRC=)
1340 # The SRC attribute inside tags accepts a list of filenames and URL's
1341 # separated by "," comma's (or ";" semicolons).
1342 # ALL the variable values defined in the CGI attribute are available
1343 # in @ARGV as if the file or block was executed from the command line,
1344 # in the exact order in which they were declared in the preceding CGI
1345 # attribute.
1347 # First, a SRC={}-block will be evaluated as if the code inside the
1348 # block was part of a <SCRIPT></SCRIPT> construct, i.e.,
1349 # "print do { code };'';" or `code` (i.e., SAFEqx('code)).
1350 # Only a single block is evaluated. Note that this is processed less
1351 # efficiently than <SCRIPT> </SCRIPT> blocks. Type of evaluation
1352 # depends on the content-type: Perl for text/ssperl and OS shell for
1353 # text/osshell. For other mime types (scripting languages), anything in
1354 # the source block is put in front of the code block "inside" the tag.
1356 # Second, executable files (i.e., -x filename != 0) are evaluated as:
1357 # print `filename \'$ARGV[0]\' \'$ARGV[1]\' ...`
1358 # That is, you can actually call executables savely from the SRC tag.
1360 # Third, text files that match the file pattern, used by CGIscriptor to
1361 # check whether files should be processed ($FilePattern), are
1362 # processed in-line (i.e., recursively) by CGIscriptor as if the code
1363 # was inserted in the original source file. Recursions, i.e., calling
1364 # a file inside itself, are blocked. If you need them, you have to code
1365 # them explicitely using "main::ProcessFile($file_path)".
1367 # Fourth, Perl text files (i.e., -T filename != 0) are evaluated as:
1368 # "do FileName;'';".
1370 # Last, URL's (i.e., starting with 'HTTP://', 'FTP://', 'GOPHER://',
1371 # 'TELNET://', 'WHOIS://' etc.) are loaded
1372 # and printed. The loading and handling of <BASE> and document header
1373 # is done by a command generated by main::GET_URL($URL [, 0]). You can enter your
1374 # own code (default is curl, wget, or snarf and some post-processing to add a <BASE> tag).
1376 # There are two pseudo-file names: PREFIX and POSTFIX. These implement
1377 # a switch from prefixing the SRC code/files (PREFIX, default) before the
1378 # content of the tag to appending the code after the content of the tag
1379 # (POSTFIX). The switches are done in the order in which the PREFIX and
1380 # POSTFIX labels are encountered. You can mix PREFIX and POSTFIX labels
1381 # in any order with the SRC files. Note that the ORDER of file execution
1382 # is determined for prefixed and postfixed files seperately.
1384 # File paths can be preceded by the URL protocol prefix "file://". This
1385 # is simply STRIPPED from the name.
1387 # Example:
1388 # The request
1389 # "http://cgi-bin/Action_Forms.pl/Statistics/Sign_Test.html?positive=8&negative=22
1390 # will result in printing "${SS_PUB}/Statistics/Sign_Test.html"
1391 # With QUERY_STRING = "positive=8&negative=22"
1393 # on encountering the lines:
1394 # <META CONTENT="text/osshell; CGI='$positive=11 $negative=3'">
1395 # <b><SCRIPT LANGUAGE=PERL TYPE="text/ssperl" SRC="./Statistics/SignTest.pl">
1396 # </SCRIPT></b><p>"
1398 # This line will be processed as:
1399 # "<b>`${SS_SCRIPT}/Statistics/SignTest.pl '8' '22'`</b><p>"
1401 # In which "${SS_SCRIPT}/Statistics/SignTest.pl" is an executable script,
1402 # This line will end up printed as:
1403 # "<b>p <= 0.0161</b><p>"
1405 # Note that the META tag itself will never be printed, and is invisible to
1406 # the outside world.
1408 # The SRC files in a DIV or INS tag will be added (pre-pended) to the body
1409 # of the <DIV></DIV> tag. Blocks are NOT executed! If you do not
1410 # need any content, you can use the <DIV...../> format.
1413 # THE CGISCRIPTOR ROOT DIRECTORIES ~/ AND ./
1415 # Inside <SCRIPT></SCRIPT> tags, filepaths starting
1416 # with "~/" are replaced by "$YOUR_HTML_FILES/", this way files in the
1417 # public directories can be accessed without direct reference to the
1418 # actual paths. Filepaths starting with "./" are replaced by
1419 # "$YOUR_SCRIPTS/" and this should only be used for scripts.
1421 # Note: this replacement can seriously affect Perl scripts. Watch
1422 # out for constructs like $a =~ s/aap\./noot./g, use
1423 # $a =~ s@aap\.@noot.@g instead.
1425 # CGIscriptor.pl will assign the values of $SS_PUB and $SS_SCRIPT
1426 # (i.e., $YOUR_HTML_FILES and $YOUR_SCRIPTS) to the environment variables
1427 # $SS_PUB and $SS_SCRIPT. These can be accessed by the scripts that are
1428 # executed.
1429 # Values not preceded by $, ~/, or ./ are used as literals
1432 # OS SHELL SCRIPT EVALUATION (CONTENT-TYPE=TEXT/OSSHELL)
1434 # OS scripts are executed by a "safe" version of the `` operator (i.e.,
1435 # SAFEqx(), see also below) and any output is printed. CGIscriptor will
1436 # interpolate the script and replace all user-supplied CGI-variables by
1437 # their ''-quoted values (actually, all variables defined in CGI attributes
1438 # are quoted). Other Perl variables are interpolated in a simple fasion,
1439 # i.e., $scalar by their value, @list by join(' ', @list), and %hash by
1440 # their name=value pairs. Complex references, e.g., @$variable, are all
1441 # evaluated in a scalar context. Quotes should be used with care.
1442 # NOTE: the results of the shell script evaluation will appear in the
1443 # @CGIscriptorResults stack just as any other result.
1444 # All occurrences of $@% that should NOT be interpolated must be
1445 # preceeded by a "\". Interpolation can be switched off completely by
1446 # setting $CGIscriptor::NoShellScriptInterpolation = 1
1447 # (set to 0 or undef to switch interpolation on again)
1448 # i.e.,
1449 # <SCRIPT TYPE="text/ssperl">
1450 # $CGIscriptor::NoShellScriptInterpolation = 1;
1451 # </SCRIPT>
1454 # RUN TIME TRANSLATION OF INPUT FILES
1456 # Allows general and global conversions of files using Regular Expressions.
1457 # Very handy (but costly) to rewrite legacy pages to a new format.
1458 # Select files to use it on with
1459 # my $TranslationPaths = 'filepattern';
1460 # This is costly. For efficiency, define:
1461 # $TranslationPaths = ''; when not using translations.
1462 # Accepts general regular expressions: [$pattern, $replacement]
1464 # Define:
1465 # my $TranslationPaths = 'filepattern'; # Pattern matching PATH_INFO
1467 # push(@TranslationTable, ['pattern', 'replacement']);
1468 # e.g. (for Ruby Rails):
1469 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
1470 # push(@TranslationTable, ['%>', '</SCRIPT>']);
1472 # Runs:
1473 # my $currentRegExp;
1474 # foreach $currentRegExp (@TranslationTable)
1476 # my ($pattern, $replacement) = @$currentRegExp;
1477 # $$text =~ s!$pattern!$replacement!msg;
1478 # };
1481 # EVALUATION OF OTHER SCRIPTING LANGUAGES
1483 # Adding a MIME-type and an interpreter command to
1484 # %ScriptingLanguages automatically will catch any other
1485 # scripting language in the standard
1486 # <SCRIPT TYPE="[mime]"></SCRIPT> manner.
1487 # E.g., adding: $ScriptingLanguages{'text/sspython'} = 'python';
1488 # will actually execute the folowing code in an HTML page
1489 # (ignore 'REMOTE_HOST' for the moment):
1490 # <SCRIPT TYPE="text/sspython">
1491 # # A Python script
1492 # x = ["A","real","python","script","Hello","World","and", REMOTE_HOST]
1493 # print x[4:8] # Prints the list ["Hello","World","and", REMOTE_HOST]
1494 # </SCRIPT>
1496 # The script code is NOT interpolated by perl, EXCEPT for those
1497 # interpreters that cannot handle variables themselves.
1498 # Currently, several interpreters are pre-installed:
1500 # Perl test - "text/testperl" => 'perl',
1501 # Python - "text/sspython" => 'python',
1502 # Ruby - "text/ssruby" => 'ruby',
1503 # Tcl - "text/sstcl" => 'tcl',
1504 # Awk - "text/ssawk" => 'awk -f-',
1505 # Gnu Lisp - "text/sslisp" => 'rep | tail +5 '.
1506 # "| egrep -v '> |^rep. |^nil\\\$'",
1507 # XLispstat - "text/xlispstat" => 'xlispstat | tail +7 '.
1508 # "| egrep -v '> \\\$|^NIL'",
1509 # Gnu Prolog- "text/ssprolog" => 'gprolog',
1510 # M4 macro's- "text/ssm4" => 'm4',
1511 # Born shell- "text/sh" => 'sh',
1512 # Bash - "text/bash" => 'bash',
1513 # C-shell - "text/csh" => 'csh',
1514 # Korn shell- "text/ksh" => 'ksh',
1515 # Praat - "text/sspraat" => "praat - | sed 's/Praat > //g'",
1516 # R - "text/ssr" => "R --vanilla --slave | sed 's/^[\[0-9\]*] //g'",
1517 # REBOL - "text/ssrebol" =>
1518 # "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\s*\[> \]* //g'",
1519 # PostgreSQL- "text/postgresql" => 'psql 2>/dev/null',
1520 # (psql)
1522 # Note that the "value" of $ScriptingLanguages{mime} must be a command
1523 # that reads Standard Input and writes to standard output. Any extra
1524 # output of interactive interpreters (banners, echo's, prompts)
1525 # should be removed by piping the output through 'tail', 'grep',
1526 # 'sed', or even 'awk' or 'perl'.
1528 # For access to CGI variables there is a special hashtable:
1529 # %ScriptingCGIvariables.
1530 # CGI variables can be accessed in three ways.
1531 # 1. If the mime type is not present in %ScriptingCGIvariables,
1532 # nothing is done and the script itself should parse the relevant
1533 # environment variables.
1534 # 2. If the mime type IS present in %ScriptingCGIvariables, but it's
1535 # value is empty, e.g., $ScriptingCGIvariables{"text/sspraat"} = '';,
1536 # the script text is interpolated by perl. That is, all $var, @array,
1537 # %hash, and \-slashes are replaced by their respective values.
1538 # 3. In all other cases, the CGI and environment variables are added
1539 # in front of the script according to the format stored in
1540 # %ScriptingCGIvariables. That is, the following (pseudo-)code is
1541 # executed for each CGI- or Environment variable defined in the CGI-tag:
1542 # printf(INTERPRETER, $ScriptingCGIvariables{$mime}, $CGI_NAME, $CGI_VALUE);
1544 # For instance, "text/testperl" => '$%s = "%s";' defines variable
1545 # definitions for Perl, and "text/sspython" => '%s = "%s"' for Python
1546 # (note that these definitions are not save, the real ones contain '-quotes).
1548 # THIS WILL NOT WORK FOR @VARIABLES, the (empty) $VARIABLES will be used
1549 # instead.
1551 # The $CGI_VALUE parameters are "shrubed" of all control characters
1552 # and quotes (by &shrubCGIparameter($CGI_VALUE)) for the options 2 and 3.
1553 # Control characters are replaced by \0<octal ascii value> (the exception
1554 # is \015, the newline, which is replaced by \n) and quotes
1555 # and backslashes by their HTML character
1556 # value (' -> &#39; ` -> &#96; " -> &quot; \ -> &#92; & -> &amper;).
1557 # For example:
1558 # if a client would supply the string value (in standard perl, e.g.,
1559 # \n means <newline>)
1560 # "/dev/null';\nrm -rf *;\necho '"
1561 # it would be processed as
1562 # '/dev/null&#39;;\nrm -rf *;\necho &#39;'
1563 # (e.g., sh or bash would process the latter more according to your
1564 # intentions).
1565 # If your intepreter requires different protection measures, you will
1566 # have to supply these in %main::SHRUBcharacterTR (string => translation),
1567 # e.g., $SHRUBcharacterTR{"\'"} = "&#39;";
1569 # Currently, the following definitions are used:
1570 # %ScriptingCGIvariables = (
1571 # "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value' (for testing)
1572 # "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
1573 # "text/ssruby" => '@%s = "%s"', # Ruby @VAR = "value"
1574 # "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
1575 # "text/ssawk" => '%s = "%s";', # Awk VAR = "value";
1576 # "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
1577 # "text/xlispstat" => '(setq %s "%s")', # Xlispstat (setq VAR "value")
1578 # "text/ssprolog" => '', # Gnu prolog (interpolated)
1579 # "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
1580 # "text/sh" => "\%s='\%s';", # Born shell VAR='value';
1581 # "text/bash" => "\%s='\%s';", # Born again shell VAR='value';
1582 # "text/csh" => "\$\%s = '\%s';", # C shell $VAR = 'value';
1583 # "text/ksh" => "\$\%s = '\%s';", # Korn shell $VAR = 'value';
1584 # "text/sspraat" => '', # Praat (interpolation)
1585 # "text/ssr" => '%s <- "%s";', # R VAR <- "value";
1586 # "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
1587 # "text/postgresql" => '', # PostgreSQL (interpolation)
1588 # "" => ""
1589 # );
1591 # Four tables allow fine-tuning of interpreter with code that should be
1592 # added before and after each code block:
1594 # Code added before each script block
1595 # %ScriptingPrefix = (
1596 # "text/testperl" => "\# Prefix Code;", # Perl script testing
1597 # "text/ssm4" => 'divert(0)' # M4 macro's (open STDOUT)
1598 # );
1599 # Code added at the end of each script block
1600 # %ScriptingPostfix = (
1601 # "text/testperl" => "\# Postfix Code;", # Perl script testing
1602 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1603 # );
1604 # Initialization code, inserted directly after opening (NEVER interpolated)
1605 # %ScriptingInitialization = (
1606 # "text/testperl" => "\# Initialization Code;", # Perl script testing
1607 # "text/ssawk" => 'BEGIN {', # Server Side awk scripts
1608 # "text/sslisp" => '(prog1 nil ', # Lisp (rep)
1609 # "text/xlispstat" => '(prog1 nil ', # xlispstat
1610 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1611 # );
1612 # Cleanup code, inserted before closing (NEVER interpolated)
1613 # %ScriptingCleanup = (
1614 # "text/testperl" => "\# Cleanup Code;", # Perl script testing
1615 # "text/sspraat" => 'Quit',
1616 # "text/ssawk" => '};', # Server Side awk scripts
1617 # "text/sslisp" => '(princ "\n" standard-output)).' # Closing print to rep
1618 # "text/xlispstat" => '(print "" *standard-output*)).' # Closing print to xlispstat
1619 # "text/postgresql" => '\q',
1620 # );
1623 # The SRC attribute is NOT magical for these interpreters. In short,
1624 # all code inside a source file or {} block is written verbattim
1625 # to the interpreter. No (pre-)processing or executional magic is done.
1627 # A serious shortcomming of the described mechanism for handling other
1628 # (scripting) languages, with respect to standard perl scripts
1629 # (i.e., 'text/ssperl'), is that the code is only executed when
1630 # the pipe to the interpreter is closed. So the pipe has to be
1631 # closed at the end of each block. This means that the state of the
1632 # interpreter (e.g., all variable values) is lost after the closing of
1633 # the next </SCRIPT> tag. The standard 'text/ssperl' scripts retain
1634 # all values and definitions.
1636 # APPLICATION MIME TYPES
1638 # To ease some important auxilliary functions from within the
1639 # html pages I have added them as MIME types. This uses
1640 # the mechanism that is also used for the evaluation of
1641 # other scripting languages, with interpolation of CGI
1642 # parameters (and perl-variables). Actually, these are
1643 # defined exactly like any other "scripting language".
1645 # text/ssdisplay: display some (HTML) text with interpolated
1646 # variables (uses `cat`).
1647 # text/sslogfile: write (append) the interpolated block to the file
1648 # mentioned on the first, non-empty line
1649 # (the filename can be preceded by 'File: ',
1650 # note the space after the ':',
1651 # uses `awk .... >> <filename>`).
1652 # text/ssmailto: send email directly from within the script block.
1653 # The first line of the body must contain
1654 # To:Name@Valid.Email.Address
1655 # (note: NO space between 'To:' and the email adres)
1656 # For other options see the mailto man pages.
1657 # It works by directly sending the (interpolated)
1658 # content of the text block to a pipe into the
1659 # Linux program 'mailto'.
1661 # In these script blocks, all Perl variables will be
1662 # replaced by their values. All CGI variables are cleaned before
1663 # they are used. These CGI variables must be redefined with a
1664 # CGI attribute to restore their original values.
1665 # In general, this will be more secure than constructing
1666 # e.g., your own email command lines. For instance, Mailto will
1667 # not execute any odd (forged) email addres, but just stops
1668 # when the email address is invalid and awk will construct
1669 # any filename you give it (e.g. '<File;rm\\\040-f' would end up
1670 # as a "valid" UNIX filename). Note that it will also gladly
1671 # store this file anywhere (/../../../etc/passwd will work!).
1672 # Use the CGIscriptor::CGIsafeFileName() function to clean the
1673 # filename.
1675 # SHELL SCRIPT PIPING
1677 # If a shell script starts with the UNIX style "#! <shell command> \n"
1678 # line, the rest of the shell script is piped into the indicated command,
1679 # i.e.,
1680 # open(COMMAND, "| command");print COMMAND $RestOfScript;
1682 # In many ways this is equivalent to the MIME-type profiling for
1683 # evaluating other scripting languages as discussed above. The
1684 # difference breaks down to convenience. Shell script piping is a
1685 # "raw" implementation. It allows you to control all aspects of
1686 # execution. Using the MIME-type profiling is easier, but has a
1687 # lot of defaults built in that might get in the way. Another
1688 # difference is that shell script piping uses the SAFEqx() function,
1689 # and MIME-type profiling does not.
1691 # Execution of shell scripts is under the control of the Perl Script blocks
1692 # in the document. The MIME-type triggered execution of <SCRIPT></SCRIPT>
1693 # blocks can be simulated easily. You can switch to a different shell,
1694 # e.g. tcl, completely by executing the following Perl commands inside
1695 # your document:
1697 # <SCRIPT TYPE="text/ssperl">
1698 # $main::ShellScriptContentType = "text/ssTcl"; # Yes, you can do this
1699 # CGIscriptor::RedirectShellScript('/usr/bin/tcl'); # Pipe to Tcl
1700 # $CGIscriptor::NoShellScriptInterpolation = 1;
1701 # </SCRIPT>
1703 # After this script is executed, CGIscriptor will parse scripts of
1704 # TYPE="text/ssTcl" and pipe their contents into '|/usr/bin/tcl'
1705 # WITHOUT interpolation (i.e., NO substitution of Perl variables).
1706 # The crucial function is :
1707 # CGIscriptor::RedirectShellScript('/usr/bin/tcl')
1708 # After executing this function, all shell scripts AND all
1709 # calls to SAFEqx()) are piped into '|/usr/bin/tcl'. If the argument
1710 # of RedirectShellScript is empty, e.g., '', the original (default)
1711 # value is reset.
1713 # The standard output, STDOUT, of any pipe is send to the client.
1714 # Currently, you should be carefull with quotes in such a piped script.
1715 # The results of a pipe is NOT put on the @CGIscriptorResults stack.
1716 # As a result, you do not have access to the output of any piped (#!)
1717 # process! If you want such access, execute
1718 # <SCRIPT TYPE="text/osshell">echo "script"|command</SCRIPT>
1719 # or
1720 # <SCRIPT TYPE="text/ssperl">
1721 # $resultvar = SAFEqx('echo "script"|command');
1722 # </SCRIPT>.
1724 # Safety is never complete. Although SAFEqx() prevents some of the
1725 # most obvious forms of attacks and security slips, it cannot prevent
1726 # them all. Especially, complex combinations of quotes and intricate
1727 # variable references cannot be handled safely by SAFEqx. So be on
1728 # guard.
1731 # PERL CODE EVALUATION (CONTENT-TYPE=TEXT/SSPERL)
1733 # All PERL scripts are evaluated inside a PERL package. This package
1734 # has a separate name space. This isolated name space protects the
1735 # CGIscriptor.pl program against interference from user code. However,
1736 # some variables, e.g., $_, are global and cannot be protected. You are
1737 # advised NOT to use such global variable names. You CAN write
1738 # directives that directly access the variables in the main program.
1739 # You do so at your own risk (there is definitely enough rope available
1740 # to hang yourself). The behavior of CGIscriptor becomes undefined if
1741 # you change its private variables during run time. The PERL code
1742 # directives are used as in:
1743 # $Result = eval($directive); print $Result;'';
1744 # ($directive contains all text between <SCRIPT></SCRIPT>).
1745 # That is, the <directive> is treated as ''-quoted string and
1746 # the result is treated as a scalar. To prevent the VALUE of the code
1747 # block from appearing on the client's screen, end the directive with
1748 # ';""</SCRIPT>'. Evaluated directives return the last value, just as
1749 # eval(), blocks, and subroutines, but only as a scalar.
1751 # IMPORTANT: All PERL variables defined are persistent. Each <SCRIPT>
1752 # </SCRIPT> construct is evaluated as a {}-block with associated scope
1753 # (e.g., for "my $var;" declarations). This means that values assigned
1754 # to a PERL variable can be used throughout the document unless they
1755 # were declared with "my". The following will actually work as intended
1756 # (note that the ``-quotes in this example are NOT evaluated, but used
1757 # as simple quotes):
1759 # <META CONTENT="text/ssperl; CGI=`$String='abcdefg'`">
1760 # anything ...
1761 # <SCRIPT TYPE=text/ssperl>@List = split('', $String);</SCRIPT>
1762 # anything ...
1763 # <SCRIPT TYPE=text/ssperl>join(", ", @List[1..$#List]);</SCRIPT>
1765 # The first <SCRIPT TYPE=text/ssperl></SCRIPT> construct will return the
1766 # value scalar(@List), the second <SCRIPT TYPE=text/ssperl></SCRIPT>
1767 # construct will print the elements of $String separated by commas, leaving
1768 # out the first element, i.e., $List[0].
1770 # Another warning: './' and '~/' are ALWAYS replaced by the values of
1771 # $YOUR_SCRIPTS and $YOUR_HTML_FILES, respectively . This can interfere
1772 # with pattern matching, e.g., $a =~ s/aap\./noot\./g will result in the
1773 # evaluations of $a =~ s/aap\\${YOUR_SCRIPTS}noot\\${YOUR_SCRIPTS}g. Use
1774 # s@<regexp>.@<replacement>.@g instead.
1777 # SERVER SIDE SESSIONS AND ACCESS CONTROL (LOGIN)
1779 # An infrastructure for user acount authorization and file access control
1780 # is available. Each request is matched against a list of URL path patterns.
1781 # If the request matches, a Session Ticket is required to access the URL.
1782 # This Session Ticket should be present as a CGI parameter or Cookie, eg:
1784 # CGI: SESSIONTICKET=&lt;value&gt;
1785 # Cookie: CGIscriptorSESSION=&lt;value&gt;
1787 # The example implementation stores Session Tickets as files in a local
1788 # directory. To create Session Tickets, a Login request must be given
1789 # with a LOGIN=&lt;value&gt; CGI parameter, a user name and a (doubly hashed)
1790 # password. The user name and (singly hashed) password are stored in a
1791 # PASSWORD ticket with the same name as the user account (name cleaned up
1792 # for security).
1794 # The example session model implements 4 functions:
1795 # - Login
1796 # The password is hashed with the user name and server side salt, and then
1797 # hashed with a random salt. Client and Server both perform these actions
1798 # and the Server only grants access if restults are the same. The server
1799 # side only stores the password hashed with the user name and
1800 # server side salt. Neither the plain password, nor the hashed password is
1801 # ever exchanged. Only values hashed with the one-time salt are exchanged.
1802 # - Session
1803 # For every access to a restricted URL, the Session Ticket is checked before
1804 # access is granted. There are three session modes. The first uses a fixed
1805 # Session Ticket that is stored as a cookie value in the browser (actually,
1806 # as a sessionStorage value). The second uses only the IP address at login
1807 # to authenticate requests. The third
1808 # is a Challenge mode, where the client has to calculate the value of the
1809 # next one-time Session Ticket from a value derived from the password and
1810 # a random string.
1811 # - Password Change
1812 # A new password is hashed with the user name and server side salt, and
1813 # then encrypted (XORed)
1814 # with the old password hashed with the user name and salt. That value is
1815 # exchanged and XORed with the stored old hashed(password+username+salt).
1816 # Again, the stored password value is never exchanged unencrypted.
1817 # - New Account
1818 # The text of a new account (Type: PASSWORD) file is constructed from
1819 # the new username (CGI: NEWUSERNAME, converted to lowercase) and
1820 # hashed new password (CGI: NEWPASSWORD). The same process is used to encrypt
1821 # the new password as is used for the Password Change function.
1822 # Again, the stored password value is never exchanged unencrypted.
1823 # Some default setting are encoded. For display in the browser, the new password
1824 # is reencrypted (XORed) with a special key, the old password hash
1825 # hashed with a session specific random hex value sent initially with the
1826 # session login ticket ($RANDOMSALT).
1827 # For example for user "NewUser" and password "NewPassword" with filename
1828 # "newuser":
1830 # Type: PASSWORD
1831 # Username: newuser
1832 # Password: 19afeadfba8d5dcd252e157fafd3010859f8762b87682b6b6cdb3e565194fa91
1833 # IPaddress: 127\.0\.0\.1
1834 # AllowedPaths: ^/Private/[\w\-]+\.html?
1835 # AllowedPaths: ^/Private/newuser/
1836 # Salt: e93cf858a1d5626bf095ea5c25df990dfa969ff5a5dc908b22c9a5229b525f65
1837 # Session: SESSION
1838 # Date: Fri Jun 29 12:46:22 2012
1839 # Time: 1340973982
1840 # Signature: 676c35d3aa63540293ea5442f12872bfb0a22665b504f58f804582493b6ef04e
1842 # The password is created with the commands:
1844 # printf '%s' 'NewPasswordnewuser970e68017413fb0ea84d7fe3c463077636dd6d53486910d4a53c693dd4109b1a'|shasum -a 256
1846 # However, the password account files are protected against unauthorized change.
1847 # To obtain a valid Password account, the following command should be given:
1849 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
1850 # masterkey='Sherlock investigates oleander curry in Bath' \
1851 # password='NewPassword' \
1852 # Private/.Passwords/newuser
1855 # Implementation
1857 # The session authentication mechanism is based on the exchange of ticket
1858 # identifiers. A ticket identifier is just a string of characters, a name
1859 # or a random 64 character hexadecimal string. Ticket identifiers should be
1860 # "safe" filenames (except user names). There are four types of tickets:
1861 # PASSWORD: User account descriptors, including a user name and password
1862 # LOGIN: Temporary anonymous tickets used during login
1863 # IPADDRESS: Authetication tokens that allow access based on the IP address of the request
1864 # SESSION: Reusable authetication tokens
1865 # CHALLENGE: One-time authetication tokens
1866 # All tickets can have an expiration date in the form of a time duration
1867 # from creation, in seconds, minutes, hours, or days (+duration[smhd]).
1868 # An absolute time can be given in seconds since the epoch of the server host.
1869 # Note that expiration times of CHALLENGE authetication tokens are calculated
1870 # from the last access time. Accounts can include a maximal lifetime
1871 # for session tickets (MaxLifetime).
1873 # A Login page should create a LOGIN ticket file locally and send a
1874 # server specific salt, a Random salt, and a LOGIN ticket
1875 # identifier. The server side compares the username and hashed password,
1876 # actually hashed(hashed(password+serversalt)+Random salt) from the client with
1877 # the values it calculates from the stored Random salt from the LOGIN
1878 # ticket and the hashed(password+serversalt) from the PASSWORD ticket. If
1879 # successful, a new SESSION ticket is generated as a hash sum of the stored
1880 # password and the LOGIN ticket. This SESSION ticket should also be
1881 # generated by the client and stored as sessionStorage and cookie values
1882 # as needed. The Username, IP address and Path are available as
1883 # $LoginUsername, $LoginIPaddress, and $LoginPath, respectively.
1885 # The CHALLENGE protocol stores the same value as the SESSION tickets.
1886 # However, this value is not exchanged, but kept secret in the JavaScript
1887 # sessionStorage object. Instead, every page returned from the
1888 # server will contain a one-time Challenge value ($CHALLENGETICKET) which
1889 # has to be hashed with the stored value to return the current ticket
1890 # id string.
1892 # In the current example implementation, all random values are created as
1893 # full, 256 bit SHA256 hash values (Hex strings) of 64 bytes read from
1894 # /dev/urandom.
1897 # Authorization
1899 # A limited level of authorization tuning is build into the login system.
1900 # Each account file (PASSWORD ticket file) can contain a number of
1901 # Capabilities lines. These control special priveliges. The
1902 # Capabilities can be checked inside the HTML pages as part of the
1903 # ticket information. Two privileges are handled internally:
1904 # CreateUser and VariableREMOTE_ADDR.
1905 # CreateUser allows the logged in user to create a new user account.
1906 # With VariableREMOTE_ADDR, the session of the logged in user is
1907 # not limited to the Remote IP address from which the inital log-in took
1908 # place. Sessions can hop from one apparant (proxy) IP address to another,
1909 # e.g., when using Tor. Any IPaddress patterns given in the PASSWORD
1910 # ticket file remain in effect during the session. For security reasons,
1911 # the VariableREMOTE_ADDR capability is only effective if the session
1912 # type is CHALLENGE.
1915 # Security considerations with Session tickets
1917 # For strong security, please use end-to-end encryption. This can be
1918 # achieved using a VPN (Virtual Private Network), SSH tunnel, or a HTTPS
1919 # capable server with OpenSSL. The session ticket system of CGIscriptor.pl
1920 # is intended to be used as a simple authentication mechanism WITHOUT
1921 # END-TO-END ENCRYPTION. The authenticating mechanism tries to use some
1922 # simple means to protect the authentication process from eavesdropping.
1923 # For this it uses a secure hash function, SHA256. For all practial purposes,
1924 # it is impossible to "decrypt" a SHA256 sum. But this login scheme is
1925 # only as secure as your browser. Which, in general, is not very secure.
1927 # One fundamental weakness of the implemented procedure is that the Client
1928 # obtains the code to encrypt the passwords from the server. It is the JavaScript
1929 # code in the HTML pages. An attacker who could place himself between Server
1930 # and Client, a man in the middle attack (MITM), could change the code to
1931 # reveal the plaintext password and other information. There is no
1932 # real protection against this attack without end-to-end encryption and
1933 # authentication. A simple, but rather cumbersome, way to check for such
1934 # attacks would be to store known good copys of the pages (downloaded
1935 # with a browser or automatically with curl or wget) and
1936 # then use other tools to download new pages at random intervals and compare
1937 # them to the old pages. For instance, the following line would remove
1938 # the variable ticket codes and give a fixed SHA256 sum for the original
1939 # Login.html page+code:
1940 # curl http://localhost:8080/Private/index.html | \
1941 # sed 's/=\"[a-z0-9]\{64\}\"/=""/g' | shasum -a 256
1942 # A simple diff command between old and new files should give only
1943 # differences in half a dozen lines, where only hexadecimal salt values
1944 # will actually differ.
1946 # A sort of solution for the MITM attack problem that might protect at
1947 # least the plaintext password would be to run a trusted web
1948 # page from local storage to handle password input. The solution would be
1949 # to add a hidden iFrame tag loading the untrusted page from the URL and
1950 # extract the needed ticket and salt values. Then run the stored, trusted,
1951 # code with these values. It is not (yet) possible to set the
1952 # required session storage inside the browser, so this method only works
1953 # for IPADDRESS sessions. There are many security problems with this
1954 # "solution".
1956 # Humans tend to reuse passwords. A compromise of a site running
1957 # CGIscriptor.pl could therefore lead to a compromise of user accounts at
1958 # other sites. Therefore, plain text passwords are never stored, used, or
1959 # exchanged. Instead, the plain password and user name are "encrypted" with
1960 # a server site salt value. Actually, all are concatenated and hashed
1961 # with a one-way secure hash function (SHA256) into a single string.
1962 # Whenever the word "password" is used, this hash sum is meant. Note that
1963 # the salts are generated from /dev/urandom. You should check whether the
1964 # implementation of /dev/urandom on your platform is secure before
1965 # relying on it. This might be a problem when running CGIscriptor under
1966 # Cygwin on MS Windows.
1967 # Note: no attempt is made to slow down the password hash, so bad
1968 # passwords can be cracked by brute force
1970 # As the (hashed) passwords are all that is needed to identify at the site,
1971 # these should not be stored in this form. A site specific passphrase
1972 # can be entered as an environment variable ($ENV{'CGIMasterKey'}). This
1973 # phrase is hashed with the server site salt and the result is hashed with
1974 # the user name and then XORed with the password when it is stored. Also, to
1975 # detect changes to the account (PASSWORD) and session tickets, a
1976 # (HMAC) hash of some of the contents of the ticket with the server salt and
1977 # CGIMasterKey is stored in each ticket.
1979 # Creating a valid (hashed) password, encrypt it with the CGIMasterKey and
1980 # construct a signature of the ticket are non-trivial. This has to be redone
1981 # with every change of the ticket file or CGIMasterKey change. CGIscriptor
1982 # can do this from the command line with the command:
1984 # perl CGIscriptor.pl --managelogin salt=Private/.Passwords/SALT \
1985 # masterkey='Sherlock investigates oleander curry in Bath' \
1986 # password='There is no password like more password' \
1987 # admin
1989 # CGIscriptor will exit after this command with the first option being
1990 # --managelogin. Options have the form:
1992 # salt=[file or string]
1993 # Server salt value to use io the value
1994 # stored in the ticket file. Will replace the stored value if a new
1995 # password is given. If you change the server salt, you have to
1996 # reset all the passwords. There is absolutely no procedure known
1997 # to recover plaintext passwords, except asking the account holders.
1998 # You are strongly adviced to make a backup before you apply such a change
1999 # masterkey=[file or string]
2000 # CGIMasterKey used to read and decrypt the ticket
2001 # newmasterkey=[file or string]
2002 # CGIMasterKey used to encrypt, sign,
2003 # and write the ticket. Defaults to the masterkey. If you change
2004 # the masterkey, you will have to reset all the accounts. You are strongly
2005 # adviced to make a backup before you apply such a change
2006 # password=[file or string]
2007 # New plaintext password
2009 # When the value of an option is a existing file path, the first line of
2010 # that file is used. Options are followed by one or more paths plus names
2011 # of existing ticket files. Each password option is only used for a single
2012 # ticket file. It is most definitely a bad idea to use a password that is
2013 # identical to an existing filepath, as the file will be read instead. Be
2014 # aware that the name of the file should be a cleaned up version of the
2015 # Username. This will not be checked.
2017 # For the authentication and a change of password, the (old) password
2018 # is used to "encrypt" a random one-time token or the new password,
2019 # respectively. For authentication, decryption is not needed, so a secure
2020 # hash function (SHA256) is used to create a one-way hash sum "encryption".
2021 # A new password must be decrypted. New passwords are encryped by XORing
2022 # them with the old password.
2024 # Strong Passwords: It is so easy
2025 # If you only could see what you are typing
2027 # Your password might be vulnerable to brute force guessing
2028 # (https://en.wikipedia.org/wiki/Brute_force_attack).
2029 # Protections against such attacks are costly in terms of code
2030 # complexity, bugs, and execution time. However, there is a very
2031 # simple and secure counter measure. See the XKCD comic
2032 # (http://xkcd.com/936/). The phrase, "There is no password like more
2033 # password" would be both much easier to remember, and still stronger
2034 # than "h4]D%@m:49", at least before this phrase was pasted as an
2035 # example on the Internet.
2037 # For the procedures used at this site, a basic computer setup can
2038 # check in the order of a billion passwords per second. You need a
2039 # password (or phrase) strength in the order of 56 bits to be a
2040 # little secure (one year on a single computer). Please be so kind
2041 # and add the name of your favorite flower, dish, fictional
2042 # character, or small town to your password. Say, Oleander, Curry,
2043 # Sherlock, or Bath, UK (each adds ~12 bits) or even the phrase "Sherlock
2044 # investigates oleander curry in Bath" (adds > 56 bits, note that
2045 # oleander is poisonous, so do not try this curry at home). That
2046 # would be more effective than adding a thousand rounds of encryption.
2047 # Typing long passwords without seeing what you are typing is
2048 # problematic. So a button should be included to make password
2049 # visible.
2052 # Technical matters
2054 # Client side JavaScript code definitions. Variable names starting with '$'
2055 # are CGIscriptor CGI variables. Some of the hashes could be strengthened
2056 # by switching to HMAC signatures. However, the security issues of
2057 # maintaining parallel functions for HMAC in both Perl and Javascript seem
2058 # to be more serious than the attack vectors against the hashes. But HMAC
2059 # is indeed used for the ticket signatures.
2061 # // On Login
2062 # HashPlaintextPassword() {
2063 # var plaintextpassword = document.getElementById('PASSWORD');
2064 # var serversalt = document.getElementById('SERVERSALT');
2065 # var username = document.getElementById('CGIUSERNAME');
2066 # return hex_sha256(plaintextpassword.value+username.value.toLowerCase()+serversalt.value);
2068 # var randomsalt = $RANDOMSALT; // From CGIscriptor
2069 # var loginticket = $LOGINTICKET; // From CGIscriptor
2070 # // Hash plaintext password
2071 # var password = HashPlaintextPassword();
2072 # // Authorize login
2073 # var hashedpassword = hex_sha256(randomsalt+password);
2074 # // Sessionticket
2075 # var sessionticket = hex_sha256(loginticket+password);
2076 # sessionStorage.setItem("CGIscriptorPRIVATE", sessionticket);
2077 # // Secretkey for encrypting new passwords, acts like a one-time pad
2078 # // Is set anew with every login, ie, also whith password changes
2079 # // and for each create new user request
2080 # var secretkey = hex_sha256(password+loginticket+randomsalt);
2081 # sessionStorage.setItem("CGIscriptorSECRET", secretkey);
2083 # // For a SESSION type request
2084 # sessionticket = sessionStorage.getItem("CGIscriptorPRIVATE");
2085 # createCookie("CGIscriptorSESSION",sessionticket, 0, "");
2087 // For a CHALLENGE type request
2088 # var sessionset = "$CHALLENGETICKET"; // From CGIscriptor
2089 # var sessionkey = sessionStorage.getItem("CGIscriptorPRIVATE");
2090 # sessionticket = hex_sha256(sessionset+sessionkey);
2091 # createCookie("CGIscriptorCHALLENGE",sessionticket, 0, "");
2093 # // For transmitting a new password
2094 # HashPlaintextNewPassword() {
2095 # var plaintextpassword = document.getElementById('NEWPASSWORD');
2096 # var serversalt = document.getElementById('SERVERSALT');
2097 # var username = document.getElementById('NEWUSERNAME');
2098 # return hex_sha256(plaintextpassword.value+username.value.toLowerCase()+serversalt.value);
2101 # var newpassword = document.getElementById('NEWPASSWORD');
2102 # var newpasswordrep = document.getElementById('NEWPASSWORDREP');
2103 # // Hash plaintext password
2104 # newpassword.value = HashPlaintextNewPassword();
2105 # var secretkey = sessionStorage.getItem("CGIscriptorSECRET");
2107 # var encrypted = XOR_hex_strings(secretkey, newpassword.value);
2108 # newpassword.value = encrypted;
2109 # newpasswordrep.value = encrypted;
2111 # // XOR of hexadecimal strings of equal length
2112 # function XOR_hex_strings(hex1, hex2) {
2113 # var resultHex = "";
2114 # var maxlength = Math.max(hex1.length, hex2.length);
2116 # for(var i=0; i &lt; maxlength; ++i) {
2117 # var h1 = hex1.charAt(i);
2118 # if(! h1) h1='0';
2119 # var h2 = hex2.charAt(i);
2120 # if(! h2) h2 ='0';
2121 # var d1 = parseInt(h1,16);
2122 # var d2 = parseInt(h2,16);
2123 # var resultD = d1^d2;
2124 # resultHex = resultHex+resultD.toString(16);
2125 # };
2126 # return resultHex;
2127 # };
2129 # Password encryption based on $ENV{'CGIMasterKey'}.
2130 # Server side Perl code:
2132 # # Password encryption
2133 # my $masterkey = $ENV{'CGIMasterKey'}
2134 # my $hash1 = hash_string($masterkey.$serversalt);
2135 # my $CryptKey = hash_string($username.$hash1);
2136 # $password = XOR_hex_strings($CryptKey,$password);
2138 # # Key for HMAC signing
2139 # my $hash1 = hash_string($masterkey.$serversalt);
2140 # my $HMACKey = hash_string($username.$hash1);
2144 # USER EXTENSIONS
2146 # A CGIscriptor package is attached to the bottom of this file. With
2147 # this package you can personalize your version of CGIscriptor by
2148 # including often used perl routines. These subroutines can be
2149 # accessed by prefixing their names with CGIscriptor::, e.g.,
2150 # <SCRIPT LANGUAGE=PERL TYPE=text/ssperl>
2151 # CGIscriptor::ListDocs("/Books/*") # List all documents in /Books
2152 # </SCRIPT>
2153 # It already contains some useful subroutines for Document Management.
2154 # As it is a separate package, it has its own namespace, isolated from
2155 # both the evaluator and the main program. To access variables from
2156 # the document <SCRIPT></SCRIPT> blocks, use $CGIexecute::<var>.
2158 # Currently, the following functions are implemented
2159 # (precede them with CGIscriptor::, see below for more information)
2160 # - SAFEqx ('String') -> result of qx/"String"/ # Safe application of ``-quotes
2161 # Is used by text/osshell Shell scripts. Protects all CGI
2162 # (client-supplied) values with single quotes before executing the
2163 # commands (one of the few functions that also works WITHOUT CGIscriptor::
2164 # in front)
2165 # - defineCGIvariable ($name[, $default) -> 0/1 (i.e., failure/success)
2166 # Is used by the META tag to define and initialize CGI and ENV
2167 # name/value pairs. Tries to obtain an initializing value from (in order):
2168 # $ENV{$name}
2169 # The Query string
2170 # The default value given (if any)
2171 # (one of the few functions that also works WITHOUT CGIscriptor::
2172 # in front)
2173 # - CGIsafeFileName (FileName) -> FileName or ""
2174 # Check a string against the Allowed File Characters (and ../ /..).
2175 # Returns an empty string for unsafe filenames.
2176 # - CGIsafeEmailAddress (Email) -> Email or ""
2177 # Check a string against correct email address pattern.
2178 # Returns an empty string for unsafe addresses.
2179 # - RedirectShellScript ('CommandString') -> FILEHANDLER or undef
2180 # Open a named PIPE for SAFEqx to receive ALL shell scripts
2181 # - URLdecode (URL encoded string) -> plain string # Decode URL encoded argument
2182 # - URLencode (plain string) -> URL encoded string # Encode argument as URL code
2183 # - CGIparseValue (ValueName [, URL_encoded_QueryString]) -> Decoded value
2184 # Extract the value of a CGI variable from the global or a private
2185 # URL-encoded query (multipart POST raw, NOT decoded)
2186 # - CGIparseValueList (ValueName [, URL_encoded_QueryString])
2187 # -> List of decoded values
2188 # As CGIparseValue, but now assembles ALL values of ValueName into a list.
2189 # - CGIparseHeader (ValueName [, URL_encoded_QueryString]) -> Header
2190 # Extract the header of a multipart CGI variable from the global or a private
2191 # URL-encoded query ("" when not a multipart variable or absent)
2192 # - CGIparseForm ([URL_encoded_QueryString]) -> Decoded Form
2193 # Decode the complete global URL-encoded query or a private
2194 # URL-encoded query
2195 # - read_url(URL) # Returns the page from URL (with added base tag, both FTP and HTTP)
2196 # Uses main::GET_URL(URL, 1) to get at the command to read the URL.
2197 # - BrowseDirs(RootDirectory [, Pattern, Startdir, CGIname]) # print browsable directories
2198 # - ListDocs(Pattern [,ListType]) # Prints a nested HTML directory listing of
2199 # all documents, e.g., ListDocs("/*", "dl");.
2200 # - HTMLdocTree(Pattern [,ListType]) # Prints a nested HTML listing of all
2201 # local links starting from a given document, e.g.,
2202 # HTMLdocTree("/Welcome.html", "dl");
2205 # THE RESULTS STACK: @CGISCRIPTORRESULTS
2207 # If the pseudo-variable "$CGIscriptorResults" has been defined in a
2208 # META tag, all subsequent SCRIPT and META results are pushed
2209 # on the @CGIscriptorResults stack. This list is just another
2210 # Perl variable and can be used and manipulated like any other list.
2211 # $CGIscriptorResults[-1] is always the last result.
2212 # This is only of limited use, e.g., to use the results of an OS shell
2213 # script inside a Perl script. Will NOT contain the results of Pipes
2214 # or code from MIME-profiling.
2217 # USEFULL CGI PREDEFINED VARIABLES (DO NOT ASSIGN TO THESE)
2219 # $CGI_HOME - The DocumentRoot directory
2220 # $CGI_Decoded_QS - The complete decoded Query String
2221 # $CGI_Content_Length - The ACTUAL length of the Query String
2222 # $CGI_Date - Current date and time
2223 # $CGI_Year $CGI_Month $CGI_Day $CGI_WeekDay - Current Date
2224 # $CGI_Time - Current Time
2225 # $CGI_Hour $CGI_Minutes $CGI_Seconds - Current Time, split
2226 # GMT Date/Time:
2227 # $CGI_GMTYear $CGI_GMTMonth $CGI_GMTDay $CGI_GMTWeekDay $CGI_GMTYearDay
2228 # $CGI_GMTHour $CGI_GMTMinutes $CGI_GMTSeconds $CGI_GMTisdst
2231 # USEFULL CGI ENVIRONMENT VARIABLES
2233 # Variables accessible (in APACHE) as $ENV{<name>}
2234 # (see: "http://hoohoo.ncsa.uiuc.edu/cgi/env.html"):
2236 # QUERY_STRING - The query part of URL, that is, everything that follows the
2237 # question mark.
2238 # PATH_INFO - Extra path information given after the script name
2239 # PATH_TRANSLATED - Extra pathinfo translated through the rule system.
2240 # (This doesn't always make sense.)
2241 # REMOTE_USER - If the server supports user authentication, and the script is
2242 # protected, this is the username they have authenticated as.
2243 # REMOTE_HOST - The hostname making the request. If the server does not have
2244 # this information, it should set REMOTE_ADDR and leave this unset
2245 # REMOTE_ADDR - The IP address of the remote host making the request.
2246 # REMOTE_IDENT - If the HTTP server supports RFC 931 identification, then this
2247 # variable will be set to the remote user name retrieved from
2248 # the server. Usage of this variable should be limited to logging
2249 # only.
2250 # AUTH_TYPE - If the server supports user authentication, and the script
2251 # is protected, this is the protocol-specific authentication
2252 # method used to validate the user.
2253 # CONTENT_TYPE - For queries which have attached information, such as HTTP
2254 # POST and PUT, this is the content type of the data.
2255 # CONTENT_LENGTH - The length of the said content as given by the client.
2256 # SERVER_SOFTWARE - The name and version of the information server software
2257 # answering the request (and running the gateway).
2258 # Format: name/version
2259 # SERVER_NAME - The server's hostname, DNS alias, or IP address as it
2260 # would appear in self-referencing URLs
2261 # GATEWAY_INTERFACE - The revision of the CGI specification to which this
2262 # server complies. Format: CGI/revision
2263 # SERVER_PROTOCOL - The name and revision of the information protocol this
2264 # request came in with. Format: protocol/revision
2265 # SERVER_PORT - The port number to which the request was sent.
2266 # REQUEST_METHOD - The method with which the request was made. For HTTP,
2267 # this is "GET", "HEAD", "POST", etc.
2268 # SCRIPT_NAME - A virtual path to the script being executed, used for
2269 # self-referencing URLs.
2270 # HTTP_ACCEPT - The MIME types which the client will accept, as given by
2271 # HTTP headers. Other protocols may need to get this
2272 # information from elsewhere. Each item in this list should
2273 # be separated by commas as per the HTTP spec.
2274 # Format: type/subtype, type/subtype
2275 # HTTP_USER_AGENT - The browser the client is using to send the request.
2276 # General format: software/version library/version.
2279 # INSTRUCTIONS FOR RUNNING CGIscriptor ON UNIX
2281 # CGIscriptor.pl will run on any WWW server that runs Perl scripts, just add
2282 # a line like the following to your srm.conf file (Apache example):
2284 # ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
2286 # URL's that refer to http://www.your.address/SHTML/... will now be handled
2287 # by CGIscriptor.pl, which can use a private directory tree (default is the
2288 # DOCUMENT_ROOT directory tree, but it can be anywhere, see manual).
2290 # If your hosting ISP won't let you add ScriptAlias lines you can use
2291 # the following "rewrite"-based "scriptalias" in .htaccess
2292 # (from Gerd Franke)
2294 # RewriteEngine On
2295 # RewriteBase /
2296 # RewriteCond %{REQUEST_FILENAME} .html$
2297 # RewriteCond %{SCRIPT_FILENAME} !cgiscriptor.pl$
2298 # RewriteCond %{REQUEST_FILENAME} -f
2299 # RewriteRule ^(.*)$ /cgi-bin/cgiscriptor.pl/$1?&%{QUERY_STRING}
2301 # Everthing with the extension ".html" and not including "cgiscriptor.pl"
2302 # in the url and where the file "path/filename.html" exists is redirected
2303 # to "/cgi.bin/cgiscriptor.pl/path/filename.html?query".
2304 # The user configuration should get the same path-level as the
2305 # .htaccess-file:
2307 # # Just enter your own directory path here
2308 # $YOUR_HTML_FILES = "$ENV{'DOCUMENT_ROOT'}";
2309 # # use DOCUMENT_ROOT only, if .htaccess lies in the root-directory.
2311 # If this .htaccess goes in a specific directory, the path to this
2312 # directory must be added to $ENV{'DOCUMENT_ROOT'}.
2314 # The CGIscriptor file contains all documentation as comments. These
2315 # comments can be removed to speed up loading (e.g., `egrep -v '^#'
2316 # CGIscriptor.pl` > leanScriptor.pl). A bare bones version of
2317 # CGIscriptor.pl, lacking documentation, most comments, access control,
2318 # example functions etc. (but still with the copyright notice and some
2319 # minimal documentation) can be obtained by calling CGIscriptor.pl on the
2320 # command line with the '-slim' command line argument, e.g.,
2322 # >CGIscriptor.pl -slim > slimCGIscriptor.pl
2324 # CGIscriptor.pl can be run from the command line with <path> and <query> as
2325 # arguments, as `CGIscriptor.pl <path> <query>`, inside a perl script
2326 # with 'do CGIscriptor.pl' after setting $ENV{PATH_INFO}
2327 # and $ENV{QUERY_STRING}, or CGIscriptor.pl can be loaded with 'require
2328 # "/real-path/CGIscriptor.pl"'. In the latter case, requests are processed
2329 # by 'Handle_Request();' (again after setting $ENV{PATH_INFO} and
2330 # $ENV{QUERY_STRING}).
2332 # Using the command line execution option, CGIscriptor.pl can be used as a
2333 # document (meta-)preprocessor. If the first argument is '-', STDIN will be read.
2334 # For example:
2336 # > cat MyDynamicDocument.html | CGIscriptor.pl - '[QueryString]' > MyStaticFile.html
2338 # This command line will produce a STATIC file with the DYNAMIC content of
2339 # MyDocument.html "interpolated".
2341 # This option would be very dangerous when available over the internet.
2342 # If someone could sneak a 'http://www.your.domain/-' URL past your
2343 # server, CGIscriptor could EXECUTE any POSTED contend.
2344 # Therefore, for security reasons, STDIN will NOT be read
2345 # if ANY of the HTTP server environment variables is set (e.g.,
2346 # SERVER_PORT, SERVER_PROTOCOL, SERVER_NAME, SERVER_SOFTWARE,
2347 # HTTP_USER_AGENT, REMOTE_ADDR).
2348 # This block on processing STDIN on HTTP requests can be lifted by setting
2349 # $BLOCK_STDIN_HTTP_REQUEST = 0;
2350 # In the security configuration. Butbe carefull when doing this.
2351 # It can be very dangerous.
2353 # Running demo's and more information can be found at
2354 # http://www.fon.hum.uva.nl/~rob/OSS/OSS.html
2356 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site or
2357 # CPAN that can use CGIscriptor.pl as the base of a µWWW server and
2358 # demonstrates its use.
2361 # PROCESSING NON-FILESYSTEM DATA
2363 # Normally, HTTP (WWW) requests map onto file that can be accessed
2364 # using the perl open() function. That is, the web server runs on top of
2365 # some directory structure. However, we can envission (and put to good
2366 # use) other systems that do not use a normal file system. The whole CGI
2367 # was developed to make dynamic document generation possible.
2369 # A special case is where we want to have it both: A normal web server
2370 # with normal "file data", but not a normal files system. For instance,
2371 # we want or normal Web Site to run directly from a RAM hash table or
2372 # other database, instead of from disk. But we do NOT want to code the
2373 # whole site structure in CGI.
2375 # CGIscriptor can do this. If the web server fills an environment variable
2376 # $ENV{'CGI_FILE_CONTENT'} with the content of the "file", then the content
2377 # of this variable is processed instead of opening a file. If this environment
2378 # variable has the value '-', the content of another environment variable,
2379 # $ENV{'CGI_DATA_ACCESS_CODE'} is executed as:
2380 # eval("\@_ = ($file_path); do {$ENV{'CGI_DATA_ACCESS_CODE'}};")
2381 # and the result is processed as if it was the content of the requested
2382 # file.
2383 # (actually, the names of the environment variables are user configurable,
2384 # they are stored in the local variables $CGI_FILE_CONTENT and
2385 # $CGI_DATA_ACCESS_CODE)
2387 # When using this mechanism, the SRC attribute mechanism will only partially work.
2388 # Only the "recursive" calls to CGIscriptor (the ProcessFile() function)
2389 # will work, the automagical execution of SRC files won't. (In this case,
2390 # the SRC attribute won't work either for other scripting languages)
2393 # NON-UNIX PLATFORMS
2395 # CGIscriptor.pl was mainly developed and tested on UNIX. However, as I
2396 # coded part of the time on an Apple Macintosh under MacPerl, I made sure
2397 # CGIscriptor did run under MacPerl (with command line options). But only
2398 # as an independend script, not as part of a HTTP server. I have used it
2399 # under Apache in Windows XP.
2401 ENDOFHELPTEXT
2402 exit;
2404 ###############################################################################
2406 # SECURITY CONFIGURATION
2408 # Special configurations related to SECURITY
2409 # (i.e., optional, see also environment variables below)
2411 # LOGGING
2412 # Log Clients and the requested paths (Redundant when loging Queries)
2414 $ClientLog = "./Client.log"; # (uncomment for use)
2416 # Format: Localtime | REMOTE_USER REMOTE_IDENT REMOTE_HOST REMOTE_ADDRESS \
2417 # PATH_INFO CONTENT_LENGTH (actually, the real query+post length)
2419 # Log Clients and the queries, the CGIQUERYDECODE is required if you want
2420 # to log queries. If you log Queries, the loging of Clients is redundant
2421 # (note that queries can be quite long, so this might not be a good idea)
2423 #$QueryLog = "./Query.log"; # (uncomment for use)
2425 # ACCESS CONTROL
2426 # the Access files should contain Hostnames or IP addresses,
2427 # i.e. REMOTE_HOST or REMOTE_ADDR, each on a separate line
2428 # optionally followed by one ore more file patterns, e.g., "edu /DEMO".
2429 # Matching is done "domain first". For example ".edu" matches all
2430 # clients whose "name" ends in ".edu" or ".EDU". The file pattern
2431 # "/DEMO" matches all paths that contain the strings "/DEMO" or "/demo"
2432 # (both matchings are done case-insensitive).
2433 # The name special symbol "-" matches ALL clients who do not supply a
2434 # REMOTE_HOST name, "*" matches all clients.
2435 # Lines starting with '-e' are evaluated. A non-zero return value indicates
2436 # a match. You can use $REMOTE_HOST, $REMOTE_ADDR, and $PATH_INFO. These
2437 # lines are evaluated in the program's own name-space. So DO NOT assign to
2438 # variables.
2440 # Accept the following users (remove comment # and adapt filename)
2441 $CGI_Accept = -s "$YOUR_SCRIPTS/ACCEPT.lis" ? "$YOUR_SCRIPTS/ACCEPT.lis" : ''; # (uncomment for use)
2443 # Reject requests from the following users (remove comment # and
2444 # adapt filename, this is only of limited use)
2445 $CGI_Reject = -s "$YOUR_SCRIPTS/REJECT.lis" ? "$YOUR_SCRIPTS/REJECT.lis" : ''; # (uncomment for use)
2447 # Empty lines or comment lines starting with '#' are ignored in both
2448 # $CGI_Accept and $CGI_Reject.
2450 # Block STDIN (i.e., '-') requests when servicing an HTTP request
2451 # Comment this out if you realy want to use STDIN in an on-line web server
2452 $BLOCK_STDIN_HTTP_REQUEST = 1;
2455 # End of security configuration
2457 ##################################################<<<<<<<<<<End Remove
2459 # PARSING CGI VALUES FROM THE QUERY STRING (USER CONFIGURABLE)
2461 # The CGI parse commands. These commands extract the values of the
2462 # CGI variables from the URL encoded Query String.
2463 # If you want to use your own CGI decoders, you can call them here
2464 # instead, using your own PATH and commenting/uncommenting the
2465 # appropriate lines
2467 # CGI parse command for individual values
2468 # (if $List > 0, returns a list value, if $List < 0, a hash table, this is optional)
2469 sub YOUR_CGIPARSE # ($Name [, $List]) -> Decoded value
2471 my $Name = shift;
2472 my $List = shift || 0;
2473 # Use one of the following by uncommenting
2474 if(!$List) # Simple value
2476 return CGIscriptor::CGIparseValue($Name) ;
2478 elsif($List < 0) # Hash tables
2480 return CGIscriptor::CGIparseValueHash($Name); # Defined in CGIscriptor below
2482 else # Lists
2484 return CGIscriptor::CGIparseValueList($Name); # Defined in CGIscriptor below
2487 # return `/PATH/cgiparse -value $Name`; # Shell commands
2488 # require "/PATH/cgiparse.pl"; return cgivalue($Name); # Library
2490 # Complete queries
2491 sub YOUR_CGIQUERYDECODE
2493 # Use one of the following by uncommenting
2494 return CGIscriptor::CGIparseForm(); # Defined in CGIscriptor below
2495 # return `/PATH/cgiparse -form`; # Shell commands
2496 # require "/PATH/cgiparse.pl"; return cgiform(); # Library
2499 # End of configuration
2501 #######################################################################
2503 # Translating input files.
2504 # Allows general and global conversions of files using Regular Expressions
2505 # Translations are applied in the order of definition.
2507 # Define:
2508 # my $TranslationPaths = 'pattern'; # Pattern matching PATH_INFO
2510 # push(@TranslationTable, ['pattern', 'replacement']);
2511 # e.g. (for Ruby Rails):
2512 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2513 # push(@TranslationTable, ['%>', '</SCRIPT>']);
2515 # Runs:
2516 # my $currentRegExp;
2517 # foreach $currentRegExp (keys(%TranslationTable))
2519 # my $currentRegExp;
2520 # foreach $currentRegExp (@TranslationTable)
2522 # my ($pattern, $replacement) = @$currentRegExp;
2523 # $$text =~ s!$pattern!$replacement!msg;
2524 # };
2525 # };
2527 # Configuration section
2529 #######################################################################
2531 # The file paths on which to apply the translation
2532 my $TranslationPaths = ''; # NO files
2533 #$TranslationPaths = '.'; # ANY file
2534 # $TranslationPaths = '\.html'; # HTML files
2536 my @TranslationTable = ();
2537 # Some legacy code
2538 push(@TranslationTable, ['\<\s*CGI\s+([^\>])*\>', '\<SCRIPT TYPE=\"text/ssperl\"\>$1\<\/SCRIPT>']);
2539 # Ruby Rails?
2540 push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2541 push(@TranslationTable, ['%>', '</SCRIPT>']);
2543 sub performTranslation # (\$text)
2545 my $text = shift || return;
2546 if(@TranslationTable && $TranslationPaths && $ENV{'PATH_INFO'} =~ m!$TranslationPaths!)
2548 my $currentRegExp;
2549 foreach $currentRegExp (@TranslationTable)
2551 my ($pattern, $replacement) = @$currentRegExp;
2552 $$text =~ s!$pattern!$replacement!msg;
2557 #######################################################################
2559 # Seamless access to other (Scripting) Languages
2560 # TYPE='text/ss<interpreter>'
2562 # Configuration section
2564 #######################################################################
2566 # OTHER SCRIPTING LANGUAGES AT THE SERVER SIDE (MIME => OScommand)
2567 # Yes, it realy is this simple! (unbelievable, isn't it)
2568 # NOTE: Some interpreters require some filtering to obtain "clean" output
2570 %ScriptingLanguages = (
2571 "text/testperl" => 'perl', # Perl for testing
2572 "text/sspython" => 'python', # Python
2573 "text/ssruby" => 'ruby', # Ruby
2574 "text/sstcl" => 'tcl', # TCL
2575 "text/ssawk" => 'awk -f-', # Awk
2576 "text/sslisp" => # lisp (rep, GNU)
2577 'rep | tail +4 '."| egrep -v '> |^rep. |^nil\\\$'",
2578 "text/xlispstat" => # xlispstat
2579 'xlispstat | tail +7 ' ."| egrep -v '> \\\$|^NIL'",
2580 "text/ssprolog" => # Prolog (GNU)
2581 "gprolog | tail +4 | sed 's/^| ?- //'",
2582 "text/ssm4" => 'm4', # M4 macro's
2583 "text/sh" => 'sh', # Born shell
2584 "text/bash" => 'bash', # Born again shell
2585 "text/csh" => 'csh', # C shell
2586 "text/ksh" => 'ksh', # Korn shell
2587 "text/sspraat" => # Praat (sound/speech analysis)
2588 "praat - | sed 's/Praat > //g'",
2589 "text/ssr" => # R
2590 "R --vanilla --slave | sed 's/^[\[0-9\]*] //'",
2591 "text/ssrebol" => # REBOL
2592 "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\\s*\[> \]* //'",
2593 "text/postgresql" => 'psql 2>/dev/null',
2595 # Not real scripting, but the use of other applications
2596 "text/ssmailto" => "awk 'NF||F{F=1;print \\\$0;}'|mailto >/dev/null", # Send mail from server
2597 "text/ssdisplay" => 'cat', # Display, (interpolation)
2598 "text/sslogfile" => # Log to file, (interpolation)
2599 "awk 'NF||L {if(!L){L=tolower(\\\$1)~/^file:\\\$/ ? \\\$2 : \\\$1;}else{print \\\$0 >> L;};}'",
2601 "" => ""
2604 # To be able to access the CGI variables in your script, they
2605 # should be passed to the scripting language in a readable form
2606 # Here you can enter how they should be printed (the first %s
2607 # is replaced by the NAME of the CGI variable as it apears in the
2608 # META tag, the second by its VALUE).
2609 # For Perl this would be:
2610 # "text/testperl" => '$%s = "%s";',
2611 # which would be executed as
2612 # printf('$%s = "%s";', $CGI_NAME, $CGI_VALUE);
2614 # If the hash table value doesn't exist, nothing is done
2615 # (you have to parse the Environment variables yourself).
2616 # If it DOES exist but is empty (e.g., "text/sspraat" => '',)
2617 # Perl string interpolation of variables (i.e., $var, @array,
2618 # %hash) is performed. This means that $@%\ must be protected
2619 # with a \.
2621 %ScriptingCGIvariables = (
2622 "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value'; (for testing)
2623 "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
2624 "text/ssruby" => '@%s = "%s"', # Ruby @VAR = 'value'
2625 "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
2626 "text/ssawk" => '%s = "%s";', # Awk VAR = 'value';
2627 "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
2628 "text/xlispstat" => '(setq %s "%s")', # xlispstat (setq VAR "value")
2629 "text/ssprolog" => '', # Gnu prolog (interpolated)
2630 "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
2631 "text/sh" => "\%s='\%s'", # Born shell VAR='value'
2632 "text/bash" => "\%s='\%s'", # Born again shell VAR='value'
2633 "text/csh" => "\$\%s='\%s';", # C shell $VAR = 'value';
2634 "text/ksh" => "\$\%s='\%s';", # Korn shell $VAR = 'value';
2636 "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
2637 "text/sspraat" => '', # Praat (interpolation)
2638 "text/ssr" => '%s <- "%s";', # R VAR <- "value";
2639 "text/postgresql" => '', # PostgreSQL (interpolation)
2641 # Not real scripting, but the use of other applications
2642 "text/ssmailto" => '', # MAILTO, (interpolation)
2643 "text/ssdisplay" => '', # Display, (interpolation)
2644 "text/sslogfile" => '', # Log to file, (interpolation)
2646 "" => ""
2649 # If you want something added in front or at the back of each script
2650 # block as send to the interpreter add it here.
2651 # mime => "string", e.g., "text/sspython" => "python commands"
2652 %ScriptingPrefix = (
2653 "text/testperl" => "\# Prefix Code;", # Perl script testing
2654 "text/ssm4" => 'divert(0)', # M4 macro's (open STDOUT)
2656 "" => ""
2658 # If you want something added at the end of each script block
2659 %ScriptingPostfix = (
2660 "text/testperl" => "\# Postfix Code;", # Perl script testing
2661 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2663 "" => ""
2665 # If you need initialization code, directly after opening
2666 %ScriptingInitialization = (
2667 "text/testperl" => "\# Initialization Code;", # Perl script testing
2668 "text/ssawk" => 'BEGIN {', # Server Side awk scripts (VAR = "value")
2669 "text/sslisp" => '(prog1 nil ', # Lisp (rep)
2670 "text/xlispstat" => '(prog1 nil ', # xlispstat
2671 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2673 "" => ""
2675 # If you need cleanup code before closing
2676 %ScriptingCleanup = (
2677 "text/testperl" => "\# Cleanup Code;", # Perl script testing
2678 "text/sspraat" => 'Quit',
2679 "text/ssawk" => '};', # Server Side awk scripts (VAR = "value")
2680 "text/sslisp" => '(princ "\n" standard-output)).', # Closing print to rep
2681 "text/xlispstat" => '(print ""))', # Closing print to xlispstat
2682 "text/postgresql" => '\q', # quit psql
2683 "text/ssdisplay" => "", # close cat
2685 "" => ""
2688 # End of configuration for foreign scripting languages
2690 ###############################################################################
2692 # Initialization Code
2695 sub Initialize_Request
2697 ###############################################################################
2699 # ENVIRONMENT VARIABLES
2701 # Use environment variables to configure CGIscriptor on a temporary basis.
2702 # If you define any of the configurable variables as environment variables,
2703 # these are used instead of the "hard coded" values above.
2705 $SS_PUB = $ENV{'SS_PUB'} || $YOUR_HTML_FILES;
2706 $SS_SCRIPT = $ENV{'SS_SCRIPT'} || $YOUR_SCRIPTS;
2709 # Substitution strings, these are used internally to handle the
2710 # directory separator strings, e.g., '~/' -> 'SS_PUB:' (Mac)
2711 $HOME_SUB = $SS_PUB;
2712 $SCRIPT_SUB = $SS_SCRIPT;
2715 # Make sure all script are reliably loaded
2716 push(@INC, $SS_SCRIPT);
2719 # Add the directory separator to the "home" directories.
2720 # (This is required for ~/ and ./ substitution)
2721 $HOME_SUB .= '/' if $HOME_SUB;
2722 $SCRIPT_SUB .= '/' if $SCRIPT_SUB;
2724 $CGI_HOME = $ENV{'DOCUMENT_ROOT'};
2725 $ENV{'PATH_TRANSLATED'} =~ /$ENV{'PATH_INFO'}/is;
2726 $CGI_HOME = $` unless $ENV{'DOCUMENT_ROOT'}; # Get the DOCUMENT_ROOT directory
2727 $default_values{'CGI_HOME'} = $CGI_HOME;
2728 $ENV{'HOME'} = $CGI_HOME;
2729 # Set SS_PUB and SS_SCRIPT as Environment variables (make them available
2730 # to the scripts)
2731 $ENV{'SS_PUB'} = $SS_PUB unless $ENV{'SS_PUB'};
2732 $ENV{'SS_SCRIPT'} = $SS_SCRIPT unless $ENV{'SS_SCRIPT'};
2734 $FilePattern = $ENV{'FilePattern'} || $FilePattern;
2735 $MaximumQuerySize = $ENV{'MaximumQuerySize'} || $MaximumQuerySize;
2736 $ClientLog = $ENV{'ClientLog'} || $ClientLog;
2737 $QueryLog = $ENV{'QueryLog'} || $QueryLog;
2738 $CGI_Accept = $ENV{'CGI_Accept'} || $CGI_Accept;
2739 $CGI_Reject = $ENV{'CGI_Reject'} || $CGI_Reject;
2741 # Parse file names
2742 $CGI_Accept =~ s@^\~/@$HOME_SUB@g if $CGI_Accept;
2743 $CGI_Reject =~ s@^\~/@$HOME_SUB@g if $CGI_Reject;
2744 $ClientLog =~ s@^\~/@$HOME_SUB@g if $ClientLog;
2745 $QueryLog =~ s@^\~/@$HOME_SUB@g if $QueryLog;
2747 $CGI_Accept =~ s@^\./@$SCRIPT_SUB@g if $CGI_Accept;
2748 $CGI_Reject =~ s@^\./@$SCRIPT_SUB@g if $CGI_Reject;
2749 $ClientLog =~ s@^\./@$SCRIPT_SUB@g if $ClientLog;
2750 $QueryLog =~ s@^\./@$SCRIPT_SUB@g if $QueryLog;
2752 @CGIscriptorResults = (); # A stack of results
2754 # end of Environment variables
2756 #############################################################################
2758 # Define and Store "standard" values
2760 # BEFORE doing ANYTHING check the size of Query String
2761 length($ENV{'QUERY_STRING'}) <= $MaximumQuerySize || dieHandler(2, "QUERY TOO LONG\n");
2763 # The Translated Query String and the Actual length of the (decoded)
2764 # Query String
2765 if($ENV{'QUERY_STRING'})
2767 # If this can contain '`"-quotes, be carefull to use it QUOTED
2768 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2769 $default_values{CGI_Content_Length} = length($default_values{CGI_Decoded_QS});
2772 # Get the current Date and time and store them as default variables
2774 # Get Local Time
2775 $LocalTime = localtime;
2777 # CGI_Year CGI_Month CGI_Day CGI_WeekDay CGI_Time
2778 # CGI_Hour CGI_Minutes CGI_Seconds
2780 $default_values{CGI_Date} = $LocalTime;
2781 ($default_values{CGI_WeekDay},
2782 $default_values{CGI_Month},
2783 $default_values{CGI_Day},
2784 $default_values{CGI_Time},
2785 $default_values{CGI_Year}) = split(' ', $LocalTime);
2786 ($default_values{CGI_Hour},
2787 $default_values{CGI_Minutes},
2788 $default_values{CGI_Seconds}) = split(':', $default_values{CGI_Time});
2790 # GMT:
2791 # CGI_GMTYear CGI_GMTMonth CGI_GMTDay CGI_GMTWeekDay CGI_GMTYearDay
2792 # CGI_GMTHour CGI_GMTMinutes CGI_GMTSeconds CGI_GMTisdst
2794 ($default_values{CGI_GMTSeconds},
2795 $default_values{CGI_GMTMinutes},
2796 $default_values{CGI_GMTHour},
2797 $default_values{CGI_GMTDay},
2798 $default_values{CGI_GMTMonth},
2799 $default_values{CGI_GMTYear},
2800 $default_values{CGI_GMTWeekDay},
2801 $default_values{CGI_GMTYearDay},
2802 $default_values{CGI_GMTisdst}) = gmtime;
2806 # End of Initialize Request
2808 ###################################################################
2810 # SECURITY: ACCESS CONTROL
2812 # Check the credentials of each client (use pattern matching, domain first).
2813 # This subroutine will kill-off (die) the current process whenever access
2814 # is denied.
2816 sub Access_Control
2818 # >>>>>>>>>>Start Remove
2820 # ACCEPTED CLIENTS
2822 # Only accept clients which are authorized, reject all unnamed clients
2823 # if REMOTE_HOST is given.
2824 # If file patterns are given, check whether the user is authorized for
2825 # THIS file.
2826 if($CGI_Accept)
2828 # Use local variables, REMOTE_HOST becomes '-' if undefined
2829 my $REMOTE_HOST = $ENV{REMOTE_HOST} || '-';
2830 my $REMOTE_ADDR = $ENV{REMOTE_ADDR};
2831 my $PATH_INFO = $ENV{'PATH_INFO'};
2833 open(CGI_Accept, "<$CGI_Accept") || dieHandler(3, "$CGI_Accept: $!\n");
2834 $NoAccess = 1;
2835 while(<CGI_Accept>)
2837 next unless /\S/; # Skip empty lines
2838 next if /^\s*\#/; # Skip comments
2840 # Full expressions
2841 if(/^\s*-e\s/is)
2843 my $Accept = $'; # Get the expression
2844 $NoAccess &&= eval($Accept); # evaluate the expresion
2846 else
2848 my ($Accept, @FilePatternList) = split;
2849 if($Accept eq '*' # Always match
2850 ||$REMOTE_HOST =~ /\Q$Accept\E$/is # REMOTE_HOST matches
2851 || (
2852 $Accept =~ /^[0-9\.]+$/
2853 && $REMOTE_ADDR =~ /^\Q$Accept\E/ # IP address matches
2857 if($FilePatternList[0])
2859 foreach $Pattern (@FilePatternList)
2861 # Check whether this patterns is accepted
2862 $NoAccess &&= ($PATH_INFO !~ m@\Q$Pattern\E@is);
2865 else
2867 $NoAccess = 0; # No file patterns -> Accepted
2871 # Blocked
2872 last unless $NoAccess;
2874 close(CGI_Accept);
2875 if($NoAccess){ dieHandler(4, "No Access: $PATH_INFO\n");};
2879 # REJECTED CLIENTS
2881 # Reject named clients, accept all unnamed clients
2882 if($CGI_Reject)
2884 # Use local variables, REMOTE_HOST becomes '-' if undefined
2885 my $REMOTE_HOST = $ENV{'REMOTE_HOST'} || '-';
2886 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
2887 my $PATH_INFO = $ENV{'PATH_INFO'};
2889 open(CGI_Reject, "<$CGI_Reject") || dieHandler(5, "$CGI_Reject: $!\n");
2890 $NoAccess = 0;
2891 while(<CGI_Reject>)
2893 next unless /\S/; # Skip empty lines
2894 next if /^\s*\#/; # Skip comments
2896 # Full expressions
2897 if(/^-e\s/is)
2899 my $Reject = $'; # Get the expression
2900 $NoAccess ||= eval($Reject); # evaluate the expresion
2902 else
2904 my ($Reject, @FilePatternList) = split;
2905 if($Reject eq '*' # Always match
2906 ||$REMOTE_HOST =~ /\Q$Reject\E$/is # REMOTE_HOST matches
2907 ||($Reject =~ /^[0-9\.]+$/
2908 && $REMOTE_ADDR =~ /^\Q$Reject\E/is # IP address matches
2912 if($FilePatternList[0])
2914 foreach $Pattern (@FilePatternList)
2916 $NoAccess ||= ($PATH_INFO =~ m@\Q$Pattern\E@is);
2919 else
2921 $NoAccess = 1; # No file patterns -> Rejected
2925 last if $NoAccess;
2927 close(CGI_Reject);
2928 if($NoAccess){ dieHandler(6, "Request rejected: $PATH_INFO\n");};
2931 ##########################################################<<<<<<<<<<End Remove
2934 # Get the filename
2936 # Does the filename contain any illegal characters (e.g., |, >, or <)
2937 dieHandler(7, "Illegal request: $ENV{'PATH_INFO'}\n") if $ENV{'PATH_INFO'} =~ /[^$FileAllowedChars]/;
2938 # Does the pathname contain an illegal (blocked) "directory"
2939 dieHandler(8, "Illegal request: $ENV{'PATH_INFO'}\n") if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@; # Access is blocked
2940 # Does the pathname contain a direct referencer to BinaryMapFile
2941 dieHandler(9, "Illegal request: $ENV{'PATH_INFO'}\n") if $BinaryMapFile && $ENV{'PATH_INFO'} =~ m@\Q$BinaryMapFile\E@; # Access is blocked
2943 # SECURITY: Is PATH_INFO allowed?
2944 if($FilePattern && $ENV{'PATH_INFO'} && $ENV{'PATH_INFO'} ne '-' &&
2945 ($ENV{'PATH_INFO'} !~ m@($FilePattern)$@is))
2947 # Unsupported file types can be processed by a special raw-file
2948 if($BinaryMapFile)
2950 $ENV{'CGI_BINARY_FILE'} = $ENV{'PATH_INFO'};
2951 $ENV{'PATH_INFO'} = $BinaryMapFile;
2953 else
2955 dieHandler(10, "Illegal file\n");
2961 # End of Security Access Control
2964 ############################################################################
2966 # Get the POST part of the query and add it to the QUERY_STRING.
2969 sub Get_POST_part_of_query
2972 # If POST, Read data from stdin to QUERY_STRING
2973 if($ENV{'REQUEST_METHOD'} =~ /POST/is)
2975 # SECURITY: Check size of Query String
2976 $ENV{'CONTENT_LENGTH'} <= $MaximumQuerySize || dieHandler(11, "Query too long: $ENV{'CONTENT_LENGTH'}\n"); # Query too long
2977 my $QueryRead = 0;
2978 my $SystemRead = $ENV{'CONTENT_LENGTH'};
2979 $ENV{'QUERY_STRING'} .= '&' if length($ENV{'QUERY_STRING'}) > 0;
2980 while($SystemRead > 0)
2982 $QueryRead = sysread(STDIN, $Post, $SystemRead); # Limit length
2983 $ENV{'QUERY_STRING'} .= $Post;
2984 $SystemRead -= $QueryRead;
2986 # Update decoded Query String
2987 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2988 $default_values{CGI_Content_Length} =
2989 length($default_values{CGI_Decoded_QS});
2993 # End of getting POST part of query
2996 ############################################################################
2998 # Start (HTML) output and logging
2999 # (if there are irregularities, it can kill the current process)
3002 sub Initialize_output
3004 # Construct the REAL file path (except for STDIN on the command line)
3005 my $file_path = $ENV{'PATH_INFO'} ne '-' ? $SS_PUB . $ENV{'PATH_INFO'} : '-';
3006 $file_path =~ s/\?.*$//; # Remove query
3007 # This is only necessary if your server does not catch ../ directives
3008 $file_path !~ m@\.\./@ || dieHandler(12, "Illegal ../ Construct\n"); # SECURITY: Do not allow ../ constructs
3010 # Block STDIN use (-) if CGIscriptor is servicing a HTTP request
3011 if($file_path eq '-')
3013 dieHandler(13, "STDIN request in On Line system\n") if $BLOCK_STDIN_HTTP_REQUEST
3014 && ($ENV{'SERVER_SOFTWARE'}
3015 || $ENV{'SERVER_NAME'}
3016 || $ENV{'GATEWAY_INTERFACE'}
3017 || $ENV{'SERVER_PROTOCOL'}
3018 || $ENV{'SERVER_PORT'}
3019 || $ENV{'REMOTE_ADDR'}
3020 || $ENV{'HTTP_USER_AGENT'});
3025 if($ClientLog)
3027 open(ClientLog, ">>$ClientLog");
3028 print ClientLog "$LocalTime | ",
3029 ($ENV{REMOTE_USER} || "-"), " ",
3030 ($ENV{REMOTE_IDENT} || "-"), " ",
3031 ($ENV{REMOTE_HOST} || "-"), " ",
3032 $ENV{REMOTE_ADDR}, " ",
3033 $ENV{PATH_INFO}, " ",
3034 $ENV{'CGI_BINARY_FILE'}, " ",
3035 ($default_values{CGI_Content_Length} || "-"),
3036 "\n";
3037 close(ClientLog);
3039 if($QueryLog)
3041 open(QueryLog, ">>$QueryLog");
3042 print QueryLog "$LocalTime\n",
3043 ($ENV{REMOTE_USER} || "-"), " ",
3044 ($ENV{REMOTE_IDENT} || "-"), " ",
3045 ($ENV{REMOTE_HOST} || "-"), " ",
3046 $ENV{REMOTE_ADDR}, ": ",
3047 $ENV{PATH_INFO}, " ",
3048 $ENV{'CGI_BINARY_FILE'}, "\n";
3050 # Write Query to Log file
3051 print QueryLog $default_values{CGI_Decoded_QS}, "\n\n";
3052 close(QueryLog);
3055 # Return the file path
3056 return $file_path;
3059 # End of Initialize output
3062 ############################################################################
3064 # Handle login access
3066 # Access is based on a valid session ticket.
3067 # Session tickets should be dependend on user name
3068 # and IP address. The patterns of URLs for which a
3069 # session ticket is needed and the login URL are stored in
3070 # %TicketRequiredPatterns as:
3071 # 'RegEx pattern' -> 'SessionPath\tPasswordPath\tLogin URL\tExpiration'
3074 sub Log_In_Access # () -> 0 = Access Allowed, Login page if access is not allowed
3076 # No patterns, no login
3077 goto Return unless %TicketRequiredPatterns;
3079 # Get and initialize values (watch out for stuff processed by BinaryMap files)
3080 my ($SessionPath, $PasswordsPath, $Login, $valid_duration) = ("", "", "", 0);
3081 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3082 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
3083 goto Return if $REMOTE_ADDR =~ /[^0-9\.]/;
3084 # Extract TICKETs, starting with returned cookies
3085 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3086 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3087 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3088 if($ENV{'COOKIE_JAR'})
3090 my $CurrentCookieJar = $ENV{'COOKIE_JAR'};
3091 $CurrentCookieJar =~ s/\w+\=\-\s*(\;\s*|$)//isg;
3092 if($CurrentCookieJar =~ /\s*CGIscriptorLOGIN\=\s*([^\;]+)/)
3094 ${"CGIexecute::LOGINTICKET"} = $1;
3096 if($CurrentCookieJar =~ /\s*CGIscriptorCHALLENGE\=\s*([^\;]+)/ && $1 ne '-')
3098 ${"CGIexecute::CHALLENGETICKET"} = $1;
3100 if($CurrentCookieJar =~ /\s*CGIscriptorSESSION\=\s*([^\;]+)/ && $1 ne '-')
3102 ${"CGIexecute::SESSIONTICKET"} = $1;
3105 # Get and check the tickets. Tickets are restricted to word-characters (alphanumeric+_+.)
3106 my $LOGINTICKET = ${"CGIexecute::LOGINTICKET"};
3107 goto Return if ($LOGINTICKET && $LOGINTICKET =~ /[^\w\.]/isg);
3108 my $SESSIONTICKET = ${"CGIexecute::SESSIONTICKET"};
3109 goto Return if ($SESSIONTICKET && $SESSIONTICKET =~ /[^\w\.]/isg);
3110 my $CHALLENGETICKET = ${"CGIexecute::CHALLENGETICKET"};
3111 goto Return if ($CHALLENGETICKET && $CHALLENGETICKET =~ /[^\w\.]/isg);
3112 # Look for a LOGOUT message
3113 my $LOGOUT = $ENV{QUERY_STRING} =~ /(^|\&)LOGOUT([\=\&]|$)/;
3114 # Username and password
3115 CGIexecute::defineCGIvariable('CGIUSERNAME', "");
3116 my $username = lc(${"CGIexecute::CGIUSERNAME"});
3117 goto Return if $username =~ m!^[^\w]!isg || $username =~ m![^\w \-]!isg;
3118 my $userfile = lc($username);
3119 $userfile =~ s/[^\w]/_/isg;
3120 CGIexecute::defineCGIvariable('PASSWORD', "");
3121 my $password = ${"CGIexecute::PASSWORD"};
3122 CGIexecute::defineCGIvariable('NEWUSERNAME', "");
3123 my $newuser = lc(${"CGIexecute::NEWUSERNAME"});
3124 CGIexecute::defineCGIvariable('NEWPASSWORD', "");
3125 my $newpassword = ${"CGIexecute::NEWPASSWORD"};
3127 foreach my $pattern (keys(%TicketRequiredPatterns))
3129 # Check BOTH the real PATH_INFO and the CGI_BINARY_FILE variable
3130 if($ENV{'PATH_INFO'} =~ m#$pattern# || $ENV{'CGI_BINARY_FILE'} =~ m#$pattern#)
3132 # Fall through a sieve of requirements
3133 ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3134 # If a LOGOUT is present, remove everything
3135 if($LOGOUT && !$LOGINTICKET)
3137 unlink "$SessionPath/$LOGINTICKET" if $LOGINTICKET && (-s "$SessionPath/$LOGINTICKET");
3138 $LOGINTICKET = "";
3139 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
3140 $SESSIONTICKET = "";
3141 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
3142 $CHALLENGETICKET = "";
3143 unlink "$SessionPath/$REMOTE_ADDR" if (-s "$SessionPath/$REMOTE_ADDR");
3144 $CHALLENGETICKET = "";
3145 goto Login;
3147 # Is there a change password request?
3148 if($newuser && $LOGINTICKET && $username)
3150 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3151 goto Login unless (-s "$PasswordsPath/$userfile");
3152 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3153 goto Login unless $ticket_valid;
3154 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".", 1);
3155 goto Login unless $ticket_valid;
3157 my ($sessiontype, $currentticket) = ("", "");
3158 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
3159 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
3160 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
3162 if($sessiontype)
3164 goto Login unless (-s "$SessionPath/$currentticket");
3165 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3166 goto Login unless $ticket_valid;
3168 # Authorize
3169 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath);
3170 goto Login unless $TMPTICKET;
3172 # Create a new user account
3173 CGIexecute::defineCGIvariable('NEWSESSION', "");
3174 my $newsession = ${"CGIexecute::NEWSESSION"};
3175 my $newaccount = create_newuser("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket",
3176 "$PasswordsPath/$userfile", $password, $newuser, $newpassword, $newsession);
3177 CGIexecute::defineCGIvariable('NEWACCOUNTTEXT', $newaccount);
3178 ${CGIexecute::NEWACCOUNTTEXT} = $newaccount;
3179 # NEWACCOUNTTEXT is NOT to be set by the query
3180 CGIexecute::ProtectCGIvariable('NEWACCOUNTTEXT');
3183 # Ready
3184 goto Return;
3186 # Is there a change password request?
3187 elsif($newpassword && $LOGINTICKET && $username)
3189 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3190 goto Login unless (-s "$PasswordsPath/$userfile");
3191 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3192 goto Login unless $ticket_valid;
3193 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".", 1);
3194 goto Login unless $ticket_valid;
3196 my ($sessiontype, $currentticket) = ("", "");
3197 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
3198 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
3199 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
3201 if($sessiontype)
3203 goto Login unless (-s "$SessionPath/$currentticket");
3204 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
3205 goto Login unless $ticket_valid;
3207 # Authorize
3208 change_password("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket", "$PasswordsPath/$userfile", $password, $newpassword);
3209 # After a change of password, you have to login again for a CHALLENGE
3210 if($CHALLENGETICKET){$CHALLENGETICKET = "";};
3211 # Ready
3212 goto Return;
3214 # Is there a login ticket of this name?
3215 elsif($LOGINTICKET)
3217 my $tickets_removed = remove_expired_tickets($SessionPath);
3218 goto Login unless (-s "$SessionPath/$LOGINTICKET");
3219 goto Login unless (-s "$PasswordsPath/$userfile");
3220 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3221 goto Login unless $ticket_valid;
3222 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".");
3223 goto Login unless $ticket_valid;
3225 # Remove any lingering tickets
3226 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
3227 $SESSIONTICKET = "";
3228 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
3229 $CHALLENGETICKET = "";
3232 # Authorize
3233 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath);
3234 if($TMPTICKET)
3236 my $authorization = read_ticket("$PasswordsPath/$userfile");
3237 goto Login unless $authorization;
3238 # Session type is read from the userfile
3239 if($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "CHALLENGE")
3241 # Create New Random CHALLENGETICKET
3242 $CHALLENGETICKET = $TMPTICKET;
3243 create_session_file("$SessionPath/$CHALLENGETICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3245 elsif($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "IPADDRESS")
3247 create_session_file("$SessionPath/$REMOTE_ADDR", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3249 else
3251 $SESSIONTICKET = $TMPTICKET;
3252 create_session_file("$SessionPath/$SESSIONTICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3253 $SETCOOKIELIST{"CGIscriptorSESSION"} = "-";
3256 # Login ticket file has been used, remove it
3257 unlink($loginfile);
3259 # Is there a session ticket of this name?
3260 # CHALLENGE
3261 if($CHALLENGETICKET)
3263 # Do not log into a CHALLENGE account if the SESSION cookie is present
3264 goto Login if $SESSIONTICKET =~ /\S/;
3265 goto Login unless (-s "$SessionPath/$CHALLENGETICKET");
3266 my $ticket_valid = check_ticket_validity("CHALLENGE", "$SessionPath/$CHALLENGETICKET", $REMOTE_ADDR, $PATH_INFO);
3267 goto Login unless $ticket_valid;
3269 my $oldchallenge = read_ticket("$SessionPath/$CHALLENGETICKET");
3270 goto Login unless $oldchallenge;
3271 # Check whether the login still exists
3272 my $userfile = lc($oldchallenge->{"Username"}->[0]);
3273 $userfile =~ s/[^\w]/_/isg;
3274 goto Login unless (-s "$PasswordsPath/$userfile");
3276 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3277 goto Login unless $ticket_valid;
3279 my $NEWCHALLENGETICKET = "";
3280 $NEWCHALLENGETICKET = copy_challenge_file("$SessionPath/$CHALLENGETICKET", "$PasswordsPath/$userfile", $SessionPath);
3281 # Sessionticket is available to scripts, do NOT set the cookie
3282 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3283 goto Return;
3285 # IPADDRESS
3286 elsif(-s "$SessionPath/$REMOTE_ADDR")
3288 my $ticket_valid = check_ticket_validity("IPADDRESS", "$SessionPath/$REMOTE_ADDR", $REMOTE_ADDR, $PATH_INFO);
3289 goto Login unless $ticket_valid;
3290 # Check whether the login still exists
3291 my $currentsessionticket = read_ticket("$SessionPath/$REMOTE_ADDR");
3292 my $userfile = lc($currentsessionticket->{"Username"}->[0]);
3293 $userfile =~ s/[^\w]/_/isg;
3294 goto Login unless (-s "$PasswordsPath/$userfile");
3296 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3297 goto Login unless $ticket_valid;
3299 goto Return;
3301 # SESSION
3302 elsif($SESSIONTICKET)
3304 goto Login unless (-s "$SessionPath/$SESSIONTICKET");
3305 my $ticket_valid = check_ticket_validity("SESSION", "$SessionPath/$SESSIONTICKET", $REMOTE_ADDR, $PATH_INFO);
3306 goto Login unless $ticket_valid;
3308 # Check whether the login still exists
3309 my $currentsessionticket = read_ticket("$SessionPath/$SESSIONTICKET");
3310 my $userfile = lc($currentsessionticket->{"Username"}->[0]);
3311 $userfile =~ s/[^\w]/_/isg;
3312 goto Login unless (-s "$PasswordsPath/$userfile");
3314 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3315 goto Login unless $ticket_valid;
3317 # Sessionticket is available to scripts
3318 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3319 goto Return;
3322 goto Login;
3323 goto Return;
3326 Return:
3327 # The Masterkey should NOT be accessible by the parsed files
3328 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3329 return 0;
3331 Login:
3332 create_login_file($PasswordsPath, $SessionPath, $REMOTE_ADDR);
3333 # Note, cookies are set only ONCE
3334 $SETCOOKIELIST{"CGIscriptorLOGIN"} = "-";
3335 # The Masterkey should NOT be accessible by the parsed files
3336 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3337 return "$YOUR_HTML_FILES/$Login";
3340 sub authorize_login # ($loginfile, $authorizationfile, $password, $SessionPath) => SESSIONTICKET First two arguments are file paths
3342 my $loginfile = shift || "";
3343 my $authorizationfile = shift || "";
3344 my $password = shift || "";
3345 my $SessionPath = shift || "";
3347 # Get Login session ticket
3348 my $loginticket = read_ticket($loginfile);
3349 return 0 unless $loginticket;
3350 # Get User credentials for authorization
3351 my $authorization = read_ticket($authorizationfile);
3352 return 0 unless $authorization;
3354 # Get Randomsalt
3355 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3356 return "" unless $Randomsalt;
3358 my $storedpassword = $authorization->{'Password'}->[0];
3359 return "" unless $storedpassword;
3360 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3361 return "" unless $password eq $Hashedpassword;
3363 # Extract Session Ticket
3364 my $loginsession = $loginticket->{'Session'}->[0];
3365 my $sessionticket = hash_string($storedpassword.$loginsession);
3366 chomp($sessionticket);
3367 $sessionticket = "" if -x "$SessionPath/$sessionticket";
3369 # No lingering password variables
3370 $Hashedpassword = $Randomsalt;
3371 $password = $Randomsalt;
3372 $authorization->{'Password'}->[0] = $Randomsalt;
3374 return $sessionticket;
3377 sub change_password # ($loginfile, $sessionfile, $authorizationfile, $password, $newpassword) First three arguments are file paths
3379 my $loginfile = shift || "";
3380 my $sessionfile = shift || "";
3381 my $authorizationfile = shift || "";
3382 my $password = shift || "";
3383 my $newpassword = shift || "";
3384 # Get Login session ticket
3385 my $loginticket = read_ticket($loginfile);
3386 return "" unless $loginticket;
3387 # Login ticket file has been used, remove it
3388 unlink($loginfile);
3389 # Get Randomsalt
3390 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3391 return "" unless $Randomsalt;
3392 my $LoginID = $loginticket->{'Session'}->[0];
3393 return "" unless $LoginID;
3395 # Get session ticket
3396 my $sessionticket = read_ticket($sessionfile);
3397 return "" unless $sessionticket;
3399 # Get User credentials for authorization
3400 my $authorization = read_ticket($authorizationfile);
3401 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3403 my $storedpassword = $authorization->{'Password'}->[0];
3404 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3405 return "" unless $password eq $Hashedpassword;
3406 my $secretkey = hash_string($storedpassword.$LoginID.$Randomsalt);
3408 # Decrypt the $newpassword
3409 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3410 return "" unless $decryptedPassword;
3411 # Authorization succeeded, change password
3412 $authorization->{'Password'}->[0] = $decryptedPassword;
3413 # Write out
3414 write_ticket($authorizationfile, $authorization, $authorization->{'Salt'}->[0]);
3416 # No lingering password variables
3417 $decryptedPassword = $Randomsalt;
3418 $secretkey = $Randomsalt;
3419 $storedpassword = $Randomsalt;
3420 $Hashedpassword = $Randomsalt;
3421 $authorization->{'Password'}->[0] = $Randomsalt;
3423 return $newpassword;
3425 # First three arguments are file paths
3426 sub create_newuser # ($loginfile, $sessionfile, $authorizationfile, $password, $newuser, $newpassword, $newsession) -> account text
3428 my $loginfile = shift || "";
3429 my $sessionfile = shift || "";
3430 my $authorizationfile = shift || "";
3431 my $password = shift || "";
3432 my $newuser = shift || "";
3433 my $newpassword = shift || "";
3434 my $newsession = shift || "";
3436 # Get Login session ticket
3437 my $loginticket = read_ticket($loginfile);
3438 return "" unless $loginticket;
3439 # Login ticket file has been used, remove it
3440 unlink($loginfile);
3441 # Get Randomsalt
3442 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3443 return "" unless $Randomsalt;
3444 my $LoginID = $loginticket->{'Session'}->[0];
3445 return "" unless $LoginID;
3447 # Get session ticket
3448 my $sessionticket = read_ticket($sessionfile);
3449 return "" unless $sessionticket;
3450 # Get User credentials for authorization
3451 my $authorization = read_ticket($authorizationfile);
3452 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3453 my $sessionkey = $sessionticket->{'Key'}->[0];
3454 my $serversalt = $authorization->{'Salt'}->[0];
3455 return "" unless $serversalt;
3457 my $storedpassword = $authorization->{'Password'}->[0];
3458 my $Hashedpassword = hash_string($storedpassword.$Randomsalt);
3459 return "" unless $password eq $Hashedpassword;
3460 my $secretkey = hash_string($storedpassword.$LoginID.$Randomsalt);
3462 # Decrypt the $newpassword
3463 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3464 return "" unless $decryptedPassword;
3466 # Authorization succeeded, create new account
3467 my $newaccount = {};
3468 $newaccount->{'Type'} = ['PASSWORD'];
3469 $newaccount->{'Username'} = [$newuser];
3470 $newaccount->{'Password'} = [$decryptedPassword];
3471 $newaccount->{'Salt'} = [$serversalt];
3472 $newaccount->{'Session'} = ['SESSION'];
3473 if($newsession eq 'IPADDRESS'){$newaccount->{'Session'} = ['IPADDRESS'];};
3474 if($newsession eq 'CHALLENGE'){$newaccount->{'Session'} = ['CHALLENGE'];};
3475 my $timesec = time();
3476 my $gmt_date = gmtime();
3477 $newaccount->{'Time'} = [$timesec];
3478 $newaccount->{'Date'} = [$gmt_date];
3480 # AllowedPaths
3481 my $NewAllowedPaths = "";
3482 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3483 my $currentRoot = "";
3484 $currentRoot = $1 if $PATH_INFO =~ m!^([\w\-\. /]+)!isg;
3485 $currentRoot =~ s![^/]+$!!isg;
3486 if($currentRoot)
3488 $currentRoot .= '/' unless $currentRoot =~ m!/$!;
3489 my $newpath = "^".${currentRoot}.'[\w\-]+\.html?';
3490 $NewAllowedPaths .= 'AllowedPaths: ^'.${currentRoot}.'[\w\-]+\.html?'."\n";
3491 $newaccount->{'AllowedPaths'} = [$newpath];
3493 else
3495 # Tricky PATH_INFO, deny all
3496 $NewAllowedPaths .= "DeniedPaths: ^/\n";
3497 $newaccount->{'DeniedPaths'} = ["DeniedPaths: ^/\n"];
3500 # Construct home directory path
3501 my $FullHomeDirectoryPath = "";
3502 my $currentHome = lc($newuser);
3503 if($currentHome && $currentHome !~ /^\s*\#/)
3505 $currentHome =~ s![^\w]!_!isg;
3506 my $newpath = "^${currentRoot}$currentHome/";
3507 push(@{$newaccount->{'AllowedPaths'}}, $newpath);
3508 # Create home directory
3509 $FullHomeDirectoryPath = $ENV{'HOME'}.${currentRoot}.$currentHome;
3512 # Allowed Paths
3513 CGIexecute::defineCGIvariable('ALLOWEDPATHS', "");
3514 my $allowedpaths = ${"CGIexecute::ALLOWEDPATHS"};
3515 if($allowedpaths && $allowedpaths !~ /^\s*\#/)
3517 $allowedpaths =~ s![^\^\w\./\;\+\*\?\[\]\$]!!isg;
3518 my @pathlist = split(/\;/, $allowedpaths);
3519 foreach my $entry (@pathlist)
3521 push(@{$newaccount->{'AllowedPaths'}}, "^".${currentRoot}.$entry);
3525 # Allowed IP addresses
3526 CGIexecute::defineCGIvariable('IPADDRESS', "");
3527 my $ipaddress = ${"CGIexecute::IPADDRESS"};
3528 if($ipaddress && $ipaddress !~ /^\s*\#/)
3530 $ipaddress =~ s![^\d\.\;]!!isg;
3531 my @iplist = split(/\;/, $ipaddress);
3532 foreach my $entry (@iplist)
3534 next unless $entry =~ /\d/;
3535 next if $entry =~ /^\s*\#/;
3536 $entry =~ s/\./\\./g;
3537 push(@{$newaccount->{'IPaddress'}}, $entry);
3541 # Sign the new ticket
3542 my $Signature = SignTicketWithMasterkey($newaccount, $newaccount->{'Salt'}->[0]);
3544 # Write
3545 my $datetime = gmtime();
3546 my $newuserfile = "";
3547 if(grep(/^CreateUser$/, @{$authorization->{'Capabilities'}}))
3549 my $newuserfilename = lc($newuser);
3550 $newuserfilename =~ s/[^\w]/_/isg;
3551 $newuserfile = $authorizationfile;
3552 $newuserfile =~ s![^/]*$!!isg;
3553 $newuserfile .= $newuserfilename;
3554 if(-s $newuserfile)
3556 $newuserfile = "";
3558 elsif($FullHomeDirectoryPath && !(-d $FullHomeDirectoryPath || -s $FullHomeDirectoryPath))
3560 if(-d "$ENV{'HOME'}${currentRoot}.SkeletonDir")
3562 `cp -r '$ENV{'HOME'}${currentRoot}.SkeletonDir' '$FullHomeDirectoryPath'`;
3564 elsif(-d "$ENV{'HOME'}${currentRoot}SkeletonDir")
3566 `cp -r '$ENV{'HOME'}${currentRoot}SkeletonDir' '$FullHomeDirectoryPath'`;
3568 elsif(-s "$ENV{'HOME'}${currentRoot}UserIndex.html")
3570 mkdir $FullHomeDirectoryPath;
3571 `cp '$ENV{'HOME'}${currentRoot}UserIndex.html' '$FullHomeDirectoryPath/index.html'`;
3573 elsif(-s "$ENV{'HOME'}${currentRoot}index.html")
3575 mkdir $FullHomeDirectoryPath;
3576 `cp '$ENV{'HOME'}${currentRoot}index.html' '$FullHomeDirectoryPath/index.html'`;
3582 my $newaccounttext = write_ticket($newuserfile, $newaccount, $serversalt);
3584 # Re-encrypt the new password for transmission
3585 if($newaccounttext =~ /^(Password\:\s+)(\S+)\s*$/)
3587 my $passwordvalue = $1;
3588 my $reencryptedpassword = XOR_hex_strings($secretkey, $passwordvalue);
3589 my $encryptedpasswordline = "<span id='newaccount'>$reencryptedpassword</span>";
3590 $newaccounttext =~ s/^(Password\:\s+)(\S+)\s*$/\1$encryptedpasswordline/gim;
3592 # No lingering passwords
3593 $passwordvalue = $serversalt;
3595 return $newaccounttext;
3598 # Copy a Challenge ticket file to a new name which is the hash of the new $CHALLENGETICKET and the password
3599 sub copy_challenge_file #($oldchallengefile, $authorizationfile, $sessionpath) -> $CHALLENGETICKET
3601 my $oldchallengefile = shift || "";
3602 my $authorizationfile = shift || "";
3603 my $sessionpath = shift || "";
3604 $sessionpath =~ s!/+$!!g;
3606 # Get Login session ticket
3607 my $oldchallenge = read_ticket($oldchallengefile);
3608 return "" unless $oldchallenge;
3610 # Get Authorization (user) session file
3611 my $authorization = read_ticket($authorizationfile);
3612 return "" unless $authorization;
3613 my $storedpassword = $authorization->{'Password'}->[0];
3614 return "" unless $storedpassword;
3615 my $challengekey = $oldchallenge->{'Key'}->[0];
3616 return "" unless $challengekey;
3618 # Create Random Hash Salt
3619 my $NEWCHALLENGETICKET = get_random_hex();;
3620 my $newchallengefile = hash_string($challengekey.$NEWCHALLENGETICKET);
3621 return "" unless $newchallengefile;
3623 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3624 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3625 ${"CGIexecute::CHALLENGETICKET"} = $NEWCHALLENGETICKET;
3627 # Write Session Ticket
3628 open(OLDCHALLENGE, "<$oldchallengefile") || die "<$oldchallengefile: $!\n";
3629 my @OldChallengeLines = <OLDCHALLENGE>;
3630 close(OLDCHALLENGE);
3631 # Old file should now be removed
3632 unlink($oldchallengefile);
3634 open(SESSION, ">$sessionpath/$newchallengefile") || die "$sessionpath/$newchallengefile: $!\n";
3635 foreach $line (@OldChallengeLines)
3637 print SESSION $line;
3639 close(SESSION);
3641 # No lingering passwords
3642 $storedpassword = $oldchallenge;
3644 return $NEWCHALLENGETICKET;
3647 sub create_login_file #($PasswordDir, $SessionDir, $IPaddress)
3649 my $PasswordDir = shift || "";
3650 my $SessionDir = shift || "";
3651 my $IPaddress = shift || "";
3653 # Create Login Ticket
3654 my $LOGINTICKET= get_random_hex ();
3656 # Create Random Hash Salt
3657 my $RANDOMSALT= get_random_hex();
3659 # Create SALT file if it does not exist
3660 # Remove this, including test account for life system
3661 unless(-d "$SessionDir")
3663 `mkdir -p "$SessionDir"`;
3665 unless(-d "$PasswordDir")
3667 `mkdir -p "$PasswordDir"`;
3669 # Create SERVERSALT and default test account
3670 my $SERVERSALT = "";
3671 unless(-s "$PasswordDir/SALT")
3673 $SERVERSALT= get_random_hex();
3674 open(SALTFILE, ">$PasswordDir/SALT") || die ">$PasswordDir/SALT: $!\n";
3675 print SALTFILE "$SERVERSALT\n";
3676 close(SALTFILE);
3678 # Update test account (should be removed in live system)
3679 my @alltestusers = ("test", "testip", "testchallenge", "admin");
3680 foreach my $testuser (@alltestusers)
3682 if(-s "$PasswordDir/$testuser")
3684 my $plainpassword = $testuser eq 'admin' ? "There is no password like more password" : "testing";
3686 my $storedpassword = hash_string(${plainpassword}.${testuser}.${SERVERSALT});
3687 # Encrypt the new password with the MasterKey
3688 my $authorization = read_ticket("$PasswordDir/$testuser") || return "";
3689 $authorization->{'Salt'} = [$SERVERSALT];
3690 $authorization->{'Type'} = ['INACTIVE PASSWORD'] if $testuser eq 'admin';
3691 set_password($authorization, $SERVERSALT, $plainpassword);
3692 write_ticket("$PasswordDir/$testuser", $authorization, $SERVERSALT);
3693 # No lingering passwords
3694 $storedpassword = $SERVERSALT;
3695 $plainpassword = $SERVERSALT;
3700 # Read in site Salt
3701 open(SALTFILE, "<$PasswordDir/SALT") || die "$PasswordDir/SALT: $!\n";
3702 $SERVERSALT=<SALTFILE>;
3703 close(SALTFILE);
3704 chomp($SERVERSALT);
3706 # Create login session ticket
3707 my $datetime = gmtime();
3708 my $timesec = time();
3709 my $loginticket = {};
3710 $loginticket->{Type} = ['LOGIN'];
3711 $loginticket->{IPaddress} = [$IPaddress];
3712 $loginticket->{Salt} = [$SERVERSALT];
3713 $loginticket->{Session} = [$LOGINTICKET];
3714 $loginticket->{Randomsalt} = [$RANDOMSALT];
3715 $loginticket->{Expires} = ['+600s'];
3716 $loginticket->{Date} = ["$datetime UTC"];
3717 $loginticket->{Time} = [$timesec];
3718 write_ticket("$SessionDir/$LOGINTICKET", $loginticket, $SERVERSALT);
3720 # Set global variables
3721 # $SERVERSALT
3722 $ENV{'SERVERSALT'} = $SERVERSALT;
3723 CGIexecute::defineCGIvariable('SERVERSALT', "");
3724 ${"CGIexecute::SERVERSALT"} = $SERVERSALT;
3726 # $SESSIONTICKET
3727 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3728 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3729 ${"CGIexecute::SESSIONTICKET"} = $SESSIONTICKET;
3731 # $RANDOMSALT
3732 $ENV{'RANDOMSALT'} = $RANDOMSALT;
3733 CGIexecute::defineCGIvariable('RANDOMSALT', "");
3734 ${"CGIexecute::RANDOMSALT"} = $RANDOMSALT;
3736 # $LOGINTICKET
3737 $ENV{'LOGINTICKET'} = $LOGINTICKET;
3738 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3739 ${"CGIexecute::LOGINTICKET"} = $LOGINTICKET;
3741 return $ENV{'LOGINTICKET'};
3744 sub create_session_file #($sessionfile, $loginfile, $authorizationfile, $path) -> Is $loginfile deleted? 0/1
3746 my $sessionfile = shift || "";
3747 my $loginfile = shift || "";
3748 my $authorizationfile = shift || "";
3749 my $path = shift || "";
3751 # Get Login session ticket
3752 my $loginticket = read_ticket($loginfile);
3753 return unlink($loginfile) unless $loginticket;
3755 # Get Authorization (user) session file
3756 my $authorization = read_ticket($authorizationfile);
3757 return unlink($loginfile) unless $authorization;
3759 # For a Session or a Challenge, we need a stored key
3760 my $sessionkey = "";
3761 my $secretkey = "";
3762 if($authorization->{'Session'} && $authorization->{'Session'}->[0] ne 'IPADDRESS')
3764 my $storedpassword = $authorization->{'Password'}->[0];
3765 my $loginticketid = $loginticket->{'Session'}->[0];
3766 my $randomsalt = $loginticket->{'Randomsalt'}->[0];
3767 $sessionkey = hash_string($storedpassword.$loginticketid);
3768 $secretkey = hash_string($storedpassword.$loginticketid.$randomsalt);
3769 # No lingering passwords
3770 $storedpassword = $loginticketid;
3772 # Get Session id
3773 my $sessionid = "";
3774 if($sessionfile =~ m!([^/]+)$!)
3776 $sessionid = $1;
3779 # Convert Authorization content to Session content
3780 my $sessionContent = {};
3781 my $SessionType = $authorization->{'Session'}->[0] ? $authorization->{'Session'}->[0] : "SESSION";
3782 $sessionContent->{Type} = [$SessionType];
3783 $sessionContent->{Username} = [lc($authorization->{'Username'}->[0])];
3784 $sessionContent->{Session} = [$sessionid];
3785 $sessionContent->{Time} = [time];
3786 # Limit communication to the login IP address, except for Tor like situations with VariableREMOTE_ADDR
3787 $sessionContent->{IPaddress} = ['.'];
3788 if($sessionContent->{Type}->[0] eq 'CHALLENGE' && grep(/^VariableREMOTE_ADDR$/, @{$authorization->{'Capabilities'}}))
3790 $sessionContent->{IPaddress} = $authorization->{'IPaddress'} if $authorization->{'IPaddress'};
3792 else
3794 $sessionContent->{IPaddress} = $loginticket->{'IPaddress'};
3796 $sessionContent->{Salt} = $authorization->{'Salt'};
3797 $sessionContent->{Randomsalt} = $loginticket->{'Randomsalt'};
3798 $sessionContent->{AllowedPaths} = $authorization->{'AllowedPaths'};
3799 $sessionContent->{DeniedPaths} = $authorization->{'DeniedPaths'};
3800 $sessionContent->{Expires} = $authorization->{'MaxLifetime'};
3801 $sessionContent->{Capabilities} = $authorization->{'Capabilities'};
3802 foreach my $pattern (keys(%TicketRequiredPatterns))
3804 if($path =~ m#$pattern#)
3806 my ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3807 push(@{$sessionContent->{Expires}}, $validtime);
3810 $sessionContent->{Key} = [$sessionkey] if $sessionkey;
3811 $sessionContent->{Secretkey} = [$secretkey] if $secretkey;
3812 $sessionContent->{Date} = [gmtime()." UTC"];
3814 # Write Session Ticket
3815 write_ticket($sessionfile, $sessionContent, $authorization->{'Salt'}->[0]);
3817 # Login file should now be removed
3818 return unlink($loginfile);
3821 sub check_ticket_validity # ($type, $ticketfile, $address, $path [, $unsigned])
3823 my $type = shift || "SESSION";
3824 my $ticketfile = shift || "";
3825 my $address = shift || "";
3826 my $path = shift || "";
3827 my $unsigned = shift || 0;
3829 # Is there a session ticket of this name?
3830 return 0 unless -s "$ticketfile";
3832 # There is a session ticket, is it linked to this IP address?
3833 my $ticket = read_ticket($ticketfile);
3834 unless($ticket)
3836 print STDERR "Ticket expired or empty: $ticketfile\n";
3837 return;
3840 # Is this the right type of ticket
3841 unless($ticket && $ticket->{'Type'}->[0] eq $type)
3843 print STDERR "Wrong ticket type: $ticket->{'Type'}->[0] eq $type\n";
3844 return;
3847 # Does the IP address match?
3848 my $IPmatches = @{$ticket->{"IPaddress"}} ? 0 : 1;
3849 for $IPpattern (@{$ticket->{"IPaddress"}})
3851 ++$IPmatches if $address =~ m#^$IPpattern#ig;
3853 if($address && ! $IPmatches)
3855 print STDERR "Wrong REMOTE ADDR for $ticket->{'Username'}->[0]: $ticket->{'IPaddress'}->[0] vs $address\n";
3856 return 0;
3859 # Is the path denied
3860 my $Pathmatches = 0;
3861 foreach $Pathpattern (@{$ticket->{"DeniedPaths"}})
3863 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3865 return 0 if @{$ticket->{"DeniedPaths"}} && $Pathmatches;
3867 # Is the path allowed
3868 $Pathmatches = 0;
3869 foreach $Pathpattern (@{$ticket->{"AllowedPaths"}})
3871 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3873 return 0 unless !@{$ticket->{"AllowedPaths"}} || $Pathmatches;
3875 # Check signature if not told to use an unsigned ticket (dangerous)
3876 my $Signature = TicketSignature($ticket, $ticket->{'Salt'}->[0]);
3877 if((! $unsigned) && $Signature && $Signature ne $ticket->{'Signature'}->[0])
3879 print STDERR "Invalid signature for $ticket->{'Type'}: $ticket->{'Username'}\n$ticketfile\n";
3880 return 0;
3883 # Make login values available (will also protect against resetting by query)
3884 $ENV{"LOGINUSERNAME"} = lc($ticket->{'Username'}->[0]);
3885 $ENV{"LOGINIPADDRESS"} = $address;
3886 $ENV{"LOGINPATH"} = $path;
3887 $ENV{"SESSIONTYPE"} = $type unless $type eq "PASSWORD";
3889 # Set Capabilities, if present
3890 if($ticket->{'Username'}->[0] && @{$ticket->{'Capabilities'}})
3892 $ENV{'CAPABILITIES'} = $ticket->{'Username'}->[0];
3893 CGIexecute::defineCGIvariableList('CAPABILITIES', "");
3894 @{"CGIexecute::CAPABILITIES"} = @{$ticket->{'Capabilities'}};
3895 # Capabilities should not be changed anymore by CGI query!
3897 # Capabilities are NOT to be set by the query
3898 CGIexecute::ProtectCGIvariable('CAPABILITIES');
3900 return 1;
3904 sub remove_expired_tickets # ($path) -> number of tickets removed
3906 my $path = shift || "";
3907 return 0 unless $path;
3908 $path =~ s!/+$!!g;
3909 my $removed_tickets = 0;
3910 my @ticketlist = glob("$path/*");
3911 foreach my $ticketfile (@ticketlist)
3913 my $ticket = read_ticket($ticketfile);
3914 unless($ticket)
3916 unlink $ticketfile;
3917 ++$removed_tickets;
3920 return $removed_tickets;
3923 sub set_password # ($ticket, $salt, $plainpassword) -> $password
3925 my $ticket = shift || "";
3926 my $salt = shift || "";
3927 my $plainpassword = shift || "";
3929 my $user = lc($ticket->{'Username'}->[0]);
3930 return "" unless $user;
3931 $salt = $ticket->{'Salt'}->[0] unless $salt;
3933 my $storedpassword = hash_string(${plainpassword}.${user}.${salt});
3934 $ticket->{'Password'} = [$storedpassword];
3935 $ticket->{'Salt'} = [$salt];
3936 # No lingering passwords
3937 $storedpassword = $salt;
3938 $plainpassword = $salt;
3940 return $ticket->{'Password'}->[0];
3943 sub write_ticket # ($ticketfile, $ticket, $salt [, $masterkey]) -> &%ticket
3945 my $ticketfile = shift || "";
3946 my $ticket = shift || "";
3947 my $salt = shift || "";
3948 my $masterkey = shift || $ENV{'CGIMasterKey'};
3950 # Encrypt password
3951 EncryptTicketWithMasterKey($ticket, $salt, $masterkey);
3953 # Sign the new ticket
3954 my $signature = SignTicketWithMasterkey($ticket, $salt, $masterkey);
3956 # Create ordered list with labels
3957 my @orderlist = ('Type', 'Username', 'Password', 'IPaddress', 'AllowedPaths', 'DeniedPaths',
3958 'Expires', 'Capabilities', 'Salt', 'Session', 'Randomsalt',
3959 'Date', 'Time', 'Signature', 'Key', 'Secretkey');
3960 my @labellist = keys(%{$ticket});
3961 foreach my $label (@orderlist)
3963 @labellist = grep(!/\b$label\b/, @labellist);
3966 # Create ticket in text
3967 my $TicketText = "";
3968 foreach my $label (@orderlist, @labellist)
3970 next unless exists($ticket->{$label}) && $ticket->{$label}->[0];
3971 foreach my $value (@{$ticket->{$label}})
3973 $TicketText .= "$label: $value\n";
3976 if($ticketfile)
3978 open(TICKET, ">$ticketfile") || die "$ticketfile: $!\n";
3979 print TICKET $TicketText;
3980 close(TICKET);
3983 return $TicketText;
3986 # Note, read_ticket will return 0 if the ticket has expired!
3987 sub read_ticket # ($ticketfile [, $salt, $masterkey]) -> &%ticket
3989 my $ticketfile = shift || "";
3990 my $serversalt = shift || "";
3991 my $masterkey = shift || $ENV{'CGIMasterKey'};
3993 my $ticket = {};
3994 if($ticketfile && -s $ticketfile)
3996 open(TICKETFILE, "<$ticketfile") || die "$ticketfile: $!\n";
3997 my @alllines = <TICKETFILE>;
3998 close(TICKETFILE);
3999 foreach my $currentline (@alllines)
4001 # Skip empty lines and comments
4002 next unless $currentline =~ /\S/;
4003 next if $currentline =~ /^\s*\#/;
4005 if($currentline =~ /^\s*(\S[^\:]+)\:\s+(.*)\s*$/)
4007 my $Label = $1;
4008 my $Value = $2;
4009 $ticket->{$Label} = () unless exists($ticket->{$Label});
4010 push(@{$ticket->{$Label}}, $Value);
4014 if($masterkey && exists($ticket->{'Password'}) && $ticket->{'Password'}->[0])
4016 # Use the ServerSalt stored in the ticket, if present
4017 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4019 $serversalt = $ticket->{Salt}->[0];
4021 # Decrypt all passwords
4022 DecryptTicketWithMasterKey($ticket, $serversalt, $masterkey) ||
4023 die "Decryption failed: DecryptTicketWithMasterKey ($ticket, $serversalt)\n";
4026 # Check whether the ticket has expired
4027 if(exists($ticket->{Expires}))
4029 my $StartTime = 0;
4030 if(exists($ticket->{Time}) && $ticket->{Time}->[0] > 0)
4032 $StartTime = [(sort(@{$ticket->{Time}}))]->[0];
4034 else
4036 # Get SessionTicket file stats
4037 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)
4038 = stat($ticketfile);
4039 $StartTime = $ctime;
4041 foreach my $Value (@{$ticket->{'Expires'}})
4043 # Recalculate expire date from relative time
4044 if($Value =~ /^\+/)
4046 if($Value =~ /^\+(\d+)\s*d(ays)?\s*$/)
4048 $ExpireTime = 24*3600*$1;
4050 elsif($Value =~ /^\+(\d+)\s*m(inutes)?\s*$/)
4052 $ExpireTime = 60*$1;
4054 elsif($Value =~ /^\+(\d+)\s*h(ours)?\s*$/)
4056 $ExpireTime = 3600*$1;
4058 elsif($Value =~ /^\+(\d+)\s*s(econds)?\s*$/)
4060 $ExpireTime = $1;
4062 elsif($Value =~ /^\+(\d+)\s*$/)
4064 $ExpireTime = $1;
4067 my $absoluteTime = $Value =~ /^\+/ ? $StartTime + $ExpireTime : $Value;
4068 return 0 unless $absoluteTime > time;
4070 @{$ticket->{Expires}} = sort(@{$ticket->{Expires}});
4072 return $ticket;
4075 # Set up a valid ticket from a given text file
4076 # Use from command line. DO NOT USE ONLINE
4077 # Watch out for passwords that get stored in the history file
4079 # perl CGIscriptor.pl --managelogin [options] [files]
4080 # Options:
4081 # salt={file or saltvalue}
4082 # masterkey={file or plaintext}
4083 # newmasterkey={file or plaintext}
4084 # password={file or palintext}
4086 # Followed by one or more file names.
4087 # Options can be interspersed between filenames,
4088 # e.g., password='plaintext'
4089 # Note that passwords are only used once!
4091 sub setup_ticket_file # (@ARGV)
4093 # Stop when run on-line
4094 return if $ENV{'PATH_INFO'} || $ENV{'QUERY_STRING'};
4096 my %Settings = ();
4097 foreach my $input (@_)
4099 if($input =~ /^([\w]+)\=/)
4101 my $name = lc($1);
4102 my $value = $';
4103 chomp($value);
4105 if($value !~ m![^\w\.\~\/\:\-]! && $value !~ /^[\-\.]/ && -s "$value" && ! -d "$value")
4107 # Warn about reading a value from file
4108 print STDERR "Read '$name' from: '$value'\n";
4109 open(INPUTVALUE, "<$value") || die "$value: $!\n";
4110 $value = <INPUTVALUE>;
4111 chomp($value);
4114 $value =~ s/(^\'([^\']*)\'$)/\1/g;
4115 $value =~ s/(^\"([^\"]*)\"$)/\1/g;
4116 $Settings{$name} = $value;
4118 elsif($input !~ m![^\w\.\~\/\:\-]!i && $input !~ /^[\-\.]/i && -s $input)
4120 # We MUST have a salt
4121 $Settings{'salt'} = $ticket->{'Salt'}->[0] unless $Settings{'salt'};
4123 # Set the new masterkey to the old masterkey if there is no new masterkey
4124 $Settings{'newmasterkey'} = $Settings{'masterkey'} unless exists($Settings{'newmasterkey'});
4126 # Get the ticket
4127 my $ticket = read_ticket($input, $Settings{'salt'}, $Settings{'masterkey'});
4129 # Set a new password from plaintext
4130 $ticket->{'Salt'}->[0] = $Settings{'salt'} if $Settings{'salt'} && $Settings{'password'};
4131 set_password ($ticket, $Settings{'salt'}, $Settings{'password'}) if $Settings{'password'};
4132 # Write the ticket back to file
4133 write_ticket($input, $ticket, $Settings{'salt'}, $Settings{'newmasterkey'});
4135 # A password is only used once
4136 $Settings{'password'} = "";
4141 # Add a signature from $masterkey to a ticket in the label $signlabel
4142 sub SignTicketWithMasterkey # ($ticket, $serversalt [, $masterkey, $signlabel]) -> $Signature
4144 my $ticket = shift || return 0;
4145 my $serversalt = shift || "";
4146 my $masterkey = shift || $ENV{'CGIMasterKey'};
4147 my $signlabel = shift || 'Signature';
4149 my $Signature = TicketSignature($ticket, $serversalt, $masterkey);
4151 $ticket->{$signlabel} = [$Signature] if $Signature;
4153 return $Signature;
4156 # Determine ticket signature
4157 sub TicketSignature # ($ticket, $serversalt [, $masterkey]) -> $Signature
4159 my $ticket = shift || return 0;
4160 my $serversalt = shift || "";
4161 my $masterkey = shift || $ENV{'CGIMasterKey'};
4162 my $Signature = "";
4164 if($masterkey)
4166 # If the ServerSalt is not stored in the ticket, the SALT file has to be found
4167 if(!$serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4169 $serversalt = $ticket->{Salt}->[0];
4171 # Sign
4172 if($serversalt)
4174 my $username = lc($ticket->{'Username'}->[0]);
4175 my $hash1 = hash_string(${masterkey}.${serversalt});
4176 my $CryptKey = hash_string($username.${'hash1'});
4177 my $SignText = "Type: ".$ticket->{'Type'}->[0]."\n";
4178 my @tmp = sort(@{$ticket->{'Username'}});
4179 $SignText .= "Username: @tmp\n";
4180 @tmp = sort(@{$ticket->{'IPaddress'}});
4181 $SignText .= "IPaddress: @tmp\n";
4182 @tmp = sort(@{$ticket->{'AllowedPaths'}});
4183 $SignText .= "AllowedPaths: @tmp\n";
4184 @tmp = sort(@{$ticket->{'DeniedPaths'}});
4185 $SignText .= "DeniedPaths: @tmp\n";
4186 @tmp = sort(@{$ticket->{'Session'}});
4187 $SignText .= "Session: @tmp\n";
4188 @tmp = sort(@{$ticket->{'Time'}});
4189 $SignText .= "Time: @tmp\n";
4190 @tmp = sort(@{$ticket->{'Expires'}});
4191 $SignText .= "Expires: @tmp\n";
4192 @tmp = sort(@{$ticket->{'Capabilities'}});
4193 $SignText .= "Capabilities: @tmp\n";
4194 @tmp = sort(@{$ticket->{'MaxLifetime'}});
4195 $SignText .= "MaxLifetime: @tmp\n";
4196 $Signature = HMAC_hex($CryptKey, $SignText);
4199 return $Signature;
4202 # Decrypts a password list IN PLACE
4203 sub DecryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4205 my $ticket = shift || return 0;
4206 my $serversalt = shift || "";
4207 my $masterkey = shift || $ENV{'CGIMasterKey'};
4209 if($masterkey && exists($ticket->{Password}) && $ticket->{Password}->[0])
4211 # If the ServerSalt is not given, read it from the the ticket
4212 if(! $serversalt && exists($ticket->{Salt}) && $ticket->{Salt}->[0])
4214 $serversalt = $ticket->{Salt}->[0];
4216 # Decrypt password(s)
4217 if($serversalt)
4219 my $hash1 = hash_string(${masterkey}.${serversalt});
4220 my $username = lc($ticket->{'Username'}->[0]);
4221 my $CryptKey = hash_string(${'hash1'}.$username);
4222 foreach my $password (@{$ticket->{Password}})
4224 $password = XOR_hex_strings($CryptKey,$password);
4228 return $ticket->{'Password'};
4230 sub EncryptTicketWithMasterKey # ($ticket, $serversalt [, $masterkey]) -> \@password_list
4232 DecryptTicketWithMasterKey(@_);
4235 # Implement HMAC signature hash.
4236 # Blocksize is length in HEX characters, NOT bytes
4237 sub HMAC_hex # ($key, $message [, $blocksizehex]) -> $hex
4239 my $key = shift || "";
4240 my $message = shift || "";
4241 my $blocksizehex = shift || length($key);
4242 $key = hash_string($key) if length($key) > $blocksizehex;
4244 my $innerkey = XOR_hex_byte ($key, "36");
4245 my $outerkey = XOR_hex_byte ($key, "5c");
4246 my $innerhash = hash_string($innerkey.$message);
4247 my $outerhash = hash_string($outerkey.$innerhash);
4249 return $outerhash;
4252 # XOR input with equally long string of repeated 2 hex character (byte)
4253 # string. Input must have even number of hex characters
4254 sub XOR_hex_byte # ($hex1, $hexbyte) -> $hex
4256 my $hex1 = shift || "";
4257 my $hexbyte = shift || "";
4258 my $bytelength = length($hexbyte);
4259 my $hex2 = $hex1;
4260 $hex2 =~ s/.{$bytelength}/$hexbyte/ig;
4261 return XOR_hex_strings($hex1, $hex2);
4264 sub XOR_hex_strings # ($hex1, $hex2) -> $hex
4266 my $hex1 = shift || "";
4267 my $hex2 = shift || "";
4268 my @hex1list = split('', $hex1);
4269 my @hex2list = split('', $hex2);
4270 my @hexresultlist = ();
4271 for(my $i; $i < scalar(@hex1list); ++$i)
4273 my $d1 = hex($hex1list[$i]);
4274 my $d2 = hex($hex2list[$i]);
4275 my $dresult = ($d1 ^ $d2);
4276 $hexresultlist[$i] = sprintf("%x", $dresult);
4278 $hexresult = join('', @hexresultlist);
4279 return $hexresult;
4282 # End of Handle login access
4285 ############################################################################
4287 # Handle foreign interpreters (i.e., scripting languages)
4289 # Insert perl code to execute scripts in foreign scripting languages.
4290 # Actually, the scripts inside the <SCRIPT></SCRIPT> blocks are piped
4291 # into an interpreter.
4292 # The code presented here is fairly confusing because it
4293 # actually writes perl code code to the output.
4295 # A table with the file handles
4296 %SCRIPTINGINPUT = ();
4298 # A function to clean up Client delivered CGI parameter values
4299 # (i.e., quote all odd characters)
4300 %SHRUBcharacterTR =
4302 "\'" => '&#39;',
4303 "\`" => '&#96;',
4304 "\"" => '&quot;',
4305 '&' => '&amper;',
4306 "\\" => '&#92;'
4309 sub shrubCGIparameter # ($String) -> Cleaned string
4311 my $String = shift || "";
4313 # Change all quotes [`'"] into HTML character entities
4314 my ($Char, $Transcript) = ('&', $SHRUBcharacterTR{'&'});
4316 # Protect &
4317 $String =~ s/\Q$Char\E/$Transcript/isg if $Transcript;
4319 while( ($Char, $Transcript) = each %SHRUBcharacterTR)
4321 next if $Char eq '&';
4322 $String =~ s/\Q$Char\E/$Transcript/isg;
4325 # Replace newlines
4326 $String =~ s/[\n]/\\n/g;
4327 # Replace control characters with their backslashed octal ordinal numbers
4328 $String =~ s/([^\S \t])/(sprintf("\\0%o", ord($1)))/eisg; #
4329 $String =~ s/([\x00-\x08\x0A-\x1F])/(sprintf("\\0%o", ord($1)))/eisg; #
4331 return $String;
4335 # The initial open statements: Open a pipe to the foreign script interpreter
4336 sub OpenForeignScript # ($ContentType) -> $DirectivePrefix
4338 my $ContentType = lc(shift) || return "";
4339 my $NewDirective = "";
4341 return $NewDirective if($SCRIPTINGINPUT{$ContentType});
4343 # Construct a unique file handle name
4344 $SCRIPTINGFILEHANDLE = uc($ContentType);
4345 $SCRIPTINGFILEHANDLE =~ s/\W/\_/isg;
4346 $SCRIPTINGINPUT{$ContentType} = $SCRIPTINGFILEHANDLE
4347 unless $SCRIPTINGINPUT{$ContentType};
4349 # Create the relevant script: Open the pipe to the interpreter
4350 $NewDirective .= <<"BLOCKCGISCRIPTOROPEN";
4351 # Open interpreter for '$ContentType'
4352 # Open pipe to interpreter (if it isn't open already)
4353 open($SCRIPTINGINPUT{$ContentType}, "|$ScriptingLanguages{$ContentType}") || main::dieHandler(14, "$ContentType: \$!\\n");
4354 BLOCKCGISCRIPTOROPEN
4356 # Insert Initialization code and CGI variables
4357 $NewDirective .= InitializeForeignScript($ContentType);
4359 # Ready
4360 return $NewDirective;
4364 # The final closing code to stop the interpreter
4365 sub CloseForeignScript # ($ContentType) -> $DirectivePrefix
4367 my $ContentType = lc(shift) || return "";
4368 my $NewDirective = "";
4370 # Do nothing unless the pipe realy IS open
4371 return "" unless $SCRIPTINGINPUT{$ContentType};
4373 # Initial comment
4374 $NewDirective .= "\# Close interpreter for '$ContentType'\n";
4377 # Write the Postfix code
4378 $NewDirective .= CleanupForeignScript($ContentType);
4380 # Create the relevant script: Close the pipe to the interpreter
4381 $NewDirective .= <<"BLOCKCGISCRIPTORCLOSE";
4382 close($SCRIPTINGINPUT{$ContentType}) || main::dieHandler(15, \"$ContentType: \$!\\n\");
4383 select(STDOUT); \$|=1;
4385 BLOCKCGISCRIPTORCLOSE
4387 # Remove the file handler of the foreign script
4388 delete($SCRIPTINGINPUT{$ContentType});
4390 return $NewDirective;
4394 # The initialization code for the foreign script interpreter
4395 sub InitializeForeignScript # ($ContentType) -> $DirectivePrefix
4397 my $ContentType = lc(shift) || return "";
4398 my $NewDirective = "";
4400 # Add initialization code
4401 if($ScriptingInitialization{$ContentType})
4403 $NewDirective .= <<"BLOCKCGISCRIPTORINIT";
4404 # Initialization Code for '$ContentType'
4405 # Select relevant output filehandle
4406 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4408 # The Initialization code (if any)
4409 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}INITIALIZATIONCODE';
4410 $ScriptingInitialization{$ContentType}
4411 ${ContentType}INITIALIZATIONCODE
4413 BLOCKCGISCRIPTORINIT
4416 # Add all CGI variables defined
4417 if(exists($ScriptingCGIvariables{$ContentType}))
4419 # Start writing variable definitions to the Interpreter
4420 if($ScriptingCGIvariables{$ContentType})
4422 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEF";
4423 # CGI variables (from the %default_values table)
4424 print $SCRIPTINGINPUT{$ContentType} << '${ContentType}CGIVARIABLES';
4425 BLOCKCGISCRIPTORVARDEF
4428 my ($N, $V);
4429 foreach $N (keys(%default_values))
4431 # Determine whether the parameter has been defined
4432 # (the eval is a workaround to get at the variable value)
4433 next unless eval("defined(\$CGIexecute::$N)");
4435 # Get the value from the EXECUTION environment
4436 $V = eval("\$CGIexecute::$N");
4437 # protect control characters (i.e., convert them to \0.. form)
4438 $V = shrubCGIparameter($V);
4440 # Protect interpolated variables
4441 eval("\$CGIexecute::$N = '$V';") unless $ScriptingCGIvariables{$ContentType};
4443 # Print the actual declaration for this scripting language
4444 if($ScriptingCGIvariables{$ContentType})
4446 $NewDirective .= sprintf($ScriptingCGIvariables{$ContentType}, $N, $V);
4447 $NewDirective .= "\n";
4451 # Stop writing variable definitions to the Interpreter
4452 if($ScriptingCGIvariables{$ContentType})
4454 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEFEND";
4455 ${ContentType}CGIVARIABLES
4456 BLOCKCGISCRIPTORVARDEFEND
4461 $NewDirective .= << "BLOCKCGISCRIPTOREND";
4463 # Select STDOUT filehandle
4464 select(STDOUT); \$|=1;
4466 BLOCKCGISCRIPTOREND
4468 return $NewDirective;
4472 # The cleanup code for the foreign script interpreter
4473 sub CleanupForeignScript # ($ContentType) -> $DirectivePrefix
4475 my $ContentType = lc(shift) || return "";
4476 my $NewDirective = "";
4478 # Return if not needed
4479 return $NewDirective unless $ScriptingCleanup{$ContentType};
4481 # Create the relevant script: Open the pipe to the interpreter
4482 $NewDirective .= <<"BLOCKCGISCRIPTORSTOP";
4483 # Cleanup Code for '$ContentType'
4484 # Select relevant output filehandle
4485 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4486 # Print Cleanup code to foreign script
4487 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}SCRIPTSTOP';
4488 $ScriptingCleanup{$ContentType}
4489 ${ContentType}SCRIPTSTOP
4491 # Select STDOUT filehandle
4492 select(STDOUT); \$|=1;
4493 BLOCKCGISCRIPTORSTOP
4495 return $NewDirective;
4499 # The prefix code for each <script></script> block
4500 sub PrefixForeignScript # ($ContentType) -> $DirectivePrefix
4502 my $ContentType = lc(shift) || return "";
4503 my $NewDirective = "";
4505 # Return if not needed
4506 return $NewDirective unless $ScriptingPrefix{$ContentType};
4508 my $Quote = "\'";
4509 # If the CGIvariables parameter is defined, but empty, interpolate
4510 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4511 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4512 !$ScriptingCGIvariables{$ContentType};
4514 # Add initialization code
4515 $NewDirective .= <<"BLOCKCGISCRIPTORPREFIX";
4516 # Prefix Code for '$ContentType'
4517 # Select relevant output filehandle
4518 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4520 # The block Prefix code (if any)
4521 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}PREFIXCODE$Quote;
4522 $ScriptingPrefix{$ContentType}
4523 ${ContentType}PREFIXCODE
4524 # Select STDOUT filehandle
4525 select(STDOUT); \$|=1;
4526 BLOCKCGISCRIPTORPREFIX
4528 return $NewDirective;
4532 # The postfix code for each <script></script> block
4533 sub PostfixForeignScript # ($ContentType) -> $DirectivePrefix
4535 my $ContentType = lc(shift) || return "";
4536 my $NewDirective = "";
4538 # Return if not needed
4539 return $NewDirective unless $ScriptingPostfix{$ContentType};
4541 my $Quote = "\'";
4542 # If the CGIvariables parameter is defined, but empty, interpolate
4543 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4544 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4545 !$ScriptingCGIvariables{$ContentType};
4547 # Create the relevant script: Open the pipe to the interpreter
4548 $NewDirective .= <<"BLOCKCGISCRIPTORPOSTFIX";
4549 # Postfix Code for '$ContentType'
4550 # Select filehandle to interpreter
4551 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4552 # Print postfix code to foreign script
4553 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SCRIPTPOSTFIX$Quote;
4554 $ScriptingPostfix{$ContentType}
4555 ${ContentType}SCRIPTPOSTFIX
4556 # Select STDOUT filehandle
4557 select(STDOUT); \$|=1;
4558 BLOCKCGISCRIPTORPOSTFIX
4560 return $NewDirective;
4563 sub InsertForeignScript # ($ContentType, $directive, @SRCfile) -> $NewDirective
4565 my $ContentType = lc(shift) || return "";
4566 my $directive = shift || return "";
4567 my @SRCfile = @_;
4568 my $NewDirective = "";
4570 my $Quote = "\'";
4571 # If the CGIvariables parameter is defined, but empty, interpolate
4572 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4573 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4574 !$ScriptingCGIvariables{$ContentType};
4576 # Create the relevant script
4577 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4578 # Insert Code for '$ContentType'
4579 # Select filehandle to interpreter
4580 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4581 BLOCKCGISCRIPTORINSERT
4583 # Use SRC feature files
4584 my $ThisSRCfile;
4585 while($ThisSRCfile = shift(@_))
4587 # Handle blocks
4588 if($ThisSRCfile =~ /^\s*\{\s*/)
4590 my $Block = $';
4591 $Block = $` if $Block =~ /\s*\}\s*$/;
4592 $NewDirective .= <<"BLOCKCGISCRIPTORSRCBLOCK";
4593 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SRCBLOCKCODE$Quote;
4594 $Block
4595 ${ContentType}SRCBLOCKCODE
4596 BLOCKCGISCRIPTORSRCBLOCK
4598 next;
4601 # Handle files
4602 $NewDirective .= <<"BLOCKCGISCRIPTORSRCFILES";
4603 # Read $ThisSRCfile
4604 open(SCRIPTINGSOURCE, "<$ThisSRCfile") || main::dieHandler(16, "$ThisSRCfILE: \$!");
4605 while(<SCRIPTINGSOURCE>)
4607 print $SCRIPTINGINPUT{$ContentType} \$_;
4609 close(SCRIPTINGSOURCE);
4611 BLOCKCGISCRIPTORSRCFILES
4615 # Add the directive
4616 if($directive)
4618 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4619 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}DIRECTIVECODE$Quote;
4620 $directive
4621 ${ContentType}DIRECTIVECODE
4622 BLOCKCGISCRIPTORINSERT
4626 $NewDirective .= <<"BLOCKCGISCRIPTORSELECT";
4627 # Select STDOUT filehandle
4628 select(STDOUT); \$|=1;
4629 BLOCKCGISCRIPTORSELECT
4631 # Ready
4632 return $NewDirective;
4635 sub CloseAllForeignScripts # Call CloseForeignScript on all open scripts
4637 my $ContentType;
4638 foreach $ContentType (keys(%SCRIPTINGINPUT))
4640 my $directive = CloseForeignScript($ContentType);
4641 print STDERR "\nDirective $CGI_Date: ", $directive;
4642 CGIexecute->evaluate($directive);
4647 # End of handling foreign (external) scripting languages.
4649 ############################################################################
4651 # A subroutine to handle "nested" quotes, it cuts off the leading
4652 # item or quoted substring
4653 # E.g.,
4654 # ' A_word and more words' -> @('A_word', ' and more words')
4655 # '"quoted string" The rest' -> @('quoted string', ' The rest')
4656 # (this is needed for parsing the <TAGS> and their attributes)
4657 my $SupportedQuotes = "\'\"\`\(\{\[";
4658 my %QuotePairs = ('('=>')','['=>']','{'=>'}'); # Brackets
4659 sub ExtractQuotedItem # ($String) -> @($QuotedString, $RestOfString)
4661 my @Result = ();
4662 my $String = shift || return @Result;
4664 if($String =~ /^\s*([\w\/\-\.]+)/is)
4666 push(@Result, $1, $');
4668 elsif($String =~ /^\s*(\\?)([\Q$SupportedQuotes\E])/is)
4670 my $BackSlash = $1 || "";
4671 my $OpenQuote = $2;
4672 my $CloseQuote = $OpenQuote;
4673 $CloseQuote = $QuotePairs{$OpenQuote} if $QuotePairs{$OpenQuote};
4675 if($BackSlash)
4677 $String =~ /^\s*\\\Q$OpenQuote\E/i;
4678 my $Onset = $';
4679 $Onset =~ /\\\Q$CloseQuote\E/i;
4680 my $Rest = $';
4681 my $Item = $`;
4682 push(@Result, $Item, $Rest);
4685 else
4687 $String =~ /^\s*\Q$OpenQuote\E([^\Q$CloseQuote\E]*)\Q$CloseQuote\E/i;
4688 push(@Result, $1, $');
4691 else
4693 push(@Result, "", $String);
4695 return @Result;
4698 # Now, start with the real work
4700 # Control the output of the Content-type: text/html\n\n message
4701 my $SupressContentType = 0;
4703 # Process a file
4704 sub ProcessFile # ($file_path)
4706 my $file_path = shift || return 0;
4709 # Generate a unique file handle (for recursions)
4710 my @SRClist = ();
4711 my $FileHandle = "file";
4712 my $n = 0;
4713 while(!eof($FileHandle.$n)) {++$n;};
4714 $FileHandle .= $n;
4716 # Start HTML output
4717 # Use the default Content-type if this is NOT a raw file
4718 unless(($RawFilePattern && $ENV{'PATH_INFO'} =~ m@($RawFilePattern)$@i)
4719 || $SupressContentType)
4721 $ENV{'PATH_INFO'} =~ m@($FilePattern)$@i;
4722 my $ContentType = $ContentTypeTable{$1};
4723 print "Content-type: $ContentType\n";
4724 if(%SETCOOKIELIST && keys(%SETCOOKIELIST))
4726 foreach my $name (keys(%SETCOOKIELIST))
4728 my $value = $SETCOOKIELIST{$name};
4729 print "Set-Cookie: $name=$value\n";
4731 # Cookies are set only ONCE
4732 %SETCOOKIELIST = ();
4734 print "\n";
4735 $SupressContentType = 1; # Content type has been printed
4739 # Get access to the actual data. This can be from RAM (by way of an
4740 # environment variable) or by opening a file.
4742 # Handle the use of RAM images (file-data is stored in the
4743 # $CGI_FILE_CONTENTS environment variable)
4744 # Note that this environment variable will be cleared, i.e., it is strictly for
4745 # single-use only!
4746 if($ENV{$CGI_FILE_CONTENTS})
4748 # File has been read already
4749 $_ = $ENV{$CGI_FILE_CONTENTS};
4750 # Sorry, you have to do the reading yourself (dynamic document creation?)
4751 # NOTE: you must read the whole document at once
4752 if($_ eq '-')
4754 $_ = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
4756 else # Clear environment variable
4758 $ENV{$CGI_FILE_CONTENTS} = '-';
4761 # Open Only PLAIN TEXT files (or STDIN) and NO executable files (i.e., scripts).
4762 # THIS IS A SECURITY FEATURE!
4763 elsif($file_path eq '-' || (-e "$file_path" && -r _ && -T _ && -f _ && ! (-x _ || -X _) ))
4765 open($FileHandle, $file_path) || dieHandler(17, "<h2>File not found</h2>\n");
4766 push(@OpenFiles, $file_path);
4767 $_ = <$FileHandle>; # Read first line
4769 else
4771 print "<h2>File not found</h2>\n";
4772 dieHandler(18, "$file_path\n");
4775 $| = 1; # Flush output buffers
4777 # Initialize variables
4778 my $METAarguments = ""; # The CGI arguments from the latest META tag
4779 my @METAvalues = (); # The ''-quoted CGI values from the latest META tag
4780 my $ClosedTag = 0; # <TAG> </TAG> versus <TAG/>
4783 # Send document to output
4784 # Process the requested document.
4785 # Do a loop BEFORE reading input again (this catches the RAM/Database
4786 # type of documents).
4787 do {
4790 # Handle translations if needed
4792 performTranslation(\$_) if $TranslationPaths;
4794 # Catch <SCRIPT LANGUAGE="PERL" TYPE="text/ssperl" > directives in $_
4795 # There can be more than 1 <SCRIPT> or META tags on a line
4796 while(/\<\s*(SCRIPT|META|DIV|INS)\s/is)
4798 my $directive = "";
4799 # Store rest of line
4800 my $Before = $`;
4801 my $ScriptTag = $&;
4802 my $After = $';
4803 my $TagType = uc($1);
4804 # The before part can be send to the output
4805 print $Before;
4807 # Read complete Tag from after and/or file
4808 until($After =~ /([^\\])\>/)
4810 $After .= <$FileHandle>;
4811 performTranslation(\$After) if $TranslationPaths;
4814 if($After =~ /([^\\])\>/)
4816 $ScriptTag .= $`.$&; # Keep the Script Tag intact
4817 $After = $';
4819 else
4821 dieHandler(19, "Closing > not found\n");
4824 # The tag could be closed by />, we handle this in the XML way
4825 # and don't process any content (we ignore whitespace)
4826 $ClosedTag = ($ScriptTag =~ m@[^\\]/\s*\>\s*$@) ? 1 : 0;
4829 # TYPE or CLASS?
4830 my $TypeName = ($TagType =~ /META/is) ? "CONTENT" : "TYPE";
4831 $TypeName = "CLASS" if $TagType eq 'DIV' || $TagType eq 'INS';
4833 # Parse <SCRIPT> or <META> directive
4834 # If NOT (TYPE|CONTENT)="text/ssperl" (i.e., $ServerScriptContentType),
4835 # send the line to the output and go to the next loop
4836 my $CurrentContentType = "";
4837 if($ScriptTag =~ /(^|\s)$TypeName\s*=\s*/is)
4839 my ($Type) = ExtractQuotedItem($');
4840 $Type =~ /^\s*([\w\/\-]+)\s*[\,\;]?/;
4841 $CurrentContentType = lc($1); # Note: mime-types are "case-less"
4842 # CSS classes are aliases of $ServerScriptContentType
4843 if($TypeName eq "CLASS" && $CurrentContentType eq $ServerScriptContentClass)
4845 $CurrentContentType = $ServerScriptContentType;
4850 # Not a known server-side content type, print and continue
4851 unless(($CurrentContentType =~
4852 /$ServerScriptContentType|$ShellScriptContentType/is) ||
4853 $ScriptingLanguages{$CurrentContentType})
4855 print $ScriptTag;
4856 $_ = $After;
4857 next;
4861 # A known server-side content type, evaluate
4863 # First, handle \> and \<
4864 $ScriptTag =~ s/\\\>/\>/isg;
4865 $ScriptTag =~ s/\\\</\</isg;
4867 # Extract the CGI, SRC, ID, IF and UNLESS attributes
4868 my %ScriptTagAttributes = ();
4869 while($ScriptTag =~ /(^|\s)(CGI|IF|UNLESS|SRC|ID)\s*=\s*/is)
4871 my $Attribute = $2;
4872 my $Rest = $';
4873 my $Value = "";
4874 ($Value, $ScriptTag) = ExtractQuotedItem($Rest);
4875 $ScriptTagAttributes{uc($Attribute)} = $Value;
4879 # The attribute used to define the CGI variables
4880 # Extract CGI-variables from
4881 # <META CONTENT="text/ssperl; CGI='' SRC=''">
4882 # <SCRIPT TYPE='text/ssperl' CGI='' SRC=''>
4883 # <DIV CLASS='ssperl' CGI='' SRC='' ID=""> tags
4884 # <INS CLASS='ssperl' CGI='' SRC='' ID=""> tags
4885 if($ScriptTagAttributes{'CGI'})
4887 @ARGV = (); # Reset ARGV
4888 $ARGC = 0;
4889 $METAarguments = ""; # Reset the META CGI arguments
4890 @METAvalues = ();
4891 my $Meta_CGI = $ScriptTagAttributes{'CGI'};
4893 # Process default values of variables ($<name> = 'default value')
4894 # Allowed quotes are '', "", ``, (), [], and {}
4895 while($Meta_CGI =~ /(^\s*|[^\\])([\$\@\%]?)([\w\-]+)\s*/is)
4897 my $varType = $2 || '$'; # Variable or list
4898 my $name = $3; # The Name
4899 my $default = "";
4900 $Meta_CGI = $';
4902 if($Meta_CGI =~ /^\s*\=\s*/is)
4904 # Locate (any) default value
4905 ($default, $Meta_CGI) = ExtractQuotedItem($'); # Cut the parameter from the CGI
4907 $RemainingTag = $Meta_CGI;
4910 # Define CGI (or ENV) variable, initalize it from the
4911 # Query string or the default value
4913 # Also construct the @ARGV and @_ arrays. This allows other (SRC=) Perl
4914 # scripts to access the CGI arguments defined in the META tag
4915 # (Not for CGI inside <SCRIPT> tags)
4916 if($varType eq '$')
4918 CGIexecute::defineCGIvariable($name, $default)
4919 || dieHandler(20, "INVALID CGI name/value pair ($name, $default)\n");
4920 push(@METAvalues, "'".${"CGIexecute::$name"}."'");
4921 # Add value to the @ARGV list
4922 push(@ARGV, ${"CGIexecute::$name"});
4923 ++$ARGC;
4925 elsif($varType eq '@')
4927 CGIexecute::defineCGIvariableList($name, $default)
4928 || dieHandler(21, "INVALID CGI name/value list pair ($name, $default)\n");
4929 push(@METAvalues, "'".join("'", @{"CGIexecute::$name"})."'");
4930 # Add value to the @ARGV list
4931 push(@ARGV, @{"CGIexecute::$name"});
4932 $ARGC = scalar(@CGIexecute::ARGV);
4934 elsif($varType eq '%')
4936 CGIexecute::defineCGIvariableHash($name, $default)
4937 || dieHandler(22, "INVALID CGI name/value hash pair ($name, $default)\n");
4938 my @PairList = map {"$_ => ".${"CGIexecute::$name"}{$_}} keys(%{"CGIexecute::$name"});
4939 push(@METAvalues, "'".join("'", @PairList)."'");
4940 # Add value to the @ARGV list
4941 push(@ARGV, %{"CGIexecute::$name"});
4942 $ARGC = scalar(@CGIexecute::ARGV);
4945 # Store the values for internal and later use
4946 $METAarguments .= "$varType".$name.","; # A string of CGI variable names
4948 push(@METAvalues, "\'".eval("\"$varType\{CGIexecute::$name\}\"")."\'"); # ALWAYS add '-quotes around values
4953 # The IF (conditional execution) Attribute
4954 # Evaluate the condition and stop unless it evaluates to true
4955 if($ScriptTagAttributes{'IF'})
4957 my $IFcondition = $ScriptTagAttributes{'IF'};
4959 # Convert SCRIPT calls, ./<script>
4960 $IFcondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
4962 # Convert FILE calls, ~/<file>
4963 $IFcondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
4965 # Block execution if necessary
4966 unless(CGIexecute->evaluate($IFcondition))
4968 %ScriptTagAttributes = ();
4969 $CurrentContentType = "";
4973 # The UNLESS (conditional execution) Attribute
4974 # Evaluate the condition and stop if it evaluates to true
4975 if($ScriptTagAttributes{'UNLESS'})
4977 my $UNLESScondition = $ScriptTagAttributes{'UNLESS'};
4979 # Convert SCRIPT calls, ./<script>
4980 $UNLESScondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
4982 # Convert FILE calls, ~/<file>
4983 $UNLESScondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
4985 # Block execution if necessary
4986 if(CGIexecute->evaluate($UNLESScondition))
4988 %ScriptTagAttributes = ();
4989 $CurrentContentType = "";
4993 # The SRC (Source File) Attribute
4994 # Extract any source script files and add them in
4995 # front of the directive
4996 # The SRC list should be emptied
4997 @SRClist = ();
4998 my $SRCtag = "";
4999 my $Prefix = 1;
5000 my $PrefixDirective = "";
5001 my $PostfixDirective = "";
5002 # There is a SRC attribute
5003 if($ScriptTagAttributes{'SRC'})
5005 $SRCtag = $ScriptTagAttributes{'SRC'};
5006 # Remove "file://" prefixes
5007 $SRCtag =~ s@([^\w\/\\]|^)file\://([^\s\/\@\=])@$1$2@gis;
5008 # Expand script filenames "./Script"
5009 $SRCtag =~ s@([^\w\/\\]|^)\./([^\s\/\@\=])@$1$SCRIPT_SUB/$2@gis;
5010 # Expand script filenames "~/Script"
5011 $SRCtag =~ s@([^\w\/\\]|^)\~/([^\s\/\@\=])@$1$HOME_SUB/$2@gis;
5014 # File source tags
5015 while($SRCtag =~ /\S/is)
5017 my $SRCdirective = "";
5019 # Pseudo file, just a switch to go from PREFIXING to POSTFIXING
5020 # SRC files
5021 if($SRCtag =~ /^[\s\;\,]*(POSTFIX|PREFIX)([^$FileAllowedChars]|$)/is)
5023 my $InsertionPlace = $1;
5024 $SRCtag = $2.$';
5026 $Prefix = $InsertionPlace =~ /POSTFIX/i ? 0 : 1;
5027 # Go to next round
5028 next;
5030 # {}-blocks are just evaluated by "do"
5031 elsif($SRCtag =~ /^[\s\;\,]*\{/is)
5033 my $SRCblock = $';
5034 if($SRCblock =~ /\}[\s\;\,]*([^\}]*)$/is)
5036 $SRCblock = $`;
5037 $SRCtag = $1.$';
5038 # SAFEqx shell script blocks
5039 if($CurrentContentType =~ /$ShellScriptContentType/is)
5041 # Handle ''-quotes inside the script
5042 $SRCblock =~ s/[\']/\\$&/gis;
5044 $SRCblock = "print do { SAFEqx(\'".$SRCblock."\'); };'';";
5045 $SRCdirective .= $SRCblock."\n";
5047 # do { SRCblocks }
5048 elsif($CurrentContentType =~ /$ServerScriptContentType/is)
5050 $SRCblock = "print do { $SRCblock };'';";
5051 $SRCdirective .= $SRCblock."\n";
5053 else # The interpreter should handle this
5055 push(@SRClist, "{ $SRCblock }");
5059 else
5060 { dieHandler(23, "Closing \} missing\n");};
5062 # Files are processed as Text or Executable files
5063 elsif($SRCtag =~ /[\s\;\,]*([$FileAllowedChars]+)[\;\,\s]*/is)
5065 my $SrcFile = $1;
5066 $SRCtag = $';
5068 # We are handling one of the external interpreters
5069 if($ScriptingLanguages{$CurrentContentType})
5071 push(@SRClist, $SrcFile);
5073 # We are at the start of a DIV tag, just load all SRC files and/or URL's
5074 elsif($TagType eq 'DIV' || $TagType eq 'INS') # All files are prepended in DIV's
5076 # $SrcFile is a URL pointing to an HTTP or FTP server
5077 if($SrcFile =~ m!^([a-z]+)\://!)
5079 my $URLoutput = CGIscriptor::read_url($SrcFile);
5080 $SRCdirective .= $URLoutput;
5082 # SRC file is an existing file
5083 elsif(-e "$SrcFile")
5085 open(DIVSOURCE, "<$SrcFile") || dieHandler(24, "<$SrcFile: $!\n");
5086 my $Content;
5087 while(sysread(DIVSOURCE, $Content, 1024) > 0)
5089 $SRCdirective .= $Content;
5091 close(DIVSOURCE);
5094 # Executable files are executed as
5095 # `$SrcFile 'ARGV[0]' 'ARGV[1]'`
5096 elsif(-x "$SrcFile")
5098 $SRCdirective .= "print \`$SrcFile @METAvalues\`;'';\n";
5100 # Handle 'standard' files, using ProcessFile
5101 elsif((-T "$SrcFile" || $ENV{$CGI_FILE_CONTENTS})
5102 && $SrcFile =~ m@($FilePattern)$@) # A recursion
5105 # Do not process still open files because it can lead
5106 # to endless recursions
5107 if(grep(/^$SrcFile$/, @OpenFiles))
5108 { dieHandler(25, "$SrcFile allready opened (endless recursion)\n")};
5109 # Prepare meta arguments
5110 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5111 # Process the file
5112 $SRCdirective .= "main::ProcessFile(\'$SrcFile\');'';\n";
5114 elsif($SrcFile =~ m!^([a-z]+)\://!) # URL's are loaded and printed
5116 $SRCdirective .= GET_URL($SrcFile);
5118 elsif(-T "$SrcFile") # Textfiles are "do"-ed (Perl execution)
5120 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
5121 $SRCdirective .= "do \'$SrcFile\';'';\n";
5123 else # This one could not be resolved (should be handled by BinaryMapFile)
5125 $SRCdirective .= 'print "'.$SrcFile.' cannot be used"'."\n";
5130 # Postfix or Prefix
5131 if($Prefix)
5133 $PrefixDirective .= $SRCdirective;
5135 else
5137 $PostfixDirective .= $SRCdirective;
5140 # The prefix should be handled immediately
5141 $directive .= $PrefixDirective;
5142 $PrefixDirective = "";
5146 # Handle the content of the <SCRIPT></SCRIPT> tags
5147 # Do not process the content of <SCRIPT/>
5148 if($TagType =~ /SCRIPT/is && !$ClosedTag) # The <SCRIPT> TAG
5150 my $EndScriptTag = "";
5152 # Execute SHELL scripts with SAFEqx()
5153 if($CurrentContentType =~ /$ShellScriptContentType/is)
5155 $directive .= "SAFEqx(\'";
5158 # Extract Program
5159 while($After !~ /\<\s*\/SCRIPT[^\>]*\>/is && !eof($FileHandle))
5161 $After .= <$FileHandle>;
5162 performTranslation(\$After) if $TranslationPaths;
5165 if($After =~ /\<\s*\/SCRIPT[^\>]*\>/is)
5167 $directive .= $`;
5168 $EndScriptTag = $&;
5169 $After = $';
5171 else
5173 dieHandler(26, "Missing </SCRIPT> end tag in $ENV{'PATH_INFO'}\n");
5176 # Process only when content should be executed
5177 if($CurrentContentType)
5180 # Remove all comments from Perl scripts
5181 # (NOT from OS shell scripts)
5182 $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
5183 if $CurrentContentType =~ /$ServerScriptContentType/i;
5185 # Convert SCRIPT calls, ./<script>
5186 $directive =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
5188 # Convert FILE calls, ~/<file>
5189 $directive =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
5191 # Execute SHELL scripts with SAFEqx(), closing bracket
5192 if($CurrentContentType =~ /$ShellScriptContentType/i)
5194 # Handle ''-quotes inside the script
5195 $directive =~ /SAFEqx\(\'/;
5196 $directive = $`.$&;
5197 my $Executable = $';
5198 $Executable =~ s/[\']/\\$&/gs;
5200 $directive .= $Executable."\');"; # Closing bracket
5203 else
5205 $directive = "";
5208 # Handle the content of the <DIV></DIV> tags
5209 # Do not process the content of <DIV/>
5210 elsif(($TagType eq 'DIV' || $TagType eq 'INS') && !$ClosedTag) # The <DIV> TAGs
5212 my $EndScriptTag = "";
5214 # Extract Text
5215 while($After !~ /\<\s*\/$TagType[^\>]*\>/is && !eof($FileHandle))
5217 $After .= <$FileHandle>;
5218 performTranslation(\$After) if $TranslationPaths;
5221 if($After =~ /\<\s*\/$TagType[^\>]*\>/is)
5223 $directive .= $`;
5224 $EndScriptTag = $&;
5225 $After = $';
5227 else
5229 dieHandler(27, "Missing </$TagType> end tag in $ENV{'PATH_INFO'}\n");
5232 # Add the Postfixed directives (but only when it contains something printable)
5233 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5234 $PostfixDirective = "";
5237 # Process only when content should be handled
5238 if($CurrentContentType)
5241 # Get the name (ID), and clean it (i.e., remove anything that is NOT part of
5242 # a valid Perl name). Names should not contain $, but we can handle it.
5243 my $name = $ScriptTagAttributes{'ID'};
5244 $name =~ /^\s*[\$\@\%]?([\w\-]+)/;
5245 $name = $1;
5247 # Assign DIV contents to $NAME value OUTSIDE the CGI values!
5248 CGIexecute::defineCGIexecuteVariable($name, $directive);
5249 $directive = "";
5252 # Nothing to execute
5253 $directive = "";
5257 # Handle Foreign scripting languages
5258 if($ScriptingLanguages{$CurrentContentType})
5260 my $newDirective = "";
5261 $newDirective .= OpenForeignScript($CurrentContentType); # Only if not already done
5262 $newDirective .= PrefixForeignScript($CurrentContentType);
5263 $newDirective .= InsertForeignScript($CurrentContentType, $directive, @SRClist);
5264 $newDirective .= PostfixForeignScript($CurrentContentType);
5265 $newDirective .= CloseForeignScript($CurrentContentType); # This shouldn't be necessary
5267 $newDirective .= '"";';
5269 $directive = $newDirective;
5273 # Add the Postfixed directives (but only when it contains something printable)
5274 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
5275 $PostfixDirective = "";
5278 # EXECUTE the script and print the results
5280 # Use this to debug the program
5281 # print STDERR "Directive $CGI_Date: \n", $directive, "\n\n";
5283 my $Result = CGIexecute->evaluate($directive) if $directive; # Evaluate as PERL code
5284 $Result =~ s/\n$//g; # Remove final newline
5286 # Print the Result of evaluating the directive
5287 # (this will handle LARGE, >64 kB output)
5288 my $BytesWritten = 1;
5289 while($Result && $BytesWritten)
5291 $BytesWritten = syswrite(STDOUT, $Result, 64);
5292 $Result = substr($Result, $BytesWritten);
5294 # print $Result; # Could be used instead of above code
5296 # Store result if wanted, i.e., if $CGIscriptorResults has been
5297 # defined in a <META> tag.
5298 push(@CGIexecute::CGIscriptorResults, $Result)
5299 if exists($default_values{'CGIscriptorResults'});
5301 # Process the rest of the input line (this could contain
5302 # another directive)
5303 $_ = $After;
5305 print $_;
5306 } while(<$FileHandle>); # Read and Test AFTER first loop!
5308 close ($FileHandle);
5309 dieHandler(28, "Error in recursion\n") unless pop(@OpenFiles) == $file_path;
5313 ###############################################################################
5315 # Call the whole package
5317 sub Handle_Request
5319 my $file_path = "";
5321 # Initialization Code
5322 Initialize_Request();
5324 # SECURITY: ACCESS CONTROL
5325 Access_Control();
5327 # Read the POST part of the query, if there is one
5328 Get_POST_part_of_query();
5330 # Start (HTML) output and logging
5331 $file_path = Initialize_output();
5333 # Check login access or divert to login procedure
5334 $Use_Login = Log_In_Access();
5335 $file_path = $Use_Login if $Use_Login;
5337 # Record which files are still open (to avoid endless recursions)
5338 my @OpenFiles = ();
5340 # Record whether the default HTML ContentType has already been printed
5341 # but only if the SERVER uses HTTP or some other protocol that might interpret
5342 # a content MIME type.
5344 $SupressContentType = !("$ENV{'SERVER_PROTOCOL'}" =~ /($ContentTypeServerProtocols)/i);
5346 # Process the specified file
5347 ProcessFile($file_path) if $file_path ne $SS_PUB;
5349 # Cleanup all open external (foreign) interpreters
5350 CloseAllForeignScripts();
5353 "" # SUCCESS
5356 # Make a single call to handle an (empty) request
5357 Handle_Request();
5360 # END OF PACKAGE MAIN
5363 ####################################################################################
5365 # The CGIEXECUTE PACKAGE
5367 ####################################################################################
5369 # Isolate the evaluation of directives as PERL code from the rest of the program.
5370 # Remember that each package has its own name space.
5371 # Note that only the FIRST argument of execute->evaluate is actually evaluated,
5372 # all other arguments are accessible inside the first argument as $_[0] to $_[$#_].
5374 package CGIexecute;
5376 sub evaluate
5378 my $self = shift;
5379 my $directive = shift;
5380 $directive = eval($directive);
5381 warn $@ if $@; # Write an error message to STDERR
5382 $directive; # Return value of directive
5386 # defineCGIexecuteVariable($name [, $value]) -> 0/1
5388 # Define and intialize variables inside CGIexecute
5389 # Does no sanity checking, for internal use only
5391 sub defineCGIexecuteVariable # ($name [, $value]) -> 0/1
5393 my $name = shift || return 0; # The Name
5394 my $value = shift || ""; # The value
5396 ${$name} = $value;
5398 return 1;
5401 # Protect certain CGI variables values when set internally
5402 # If not defined internally, there will be no variable set AT ALL
5403 my %CGIprotectedVariable = ();
5404 sub ProtectCGIvariable # ($name) -> 0/1
5406 my $name = shift || "";
5407 return 0 unless $name && $name =~ /\w/;
5409 ++$CGIprotectedVariable{$name};
5411 return $CGIprotectedVariable{$name};
5414 # defineCGIvariable($name [, $default]) -> 0/1
5416 # Define and intialize CGI variables
5417 # Tries (in order) $ENV{$name}, the Query string and the
5418 # default value.
5419 # Removes all '-quotes etc.
5421 sub defineCGIvariable # ($name [, $default]) -> 0/1
5423 my $name = shift || return 0; # The Name
5424 my $default = shift || ""; # The default value
5426 # Protect variables set internally
5427 return 1 if !$name || exists($CGIprotectedVariable{$name});
5429 # Remove \-quoted characters
5430 $default =~ s/\\(.)/$1/g;
5431 # Store default values
5432 $::default_values{$name} = $default if $default;
5434 # Process variables
5435 my $temp = undef;
5436 # If there is a user supplied value, it replaces the
5437 # default value.
5439 # Environment values have precedence
5440 if(exists($ENV{$name}))
5442 $temp = $ENV{$name};
5444 # Get name and its value from the query string
5445 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5447 $temp = ::YOUR_CGIPARSE($name);
5449 # Defined values must exist for security
5450 elsif(!exists($::default_values{$name}))
5452 $::default_values{$name} = undef;
5455 # SECURITY, do not allow '- and `-quotes in
5456 # client values.
5457 # Remove all existing '-quotes
5458 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5459 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5460 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5461 # If $temp is empty, use the default value (if it exists)
5462 unless($temp =~ /\S/ || length($temp) > 0) # I.e., $temp is empty
5464 $temp = $::default_values{$name};
5465 # Remove all existing '-quotes
5466 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5467 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5468 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5470 else # Store current CGI values and remove defaults
5472 $::default_values{$name} = $temp;
5474 # Define the CGI variable and its value (in the execute package)
5475 ${$name} = $temp;
5477 # return SUCCES
5478 return 1;
5481 sub defineCGIvariableList # ($name [, $default]) -> 0/1)
5483 my $name = shift || return 0; # The Name
5484 my $default = shift || ""; # The default value
5486 # Protect variables set internally
5487 return 1 if !$name || exists($CGIprotectedVariable{$name});
5489 # Defined values must exist for security
5490 if(!exists($::default_values{$name}))
5492 $::default_values{$name} = $default;
5495 my @temp = ();
5498 # For security:
5499 # Environment values have precedence
5500 if(exists($ENV{$name}))
5502 push(@temp, $ENV{$name});
5504 # Get name and its values from the query string
5505 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5507 push(@temp, ::YOUR_CGIPARSE($name, 1)); # Extract LIST
5509 else
5511 push(@temp, $::default_values{$name});
5515 # SECURITY, do not allow '- and `-quotes in
5516 # client values.
5517 # Remove all existing '-quotes
5518 @temp = map {s/([\r\f]+\n)/\n/g; $_} @temp; # Only \n is allowed
5519 @temp = map {s/[\']/&#8217;/igs; $_} @temp; # Remove all single quotes
5520 @temp = map {s/[\`]/&#8216;/igs; $_} @temp; # Remove all backtick quotes
5522 # Store current CGI values and remove defaults
5523 $::default_values{$name} = $temp[0];
5525 # Define the CGI variable and its value (in the execute package)
5526 @{$name} = @temp;
5528 # return SUCCES
5529 return 1;
5532 sub defineCGIvariableHash # ($name [, $default]) -> 0/1) Note: '$name{""} = $default';
5534 my $name = shift || return 0; # The Name
5535 my $default = shift || ""; # The default value
5537 # Protect variables set internally
5538 return 1 if !$name || exists($CGIprotectedVariable{$name});
5540 # Defined values must exist for security
5541 if(!exists($::default_values{$name}))
5543 $::default_values{$name} = $default;
5546 my %temp = ();
5549 # For security:
5550 # Environment values have precedence
5551 if(exists($ENV{$name}))
5553 $temp{""} = $ENV{$name};
5555 # Get name and its values from the query string
5556 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5558 %temp = ::YOUR_CGIPARSE($name, -1); # Extract HASH table
5560 elsif($::default_values{$name} ne "")
5562 $temp{""} = $::default_values{$name};
5566 # SECURITY, do not allow '- and `-quotes in
5567 # client values.
5568 # Remove all existing '-quotes
5569 my $Key;
5570 foreach $Key (keys(%temp))
5572 $temp{$Key} =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5573 $temp{$Key} =~ s/[\']/&#8217;/igs; # Remove all single quotes
5574 $temp{$Key} =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5577 # Store current CGI values and remove defaults
5578 $::default_values{$name} = $temp{""};
5580 # Define the CGI variable and its value (in the execute package)
5581 %{$name} = ();
5582 my $tempKey;
5583 foreach $tempKey (keys(%temp))
5585 ${$name}{$tempKey} = $temp{$tempKey};
5588 # return SUCCES
5589 return 1;
5593 # SAFEqx('CommandString')
5595 # A special function that is a safe alternative to backtick quotes (and qx//)
5596 # with client-supplied CGI values. All CGI variables are surrounded by
5597 # single ''-quotes (except between existing \'\'-quotes, don't try to be
5598 # too smart). All variables are then interpolated. Simple (@) lists are
5599 # expanded with join(' ', @List), and simple (%) hash tables expanded
5600 # as a list of "key=value" pairs. Complex variables, e.g., @$var, are
5601 # evaluated in a scalar context (e.g., as scalar(@$var)). All occurrences of
5602 # $@% that should NOT be interpolated must be preceeded by a "\".
5603 # If the first line of the String starts with "#! interpreter", the
5604 # remainder of the string is piped into interpreter (after interpolation), i.e.,
5605 # open(INTERPRETER, "|interpreter");print INTERPRETER remainder;
5606 # just like in UNIX. There are some problems with quotes. Be carefull in
5607 # using them. You do not have access to the output of any piped (#!)
5608 # process! If you want such access, execute
5609 # <SCRIPT TYPE="text/osshell">echo "script"|interpreter</SCRIPT> or
5610 # <SCRIPT TYPE="text/ssperl">$resultvar = SAFEqx('echo "script"|interpreter');
5611 # </SCRIPT>.
5613 # SAFEqx ONLY WORKS WHEN THE STRING ITSELF IS SURROUNDED BY SINGLE QUOTES
5614 # (SO THAT IT IS NOT INTERPOLATED BEFORE IT CAN BE PROTECTED)
5615 sub SAFEqx # ('String') -> result of executing qx/"String"/
5617 my $CommandString = shift;
5618 my $NewCommandString = "";
5620 # Only interpolate when required (check the On/Off switch)
5621 unless($CGIscriptor::NoShellScriptInterpolation)
5624 # Handle existing single quotes around CGI values
5625 while($CommandString =~ /\'[^\']+\'/s)
5627 my $CurrentQuotedString = $&;
5628 $NewCommandString .= $`;
5629 $CommandString = $'; # The remaining string
5630 # Interpolate CGI variables between quotes
5631 # (e.g., '$CGIscriptorResults[-1]')
5632 $CurrentQuotedString =~
5633 s/(^|[^\\])([\$\@])((\w*)([\{\[][\$\@\%]?[\:\w\-]+[\}\]])*)/if(exists($main::default_values{$4})){
5634 "$1".eval("$2$3")}else{"$&"}/egs;
5636 # Combine result with previous result
5637 $NewCommandString .= $CurrentQuotedString;
5639 $CommandString = $NewCommandString.$CommandString;
5641 # Select known CGI variables and surround them with single quotes,
5642 # then interpolate all variables
5643 $CommandString =~
5644 s/(^|[^\\])([\$\@\%]+)((\w*)([\{\[][\w\:\$\"\-]+[\}\]])*)/
5645 if($2 eq '$' && exists($main::default_values{$4}))
5646 {"$1\'".eval("\$$3")."\'";}
5647 elsif($2 eq '@'){$1.join(' ', @{"$3"});}
5648 elsif($2 eq '%'){my $t=$1;map {$t.=" $_=".${"$3"}{$_}}
5649 keys(%{"$3"});$t}
5650 else{$1.eval("${2}$3");
5651 }/egs;
5653 # Remove backslashed [$@%]
5654 $CommandString =~ s/\\([\$\@\%])/$1/gs;
5657 # Debugging
5658 # return $CommandString;
5660 # Handle UNIX style "#! shell command\n" constructs as
5661 # a pipe into the shell command. The output cannot be tapped.
5662 my $ReturnValue = "";
5663 if($CommandString =~ /^\s*\#\!([^\f\n\r]+)[\f\n\r]/is)
5665 my $ShellScripts = $';
5666 my $ShellCommand = $1;
5667 open(INTERPRETER, "|$ShellCommand") || dieHandler(29, "\'$ShellCommand\' PIPE not opened: &!\n");
5668 select(INTERPRETER);$| = 1;
5669 print INTERPRETER $ShellScripts;
5670 close(INTERPRETER);
5671 select(STDOUT);$| = 1;
5673 # Shell scripts which are redirected to an existing named pipe.
5674 # The output cannot be tapped.
5675 elsif($CGIscriptor::ShellScriptPIPE)
5677 CGIscriptor::printSAFEqxPIPE($CommandString);
5679 else # Plain ``-backtick execution
5681 # Execute the commands
5682 $ReturnValue = qx/$CommandString/;
5684 return $ReturnValue;
5687 ####################################################################################
5689 # The CGIscriptor PACKAGE
5691 ####################################################################################
5693 # Isolate the evaluation of CGIscriptor functions, i.e., those prefixed with
5694 # "CGIscriptor::"
5696 package CGIscriptor;
5699 # The Interpolation On/Off switch
5700 my $NoShellScriptInterpolation = undef;
5701 # The ShellScript redirection pipe
5702 my $ShellScriptPIPE = undef;
5704 # Open a named PIPE for SAFEqx to receive ALL shell scripts
5705 sub RedirectShellScript # ('CommandString')
5707 my $CommandString = shift || undef;
5709 if($CommandString)
5711 $ShellScriptPIPE = "ShellScriptNamedPipe";
5712 open($ShellScriptPIPE, "|$CommandString")
5713 || main::dieHandler(30, "\'|$CommandString\' PIPE open failed: $!\n");
5715 else
5717 close($ShellScriptPIPE);
5718 $ShellScriptPIPE = undef;
5720 return $ShellScriptPIPE;
5723 # Print to redirected shell script pipe
5724 sub printSAFEqxPIPE # ("String") -> print return value
5726 my $String = shift || undef;
5728 select($ShellScriptPIPE); $| = 1;
5729 my $returnvalue = print $ShellScriptPIPE ($String);
5730 select(STDOUT); $| = 1;
5732 return $returnvalue;
5735 # a pointer to CGIexecute::SAFEqx
5736 sub SAFEqx # ('String') -> result of qx/"String"/
5738 my $CommandString = shift;
5739 return CGIexecute::SAFEqx($CommandString);
5743 # a pointer to CGIexecute::defineCGIvariable
5744 sub defineCGIvariable # ($name[, $default]) ->0/1
5746 my $name = shift;
5747 my $default = shift;
5748 return CGIexecute::defineCGIvariable($name, $default);
5752 # a pointer to CGIexecute::defineCGIvariable
5753 sub defineCGIvariableList # ($name[, $default]) ->0/1
5755 my $name = shift;
5756 my $default = shift;
5757 return CGIexecute::defineCGIvariableList($name, $default);
5761 # a pointer to CGIexecute::defineCGIvariable
5762 sub defineCGIvariableHash # ($name[, $default]) ->0/1
5764 my $name = shift;
5765 my $default = shift;
5766 return CGIexecute::defineCGIvariableHash($name, $default);
5770 # Decode URL encoded arguments
5771 sub URLdecode # (URL encoded input) -> string
5773 my $output = "";
5774 my $char;
5775 my $Value;
5776 foreach $Value (@_)
5778 my $EncodedValue = $Value; # Do not change the loop variable
5779 # Convert all "+" to " "
5780 $EncodedValue =~ s/\+/ /g;
5781 # Convert all hexadecimal codes (%FF) to their byte values
5782 while($EncodedValue =~ /\%([0-9A-F]{2})/i)
5784 $output .= $`.chr(hex($1));
5785 $EncodedValue = $';
5787 $output .= $EncodedValue; # The remaining part of $Value
5789 $output;
5792 # Encode arguments as URL codes.
5793 sub URLencode # (input) -> URL encoded string
5795 my $output = "";
5796 my $char;
5797 my $Value;
5798 foreach $Value (@_)
5800 my @CharList = split('', $Value);
5801 foreach $char (@CharList)
5803 if($char =~ /\s/)
5804 { $output .= "+";}
5805 elsif($char =~ /\w\-/)
5806 { $output .= $char;}
5807 else
5809 $output .= uc(sprintf("%%%2.2x", ord($char)));
5813 $output;
5816 # Extract the value of a CGI variable from the URL-encoded $string
5817 # Also extracts the data blocks from a multipart request. Does NOT
5818 # decode the multipart blocks
5819 sub CGIparseValue # (ValueName [, URL_encoded_QueryString [, \$QueryReturnReference]]) -> Decoded value
5821 my $ValueName = shift;
5822 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5823 my $ReturnReference = shift || undef;
5824 my $output = "";
5826 if($QueryString =~ /(^|\&)$ValueName\=([^\&]*)(\&|$)/)
5828 $output = URLdecode($2);
5829 $$ReturnReference = $' if ref($ReturnReference);
5831 # Get multipart POST or PUT methods
5832 elsif($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
5834 my $MultipartType = $2;
5835 my $BoundaryString = $3;
5836 # Remove the boundary-string
5837 my $temp = $QueryString;
5838 $temp =~ /^\Q--$BoundaryString\E/m;
5839 $temp = $';
5841 # Identify the newline character(s), this is the first character in $temp
5842 my $NewLine = "\r\n"; # Actually, this IS the correct one
5843 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
5845 # Is this correct??? I have to check.
5846 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
5847 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
5848 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
5849 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
5852 # search through all data blocks
5853 while($temp =~ /^\Q--$BoundaryString\E/m)
5855 my $DataBlock = $`;
5856 $temp = $';
5857 # Get the empty line after the header
5858 $DataBlock =~ /$NewLine$NewLine/;
5859 $Header = $`;
5860 $output = $';
5861 my $Header = $`;
5862 $output = $';
5864 # Remove newlines from the header
5865 $Header =~ s/$NewLine/ /g;
5867 # Look whether this block is the one you are looking for
5868 # Require the quotes!
5869 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
5871 my $i;
5872 for($i=length($NewLine); $i; --$i)
5874 chop($output);
5876 # OK, get out
5877 last;
5879 # reinitialize the output
5880 $output = "";
5882 $$ReturnReference = $temp if ref($ReturnReference);
5884 elsif($QueryString !~ /(^|\&)$ValueName\=/) # The value simply isn't there
5886 return undef;
5887 $$ReturnReference = undef if ref($ReturnReference);
5889 else
5891 print "ERROR: $ValueName $main::ENV{'CONTENT_TYPE'}\n";
5893 return $output;
5897 # Get a list of values for the same ValueName. Uses CGIparseValue
5899 sub CGIparseValueList # (ValueName [, URL_encoded_QueryString]) -> List of decoded values
5901 my $ValueName = shift;
5902 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5903 my @output = ();
5904 my $RestQueryString;
5905 my $Value;
5906 while($QueryString &&
5907 (($Value = CGIparseValue($ValueName, $QueryString, \$RestQueryString))
5908 || defined($Value)))
5910 push(@output, $Value);
5911 $QueryString = $RestQueryString; # QueryString is consumed!
5913 # ready, return list with values
5914 return @output;
5917 sub CGIparseValueHash # (ValueName [, URL_encoded_QueryString]) -> Hash table of decoded values
5919 my $ValueName = shift;
5920 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5921 my $RestQueryString;
5922 my %output = ();
5923 while($QueryString && $QueryString =~ /(^|\&)$ValueName([\w]*)\=/)
5925 my $Key = $2;
5926 my $Value = CGIparseValue("$ValueName$Key", $QueryString, \$RestQueryString);
5927 $output{$Key} = $Value;
5928 $QueryString = $RestQueryString; # QueryString is consumed!
5930 # ready, return list with values
5931 return %output;
5934 sub CGIparseForm # ([URL_encoded_QueryString]) -> Decoded Form (NO multipart)
5936 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5937 my $output = "";
5939 $QueryString =~ s/\&/\n/g;
5940 $output = URLdecode($QueryString);
5942 $output;
5945 # Extract the header of a multipart CGI variable from the POST input
5946 sub CGIparseHeader # (ValueName [, URL_encoded_QueryString]) -> Decoded value
5948 my $ValueName = shift;
5949 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5950 my $output = "";
5952 if($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
5954 my $MultipartType = $2;
5955 my $BoundaryString = $3;
5956 # Remove the boundary-string
5957 my $temp = $QueryString;
5958 $temp =~ /^\Q--$BoundaryString\E/m;
5959 $temp = $';
5961 # Identify the newline character(s), this is the first character in $temp
5962 my $NewLine = "\r\n"; # Actually, this IS the correct one
5963 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
5965 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
5966 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
5967 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
5968 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
5971 # search through all data blocks
5972 while($temp =~ /^\Q--$BoundaryString\E/m)
5974 my $DataBlock = $`;
5975 $temp = $';
5976 # Get the empty line after the header
5977 $DataBlock =~ /$NewLine$NewLine/;
5978 $Header = $`;
5979 my $Header = $`;
5981 # Remove newlines from the header
5982 $Header =~ s/$NewLine/ /g;
5984 # Look whether this block is the one you are looking for
5985 # Require the quotes!
5986 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
5988 $output = $Header;
5989 last;
5991 # reinitialize the output
5992 $output = "";
5995 return $output;
5999 # Checking variables for security (e.g., file names and email addresses)
6000 # File names are tested against the $::FileAllowedChars and $::BlockPathAccess variables
6001 sub CGIsafeFileName # FileName -> FileName or ""
6003 my $FileName = shift || "";
6004 return "" if $FileName =~ m?[^$::FileAllowedChars]?;
6005 return "" if $FileName =~ m!(^|/|\:)[\-\.]!;
6006 return "" if $FileName =~ m@\.\.\Q$::DirectorySeparator\E@; # Higher directory not allowed
6007 return "" if $FileName =~ m@\Q$::DirectorySeparator\E\.\.@; # Higher directory not allowed
6008 return "" if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@; # Invisible (blocked) file
6010 return $FileName;
6013 sub CGIsafeEmailAddress # email -> email or ""
6015 my $Email = shift || "";
6016 return "" unless $Email =~ m/^[\w\.\-]+[\@][\w\.\-\:]+$/;
6017 return $Email;
6020 # Get a URL from the web. Needs main::GET_URL($URL) function
6021 # (i.e., curl, snarf, or wget)
6022 sub read_url # ($URL) -> page/file
6024 my $URL = shift || return "";
6026 # Get the commands to read the URL, do NOT add a print command
6027 my $URL_command = main::GET_URL($URL, 1);
6028 # execute the commands, i.e., actually read it
6029 my $URLcontent = CGIexecute->evaluate($URL_command);
6031 # Ready, return the content.
6032 return $URLcontent;
6035 ################################################>>>>>>>>>>Start Remove
6037 # BrowseAllDirs(Directory, indexfile)
6039 # usage:
6040 # <SCRIPT TYPE='text/ssperl'>
6041 # CGIscriptor::BrowseAllDirs('Sounds', 'index.html', '\.wav$')
6042 # </SCRIPT>
6044 # Allows to browse all directories. Stops at '/'. If the directory contains
6045 # an indexfile, eg, index.html, that file will be used instead. Files must match
6046 # the $Pattern, if it is given. Default is
6047 # CGIscriptor::BrowseAllDirs('/', 'index.html', '')
6049 sub BrowseAllDirs # (Directory, indexfile, $Pattern) -> Print HTML code
6051 my $Directory = shift || '/';
6052 my $indexfile = shift || 'index.html';
6053 my $Pattern = shift || '';
6054 $Directory =~ s!/$!!g;
6056 # If the index directory exists, use that one
6057 if(-s "$::CGI_HOME$Directory/$indexfile")
6059 return main::ProcessFile("$::CGI_HOME$Directory/$indexfile");
6062 # No indexfile, continue
6063 my @DirectoryList = glob("$::CGI_HOME$Directory");
6064 $CurrentDirectory = shift(@DirectoryList);
6065 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
6066 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
6067 print "<h1>";
6068 print "$CurrentDirectory" if $CurrentDirectory;
6069 print "</h1>\n";
6071 opendir(BROWSE, "$::CGI_HOME$Directory") || main::dieHandler(31, "$::CGI_HOME$Directory $!");
6072 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
6074 # Print directories
6075 my $file;
6076 print "<pre><ul TYPE='NONE'>\n";
6077 foreach $file (@AllFiles)
6079 next unless -d "$::CGI_HOME$Directory/$file";
6080 # Check whether this file should be visible
6081 next if $::BlockPathAccess &&
6082 "$Directory/$file/" =~ m@$::BlockPathAccess@;
6083 print "<dt><a href='$Directory/$file'>$file</a></dt>\n";
6085 print "</ul></pre>\n";
6087 # Print files
6088 print "<pre><ul TYPE='CIRCLE'>\n";
6089 my $TotalSize = 0;
6090 foreach $file (@AllFiles)
6092 next if $file =~ /^\./;
6093 next if -d "$::CGI_HOME$Directory/$file";
6094 next if -l "$::CGI_HOME$Directory/$file";
6095 # Check whether this file should be visible
6096 next if $::BlockPathAccess &&
6097 "$Directory/$file" =~ m@$::BlockPathAccess@;
6099 if(!$Pattern || $file =~ m@$Pattern@)
6101 my $Date = localtime($^T - (-M "$::CGI_HOME$Directory/$file")*3600*24);
6102 my $Size = -s "$::CGI_HOME$Directory/$file";
6103 $Size = sprintf("%6.0F kB", $Size/1024);
6104 my $Type = `file $::CGI_HOME$Directory/$file`;
6105 $Type =~ s@\s*$::CGI_HOME$Directory/$file\s*\:\s*@@ig;
6106 chomp($Type);
6108 print "<li>";
6109 print "<a href='$Directory/$file'>";
6110 printf("%-40s", "$file</a>");
6111 print "\t$Size\t$Date\t$Type";
6112 print "</li>\n";
6115 print "</ul></pre>";
6117 return 1;
6121 ################################################
6123 # BrowseDirs(RootDirectory [, Pattern, Start])
6125 # usage:
6126 # <SCRIPT TYPE='text/ssperl'>
6127 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', 'Speech', 'DIRECTORY')
6128 # </SCRIPT>
6130 # Allows to browse subdirectories. Start should be relative to the RootDirectory,
6131 # e.g., the full path of the directory 'Speech' is '~/Sounds/Speech'.
6132 # Only files which fit /$Pattern/ and directories are displayed.
6133 # Directories down or up the directory tree are supplied with a
6134 # GET request with the name of the CGI variable in the fourth argument (default
6135 # is 'BROWSEDIRS'). So the correct call for a subdirectory could be:
6136 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', $DIRECTORY, 'DIRECTORY')
6138 sub BrowseDirs # (RootDirectory [, Pattern, Start, CGIvariable, HTTPserver]) -> Print HTML code
6140 my $RootDirectory = shift; # || return 0;
6141 my $Pattern = shift || '\S';
6142 my $Start = shift || "";
6143 my $CGIvariable = shift || "BROWSEDIRS";
6144 my $HTTPserver = shift || '';
6146 $Start = CGIscriptor::URLdecode($Start); # Sometimes, too much has been encoded
6147 $Start =~ s@//+@/@g;
6148 $Start =~ s@[^/]+/\.\.@@ig;
6149 $Start =~ s@^\.\.@@ig;
6150 $Start =~ s@/\.$@@ig;
6151 $Start =~ s!/+$!!g;
6152 $Start .= "/" if $Start;
6154 my @Directory = glob("$::CGI_HOME/$RootDirectory/$Start");
6155 $CurrentDirectory = shift(@Directory);
6156 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
6157 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
6158 print "<h1>";
6159 print "$CurrentDirectory" if $CurrentDirectory;
6160 print "</h1>\n";
6161 opendir(BROWSE, "$::CGI_HOME/$RootDirectory/$Start") || main::dieHandler(31, "$::CGI_HOME/$RootDirectory/$Start $!");
6162 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
6164 # Print directories
6165 my $file;
6166 print "<pre><ul TYPE='NONE'>\n";
6167 foreach $file (@AllFiles)
6169 next unless -d "$::CGI_HOME/$RootDirectory/$Start$file";
6170 # Check whether this file should be visible
6171 next if $::BlockPathAccess &&
6172 "/$RootDirectory/$Start$file/" =~ m@$::BlockPathAccess@;
6174 my $NewURL = $Start ? "$Start$file" : $file;
6175 $NewURL = CGIscriptor::URLencode($NewURL);
6176 print "<dt><a href='";
6177 print "$ENV{SCRIPT_NAME}" if $ENV{SCRIPT_NAME} !~ m@[^\w+\-/]@;
6178 print "$ENV{PATH_INFO}?$CGIvariable=$NewURL'>$file</a></dt>\n";
6180 print "</ul></pre>\n";
6182 # Print files
6183 print "<pre><ul TYPE='CIRCLE'>\n";
6184 my $TotalSize = 0;
6185 foreach $file (@AllFiles)
6187 next if $file =~ /^\./;
6188 next if -d "$::CGI_HOME/$RootDirectory/$Start$file";
6189 next if -l "$::CGI_HOME/$RootDirectory/$Start$file";
6190 # Check whether this file should be visible
6191 next if $::BlockPathAccess &&
6192 "$::CGI_HOME/$RootDirectory/$Start$file" =~ m@$::BlockPathAccess@;
6194 if($file =~ m@$Pattern@)
6196 my $Date = localtime($^T - (-M "$::CGI_HOME/$RootDirectory/$Start$file")*3600*24);
6197 my $Size = -s "$::CGI_HOME/$RootDirectory/$Start$file";
6198 $Size = sprintf("%6.0F kB", $Size/1024);
6199 my $Type = `file $::CGI_HOME/$RootDirectory/$Start$file`;
6200 $Type =~ s@\s*$::CGI_HOME/$RootDirectory/$Start$file\s*\:\s*@@ig;
6201 chomp($Type);
6203 print "<li>";
6204 if($HTTPserver =~ /^\s*[\.\~]\s*$/)
6206 print "<a href='$RootDirectory/$Start$file'>";
6208 elsif($HTTPserver)
6210 print "<a href='$HTTPserver/$RootDirectory/$Start$file'>";
6212 printf("%-40s", "$file</a>") if $HTTPserver;
6213 printf("%-40s", "$file") unless $HTTPserver;
6214 print "\t$Size\t$Date\t$Type";
6215 print "</li>\n";
6218 print "</ul></pre>";
6220 return 1;
6224 # ListDocs(Pattern [,ListType])
6226 # usage:
6227 # <SCRIPT TYPE=text/ssperl>
6228 # CGIscriptor::ListDocs("/*", "dl");
6229 # </SCRIPT>
6231 # This subroutine is very usefull to manage collections of independent
6232 # documents. The resulting list will display the tree-like directory
6233 # structure. If this routine is too slow for online use, you can
6234 # store the result and use a link to that stored file.
6236 # List HTML and Text files with title and first header (HTML)
6237 # or filename and first meaningfull line (general text files).
6238 # The listing starts at the ServerRoot directory. Directories are
6239 # listed recursively.
6241 # You can change the list type (default is dl).
6242 # e.g.,
6243 # <dt><a href=<file.html>>title</a>
6244 # <dd>First Header
6245 # <dt><a href=<file.txt>>file.txt</a>
6246 # <dd>First meaningfull line of text
6248 sub ListDocs # ($Pattern [, prefix]) e.g., ("/Books/*", [, "dl"])
6250 my $Pattern = shift;
6251 $Pattern =~ /\*/;
6252 my $ListType = shift || "dl";
6253 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
6254 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
6255 my @FileList = glob("$::CGI_HOME$Pattern");
6256 my ($FileName, $Path, $Link);
6258 # Print List markers
6259 print "<$ListType>\n";
6261 # Glob all files
6262 File: foreach $FileName (@FileList)
6264 # Check whether this file should be visible
6265 next if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@;
6267 # Recursively list files in all directories
6268 if(-d $FileName)
6270 $FileName =~ m@([^/]*)$@;
6271 my $DirName = $1;
6272 print "<$Prefix>$DirName\n";
6273 $Pattern =~ m@([^/]*)$@;
6274 &ListDocs("$`$DirName/$1", $ListType);
6275 next;
6277 # Use textfiles
6278 elsif(-T "$FileName")
6280 open(TextFile, $FileName) || next;
6282 # Ignore all other file types
6283 else
6284 { next;};
6286 # Get file path for link
6287 $FileName =~ /$::CGI_HOME/;
6288 print "<$Prefix><a href=$URL_root$'>";
6289 # Initialize all variables
6290 my $Line = "";
6291 my $TitleFound = 0;
6292 my $Caption = "";
6293 my $Title = "";
6294 # Read file and step through
6295 while(<TextFile>)
6297 chop $_;
6298 $Line = $_;
6299 # HTML files
6300 if($FileName =~ /\.ht[a-zA-Z]*$/i)
6302 # Catch Title
6303 while(!$Title)
6305 if($Line =~ m@<title>([^<]*)</title>@i)
6307 $Title = $1;
6308 $Line = $';
6310 else
6312 $Line .= <TextFile> || goto Print;
6313 chop $Line;
6316 # Catch First Header
6317 while(!$Caption)
6319 if($Line =~ m@</h1>@i)
6321 $Caption = $`;
6322 $Line = $';
6323 $Caption =~ m@<h1>@i;
6324 $Caption = $';
6325 $Line = $`.$Caption.$Line;
6327 else
6329 $Line .= <TextFile> || goto Print;
6330 chop $Line;
6334 # Other text files
6335 else
6337 # Title equals file name
6338 $FileName =~ /([^\/]+)$/;
6339 $Title = $1;
6340 # Catch equals First Meaningfull line
6341 while(!$Caption)
6343 if($Line =~ /[A-Z]/ &&
6344 ($Line =~ /subject|title/i || $Line =~ /^[\w,\.\s\?\:]+$/)
6345 && $Line !~ /Newsgroup/ && $Line !~ /\:\s*$/)
6347 $Line =~ s/\<[^\>]+\>//g;
6348 $Caption = $Line;
6350 else
6352 $Line = <TextFile> || goto Print;
6356 Print: # Print title and subject
6357 print "$Title</a>\n";
6358 print "<dd>$Caption\n" if $ListType eq "dl";
6359 $TitleFound = 0;
6360 $Caption = "";
6361 close TextFile;
6362 next File;
6365 # Print Closing List Marker
6366 print "</$ListType>\n";
6367 ""; # Empty return value
6371 # HTMLdocTree(Pattern [,ListType])
6373 # usage:
6374 # <SCRIPT TYPE=text/ssperl>
6375 # CGIscriptor::HTMLdocTree("/Welcome.html", "dl");
6376 # </SCRIPT>
6378 # The following subroutine is very usefull for checking large document
6379 # trees. Starting from the root (s), it reads all files and prints out
6380 # a nested list of links to all attached files. Non-existing or misplaced
6381 # files are flagged. This is quite a file-i/o intensive routine
6382 # so you would not like it to be accessible to everyone. If you want to
6383 # use the result, save the whole resulting page to disk and use a link
6384 # to this file.
6386 # HTMLdocTree takes an HTML file or file pattern and constructs nested lists
6387 # with links to *local* files (i.e., only links to the local server are
6388 # followed). The list entries are the document titles.
6389 # If the list type is <dl>, the first <H1> header is used too.
6390 # For each file matching the pattern, a list is made recursively of all
6391 # HTML documents that are linked from it and are stored in the same directory
6392 # or a sub-directory. Warnings are given for missing files.
6393 # The listing starts for the ServerRoot directory.
6394 # You can change the default list type <dl> (<dl>, <ul>, <ol>).
6396 %LinkUsed = ();
6398 sub HTMLdocTree # ($Pattern [, listtype])
6399 # e.g., ("/Welcome.html", [, "ul"])
6401 my $Pattern = shift;
6402 my $ListType = shift || "dl";
6403 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
6404 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
6405 my ($Filename, $Path, $Link);
6406 my %LocalLinks = {};
6408 # Read files (glob them for expansion of wildcards)
6409 my @FileList = glob("$::CGI_HOME$Pattern");
6410 foreach $Path (@FileList)
6412 # Get URL_path
6413 $Path =~ /$::CGI_HOME/;
6414 my $URL_path = $';
6415 # Check whether this file should be visible
6416 next if $::BlockPathAccess && $URL_path =~ m@$::BlockPathAccess@;
6418 my $Title = $URL_path;
6419 my $Caption = "";
6420 # Current file should not be used again
6421 ++$LinkUsed{$URL_path};
6422 # Open HTML doc
6423 unless(open(TextFile, $Path))
6425 print "<$Prefix>$Title <blink>(not found)</blink><br>\n";
6426 next;
6428 while(<TextFile>)
6430 chop $_;
6431 $Line = $_;
6432 # Catch Title
6433 while($Line =~ m@<title>@i)
6435 if($Line =~ m@<title>([^<]*)</title>@i)
6437 $Title = $1;
6438 $Line = $';
6440 else
6442 $Line .= <TextFile>;
6443 chop $Line;
6446 # Catch First Header
6447 while(!$Caption && $Line =~ m@<h1>@i)
6449 if($Line =~ m@</h[1-9]>@i)
6451 $Caption = $`;
6452 $Line = $';
6453 $Caption =~ m@<h1>@i;
6454 $Caption = $';
6455 $Line = $`.$Caption.$Line;
6457 else
6459 $Line .= <TextFile>;
6460 chop $Line;
6463 # Catch and print Links
6464 while($Line =~ m@<a href\=([^>]*)>@i)
6466 $Link = $1;
6467 $Line = $';
6468 # Remove quotes
6469 $Link =~ s/\"//g;
6470 # Remove extras
6471 $Link =~ s/[\#\?].*$//g;
6472 # Remove Servername
6473 if($Link =~ m@(http://|^)@i)
6475 $Link = $';
6476 # Only build tree for current server
6477 next unless $Link =~ m@$::ENV{'SERVER_NAME'}|^/@;
6478 # Remove server name and port
6479 $Link =~ s@^[^\/]*@@g;
6481 # Store the current link
6482 next if $LinkUsed{$Link} || $Link eq $URL_path;
6483 ++$LinkUsed{$Link};
6484 ++$LocalLinks{$Link};
6488 close TextFile;
6489 print "<$Prefix>";
6490 print "<a href=http://";
6491 print "$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}$URL_path>";
6492 print "$Title</a>\n";
6493 print "<br>$Caption\n"
6494 if $Caption && $Caption ne $Title && $ListType =~ /dl/i;
6495 print "<$ListType>\n";
6496 foreach $Link (keys(%LocalLinks))
6498 &HTMLdocTree($Link, $ListType);
6500 print "</$ListType>\n";
6504 ###########################<<<<<<<<<<End Remove
6506 # Make require happy
6509 =head1 NAME
6511 CGIscriptor -
6513 =head1 DESCRIPTION
6515 A flexible HTML 4 compliant script/module for CGI-aware
6516 embeded Perl, shell-scripts, and other scripting languages,
6517 executed at the server side.
6519 =head1 README
6521 Executes embeded Perl code in HTML pages with easy
6522 access to CGI variables. Also processes embeded shell
6523 scripts and scripts in any other language with an
6524 interactive interpreter (e.g., in-line Python, Tcl,
6525 Ruby, Awk, Lisp, Xlispstat, Prolog, M4, R, REBOL, Praat,
6526 sh, bash, csh, ksh).
6528 CGIscriptor is very flexible and hides all the specifics
6529 and idiosyncrasies of correct output and CGI coding and naming.
6530 CGIscriptor complies with the W3C HTML 4.0 recommendations.
6532 This Perl program will run on any WWW server that runs
6533 Perl scripts, just add a line like the following to your
6534 srm.conf file (Apache example):
6536 ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
6538 URL's that refer to http://www.your.address/SHTML/... will
6539 now be handled by CGIscriptor.pl, which can use a private
6540 directory tree (default is the DOCUMENT_ROOT directory tree,
6541 but it can be anywhere).
6543 =head1 PREREQUISITES
6546 =head1 COREQUISITES
6549 =pod OSNAMES
6551 Linux, *BSD, *nix, MS WinXP
6553 =pod SCRIPT CATEGORIES
6555 Servers
6559 =cut