# -- # Kernel/System/AuthSession/IPC.pm - provides session IPC/Mem backend # Copyright (C) 2001-2007 OTRS GmbH, http://otrs.org/ # -- # $Id: IPC.pm,v 1.25 2007/06/27 12:09:56 martin Exp $ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you # did not receive this file, see http://www.gnu.org/licenses/gpl.txt. # -- package Kernel::System::AuthSession::IPC; use strict; use warnings; use IPC::SysV qw(IPC_PRIVATE IPC_RMID S_IRWXU); use Digest::MD5; use MIME::Base64; use Kernel::System::Encode; use vars qw($VERSION); $VERSION = '$Revision: 1.25 $'; $VERSION =~ s/^\$.*:\W(.*)\W.+?$/$1/; sub new { my $Type = shift; my %Param = @_; # allocate new hash for object my $Self = {}; bless ($Self, $Type); # check needed objects foreach (qw(LogObject ConfigObject DBObject TimeObject)) { $Self->{$_} = $Param{$_} || die "No $_!"; } # Debug 0=off 1=on $Self->{Debug} = 0; # encode object $Self->{EncodeObject} = Kernel::System::Encode->new(%Param); # get more common params $Self->{SystemID} = $Self->{ConfigObject}->Get('SystemID'); # ipc stuff $Self->{IPCKeyMeta} = "444421$Self->{SystemID}"; $Self->{IPCSizeMeta} = 20; $Self->{IPCKey} = "444422$Self->{SystemID}"; $Self->{IPCAddBufferSize} = 10*1024; $Self->{IPCSize} = 80*1024; $Self->{IPCSizeMax} = (2048*1024) - $Self->{IPCAddBufferSize}; $Self->{CMD} = $Param{CMD} || 0; $Self->_InitSHM(); return $Self; } sub _InitSHM { my $Self = shift; # init meta data mem $Self->{KeyMeta} = shmget($Self->{IPCKeyMeta}, $Self->{IPCSizeMeta}, 0777 | 0001000) || die $!; # init session data mem $Self->{Key} = shmget($Self->{IPCKey}, $Self->_GetSHMDataSize(), 0777 | 0001000) || die $!; return 1; } sub _WriteSHM { my $Self = shift; my %Param = @_; # get size of data my $DataSize = (length($Param{Data})+1); my $AddBuffer = 4000; my $CurrentDataSize = $Self->_GetSHMDataSize(); # overwrite with new session data if ($Self->{CMD} || $DataSize < $CurrentDataSize || $DataSize > $Self->{IPCSizeMax}) { shmwrite($Self->{Key}, $Param{Data}, 0, $CurrentDataSize) || die $!; if ($DataSize > $Self->{IPCSizeMax}) { $Self->{LogObject}->Log( Priority => 'error', Message => "Can't write session data. Max. size ". "($Self->{IPCSizeMax} Bytes) of SessionData reached! Drop old sessions!", ); } } else { my $NewIPCSize = $DataSize + $Self->{IPCAddBufferSize}; if ($NewIPCSize > $Self->{IPCSizeMax}) { $NewIPCSize = $Self->{IPCSizeMax}; } # delete old shm shmctl($Self->{Key}, IPC_RMID, 0) || die "$!"; # init new mem $Self->{Key} = shmget($Self->{IPCKey}, $NewIPCSize, 0777 | 0001000) || die $!; # write session data to mem shmwrite($Self->{Key}, $Param{Data}, 0, $NewIPCSize) || die $!; # write new meta data $Self->_SetSHMDataSize($NewIPCSize); } } sub _ReadSHM { my $Self = shift; # read session data from mem my $String = ''; shmread($Self->{Key}, $String, 0, $Self->_GetSHMDataSize()) || die "$!"; my @Lines = split(/\n/, $String); $String = ''; foreach (@Lines) { if ($_ =~ /^SessionID/) { $String .= $_."\n"; } } return $String; } sub _SetSHMDataSize { my $Self = shift; my $Size = shift || return; # read meta data from mem shmwrite($Self->{KeyMeta}, $Size.";", 0, $Self->{IPCSizeMeta}) || die $!; return 1; } sub _GetSHMDataSize { my $Self = shift; # read meta data from mem my $MetaString = ''; shmread($Self->{KeyMeta}, $MetaString, 0, $Self->{IPCSizeMeta}) || die "$!"; my @Items = split(/;/, $MetaString); if ($MetaString !~ /;/) { $Items[0] = $Self->{IPCSize}; } return $Items[0]; } sub CheckSessionID { my $Self = shift; my %Param = @_; my $SessionID = $Param{SessionID}; my $RemoteAddr = $ENV{REMOTE_ADDR} || 'none'; # set default message $Self->{CheckSessionIDMessage} = "SessionID is invalid!!!"; # session id check my %Data = $Self->GetSessionIDData(SessionID => $SessionID); if (!$Data{UserID} || !$Data{UserLogin}) { $Self->{CheckSessionIDMessage} = "SessionID invalid! Need user data!"; $Self->{LogObject}->Log( Priority => 'notice', Message => "SessionID: '$SessionID' is invalid!!!", ); return; } # remote ip check if ( $Data{UserRemoteAddr} ne $RemoteAddr && $Self->{ConfigObject}->Get('SessionCheckRemoteIP') ) { $Self->{LogObject}->Log( Priority => 'notice', Message => "RemoteIP of '$SessionID' ($Data{UserRemoteAddr}) is different with the ". "request IP ($RemoteAddr). Don't grant access!!!", ); # delete session id if it isn't the same remote ip? if ($Self->{ConfigObject}->Get('SessionDeleteIfNotRemoteID')) { $Self->RemoveSessionID(SessionID => $SessionID); } return; } # check session idle time my $MaxSessionIdleTime = $Self->{ConfigObject}->Get('SessionMaxIdleTime'); if ( ($Self->{TimeObject}->SystemTime() - $MaxSessionIdleTime) >= $Data{UserLastRequest} ) { $Self->{CheckSessionIDMessage} = 'Session has timed out. Please log in again.'; $Self->{LogObject}->Log( Priority => 'notice', Message => "SessionID ($SessionID) idle timeout (". int(($Self->{TimeObject}->SystemTime() - $Data{UserLastRequest})/(60*60)) ."h)! Don't grant access!!!", ); # delete session id if too old? if ($Self->{ConfigObject}->Get('SessionDeleteIfTimeToOld')) { $Self->RemoveSessionID(SessionID => $SessionID); } return; } # check session time my $MaxSessionTime = $Self->{ConfigObject}->Get('SessionMaxTime'); if ( ($Self->{TimeObject}->SystemTime() - $MaxSessionTime) >= $Data{UserSessionStart} ) { $Self->{CheckSessionIDMessage} = 'Session has timed out. Please log in again.'; $Self->{LogObject}->Log( Priority => 'notice', Message => "SessionID ($SessionID) too old (". int(($Self->{TimeObject}->SystemTime() - $Data{UserSessionStart})/(60*60)) ."h)! Don't grant access!!!", ); # delete session id if too old? if ($Self->{ConfigObject}->Get('SessionDeleteIfTimeToOld')) { $Self->RemoveSessionID(SessionID => $SessionID); } return; } return 1; } sub CheckSessionIDMessage { my $Self = shift; my %Param = @_; return $Self->{CheckSessionIDMessage} || ''; } sub GetSessionIDData { my $Self = shift; my %Param = @_; my $SessionID = $Param{SessionID} || ''; my $SessionIDBase64 = encode_base64($SessionID, ''); my %Data; # check session id if (!$SessionID) { $Self->{LogObject}->Log(Priority => 'error', Message => "Got no SessionID!!"); return; } # read data my $String = $Self->_ReadSHM(); if (!$String) { return; } # split data my @Items = split(/\n/, $String); foreach my $Item (@Items) { my @PaarData = split(/;/, $Item); if ($PaarData[0]) { if ($Item =~ /^SessionID:$SessionIDBase64;/) { foreach (@PaarData) { my ($Key, $Value) = split(/:/, $_); $Data{$Key} = decode_base64($Value); $Self->{EncodeObject}->Encode(\$Data{$Key}); } # Debug if ($Self->{Debug}) { $Self->{LogObject}->Log( Priority => 'debug', Message => "GetSessionIDData: '$PaarData[1]:".decode_base64($PaarData[2])."'", ); } } } } return %Data; } sub CreateSessionID { my $Self = shift; my %Param = @_; # get REMOTE_ADDR my $RemoteAddr = $ENV{REMOTE_ADDR} || 'none'; # get HTTP_USER_AGENT my $RemoteUserAgent = $ENV{HTTP_USER_AGENT} || 'none'; # create SessionID my $md5 = Digest::MD5->new(); $md5->add( ($Self->{TimeObject}->SystemTime() . int(rand(999999999)) . $Self->{SystemID}) . $RemoteAddr . $RemoteUserAgent ); my $SessionID = $Self->{SystemID} . $md5->hexdigest; my $SessionIDBase64 = encode_base64($SessionID, ''); # data 2 strg my $DataToStore = "SessionID:". encode_base64($SessionID, '') .";"; foreach (keys %Param) { if (defined($Param{$_})) { $Self->{EncodeObject}->EncodeOutput(\$Param{$_}); $DataToStore .= "$_:". encode_base64($Param{$_}, '') .";"; } } $DataToStore .= "UserSessionStart:". encode_base64($Self->{TimeObject}->SystemTime(), '') .";"; $DataToStore .= "UserRemoteAddr:". encode_base64($RemoteAddr, '') .";"; $DataToStore .= "UserRemoteUserAgent:". encode_base64($RemoteUserAgent, '') .";\n"; # read old session data (the rest) my $String = $Self->_ReadSHM(); # split data my @Items = split(/\n/, $String); foreach my $Item (@Items) { if ($Item !~ /^SessionID:$SessionIDBase64;/) { $DataToStore .= $Item ."\n"; } } # store SessionID + data $Self->_WriteSHM(Data => $DataToStore); return $SessionID; } sub RemoveSessionID { my $Self = shift; my %Param = @_; my $SessionID = $Param{SessionID}; my $SessionIDBase64 = encode_base64($SessionID, ''); # read old session data (the rest) my $DataToStore = ''; my $String = $Self->_ReadSHM(); # split data my @Items = split(/\n/, $String); foreach my $Item (@Items) { if ($Item !~ /^SessionID:$SessionIDBase64;/) { $DataToStore .= $Item ."\n"; } } # update shm $Self->_WriteSHM(Data => $DataToStore); # log event $Self->{LogObject}->Log( Priority => 'notice', Message => "Removed SessionID $Param{SessionID}." ); return 1; } sub UpdateSessionID { my $Self = shift; my %Param = @_; my $Key = defined($Param{Key}) ? $Param{Key} : ''; my $Value = defined($Param{Value}) ? $Param{Value} : ''; my $SessionID = $Param{SessionID}; # check needed stuff if (!$SessionID) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need SessionID!", ); return; } my %SessionData = $Self->GetSessionIDData(SessionID => $SessionID); # check needed update! (no changes) if (((exists $SessionData{$Key}) && $SessionData{$Key} eq $Value) || (!exists $SessionData{$Key} && $Value eq '')) { return 1; } # update the value if (defined($Value)) { $SessionData{$Key} = $Value; } else { delete $SessionData{$Key}; } # set new data sting my $NewDataToStore = "SessionID:". encode_base64($SessionID, '').";"; foreach (keys %SessionData) { $Self->{EncodeObject}->EncodeOutput(\$SessionData{$_}); $SessionData{$_} = encode_base64($SessionData{$_}, ''); $NewDataToStore .= "$_:$SessionData{$_};"; chomp ($SessionData{$_}); # Debug if ($Self->{Debug}) { $Self->{LogObject}->Log( Priority => 'debug', Message => "UpdateSessionID: $_=$SessionData{$_}", ); } } $NewDataToStore .= "\n"; # read old session data (the rest) my $String = $Self->_ReadSHM(); # split data my @Items = split(/\n/, $String); foreach my $Item (@Items) { my $SessionIDBase64 = encode_base64($SessionID, ''); if ($Item !~ /^SessionID:$SessionIDBase64;/) { $NewDataToStore .= $Item ."\n"; } } # update shm $Self->_WriteSHM(Data => $NewDataToStore); return 1; } sub GetAllSessionIDs { my $Self = shift; my %Param = @_; my @SessionIDs = (); # read data my $String = $Self->_ReadSHM(); if (!$String) { return; } # split data my @Items = split(/\n/, $String); foreach my $Item (@Items) { my @PaarData = split(/;/, $Item); if ($PaarData[0]) { my ($Key, $Value) = split(/:/, $PaarData[0]); if ($Value) { my $SessionID = decode_base64($Value); push (@SessionIDs, $SessionID); } } } return @SessionIDs; } sub CleanUp { my $Self = shift; # remove ipc meta data mem if (!shmctl($Self->{KeyMeta}, IPC_RMID, 0)) { $Self->{LogObject}->Log( Priority => 'error', Message => "Can't remove shm for session meta data: $!", ); return; } # remove ipc session data mem if (!shmctl($Self->{Key}, IPC_RMID, 0)) { $Self->{LogObject}->Log( Priority => 'error', Message => "Can't remove shm for session data: $!", ); return; } return 1; } 1;