NAME

CGI::SecureState -- Transparent, secure statefulness for CGI programs

SYNOPSIS

use CGI::SecureState;

my ($optional_state_dir,$optional_rand)=("states","gf8w7reh7");
my @memory = qw(param1 param2 other_params_to_remember);
my $cgi = new CGI::SecureState(-stateDir => $optional_state_dir,
                               -mindSet => 'forgetful',
                               -memory => \@memory,
                               -key => $optional_rand);
print $cgi->header(); 
my $url = $cgi->state_url(); 
my $param = $cgi->state_param();
print "<a href=\"$url\">I am a stateful CGI session.</a>";
print "<a href=\"other_url.pl?$param\">I am a different ",
      "script that also has access to this session.</a>";

Very Important Note for Current Users

For current users who would otherwise skip reading the rest of the file, CGI::SecureState has changed enormously between the 0.2x series and version 0.30. The most visible change will appear if you try to run your old scripts unchanged under CGI::SecureState. If you do so, you will receive nasty warnings (for most people this means both in the output web page and your log files) that will alert you to the fact that you need to specify a mindset for your scripts. If you installed this module from the CPAN, you should also have received a notice to read this documentation. Please do so, as this mysterious mindset business (as well as all the scrumptious new features will shortly be explained).

Of course, any and all comments on the changes above are welcome. If you are interested, send mail to behroozi@cpan.org with the subject "CGI::SecureState Comment".

DESCRIPTION

A Better Solution to the stateless problem

HTTP is a stateless protocol; a HTTP server closes the connection after serving an object. It retains no memory of the request details and doesn't relate subsequent requests with what it has already served.

There are a few methods available to deal with this problem, such as cookies and fields in forms, but they have many limitations and may not work on older browsers.

CGI::SecureState solves this problem by introducing persistent CGI sessions that store their data on the server side in an encrypted state file. CGI::SecureState is similar in purpose to CGI::Persistent (and retains much of the same user interface) but has a completely different implementation. For those of you who have worked with CGI::Persistent before, you will be pleased to learn that CGI::SecureState was designed to work with Perl's taint mode and has worked flawlessly with mod_perl and Apache::Registry for more than a year. CGI::SecureState was designed from the ground up for security, a fact which may rear its ugly head if anybody tries to do something tricksy.

MINDSETS

If you were curious about the mindset business mentioned earlier, this section is for you. In the past, CGI::SecureState had only one mind-set, which was to remember everything and anything that the client web page sent to it. Besides causing severe bloat of the session file, this behavior led to all sorts of insidious bugs where parameters saved by one web page would continue to lurk in the state file and cause problems in web pages down the line.

As a result, it was necessary to include the idea of different mindsets in CGI::SecureState 0.30. The old mindset remains, slightly modified, in the form of the "unforgetful" mindset. In this mindset, CGI::SecureState will save (and recall) all the parameters passed to the script excepting those that are in its "memory". The new mindset available is the "forgetful" mindset which will save (and recall) everything in "memory" and nothing else.

You may wonder why "memory" is in quotes. The answer is simple: you pass the "memory" to the CGI::SecureState object when it is initialized. So, to have a script that remembers everything except the parameters "foo" and "bar", do

my $cgi = new CGI::SecureState(-mindSet => 'unforgetful',
                               -memory => [qw(foo bar)]);

but to have a script that forgets everything except the parameters "user" and "pass", you would do instead

my $cgi = new CGI::SecureState(-mindSet => 'forgetful',
                               -memory => [qw(user pass)]);

Simple, really. In accord with the mindset of Perl, which is that methods should Do the Right Thing, the "forgetful" mindset will remember parameters when you tell it to, and not forget them until you decide that it should be so. This means that if you have a script to handle logins, like

my $cgi = new CGI::SecureState(-mindSet => 'forgetful',
                               -memory => [qw(user pass)]);

then other scripts do not have to re-memorize the "user" and "pass" parameters; a mere

my $cgi = new CGI::SecureState(-mindSet => 'forgetful');
my ($user,$pass) = ($cgi->param('user'),$cgi->param('pass'));

