Package: interchange
Version: 5.5.0

Sessions per IP limit protection needs more flexibility.  Currently the
time it takes for a lockout to expire is the same amount of time that a
particular IP has to go without any new sessions in order for the
counter to reset and defaults to 24 hours.  The fact that you have to
wait 24 hours without any new sessions means that it is possible that
someone who visits the site only once per day, but does so consistently
will eventually get locked out no matter how high RobotLimit is set to.

Following are my notes on the attached patch which is well tested by
this point (I have been running a production install with this patch for
several months now)...

This patch makes the following changes to sessions per IP limit protection:

* There are now seperate settings for the amount of time allowed to
trigger a lockout and the amount of time a lockout lasts for.

* Lockouts are now triggered based on new sessions per time limit, which
is different from the old critera of new sessions between pauses of
length time.  This means that if RobotLimit is set to 100 and the other
settings were left at thier defaults then a 24 hour lockout would be
triggered if a given IP address had 100 new sessions in any given 60
minute time period.

Settings used by this patch:

RobotLimit: Used to determine the number of new sessions required to
trigger a lockout.  Default is 0 which disables this feature alltogether.

Limit robot_expire: Used to determine the amount of time a lockout will
last in days once triggered.  Can be less than 1, for example 0.04 is
slightly less than an hour.  Default is 1.

Limit ip_session_expire: Used to determine the length of time in minutes
for RobotLimit sessions to build up in the counter file and trigger a
lockout.  Default is 60 (1 hour).  This can also be set to fractional
numbers, for example 0.5 will allow 30 seconds.


Also make note of the following:

