S
stratfan
I have an application originally written for the combination of Apache
1.3.31, mod_perl 1.39, and MySQL 4.0.16 that I'm moving to new servers
so I wanted to take the opportunity to migrate these components to the
latest versions:
Perl v5.8.8 built for i586-linux-thread-multi
Apache 2.2.6
mod_perl 2.0.3
OpenSSL 0.9.8g
libapreq 2-2.08
MySQL 5.0.45
The APIs for mod_perl changed substantially between 1.0 and 2.0 so
I've gone through the code that involves the old 1.0 vintage
Apache::xxxxxx modules and updated them to 2.0 equivalents in the
Apache2::xxxxxx or ModPerl::xxxxxxx modules.
The revamped application seems to work properly from the following
client combinations:
* consumer Windows XP machine with IE 7.0
* consumer Windows XP machine with Firefox
However, my corporate laptop (typical corporate locked down image)
with Windows XP and IE 6.0.2900 cannot establish a session within the
application.
In my application, the only changes required by mod_perl 2.0 were all
localized to the modules that handled authentication and session
management which were contained in modules named MyApp::CookieAuth and
CookieLib. Some of the key "diffs" in the updated code are summarized
below:
CHANGES TO use STATEMENTS
< use Apache::Constants qw(DECLINED OK REDIRECT FORBIDDEN);
< use Apache::Cookie;
---
CHANGES TO COOKIE RETRIEVAL LOGIC
CHANGES TO COOKIE SETTING LOGIC
< $r->connection->auth_type('Basic');
< $r->connection->user($session_key{'username'});
< $r->header_out(
< "Set-Cookie" => &CookieLib::generate(\%session_key)
< );
---
The generate() method in the CookieLib library that actually sets the
session key data in responses appears as follows:
======================
sub generate {
my ($args) = @_;
my $query = new CGI;
# this call to Crypt::CBC with only two parameters requires
# the older 2.08 version instead of 2.18 -- the new version
# requires a salt value which raises other compatibility issues
my $cipher = new Crypt::CBC($KEYTEXT, $CIPHER);
my $plaintext = join(':',
$args->{'username'}, $ENV{'REMOTE_ADDR'},
($args->{'timestamp'} eq 0) ? 0 : time,
$LIFETIME, rand(), $args->{'password'},
$args->{'redirect'});
print STDERR "CookieLib generate - $plaintext\n";
my $ciphertext = encode_base64($cipher->encrypt($plaintext), "");
return $query->cookie(
-name => $COOKIE_NAME,
-domain => $COOKIE_DOMAIN,
-path => '/',
-value => $ciphertext
);
}
======================
The normal login flow is:
1) surf to the / page of the server root,
2) Apache sees no session in the request so it serves a template with
an empty login form
3) submit a POST with a valid login, pull the login & password from
the POST request and authenticate it
4) if successful, set a cookie in the HTTP response header and send a
template for the logged in main page which is a frame that loads /
top.cgi (for navigation links) and /bottom.html (a static HTML file)
5) when the browser parses the logged in main page, it performs the
GETs for top.cgi and bottom.html giving the user their navigation
commands and a welcome page.
For logins from the corporate laptop, steps 1-4 all happen based upon
print debug statements writing to STDERR. However, when Apache
processes the GET requests for top.cgi and bottom.html, the mod_perl
authentication handler isn't detecting the session data in the headers
(as if they weren't set by Apache when sending the reply in step #4)
so the logic in the authentication handler re-serves the default index
page (the login form) for each of those frame elements.
Logins from the other browsers work correctly and the print statements
for debugging show session data being retrieved from the subsequent
GET requests. That tells me I don't have PERL5LIB envrionment
problems finding the modules, etc.
I suspect the problem is due to some interaction between the Apache
layer <Directory>, <FilesMatch> and <Location> directives (see below)
and the authentication logic used in my application's module named
Apache::CookieAuth. However, I can't figure out why the problem is
browser dependent to figure out exactly where to fix the code.
Any ideas / suggestions would be greatly appreciated.
stratfan
=======================
#-----------------------------------------------------------------------
# These <Directory> directives set explicit rules for actual UNIX
# file paths referenced by Apache to serve incoming requests.
Requests
# are subjected to <Directory> rules, then <Files> and <FilesMatch>
# directives, then <Location> filters.
#-----------------------------------------------------------------------
<Directory "/myapp/htdocs">
AddHandler cgi-script pl
Options +Indexes +ExecCGI +FollowSymLinks +Includes +MultiViews
AllowOverride None
Order allow,deny
Allow from all
</Directory>
<Directory "/myapp/templates">
Order allow,deny
Allow from all
AllowOverride none
</Directory>
#
# DirectoryIndex: sets the file that Apache will serve if a directory
# is requested.
#
<IfModule dir_module>
DirectoryIndex index.html index.cgi index.pl
</IfModule>
#------------------------------------------------------------------------
# Define pattern match to steer references to CGI scripts to the the
# registry within mod_perl while serving static content the old
# fashioned way
#------------------------------------------------------------------------
<FilesMatch "\.cgi">
SetHandler perl-script
PerlSetVar Filter On
PerlResponseHandler ModPerl::Registry
PerlSendHeader off
Options +ExecCGI
</FilesMatch>
#------------------------------------------------------------------------
# Use <Location> to capture all incoming URI references and ensure
# they only get served if user has valid session -- session is managed
# by the Apache::CookieAuth module in the source tree located at
# /myapp/lib/Apache/CookieAuth.pm
#------------------------------------------------------------------------
<Location />
PerlAuthenHandler Apache::CookieAuth
Options +ExecCGI
AuthName "MYAPP"
AuthType Basic
Require valid-user
</Location>
1.3.31, mod_perl 1.39, and MySQL 4.0.16 that I'm moving to new servers
so I wanted to take the opportunity to migrate these components to the
latest versions:
Perl v5.8.8 built for i586-linux-thread-multi
Apache 2.2.6
mod_perl 2.0.3
OpenSSL 0.9.8g
libapreq 2-2.08
MySQL 5.0.45
The APIs for mod_perl changed substantially between 1.0 and 2.0 so
I've gone through the code that involves the old 1.0 vintage
Apache::xxxxxx modules and updated them to 2.0 equivalents in the
Apache2::xxxxxx or ModPerl::xxxxxxx modules.
The revamped application seems to work properly from the following
client combinations:
* consumer Windows XP machine with IE 7.0
* consumer Windows XP machine with Firefox
However, my corporate laptop (typical corporate locked down image)
with Windows XP and IE 6.0.2900 cannot establish a session within the
application.
In my application, the only changes required by mod_perl 2.0 were all
localized to the modules that handled authentication and session
management which were contained in modules named MyApp::CookieAuth and
CookieLib. Some of the key "diffs" in the updated code are summarized
below:
CHANGES TO use STATEMENTS
< use Apache::Constants qw(DECLINED OK REDIRECT FORBIDDEN);
< use Apache::Cookie;
---
use Apache2::Const qw(DECLINED OK REDIRECT FORBIDDEN);
use Apache2::Cookie;
use Apache2::RequestRec;
use Apache2::Connection;
CHANGES TO COOKIE RETRIEVAL LOGIC
my %cookies = Apache2::Cookie->fetch();
CHANGES TO COOKIE SETTING LOGIC
< $r->connection->auth_type('Basic');
< $r->connection->user($session_key{'username'});
< $r->header_out(
< "Set-Cookie" => &CookieLib::generate(\%session_key)
< );
---
$r->ap_auth_type('Basic');
$r->user($session_key{'username'});
$r->headers_out->add(
"Set-Cookie" => &CookieLib::generate(\%session_key)
);
The generate() method in the CookieLib library that actually sets the
session key data in responses appears as follows:
======================
sub generate {
my ($args) = @_;
my $query = new CGI;
# this call to Crypt::CBC with only two parameters requires
# the older 2.08 version instead of 2.18 -- the new version
# requires a salt value which raises other compatibility issues
my $cipher = new Crypt::CBC($KEYTEXT, $CIPHER);
my $plaintext = join(':',
$args->{'username'}, $ENV{'REMOTE_ADDR'},
($args->{'timestamp'} eq 0) ? 0 : time,
$LIFETIME, rand(), $args->{'password'},
$args->{'redirect'});
print STDERR "CookieLib generate - $plaintext\n";
my $ciphertext = encode_base64($cipher->encrypt($plaintext), "");
return $query->cookie(
-name => $COOKIE_NAME,
-domain => $COOKIE_DOMAIN,
-path => '/',
-value => $ciphertext
);
}
======================
The normal login flow is:
1) surf to the / page of the server root,
2) Apache sees no session in the request so it serves a template with
an empty login form
3) submit a POST with a valid login, pull the login & password from
the POST request and authenticate it
4) if successful, set a cookie in the HTTP response header and send a
template for the logged in main page which is a frame that loads /
top.cgi (for navigation links) and /bottom.html (a static HTML file)
5) when the browser parses the logged in main page, it performs the
GETs for top.cgi and bottom.html giving the user their navigation
commands and a welcome page.
For logins from the corporate laptop, steps 1-4 all happen based upon
print debug statements writing to STDERR. However, when Apache
processes the GET requests for top.cgi and bottom.html, the mod_perl
authentication handler isn't detecting the session data in the headers
(as if they weren't set by Apache when sending the reply in step #4)
so the logic in the authentication handler re-serves the default index
page (the login form) for each of those frame elements.
Logins from the other browsers work correctly and the print statements
for debugging show session data being retrieved from the subsequent
GET requests. That tells me I don't have PERL5LIB envrionment
problems finding the modules, etc.
I suspect the problem is due to some interaction between the Apache
layer <Directory>, <FilesMatch> and <Location> directives (see below)
and the authentication logic used in my application's module named
Apache::CookieAuth. However, I can't figure out why the problem is
browser dependent to figure out exactly where to fix the code.
Any ideas / suggestions would be greatly appreciated.
stratfan
=======================
#-----------------------------------------------------------------------
# These <Directory> directives set explicit rules for actual UNIX
# file paths referenced by Apache to serve incoming requests.
Requests
# are subjected to <Directory> rules, then <Files> and <FilesMatch>
# directives, then <Location> filters.
#-----------------------------------------------------------------------
<Directory "/myapp/htdocs">
AddHandler cgi-script pl
Options +Indexes +ExecCGI +FollowSymLinks +Includes +MultiViews
AllowOverride None
Order allow,deny
Allow from all
</Directory>
<Directory "/myapp/templates">
Order allow,deny
Allow from all
AllowOverride none
</Directory>
#
# DirectoryIndex: sets the file that Apache will serve if a directory
# is requested.
#
<IfModule dir_module>
DirectoryIndex index.html index.cgi index.pl
</IfModule>
#------------------------------------------------------------------------
# Define pattern match to steer references to CGI scripts to the the
# registry within mod_perl while serving static content the old
# fashioned way
#------------------------------------------------------------------------
<FilesMatch "\.cgi">
SetHandler perl-script
PerlSetVar Filter On
PerlResponseHandler ModPerl::Registry
PerlSendHeader off
Options +ExecCGI
</FilesMatch>
#------------------------------------------------------------------------
# Use <Location> to capture all incoming URI references and ensure
# they only get served if user has valid session -- session is managed
# by the Apache::CookieAuth module in the source tree located at
# /myapp/lib/Apache/CookieAuth.pm
#------------------------------------------------------------------------
<Location />
PerlAuthenHandler Apache::CookieAuth
Options +ExecCGI
AuthName "MYAPP"
AuthType Basic
Require valid-user
</Location>