would suffice. However, if you read the rest of the documentation, that last line could even have been

my ($user,$pass) = $cgi->params('user','pass');

Once you all see how more intuitive this new mindset is, I am sure that you will make the switch, but, in the meantime, the 'unforgetful' mindset remains.

One more note about mindsets. In order to retain compatibility with older scripts, the "unforgetful" mindset will allow CGI parameters received from a client to overwrite previously saved parameters on disk. The new "forgetful" mindset discards parameters from clients if they already exist on disk. A future version of CGI::SecureState may make this behavior separate from the mindset.

METHODS

CGI::SecureState inherits its methods from CGI.pm, overriding them as necessary.

new()

Creates a new CGI object and creates an associated state file and key if none already exist. new() has exactly one required argument (the mindset, of course!), and takes three optional arguments. If the mindset is not specified, then CGI::SecureState will spit out nasty warnings until you change your scripts or set $CGI::SecureState::NASTY_WARNINGS to 0.

The mindset may be specified in a few different ways, the most common being to spell out 'forgetful' or 'unforgetful'. If it pleases you, you may also use '1' to specify forgetfulness, and '0' to specify unforgetfulness.

The optional arguments include the "memory" of the object (as an array reference), the directory in which state files should be stored (otherwise they will get dumped in whatever the current directory is!), random data for key generation (only necessary if you are concerned about the randomness generated by the module itself), and a subroutine reference to override the built-in error printing subroutine.

A quick note about that last one. Many people have complained about the menacing nature of the warnings and errors produced by CGI::SecureState and have wanted a quick and easy way to print out their own. Now they have it. The subroutine should print out a complete web page and include the "Content-Type" header. The possible errors that can be caught by the subroutine are:

failed to close the state file
failed to delete the state file
failed to lock the state file
failed to open the state file
failed to unlock the state file
invalid state file
statefile inconsistent with mindset
symlink encountered

If the subroutine can handle the error, it should return a true value, otherwise it should return false.

Examples:

    #forget everything but the "user" and "pass" params.
    $cgi = new CGI::SecureState(-mindSet => 'forgetful',
                                -memory => [qw(user pass)]);


    #invoke the old behavior of CGI::SecureState
    $cgi = new CGI::SecureState(-mindSet => 'unforgetful');
    $cgi = new CGI::SecureState(-mindSet => 0); #same thing

    #full listing
    $cgi = new CGI::SecureState(-stateDir => $statedir, 
				-mindSet => $mindset, 
				-memory => \@memory,
				-errorSub => \&errorSub,
				-key => $key);

    #if you don't like my capitalizations, then try
    $cgi = new CGI::SecureState(-statedir => $statedir, 
				-mindset => $mindset, 
				-memory => \@memory,
				-errorsub => \&errorSub,
				-key => $key);

    #if you prefer the straight argument style (note absence of
    #errorSub -- it is only supported with the new argument style)
    $cgi = new CGI::SecureState($statedir, $mindset, \@memory, $key);

    #cause nasty warnings
    $cgi = new CGI::SecureState;
state_url()

Returns the URL of the current script with the state identification string attached. This URL should be used for referring to the stateful session associated with the query.

state_param()

Returns a key-value pair that you can use to retain the session when linking to other scripts. If, for example, you want the script "other.pl" to be able to see your current script's session, you would use

print "<a href=\"other.pl?",$cgi->state_param,
       "\">Click Here!</a>";

to do so.

state_field()

Returns a hidden INPUT type for inclusion in HTML forms. Like state_url(), this element is used in forms to refer to the stateful session associated with the query.

params()

Allows you to get the scalar values of multiple parameters at once.

my ($user,$pass) = $cgi->params(qw(user pass));

is equivalent to

my ($user,$pass) = (scalar $cgi->params('user'),
                    scalar $cgi->params('pass'));
add()

This command adds a new parameter to the CGI object and stores it to disk. Use this command if you want something to be saved, since the param() method will only temporarily set a parameter. add() uses the same syntax as param(), but you may also add more than one parameter at once if the values are in a reference to an array:

