Created
March 7, 2012 20:13
-
-
Save rob-mcgrail/1995721 to your computer and use it in GitHub Desktop.
Modified SympaSessions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# SympaSession.pm - This module includes functions managing HTTP sessions in Sympa | |
# | |
# Sympa - SYsteme de Multi-Postage Automatique | |
# Copyright (c) 1997, 1998, 1999, 2000, 2001 Comite Reseau des Universites | |
# Copyright (c) 1997,1998, 1999 Institut Pasteur & Christophe Wolfhugel | |
# | |
# This program is free software; you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation; either version 2 of the License, or | |
# (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with this program; if not, write to the Free Software | |
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
package SympaSession; | |
use strict ; | |
use Digest::MD5; | |
use POSIX; | |
use CGI::Cookie; | |
use Time::Local; | |
use Log; | |
use Conf; | |
# this structure is used to define which session attributes are stored in a dedicated database col where others are compiled in col 'data_session' | |
my %session_hard_attributes = ('id_session' => 1, | |
'date' => 1, | |
'remote_addr' => 1, | |
'robot' => 1, | |
'email' => 1, | |
'start_date' => 1, | |
'hit' => 1, | |
'new_session' => 1, | |
); | |
sub new { | |
my $pkg = shift; | |
my $robot = shift; | |
my $context = shift; | |
my $cookie = $context->{'cookie'}; | |
my $action = $context->{'action'}; | |
my $rss = $context->{'rss'}; | |
do_log('debug', 'SympaSession::new(%s,%s,%s)', $robot,$cookie,$action); | |
my $session={}; | |
bless $session, $pkg; | |
unless ($robot) { | |
&do_log('err', 'Missing robot parameter, cannot create session object') ; | |
return undef; | |
} | |
# passive_session are session not stored in the database, they are used for crawler bots and action such as css, wsdl and rss | |
if (&tools::is_a_crawler($robot,{'user_agent_string' => $ENV{'HTTP_USER_AGENT'}})) { | |
$session->{'is_a_crawler'} = 1; | |
$session->{'passive_session'} = 1; | |
} | |
$session->{'passive_session'} = 1 if ($rss||$action eq 'wsdl'||$action eq 'css'); | |
# if a session cookie exist, try to restore an existing session, don't store sessions from bots | |
if (($cookie)&&($session->{'passive_session'} != 1)){ | |
my $status ; | |
$status = $session->load($cookie); | |
unless (defined $status) { | |
return undef; | |
} | |
if ($status eq 'not_found') { | |
do_log('info',"SympaSession::new ignoring unknown session cookie '$cookie'" ); # start a new session (may ne a fake cookie) | |
return (new SympaSession ($robot)); | |
} | |
# checking if the client host is unchanged during the session brake sessions when using multiple proxy with | |
# load balancing (round robin, etc). This check is removed until we introduce some other method | |
# if($session->{'remote_addr'} ne $ENV{'REMOTE_ADDR'}){ | |
# do_log('info','SympaSession::new ignoring session cookie because remote host %s is not the original host %s', $ENV{'REMOTE_ADDR'},$session->{'remote_addr'}); # start a new session | |
# return (new SympaSession ($robot)); | |
#} | |
}else{ | |
# create a new session context | |
$session->{'new_session'} = 1; ## Tag this session as new, ie no data in the DB exist | |
$session->{'id_session'} = &get_random(); | |
$session->{'email'} = 'nobody'; | |
$session->{'remote_addr'} = $ENV{'REMOTE_ADDR'}; | |
$session->{'date'} = time; | |
$session->{'start_date'} = time; | |
$session->{'hit'} = 1; | |
$session->{'robot'} = $robot; | |
$session->{'data'} = ''; | |
} | |
return $session; | |
} | |
sub load { | |
my $self = shift; | |
my $cookie = shift; | |
do_log('debug', 'SympaSession::load(%s)', $cookie); | |
unless ($cookie) { | |
do_log('err', 'SympaSession::load() : internal error, SympaSession::load called with undef id_session'); | |
return undef; | |
} | |
my $statement ; | |
$statement = "SELECT id_session AS id_session, date_session AS \"date\", remote_addr_session AS remote_addr, robot_session AS robot, email_session AS email, data_session AS data, hit_session AS hit, start_date_session AS start_date FROM session_table WHERE id_session = ?"; | |
my $dbh = &List::db_get_handler(); | |
my $sth; | |
## Check database connection | |
unless ($dbh and $dbh->ping) { | |
return undef unless &List::db_connect(); | |
} | |
unless ($sth = $dbh->prepare($statement)) { | |
do_log('err','Unable to prepare SQL statement : %s', $dbh->errstr); | |
return undef; | |
} | |
unless ($sth->execute($cookie)) { | |
do_log('err','Unable to execute SQL statement "%s" : %s', $statement, $dbh->errstr); | |
return undef; | |
} | |
my $session = $sth->fetchrow_hashref('NAME_lc'); | |
if ( $sth->fetchrow_hashref('NAME_lc')){ | |
do_log('err',"the SQL statement %s did return more then one session. Is this a bug comming from dbi or mysql ? "); | |
$session->{'email'} = ''; | |
} | |
$sth->finish(); | |
unless ($session) { | |
return 'not_found'; | |
} | |
my %datas= &tools::string_2_hash($session->{'data'}); | |
foreach my $key (keys %datas) {$self->{$key} = $datas{$key};} | |
$self->{'id_session'} = $session->{'id_session'}; | |
$self->{'date'} = $session->{'date'}; | |
$self->{'start_date'} = $session->{'start_date'}; | |
$self->{'hit'} = $session->{'hit'} +1 ; | |
$self->{'remote_addr'} = $session->{'remote_addr'}; | |
$self->{'robot'} = $session->{'robot'}; | |
$self->{'email'} = $session->{'email'}; | |
return ($self); | |
} | |
## This method will both store the session information in the database | |
sub store { | |
my $self = shift; | |
do_log('debug', ''); | |
return undef unless ($self->{'id_session'}); | |
return if ($self->{'is_a_crawler'}); # do not create a session in session table for crawlers; | |
return if ($self->{'passive_session'}); # do not create a session in session table for action such as RSS or CSS or wsdlthat do not require this sophistication; | |
my %hash ; | |
foreach my $var (keys %$self ) { | |
next if ($session_hard_attributes{$var}); | |
next unless ($var); | |
$hash{$var} = $self->{$var}; | |
} | |
my $data_string = &tools::hash_2_string (\%hash); | |
my $dbh = &List::db_get_handler(); | |
my $sth; | |
## Check database connection | |
unless ($dbh and $dbh->ping) { | |
return undef unless &List::db_connect(); | |
} | |
## If this is a new session, then perform an INSERT | |
if ($self->{'new_session'}) { | |
## Store the new session ID in the DB | |
my $add_statement = sprintf "INSERT INTO session_table (id_session, date_session, remote_addr_session, robot_session, email_session, start_date_session, hit_session, data_session) VALUES (%s,%d,%s,%s,%s,%d,%d,%s)",$dbh->quote($self->{'id_session'}),time,$dbh->quote($ENV{'REMOTE_ADDR'}),$dbh->quote($self->{'robot'}),$dbh->quote($self->{'email'}),$self->{'start_date'},$self->{'hit'}, $dbh->quote($data_string); | |
unless ($dbh->do($add_statement)) { | |
do_log('err','Unable to update session information in database while execute SQL statement "%s" : %s', $add_statement, $dbh->errstr); | |
return undef; | |
} | |
## If the session already exists in DB, then perform an UPDATE | |
}else { | |
## Update the new session in the DB | |
my $update_statement = sprintf "UPDATE session_table SET date_session=%d, remote_addr_session=%s, robot_session=%s, email_session=%s, start_date_session=%d, hit_session=%d, data_session=%s WHERE (id_session=%s)",time,$dbh->quote($ENV{'REMOTE_ADDR'}),$dbh->quote($self->{'robot'}),$dbh->quote($self->{'email'}),$self->{'start_date'},$self->{'hit'}, $dbh->quote($data_string), $dbh->quote($self->{'id_session'}); | |
unless ($dbh->do($update_statement)) { | |
do_log('err','Unable to update session information in database while execute SQL statement "%s" : %s', $update_statement, $dbh->errstr); | |
return undef; | |
} | |
} | |
return 1; | |
} | |
## This method will renew the session ID | |
sub renew { | |
my $self = shift; | |
do_log('debug', 'id_session=(%s)',$self->{'id_session'}); | |
return undef unless ($self->{'id_session'}); | |
return if ($self->{'is_a_crawler'}); # do not create a session in session table for crawlers; | |
return if ($self->{'passive_session'}); # do not create a session in session table for action such as RSS or CSS or wsdlthat do not require this sophistication; | |
my %hash ; | |
foreach my $var (keys %$self ) { | |
next if ($session_hard_attributes{$var}); | |
next unless ($var); | |
$hash{$var} = $self->{$var}; | |
} | |
my $data_string = &tools::hash_2_string (\%hash); | |
my $dbh = &List::db_get_handler(); | |
my $sth; | |
## Check database connection | |
unless ($dbh and $dbh->ping) { | |
return undef unless &List::db_connect(); | |
} | |
# Hack by robert.mcgrail@cwa.co.nz to prevent issue https://sourcesup.cru.fr/tracker/?func=detail&aid=6180 | |
# Prevents randomisation of session id for image requests. | |
# To remove this patch, delete the unless wrapper starting on the line below. The contents of the unless block are | |
# pre-existing sympa source. | |
unless ($ENV{'PATH_INFO'} =~ /\.jpg$|\.png$|\.gif$/) { | |
## Renew the session ID in order to prevent session hijacking | |
my $new_id = &get_random(); | |
## First remove the DB entry for the previous session ID | |
my $update_statement = sprintf "UPDATE session_table SET id_session=%s WHERE (id_session=%s)",$dbh->quote($new_id), $dbh->quote($self->{'id_session'}); | |
unless ($dbh->do($update_statement)) { | |
do_log('err','Unable to renew session ID for session %s',$self->{'id_session'}); | |
return undef; | |
} | |
## Renew the session ID in order to prevent session hijacking | |
$self->{'id_session'} = $new_id; | |
} | |
return 1; | |
} | |
## remove old sessions from a particular robot or from all robots. delay is a parameter in seconds | |
## | |
sub purge_old_sessions { | |
my $robot = shift; | |
do_log('info', 'SympaSession::purge_old_sessions(%s,%s)',$robot); | |
my $delay = &tools::duration_conv($Conf{'session_table_ttl'}) ; | |
my $anonymous_delay = &tools::duration_conv($Conf{'anonymous_session_table_ttl'}) ; | |
unless ($delay) { do_log('info', 'SympaSession::purge_old_session(%s) exit with delay null',$robot); return;} | |
unless ($anonymous_delay) { do_log('info', 'SympaSession::purge_old_session(%s) exit with anonymous delay null',$robot); return;} | |
my @sessions ; | |
my $sth; | |
my $dbh = &List::db_get_handler(); | |
my $robot_condition = sprintf "robot_session = %s", $dbh->quote($robot) unless (($robot eq '*')||($robot)); | |
my $delay_condition = time-$delay.' > date_session' if ($delay); | |
my $anonymous_delay_condition = time-$anonymous_delay.' > date_session' if ($anonymous_delay); | |
my $and = ' AND ' if (($delay_condition) && ($robot_condition)); | |
my $anonymous_and = ' AND ' if (($anonymous_delay_condition) && ($robot_condition)); | |
my $count_statement = sprintf "SELECT count(*) FROM session_table WHERE $robot_condition $and $delay_condition"; | |
my $anonymous_count_statement = sprintf "SELECT count(*) FROM session_table WHERE $robot_condition $anonymous_and $anonymous_delay_condition AND email_session = 'nobody' AND hit_session = '1'"; | |
my $statement = sprintf "DELETE FROM session_table WHERE $robot_condition $and $delay_condition"; | |
my $anonymous_statement = sprintf "DELETE FROM session_table WHERE $robot_condition $anonymous_and $anonymous_delay_condition AND email_session = 'nobody' AND hit_session = '1'"; | |
## Check database connection | |
unless ($dbh and $dbh->ping) { | |
return undef unless &List::db_connect(); | |
} | |
unless ($sth = $dbh->prepare($count_statement)) { | |
do_log('err','Unable to prepare SQL statement %s : %s',$count_statement, $dbh->errstr); | |
return undef; | |
} | |
unless ($sth->execute) { | |
do_log('err','Unable to execute SQL statement "%s" : %s', $statement, $dbh->errstr); | |
return undef; | |
} | |
my $total = $sth->fetchrow; | |
if ($total == 0) { | |
do_log('debug','SympaSession::purge_old_sessions no sessions to expire'); | |
}else{ | |
unless ($sth = $dbh->prepare($statement)) { | |
do_log('err','Unable to prepare SQL statement : %s', $dbh->errstr); | |
return undef; | |
} | |
unless ($sth->execute) { | |
do_log('err','Unable to execute SQL statement "%s" : %s', $statement, $dbh->errstr); | |
return undef; | |
} | |
} | |
unless ($sth = $dbh->prepare($anonymous_count_statement)) { | |
do_log('err','Unable to prepare SQL statement %s : %s',$anonymous_count_statement, $dbh->errstr); | |
return undef; | |
} | |
unless ($sth->execute) { | |
do_log('err','Unable to execute SQL statement "%s" : %s', $anonymous_statement, $dbh->errstr); | |
return undef; | |
} | |
my $anonymous_total = $sth->fetchrow; | |
if ($anonymous_total == 0) { | |
do_log('debug','SympaSession::purge_old_sessions no anonymous sessions to expire'); | |
return $total ; | |
} | |
unless ($sth = $dbh->prepare($anonymous_statement)) { | |
do_log('err','Unable to prepare SQL statement : %s', $dbh->errstr); | |
return undef; | |
} | |
unless ($sth->execute) { | |
do_log('err','Unable to execute SQL statement "%s" : %s', $anonymous_statement, $dbh->errstr); | |
return undef; | |
} | |
return $total+$anonymous_total; | |
} | |
## remove old one_time_ticket from a particular robot or from all robots. delay is a parameter in seconds | |
## | |
sub purge_old_tickets { | |
my $robot = shift; | |
do_log('info', 'SympaSession::purge_old_tickets(%s,%s)',$robot); | |
my $delay = &tools::duration_conv($Conf{'one_time_ticket_table_ttl'}) ; | |
unless ($delay) { do_log('info', 'SympaSession::purge_old_tickets(%s) exit with delay null',$robot); return;} | |
my @tickets ; | |
my $sth; | |
my $dbh = &List::db_get_handler(); | |
my $robot_condition = sprintf "robot_one_time_ticket = %s", $dbh->quote($robot) unless (($robot eq '*')||($robot)); | |
my $delay_condition = time-$delay.' > date_one_time_ticket' if ($delay); | |
my $and = ' AND ' if (($delay_condition) && ($robot_condition)); | |
my $count_statement = sprintf "SELECT count(*) FROM one_time_ticket_table WHERE $robot_condition $and $delay_condition"; | |
my $statement = sprintf "DELETE FROM one_time_ticket_table WHERE $robot_condition $and $delay_condition"; | |
## Check database connection | |
unless ($dbh and $dbh->ping) { | |
return undef unless &List::db_connect(); | |
} | |
unless ($sth = $dbh->prepare($count_statement)) { | |
do_log('err','Unable to prepare SQL statement %s : %s',$count_statement, $dbh->errstr); | |
return undef; | |
} | |
unless ($sth->execute) { | |
do_log('err','Unable to execute SQL statement "%s" : %s', $statement, $dbh->errstr); | |
return undef; | |
} | |
my $total = $sth->fetchrow; | |
if ($total == 0) { | |
do_log('debug','SympaSession::purge_old_tickets no tickets to expire'); | |
}else{ | |
unless ($sth = $dbh->prepare($statement)) { | |
do_log('err','Unable to prepare SQL statement : %s', $dbh->errstr); | |
return undef; | |
} | |
unless ($sth->execute) { | |
do_log('err','Unable to execute SQL statement "%s" : %s', $statement, $dbh->errstr); | |
return undef; | |
} | |
} | |
return $total; | |
} | |
# list sessions for $robot where last access is newer then $delay. List is limited to connected users if $connected_only | |
sub list_sessions { | |
my $delay = shift; | |
my $robot = shift; | |
my $connected_only = shift; | |
do_log('debug', 'SympaSession::list_session(%s,%s,%s)',$delay,$robot,$connected_only); | |
my @sessions ; | |
my $sth; | |
my $dbh = &List::db_get_handler(); | |
my $condition = sprintf "robot_session = %s", $dbh->quote($robot) unless ($robot eq '*'); | |
my $condition2 = time-$delay.' < date_session ' if ($delay); | |
my $and = ' AND ' if (($condition) && ($condition2)); | |
$condition = $condition.$and.$condition2 ; | |
my $condition3 = " email_session != 'nobody' " if ($connected_only eq 'on'); | |
my $and2 = ' AND ' if (($condition) && ($condition3)); | |
$condition = $condition.$and2.$condition3 ; | |
my $statement = sprintf "SELECT remote_addr_session, email_session, robot_session, date_session, start_date_session, hit_session FROM session_table WHERE $condition"; | |
do_log('debug', 'SympaSession::list_session() : statement = %s',$statement); | |
## Check database connection | |
unless ($dbh and $dbh->ping) { | |
return undef unless &List::db_connect(); | |
} | |
unless ($sth = $dbh->prepare($statement)) { | |
do_log('err','Unable to prepare SQL statement : %s', $dbh->errstr); | |
return undef; | |
} | |
unless ($sth->execute) { | |
do_log('err','Unable to execute SQL statement "%s" : %s', $statement, $dbh->errstr); | |
return undef; | |
} | |
while (my $session = ($sth->fetchrow_hashref('NAME_lc'))) { | |
$session->{'formated_date'} = &Language::gettext_strftime ("%d %b %y %H:%M", localtime($session->{'date_session'})); | |
$session->{'formated_start_date'} = &Language::gettext_strftime ("%d %b %y %H:%M", localtime($session->{'start_date_session'})); | |
# do_log('debug', 'SympaSession::list_session() DUMP : %s,%s,%s,%s',$session->{'remote_addr_session'}, $session->{'email_session'}, $session->{'robot_session'}, $session->{'formated_date'}); | |
push @sessions, $session; | |
} | |
$sth->finish(); | |
return \@sessions; | |
} | |
############################### | |
# Subroutines to read cookies # | |
############################### | |
## Generic subroutine to get a cookie value | |
sub get_session_cookie { | |
my $http_cookie = shift; | |
if ($http_cookie =~/\S+/g) { | |
my %cookies = parse CGI::Cookie($http_cookie); | |
foreach (keys %cookies) { | |
my $cookie = $cookies{$_}; | |
next unless ($cookie->name eq 'sympa_session'); | |
return ($cookie->value); | |
} | |
} | |
return (undef); | |
} | |
## Generic subroutine to set a cookie | |
## Set user $email cookie, ckecksum use $secret, expire=(now|session|#sec) domain=(localhost|<a domain>) | |
sub set_cookie { | |
my ($self, $http_domain, $expires,$use_ssl) = @_ ; | |
do_log('debug','Session::set_cookie(%s,%s,secure= %s)',$http_domain, $expires,$use_ssl ); | |
my $expiration; | |
if ($expires =~ /now/i) { | |
## 10 years ago | |
$expiration = '-10y'; | |
}else{ | |
$expiration = '+'.$expires.'m'; | |
} | |
if ($http_domain eq 'localhost') { | |
$http_domain=""; | |
} | |
my $cookie; | |
if ($expires =~ /session/i) { | |
$cookie = new CGI::Cookie (-name => 'sympa_session', | |
-value => $self->{'id_session'}, | |
-domain => $http_domain, | |
-path => '/', | |
-secure => $use_ssl, | |
-httponly => 1 | |
); | |
}else { | |
$cookie = new CGI::Cookie (-name => 'sympa_session', | |
-value => $self->{'id_session'}, | |
-expires => $expiration, | |
-domain => $http_domain, | |
-path => '/', | |
-secure => $use_ssl, | |
-httponly => 1 | |
); | |
} | |
#rob | |
## Send cookie to the client | |
printf "Set-Cookie: %s\n", $cookie->as_string; | |
return 1; | |
} | |
sub get_random { | |
do_log('debug', 'SympaSession::random '); | |
my $random = int(rand(10**7)).int(rand(10**7)); ## Concatenates 2 integers for a better entropy | |
$random =~ s/^0(\.|\,)//; | |
return ($random) | |
} | |
## Return the session object content, as a hashref | |
sub as_hashref { | |
my $self = shift; | |
my $data; | |
foreach my $key (keys %{$self}) { | |
$data->{$key} = $self->{$key}; | |
} | |
return $data; | |
} | |
1; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment