Permalink
Browse files

This patch makes the following changes to sessions per IP limit prote…

…ction:

* 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 -e 'binmode STDIN;' -e '$/=undef;' -e '$_ = <STDIN>;' \
	-e 's/(.{4})/localtime(unpack("N",$1))."\n"/seg;' \
	-e 'print;' < 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.
  • Loading branch information...
1 parent 3f45ec1 commit 00ba588c26e059aab335e659509b64db2e85568a @pajamian pajamian committed Jul 5, 2007
Showing with 150 additions and 42 deletions.
  1. +7 −0 UPGRADE
  2. +5 −0 WHATSNEW-5.5
  3. +20 −22 lib/Vend/Dispatch.pm
  4. +64 −18 lib/Vend/Session.pm
  5. +54 −2 lib/Vend/Util.pm
View
@@ -116,6 +116,13 @@ file into a new robots.cfg file. If you are upgrading then you will
need to remove any existing *Robot* directives from your interchange.cfg
file and add "include robots.cfg" in their place.
+The session per IP counters have been changed to the new "timecard" round-robin
+style counters. You will need to delete the old counter files from the
+tmp/addr_ctr directory with a command similar to the following:
+ rm -rf catroot/tmp/addr_ctr/*
+...be careful with the above command, if mistyped it can seriously mess up
+your filesystem.
+
KNOWN ISSUES UPGRADING FROM 5.2.x
View
@@ -60,6 +60,11 @@ Core
from last_sequence_value() for returning even if we already know the key and
even on an UPDATE which can cause problems.
+* New "timecard" round-robin style counters added with the timecard_stamp and
+ timecard_read subs in Util.pm. These are now used for better control of the
+ session per IP lockouts (when RobotLimit is set). See CVS log for more
+ details.
+
UserDB
------
View
@@ -1,6 +1,6 @@
# Vend::Dispatch - Handle Interchange page requests
#
-# $Id: Dispatch.pm,v 1.78 2007-07-03 05:48:37 jon Exp $
+# $Id: Dispatch.pm,v 1.79 2007-07-05 11:19:42 pajamian Exp $
#
# Copyright (C) 2002-2006 Interchange Development Group
# Copyright (C) 2002 Mike Heins <mike@perusion.net>
@@ -26,7 +26,7 @@
package Vend::Dispatch;
use vars qw($VERSION);
-$VERSION = substr(q$Revision: 1.78 $, 10);
+$VERSION = substr(q$Revision: 1.79 $, 10);
use POSIX qw(strftime);
use Vend::Util;
@@ -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");
View
@@ -1,6 +1,6 @@
# Vend::Session - Interchange session routines
#
-# $Id: Session.pm,v 2.28 2007-03-30 11:39:45 pajamian Exp $
+# $Id: Session.pm,v 2.29 2007-07-05 11:19:42 pajamian Exp $
#
# Copyright (C) 2002-2006 Interchange Development Group
# Copyright (C) 1996-2002 Red Hat, Inc.
@@ -27,7 +27,7 @@ package Vend::Session;
require Exporter;
use vars qw($VERSION);
-$VERSION = substr(q$Revision: 2.28 $, 10);
+$VERSION = substr(q$Revision: 2.29 $, 10);
@ISA = qw(Exporter);
@@ -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 {
View
@@ -1,6 +1,6 @@
# Vend::Util - Interchange utility functions
#
-# $Id: Util.pm,v 2.105 2007-07-05 10:01:25 racke Exp $
+# $Id: Util.pm,v 2.106 2007-07-05 11:19:42 pajamian Exp $
#
# Copyright (C) 2002-2007 Interchange Development Group
# Copyright (C) 1996-2002 Red Hat, Inc.
@@ -69,6 +69,8 @@ require Exporter;
show_times
string_to_ref
tag_nitems
+ timecard_stamp
+ timecard_read
uneval
uneval_it
uneval_fast
@@ -88,7 +90,7 @@ use Safe;
use Vend::File;
use subs qw(logError logGlobal);
use vars qw($VERSION @EXPORT @EXPORT_OK);
-$VERSION = substr(q$Revision: 2.105 $, 10);
+$VERSION = substr(q$Revision: 2.106 $, 10);
my $Eval_routine;
my $Eval_routine_file;
@@ -2194,6 +2196,56 @@ sub codedef_options {
return \@out;
}
+
+# 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);
+ binmode FH;
+ 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;
+
+ if (-f $filename && (stat(_))[7] % 4) {
+ # The file is corrupt, delete it and start over.
+ ::logError("Counter file $filename found to be corrupt, deleting.");
+ unlink($filename);
+ return;
+ }
+ return unless (-f _ && (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);
+ binmode FH;
+ 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;
*catdir = \&Vend::File::catdir;

0 comments on commit 00ba588

Please sign in to comment.