$cgi->add(param_a => ['value'], param_b => ['value1', 'value2']);
remember()

This command is similar to add(), but saves current parameters to disk instead of new ones. For example, if "foo" and "bar" were passed in by the user and were not previously stored on disk,

$cgi->remember('foo','bar');

will save their values to the state file.

delete()

delete() is an overridden method that deletes named attributes from the query. The state file on disk is updated to reflect the removal of the parameter. Note that this has changed to accept a list of params to delete because otherwise the state file would be seperately rewritten for each delete().

Important note: Attributes that are NOT explicitly delete()ed will lurk about and come back to haunt you unless you use the 'forgetful' mindset!

delete_all()

This command toasts all the current cgi parameters, but it merely clears the state file instead of deleting it. For that, use delete_session() instead.

delete_session()

This command not only deletes all the cgi parameters, but kills the disk image of the session as well. This method should be used when you want to irrevocably destroy a session.

age()

This returns the time in days since the session was last accessed.

GLOBALS

You may set these options to globally affect the behavior of CGI::SecureState.

NASTY_WARNINGS

Set this to 0 if you want warnings about deprecated behavior to be suppressed. This is especially true if you want to be left in peace while updating scripts based on older versions of CGI::SecureState. However, the warnings issued should be heeded because they generally result in better coding style and program security.

You may either do use CGI::SecureState qw(:no_nasty_warnings); #or $CGI::SecureState::NASTY_WARNINGS = 0;

Set this to 0 if you don't want CGI::SecureState to test for the presence of a symlink before writing to a state file. If this is set to 1 and CGI::SecureState sees a symlink in place of a real file, it will spit out a fatal error. It is generally a good idea to keep this in place, but if you have a good reason to, then do use CGI::SecureState qw(:dont_avoid_symlinks); #or $CGI::SecureState::AVOID_SYMLINKS = 1;

USE_FLOCK

Set this to 0 if you do not want CGI::SecureState to use "flock" to assure that only one instance of CGI::SecureState is accessing the state file at a time. Leave this at 1 unless you really have a good reason not to.

For users running a version of Windows NT (including 2000 and XP), you should set this variable to 1 because $^O will always report "MSWin32", regardless of whether your system is Win9x (which does not support flock) or WinNT (which does).

To set to 0, do use CGI::SecureState qw(:no_flock); #or $CGI::SecureState::USE_FLOCK = 0;

To set to 1, do use CGI::SecureState qw(:use_flock); #or $CGI::SecureState::USE_FLOCK = 1;

Extra and Paranoid Security

If the standard security is not enough, CGI::SecureState provides extra security by setting the appropriate options in CGI.pm. The ":extra_security" option enables private file uploads and sets the maximum size for a CGI POST to be 10 kilobytes. The ":paranoid_security" option disables file uploads entirely. To use them, do use CGI::SecureState qw(:extra_security); #or use CGI::SecureState qw(:paranoid_security);

To disable them, do use CGI::SecureState qw(:no_security); =back

EXAMPLES

This example is a simple log-in script. It should have a directory called "states" that it can write to.

  #!/usr/bin/perl -wT
  use CGI::SecureState qw(:paranoid_security);

  my $cgi = new CGI::SecureState(-stateDir => 'states', 
                                 -mindSet => 'forgetful');

  my ($user,$pass,$lo)=$cgi->params(qw(user pass logout));
  my $failtime = $cgi->param('failtime') || 0;

  print $cgi->header();
  $cgi->start_html(-title => "CGI::SecureState Example");

  if ($user ne 'Cottleston' || $pass ne 'Pie') {
    if (defined $user) {
      $failtime+=$cgi->age()*86400;
      print "Incorrect Username/Password. It took you only ",
	     $cgi->age*86400, " seconds to fail this time.";
      print " It has been $failtime seconds since you started.";
      $cgi->add(failtime => $failtime);
    }
    print $cgi->start_form(-action => $cgi->url());
    print $cgi->state_field();
    print "\n<b>Username: </b>", $cgi->textfield("user");
    print "\n<br><b>Password: </b>", $cgi->password_field("pass");
    print "<br>",$cgi->submit("Login"),$cgi->reset;
    print $cgi->end_form;
  } elsif (! defined $lo) {
    print "You logged in!\n<br>";
    print "Click <a href=\"",$cgi->url,"?",$cgi->state_param;
    print ";logout=true\">here</a> to logout.";
    $cgi->remember('user','pass');
  } else {
    print "You have logged out.";
    $cgi->delete_session;
  }
  print $cgi->end_html;

