1 # Vend::Session - Interchange session routines
3 # $Id: Session.pm,v 2.31 2007-08-20 18:29:10 kwalsh Exp $
5 # Copyright (C) 2002-2007 Interchange Development Group
6 # Copyright (C) 1996-2002 Red Hat, Inc.
8 # This program was originally based on Vend 0.2 and 0.3
9 # Copyright 1995 by Andrew M. Wilcox <amw@wilcoxsolutions.com>
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public
22 # License along with this program; if not, write to the Free
23 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
26 package Vend::Session;
29 use vars qw($VERSION);
30 $VERSION = substr(q$Revision: 2.31 $, 10);
55 require Vend::SessionFile;
62 if($Global::DB_File) {
67 require Vend::SessionDB;
72 my ($Session_open, $File_sessions, $Lock_sessions);
75 # Selects based on initial config
78 # $File_sessions, $Lock_sessions, &$Session_open
80 $::Instance->{DB_object} =
81 tie(%Vend::SessionDBM,
83 $Vend::Cfg->{SessionDatabase} . ".gdbm",
85 $Vend::Cfg->{FileCreationMask}
87 die "Could not tie to $Vend::Cfg->{SessionDatabase}: $!\n"
88 unless defined $::Instance->{DB_object};
91 DB_File => [ 0, 1, sub {
95 $Vend::Cfg->{SessionDatabase} . ".db",
97 $Vend::Cfg->{FileCreationMask}
99 or die "Could not tie to $Vend::Cfg->{SessionDatabase}: $!\n";
104 return 1 if $::Instance->{DB_sessions};
108 $Vend::Cfg->{SessionDB}
110 or die "Could not tie to $Vend::Cfg->{SessionDB}: $!\n";
111 $::Instance->{DB_sessions} = 1;
115 File => [ 1, 0, sub {
119 $Vend::Cfg->{SessionDatabase}
121 or die "Could not tie to $Vend::Cfg->{SessionDatabase}: $!\n";
130 $Vend::Cfg->{SessionDatabase},
133 or die "Could not tie to $Vend::Cfg->{SessionDatabase}: $!\n";
139 # SESSIONS implemented using DBM
144 if($seed and ! $Vend::SessionID) {
145 #::logDebug("received seed=$seed");
146 $Vend::SessionID = $seed;
148 $Vend::SessionName = session_name() if ! $Vend::SessionName;
149 #::logDebug("session name now $Vend::SessionName");
151 $Vend::HaveSession = 0;
154 $new = read_session($seed) unless $Vend::ExternalProgram;
155 unless($File_sessions) {
159 $Vend::HaveSession = 1;
164 return unless $Vend::HaveSession;
165 unless($File_sessions) {
174 $Vend::HaveSession = 0;
178 return 1 if defined $Vend::SessionOpen;
179 ($File_sessions, $Lock_sessions, $Session_open)
180 = @{$Session_class{ $Vend::Cfg->{SessionType} || 'File' }};
181 if (! defined $File_sessions) {
182 ($File_sessions, $Lock_sessions, $Session_open) = @{$Session_class{File}};
184 #::logDebug("open_session: File_sessions=$File_sessions Sub=$Session_open");
186 open(Vend::SessionLock, "+>>$Vend::Cfg->{SessionLockFile}")
187 or die "Could not open lock file '$Vend::Cfg->{SessionLockFile}': $!\n";
188 lockfile(\*Vend::SessionLock, 1, 1)
189 or die "Could not lock '$Vend::Cfg->{SessionLockFile}': $!\n";
193 $Vend::SessionOpen = 1;
200 # Immediate return if RobotLimit is not defined
201 my $index = $Vend::Cfg->{RobotLimit} or return 0;
205 my $ip = $CGI::remote_addr;
208 my $dir = "$Vend::Cfg->{ScratchDir}/addr_ctr";
209 my $fn = Vend::Util::get_filename($ip, 2, 1, $dir);
212 # Unlink the "counter" file if applicable.
215 my $grace = $::Limit->{ip_session_expire} || 60;
217 my $mtime = (time() - $st[9]) / 60;
218 if($mtime > $grace) {
219 #::logDebug("ip $ip session limit expired due to '$mtime' > '$grace' minutes");
224 my $lfn = $fn . '.lockout';
227 # Unlink the lockout file if applicable, otherwise lock.
230 my $grace = $::Limit->{robot_expire} || 1;
232 my $mtime = (time() - $st[9]) / 86400;
233 if($mtime > $grace) {
234 #::logDebug("ip $ip allowed back in due to '$mtime' > '$grace' days");
242 # Append a new timestamp to the counter file (if applicable)
244 timecard_stamp($fn) if $inc;
247 # Get timestamp from timecard file and see if it's expired yet.
250 return 0 unless $rtime = timecard_read($fn, $index);
251 my $grace = $::Limit->{ip_session_expire} || 60;
252 return 0 if time - $rtime > $grace * 60;
254 #::logDebug("ip $ip locked out due to too many new sessions in the last $grace minutes");
255 # Create the lockout file
256 open(FH, '>', $lfn) or die "Can't create $lfn: $!";
264 mkdir "$Vend::Cfg->{ScratchDir}/retired", 0777
265 unless -d "$Vend::Cfg->{ScratchDir}/retired";
266 my $fn = Vend::Util::get_filename($id, 2, 1, "$Vend::Cfg->{ScratchDir}/retired");
267 return -f $fn ? 1 : 0;
272 return unless $id =~ /^\w+$/;
273 mkdir "$Vend::Cfg->{ScratchDir}/retired", 0777
274 unless -d "$Vend::Cfg->{ScratchDir}/retired";
275 my $fn = Vend::Util::get_filename($id, 2, 1, "$Vend::Cfg->{ScratchDir}/retired");
277 or die "retire id open: $!\n";
286 #::logDebug ("new session id=$Vend::SessionID name=$Vend::SessionName seed=$seed");
289 unless (defined $seed) {
290 $Vend::SessionID = random_string($::Limit->{session_id_length} ||= 8);
291 undef $Vend::CookieID;
294 if (is_retired($Vend::SessionID)) {
295 retire_id($Vend::SessionID);
298 $name = session_name();
299 unless ($File_sessions) {
301 last unless defined $Vend::SessionDBM{$name};
302 unlock_session($name);
305 last unless exists $Vend::SessionDBM{$name};
308 $Vend::new_session = 1;
309 count_ip(1) if $Vend::Cfg->{RobotLimit};
311 $Vend::SessionName = $name;
313 #::logDebug("init_session $Vend::SessionName is: " . ::uneval($Vend::Session));
314 #::logDebug("init_session $Vend::SessionName");
315 $Vend::HaveSession = 1;
316 return if $File_sessions || $::Instance->{DB_sessions};
323 #::logDebug ("try to close session id=$Vend::SessionID name=$Vend::SessionName");
324 return 0 unless defined $Vend::SessionOpen;
326 unless($::Instance->{DB_sessions}) {
327 undef $::Instance->{DB_object};
328 undef $File_sessions;
329 untie %Vend::SessionDBM
330 or die "Could not close $Vend::Cfg->{SessionDatabase}: $!\n";
331 undef $Vend::SessionOpen;
334 undef $::Instance->{DB_sessions};
335 untie %Vend::SessionDBM
336 or die "Could not close $Vend::Cfg->{SessionDatabase}: $!\n";
337 undef $Vend::SessionOpen;
340 return 1 unless $Lock_sessions;
342 unlockfile(\*Vend::SessionLock)
343 or die "Could not unlock '$Vend::Cfg->{SessionLockFile}': $!\n";
344 close(Vend::SessionLock)
345 or die "Could not close '$Vend::Cfg->{SessionLockFile}': $!\n";
346 undef $Vend::SessionOpen;
352 #::logDebug ("write session id=$Vend::SessionID name=$Vend::SessionName\n");
354 $Vend::Session->{'time'} = $time;
355 delete $Vend::Session->{values}->{mv_credit_card_number};
356 my $save = delete $Vend::Session->{'user'};
358 delete @{$::Scratch}{@Vend::TmpScratch};
360 $Vend::Session->{username} = $Vend::username;
361 $Vend::Session->{admin} = $Vend::admin;
362 $Vend::Session->{groups} = $Vend::groups;
363 $Vend::Session->{superuser} = $Vend::superuser;
364 $Vend::Session->{login_table} = $Vend::login_table;
365 $s = ! $File_sessions ? uneval_fast($Vend::Session) : $Vend::Session;
366 #::logDebug("writing \$s of length " . length($s) . " to SessionDB");
367 $Vend::SessionDBM{$Vend::SessionName} = $s or
368 die "Data was not stored in SessionDBM\n";
369 $Vend::Session->{'user'} = $save;
373 #::logDebug ("unlock session id=$Vend::SessionID name=$Vend::SessionName\n");
375 $name ||= $Vend::SessionName;
376 delete $Vend::SessionDBM{'LOCK_' . $Vend::SessionName}
377 unless $File_sessions;
381 return 1 if $File_sessions;
383 $name ||= $Vend::SessionName;
384 #::logDebug ("lock session id=$Vend::SessionID name=$Vend::SessionName\n");
385 my $lockname = "LOCK_$name";
386 my ($tried, $locktime, $sleepleft, $pid, $now, $left);
391 if (defined ($lv = $Vend::SessionDBM{$lockname}) ) {
392 ($locktime, $pid) = split /:/, $lv, 2;
395 if(defined $locktime and $locktime) {
396 $left = $now - $locktime;
397 if ( $left > $Global::HammerLock ) {
398 $Vend::SessionDBM{$lockname} = "$now:$$";
399 logError("Hammered session lock %s left by PID %s" , $lockname, $pid );
404 lock_session: Time earlier than lock time for $lockname
408 die errmsg("Locking error!\n", '');
416 $sleepleft = int($left / 2);
417 if ($sleepleft < 3) {
421 if($::Instance->{DB_sessions}) {
435 $Vend::SessionDBM{$lockname} = "$now:$$";
440 # Should never get here
448 #::logDebug ("read session id=$Vend::SessionID name=$Vend::SessionName\n");
449 $s = $Vend::SessionDBM{$Vend::SessionName}
450 or $Global::Variable->{MV_SESSION_READ_RETRY}
453 my $tries = $Global::Variable->{MV_SESSION_READ_RETRY} + 0 || 5;
454 while($i++ < $tries) {
455 ::logDebug("retrying session read on undef, try $i");
456 $s = $Vend::SessionDBM{$Vend::SessionName};
458 ::logDebug("Session re-read successfully on try $i");
463 #::logDebug ("Session:\n$s\n");
464 return new_session($seed) unless $s;
465 $Vend::Session = ref $s ? $s : evalr($s);
466 die "Could not eval '$s' from session dbm: $@\n" if $@;
468 $Vend::Session->{host} = $CGI::host;
470 $Vend::username = $Vend::Session->{username};
471 $Vend::admin = $Vend::Session->{admin};
472 $Vend::superuser = $Vend::Session->{superuser};
473 $Vend::groups = $Vend::Session->{groups};
474 $Vend::login_table = $Vend::Session->{login_table};
476 $Vend::Session->{arg} = $Vend::Argument;
478 $::Values = $Vend::Session->{values};
479 $::Scratch = $Vend::Session->{scratch};
480 $::Carts = $Vend::Session->{carts};
481 $::Discounts = $Vend::Session->{discount};
482 $Vend::Interpolate::Tmp ||= {};
483 $::Control = $Vend::Interpolate::Tmp->{control} = [];
484 tie $Vend::Items, 'Vend::Cart';
490 my $joiner = $Global::Windows ? '_' : ':';
493 my($host, $user, $fn, $proxy);
495 return $Vend::SessionID if $::Instance->{ExternalCookie};
497 if(defined $CGI::user and $CGI::user) {
498 $host = escape_chars($CGI::user);
500 elsif($CGI::cookieuser) {
501 $host = $CGI::cookieuser;
503 elsif($CGI::cookiehost) {
504 $host = $CGI::cookiehost;
508 $proxy = index($host,"proxy");
509 $host = substr($host,$proxy)
511 $host = escape_chars($host);
513 #::logDebug ("name session user=$CGI::user host=$host ($CGI::host)\n");
514 $fn = $Vend::SessionID . $joiner . $host;
515 #::logDebug ("name session id=$Vend::SessionID name=$fn\n");
521 undef $Vend::username;
524 undef $Vend::superuser;
525 undef $Vend::login_table;
527 'host' => $CGI::host,
528 'ohost' => $CGI::remote_addr,
529 'arg' => $Vend::Argument,
530 'browser' => $CGI::useragent,
531 'referer' => $CGI::referer,
532 'spider' => $Vend::Robot,
533 'scratch' => { %{$Vend::Cfg->{ScratchDefault}} },
534 'values' => { %{$Vend::Cfg->{ValuesDefault}} },
535 'carts' => {main => []},
536 'levies' => {main => []},
537 'discount_space' => {main => {}},
539 $Vend::Session->{shost} = $CGI::remote_addr
541 $::Values = $Vend::Session->{values};
542 $::Scratch = $Vend::Session->{scratch};
543 $::Scratch->{mv_locale} ||= $Vend::Cfg->{DefaultLocale};
544 $::Carts = $Vend::Session->{carts};
545 tie $Vend::Items, 'Vend::Cart';
546 $::Values->{mv_shipmode} = $Vend::Cfg->{DefaultShipping}
547 if ! defined $::Values->{mv_shipmode};
549 = $Vend::Session->{discount}
550 = $Vend::Session->{discount_space}{main};
551 if(my $macro = $Vend::Cfg->{SpecialSub}{init_session}) {
552 Vend::Dispatch::run_macro(
561 my($session_name, $s);
562 die "Can't dump file-based sessions.\n" if $File_sessions;
565 eval { require Data::Dumper;
566 $Data::Dumper::Indent = 3;
567 $Data::Dumper::Terse = 1; };
568 $pretty = $@ ? 0 : 1;
570 while(($session_name, $s) = each %Vend::SessionDBM) {
571 next if $session_name eq 'dumpprog:DUMP';
572 next if $session_name =~ /^LOCK_/;
573 if(defined $called) {
574 next unless $session_name =~ /$called/;
576 if ($pretty or defined $Storable::VERSION) {
580 print "$session_name $s\n\n";
586 return unless $::Instance->{DB_object};
587 GDBM_File::reorganize($::Instance->{DB_object});
588 GDBM_File::sync($::Instance->{DB_object});
591 sub expire_sessions {
593 my($time, $session_name, $s, $session, @delete);
597 while(($session_name, $s) = each %Vend::SessionDBM) {
600 if ($session_name =~ /^LOCK_/) {;
601 delete $Vend::SessionDBM{$session_name}
602 unless ($File_sessions or $s);
607 if ($session_name =~ /^\w{8}$/) {
608 $session = evalr ($s);
609 die "Could not eval '$s' from session dbm: $@\n" if $@;
610 next if keys %$session; # Don't remove if has session marker
611 push @delete, $session_name;
614 $session = evalr($s);
615 die "Could not eval '$s' from session dbm: $@\n" if $@;
616 next if check_save($time);
617 if ( (! defined $session) ||
618 $time - $session->{'time'} > $Vend::Cfg->{SessionExpire}) {
619 push @delete, $session_name;
622 foreach $session_name (@delete) {
623 delete $Vend::SessionDBM{$session_name};
624 delete $Vend::SessionDBM{"LOCK_$session_name"}
625 if ! $File_sessions && $Vend::SessionDBM{"LOCK_$session_name"};
626 my $file = $session_name;
628 opendir(Vend::DELDIR, $Vend::Cfg->{ScratchDir}) ||
629 die "Could not open configuration directory $Vend::Cfg->{ScratchDir}: $!\n";
630 my @files = grep /^$file/, readdir(Vend::DELDIR);
632 unlink "$Vend::Cfg->{ScratchDir}/$_";
634 closedir(Vend::DELDIR);
644 $time = $time || time();
646 if(defined $::Values->{mv_expire_time}) {
647 $expire = $::Values->{mv_expire_time};
648 unless($expire =~ /^\d{6,}$/) {
649 $expire = Vend::Config::time_to_seconds($expire);
652 $expire = $Vend::Cfg->{SaveExpire} unless $expire;
654 $Vend::Session->{'expire'} = $Vend::Expire = $time + $expire;
656 return ($expire > $time);