Session Cookie Glitch with mod_perl 2.03 and Apache 2.2.6

Discussion in 'Perl Misc' started by stratfan, Nov 15, 2007.

  1. stratfan

    stratfan Guest

    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;
    ---
    > use Apache2::Const qw(DECLINED OK REDIRECT FORBIDDEN);
    > use Apache2::Cookie;
    > use Apache2::RequestRec;
    > use Apache2::Connection;


    CHANGES TO COOKIE RETRIEVAL LOGIC

    < my %cookies = Apache::Cookie->fetch();
    ---
    > 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>
     
    stratfan, Nov 15, 2007
    #1
    1. Advertising

  2. stratfan

    stratfan Guest

    I've managed to solve the mystery so I am posting my findings to help
    save others some frustration. The core problem involved the code
    retrieving cookie data from the HTTP headers. Many of the examples
    for using mod_perl 2.x use the following syntax for retrieving cookies
    inside the module used for authentication:

    ---------------------------------------
    my $cookiejar = Apache2::Cookie::Jar->new($r); # where $r is the
    Apache2::RequestRec object
    my $appcookie = $cookiejar->cookies('MYAPPCOOKIENAME');
    ---------------------------------------

    Unfortunately, the Jar->new() call in the underlying libapreq module
    was abandoned in libapreq 2.05 and higher versions. Changes in that
    release now rely upon methods in the APR::Request module so the
    following steps are required:

    1) create a new APR::Request object from the incoming
    Apache2::RequestRec passed to the handler
    2) test if the APR::Request object successfully parsed the headers of
    the incoming HTTP request
    3) retrieve a APR::Request::Cookie::Table object via the jar() method
    4) retrieve your desired cookie via a get() call in the Table object

    The code looks like this:

    ---------------------------------------
    my $aprreq = APR::Request::Apache2->handle($r);
    if ($aprreq->jar_status()) {
    # if jar_status is non-zero, headers could not be parsed, nothing
    to do here
    # so just return OK and let the Apache engine handle the request
    return $Apache2::Const::OK;
    }
    my $cookiejar = $aprreq->jar;
    my $appcookie = '';
    $appcookie = $cookiejar->get('MYAPPCOOKIENAME');
    ---------------------------------------

    This code would go in the handler() method of the PERL module you
    reference in the PerlAuthenHandler configuration of the Apache server
    configuration.

    I still have no idea why the old code with the references to broken
    calls in libapreq worked on ANY browser clients but the code is now
    working for my intended client audience.


    stratfan
     
    stratfan, Nov 19, 2007
    #2
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Selmar
    Replies:
    2
    Views:
    181
    krakle
    Jul 21, 2004
  2. Pablo S
    Replies:
    1
    Views:
    302
    Tore Aursand
    Sep 1, 2004
  3. it_says_BALLS_on_your forehead
    Replies:
    0
    Views:
    244
    it_says_BALLS_on_your forehead
    Jan 13, 2006
  4. Replies:
    2
    Views:
    456
    Big and Blue
    Jan 25, 2006
  5. PerlGuy
    Replies:
    2
    Views:
    142
Loading...

Share This Page