This example will show a form that will tell you what what previously entered. It should have a directory called "states" that it can write to.

  #!/usr/bin/perl -wT
  use CGI::SecureState qw(:paranoid_security);

  my $cgi = new CGI::SecureState(-stateDir => 'states', 
                                -mindSet => 'unforgetful');
  print $cgi->header(); 
  $cgi->start_html(-title => "CGI::SecureState test", 
		 -bgcolor => "white");
  print $cgi->start_form(-action => $cgi->url());
  print $cgi->state_field();
  print "\n<b>Enter some text: </b>";
  print $cgi->textfield("input","");
  print "<br>",$cgi->submit,$cgi->reset;
  print $cgi->end_form;
  print "\n<br><br><br>";

  unless (defined $cgi->param('num_inputs')) {
      $cgi->add('num_inputs' => '1');
  }
  else {
      $cgi->add('num_inputs' => ($cgi->param('num_inputs')+1));
  }
  $cgi->add('input'.$cgi->param('num_inputs') => 
  	  $cgi->param('input')); 
  $cgi->delete('input');

  foreach ($cgi->param()) {
      print "\n<br>$_ -> ",$cgi->param($_) if (/input/);
  }
  print $cgi->end_html;

BUGS

There are no known bugs with the current version. However, take note of the limitations section.

If you do find a bug, you should send it immediately to behroozi@www.pls.uni.edu with the subject "CGI::SecureState Bug". I am not responsible for problems in your code, so make sure that an example actually works before sending it. It is merely acceptable if you send me a bug report, it is better if you send a small chunk of code that points it out, and it is best if you send a patch--if the patch is good, you might see a release the next day on CPAN. Otherwise, it could take weeks . . .

LIMITATIONS

Crypt::Blowfish is the only cipher that CGI::SecureState is using at the moment. Change at your own risk.

CGI.pm has its own funky way of doing state persistence that CGI::SecureState does NOT override. This includes setting default values for form input fields. If this becomes problematic, use the -override setting when calling things like hidden().

Many of the previous limitations of CGI::SecureState have been removed in the 0.30 version.

CGI::SecureState requires:

Long file names (at least 27 chars): needed to ensure remote ticket authenticity.

Crypt::Blowfish: it couldn't be called "Secure" without. At some point in the future (as better algorithms become available), this requirement may be changed. Tested with versions 2.06, 2.09.

Digest::SHA1: for super-strong (160 bit) hashing of data. It is used in key generation and filename generation. Tested with versions 1.03, 2.01.

CGI.pm: it couldn't be called "CGI" without. Should not be a problem as it comes standard with Perl 5.004 and above. Tested with versions 2.56, 2.74, 2.79.

Fcntl: for file flags that are portable (like LOCK_SH and SEEK_SET). Comes with Perl. Tested with version 1.03.

File::Spec: for concatenating directories and filenames in a portable way. Comes with Perl. Tested with version 0.82.

Perl: Hmmm. Tested with v5.6.[01]. This module has NOT been tested with 5.005 or below. Use at your own risk. There may be several bugs induced by lower versions of Perl, which are not limited to the failure to compile, the failure to behave properly, or the mysterious absence of your favorite pair of lemming slippers. The author is exempt from wrongdoing and liability in case you decide to use CGI::SecureState with a Perl less than 5.6.0.

SEE ALSO

CGI(3), CGI::Persistent(3)

AUTHORS

Peter Behroozi, behroozi@www.pls.uni.edu

1 POD Error

The following errors were encountered while parsing the POD:

Around line 827:

You forgot a '=back' before '=head1'