* When first implementing you should delete all the old counters with:
        rm -rf catroot/tmp/addr_ctr/*
...be careful with the above command, if mistyped it can seriously mess
up your filesystem.

* Shell command to view the contents of one of the binary new counter files:
        perl -pe 's/(.{4})/localtime(unpack("N",$1))."\n"/seg;' < 0_0_0_0
...where 0_0_0_0 is the filename of the binary counter.  The command
will show you a list of timestamps in human readable form.
Index: lib/Vend/Dispatch.pm
===================================================================
RCS file: /var/cvs/interchange/lib/Vend/Dispatch.pm,v
retrieving revision 1.74
diff -u -p -r1.74 Dispatch.pm
--- lib/Vend/Dispatch.pm        22 Sep 2006 07:10:03 -0000      1.74
+++ lib/Vend/Dispatch.pm        26 Jan 2007 05:24:04 -0000
@@ -1368,34 +1368,32 @@ RESOLVEID: {
                                }
                        }
                }
-    }
-       else {
-               if($Vend::Cfg->{RobotLimit}) {
-                       if (Vend::Session::count_ip() > 
$Vend::Cfg->{RobotLimit}) {
-                               my $msg;
-                               # Here they can get it back if they pass 
expiration time
-                               my $wait = $::Limit->{robot_expire} || 1;
-                               $wait *= 24;
-                               $msg = errmsg(<<EOF, $wait); 
+       } else {
+           if (Vend::Session::count_ip()) {
+               my $msg;
+               # Here they can get it back if they pass expiration time
+               my $wait = $::Limit->{robot_expire} || 1;
+               $wait *= 24;
+               $msg = errmsg(<<EOF, $wait); 
 Too many new ID assignments for this IP address. Please wait at least %d hours
 before trying again. Only waiting that period will allow access. Terminating.
 EOF
-                               $msg = get_locale_message(403, $msg);
-                               do_lockout();
+               $msg = get_locale_message(403, $msg);
+               do_lockout();
 
-                               ::logError('Too many IDs, %d hour wait 
enforced.', $wait);
+               ::logError('Too many IDs, %d hour wait enforced.', $wait);
 
-                               $Vend::StatusLine = <<EOF;
+               $Vend::StatusLine = <<EOF;
 Status: 403 Forbidden
 Content-Type: text/plain
 EOF
-                                       response($msg);
-                                       close_cat();
-                                       return;
-                       }
-               }
-               new_session();
-    }
+               response($msg);
+               close_cat();
+               return;
+           }
+           new_session();
+       }
+
 }
 
 #::logDebug("session name='$Vend::SessionName'\n");
Index: lib/Vend/Session.pm
===================================================================
RCS file: /var/cvs/interchange/lib/Vend/Session.pm,v
retrieving revision 2.27
diff -u -p -r2.27 Session.pm
--- lib/Vend/Session.pm 20 Sep 2006 18:52:06 -0000      2.27
+++ lib/Vend/Session.pm 26 Jan 2007 05:24:04 -0000
@@ -195,22 +195,68 @@ sub open_session {
 }
 
 sub count_ip {
-       my $inc = shift;
-       my $ip = $CGI::remote_addr;
-       $ip =~ s/\W/_/g;
-       my $dir = "$Vend::Cfg->{ScratchDir}/addr_ctr";
-       my $fn = Vend::Util::get_filename($ip, 2, 1, $dir);
-       if(-f $fn) {
-               my $grace = $::Limit->{robot_expire} || 1;
-               my @st = stat(_);
-               my $mtime = (time() - $st[9]) / 86400;
-               if($mtime > $grace) {
-                       ::logDebug("ip $ip allowed back in due to '$mtime' > 
'$grace' days");
-                       unlink $fn;
-               }
-       }
-       return Vend::CounterFile->new($fn)->inc() if $inc;
-       return Vend::CounterFile->new($fn)->value();
+        my ($inc) = @_;
+
+        # Immediate return if RobotLimit is not defined
+       my $index = $Vend::Cfg->{RobotLimit} or return 0;
+       $index *= -1;
+
+
+        my $ip = $CGI::remote_addr;
+        $ip =~ s/\W/_/g;
+
+        my $dir = "$Vend::Cfg->{ScratchDir}/addr_ctr";
+        my $fn = Vend::Util::get_filename($ip, 2, 1, $dir);
+
+
+        # Unlink the "counter" file if applicable.
+
+        if(-f $fn) {
+                my $grace = $::Limit->{ip_session_expire} || 60;
+                my @st = stat(_);
+                my $mtime = (time() - $st[9]) / 60;
+                if($mtime > $grace) {
+#::logDebug("ip $ip session limit expired due to '$mtime' > '$grace' minutes");
+                    unlink $fn;
+                }
+        }
+
+        my $lfn = $fn . '.lockout';
+
+
+        # Unlink the lockout file if applicable, otherwise lock.
+
+        if(-f $lfn) {
+                my $grace = $::Limit->{robot_expire} || 1;
+                my @st = stat(_);
+                my $mtime = (time() - $st[9]) / 86400;
+                if($mtime > $grace) {
+#::logDebug("ip $ip allowed back in due to '$mtime' > '$grace' days");
+                        unlink $lfn;
+                } else {
+                        return 1;
+                }
+        }
+
+
+        # Append a new timestamp to the counter file (if applicable)
+
+       timecard_stamp($fn) if $inc;
+
+
+       # Get timestamp from timecard file and see if it's expired yet.
+
+       my $rtime;
+       return 0 unless $rtime = timecard_read($fn, $index);
+       my $grace = $::Limit->{ip_session_expire} || 60;
+       return 0 if time - $rtime > $grace * 60;
+
+#::logDebug("ip $ip locked out due to too many new sessions in the last $grace 
minutes");
+       # Create the lockout file
+       open(FH, '>', $lfn) or die "Can't create $lfn: $!";
+       close FH;
+
+       return 1;
 }
 
 sub is_retired {
Index: lib/Vend/Util.pm
===================================================================
RCS file: /var/cvs/interchange/lib/Vend/Util.pm,v
retrieving revision 2.101
diff -u -p -r2.101 Util.pm
--- lib/Vend/Util.pm    21 Nov 2006 22:32:12 -0000      2.101
+++ lib/Vend/Util.pm    26 Jan 2007 05:24:05 -0000
@@ -69,6 +69,8 @@ require Exporter;
        show_times
        string_to_ref
        tag_nitems
+       timecard_stamp
+       timecard_read
        uneval
        uneval_it
        uneval_fast
@@ -2163,6 +2165,48 @@ sub codedef_options {
        }
        return [EMAIL PROTECTED];
 }
+
+
+# Adds a timestamp to the end of a binary timecard file. You can specify the 
timestamp
+# as the second arg (unixtime) or just leave it out (or undefined) and it will 
be set
+# to the current time.
+sub timecard_stamp {
+       my ($filename,$timestamp) = @_;
+       $timestamp ||= time;
+
+       open(FH, '>>', $filename) or die "Can't open $filename for append: $!";
+       lockfile(\*FH, 1, 1);
+       print FH pack('N',time);
+       unlockfile(\*FH);
+       close FH;
+}
+
+
+# Reads a timestamp from a binary timecard file.  If $index is negative 
indexes back from
+# the end of the file, otherwise indexes from the front of the file so that 0 
is the first
+# (oldest) timestamp and -1 the last (most recent). Returns the timestamp or 
undefined if
+# the file doesn't exist or the index falls outside of the bounds of the 
timecard file.
+sub timecard_read {
+       my ($filename,$index) = @_;
+       $index *= 4;
+       my $limit = $index >= 0 ? $index + 4 : $index * -1;
+
+       return undef unless (-f $filename && (stat(_))[7] > $limit);
+
+       # The file exists and is big enough to cover the $index. Seek to the 
$index
+       # and return the timestamp from that position.
+
+       open (FH, '<', $filename) or die "Can't open $filename for read: $!";
+       lockfile(\*FH, 0, 1);
+       seek(FH, $index, $index >= 0 ? 0 : 2) or die "Can't seek $filename to 
$index: $!";
+       my $rtime;
+       read(FH,$rtime,4) or die "Can't read from $filename: $!";
+       unlockfile(\*FH);
+       close FH;
+
+       return unpack('N',$rtime);
+}
+
 
 ### Provide stubs for former Vend::Util functions relocated to Vend::File
 *canonpath = \&Vend::File::canonpath;

Reply via email to