#!/usr/bin/perl # # $Id: login.pl,v 1.9 2008/05/03 06:43:24 pauldoom Exp $ # Decide which mod_perl to load BEGIN { use vars qw($MP); if (eval{require mod_perl2;}) { $MP = 2; } else { require mod_perl; $MP = 1; } } use strict; use warnings; # Use the session key maker and session ID computer (which is just a # HMAC) for CSRF protection in the login form use Apache::AppSamurai::Util qw(CreateSessionAuthKey ComputeSessionId CheckSidFormat HashPass); # Point to HTML login page my $formsource = "login.html"; # Mod_Perl 2 does not chdir to the script's folder, so you must use # a full path. The list below includes common base paths. Remove # the other array items and enter your local path if none of these # match you setup. my @formpaths = ( "/var/www/htdocs/AppSamurai", "/var/www/html/AppSamurai", "/htdocs/AppSamurai", "/html/AppSamurai" ); # This is lame. Just cycles the paths looking for the form source # template ($formsource) my $ffound = 0; foreach (@formpaths) { if (-f "$_/$formsource") { $formsource = "$_/$formsource"; $ffound = 1; last; } } ($ffound) or die "FATAL: Could not find form source template file $formsource\n"; # These will replace any __NAME__ values in the form my %params = ( MESSAGE => '', REASON => '', URI => '', FORMACTION => '/AppSamurai/LOGIN', USERNAME => '' ); my $r = shift; ($r) or die "FATAL: NO REQUEST SENT TO SCRIPT!\n"; # if there are args, append that to the uri after checking for and removing # any ASERRCODE code. $params{URI} = $r->prev->uri || ''; my $args = $r->prev->args || ''; if (($args) && ($args =~ s/&?ASERRCODE\=(bad_credentials|no_cookie|bad_cookie|expired_cookie)//)) { $params{REASON} = $1; } if ($args) { $params{URI} .= '?' . $args; } # These messages have HTML in them with CSS. (Update as needed, or add a # JavaScript snippet to check a hidden value and display the corresponding # message, then just set a variable.) # Default message $params{MESSAGE} = "Please log in"; if ($params{REASON} eq 'bad_credentials') { # Login failure $params{MESSAGE} = "Access Denied - The credentials supplied were invalid. Please try again."; } elsif ($params{REASON} eq 'expired_cookie') { # Expired session $params{MESSAGE} = "Access Denied - Your session has expired. Please log in."; } # Build nonce and HMAC (using server key) fro CSRF protection. (Note - this # only protects the login form.... once logged in, the app must protect itself. # Yet another place where having bidirectional filtering would be useful) # Required for CSRF protection # Note - Pulling session config code out of the main module would allow this # to be much shorter/simpler. Strike 90834895345 against the giant module. # TODO - This should be in a module!!! my $auth_name = ($r->auth_name()) || (die("login.pl(): No auth name defined!\n")); my $dirconfig = $r->dir_config; my $serverkey = ''; if (exists($dirconfig->{$auth_name . "SessionServerPass"})) { my $serverpass = $dirconfig->{$auth_name . "SessionServerPass"}; ($serverpass =~ s/^\s*([[:print:]]{8,}?)\s*$/$1/s) || die('error', "login.pl(): Invalid ${auth_name}SessionServerPass (must be use at least 8 printable characters\n"); ($serverpass =~ /^(password|serverkey|serverpass|12345678)$/i) && die("login.pl: ${auth_name}SessionServerPass is $1... That is too lousy\n"); ($serverkey = HashPass($serverpass)) || die("login.pl: Problem computing server key hash for $auth_name"); } elsif (exists($dirconfig->{$auth_name . "SessionServerKey"})) { $serverkey = $dirconfig->{$auth_name . "SessionServerKey"}; } else { die("login.pl(): You must configure either ${auth_name}SessionServerPass or ${auth_name}SessionServerKey in your Apache configuration\n"); } # Check for valid key format (CheckSidFormat($serverkey)) || die("login.pl(): You must a valid ${auth_name}SessionServerPass or ${auth_name}SessionServerKey configured!"); # Get a nonce. Note - since this gets sent back, and it is the same as the alg # used to get the random session key, PRNG weakness could be an issue. $params{NONCE} = CreateSessionAuthKey(); # Get HMAC of nonce with server key (this is just like we use the session key # and server key to get the real session ID, though THIS time we are sending it # to the browser.) $params{SIG} = ComputeSessionId($params{NONCE}, $serverkey); # Read in form my $form = ''; open(F, "$formsource") or die "FATAL: Could not find/open login page content\n"; while () { $form .= $_; } close(F); # Apply parameters foreach (keys %params) { $form =~ s/__${_}__/$params{$_}/gs; } $r->no_cache(1); $r->content_type("text/html"); $r->headers_out->set("Content-length", length($form)); $r->headers_out->set("Pragma", "no-cache"); # Only for mod_perl 1 ($MP eq 1) and $r->send_http_header; $r->print ($form);