This includes WikiPatches/RecentVisitorsFeature, since that also requires to save username and time. The surge protector saves several times, and it saves the host if no username is found.
More documentation is availabe at http://www.emacswiki.org/cgi-bin/oddmuse.pl?ConfigOptions
Displaying a page with no links is nice -- but confusing to innocent readers. Plus parsing the page takes up time and CPU resources. And you can still start the bot on the list of all pages -- so having no links does not stop the bot.
A delay -- even a random delay -- will keep requests coming in in ever increasing waves, unless you introduce a cut-off time. That means that some requests will result in an error. Therefore, the benefit over returning an error immediately is marginal.
If a lot of users are behind a proxy (cache or firewall), then all requests will appear to be from the same host, and these people will lock each other out. If they have a username set, however, they will count as separate entities, which is good.
New variables for the use vars section at the beginning:
$VisitorTime $VisitorFile $Visitors$SurgeProtection $SurgeProtectionViews $SurgeProtectionTime
Don't forget to add:
%RecentVisitors
Default values:
$VisitorTime = 120 * 60; # Timespan to remember visitors $SurgeProtectionTime = 10; # Size of the protected window in seconds $SurgeProtectionViews = 5; # How many page views to allow in this window $SurgeProtection = 1; # 1 = protect against slamming, 0 = trust your server $Visitors = 1; # 1 = maintain list of recent visitors, 0 = don't $VisitorFile = "$DataDir/visitors"; # List of recent visitors
Change DoWikiRequest at the very beginning, and add the following after the call to &InitRequest():
&DoSurgeProtection();
And add the following code somewhere (I added it at the very end):
# == Maintaining a list of recent visitors plus surge protection == sub RequestVisitorsLock { # 4 tries, 2 second wait, do not die on error return &RequestLockDir('visitors', 4, 2, 0); } sub ReleaseVisitorsLock { &ReleaseLockDir('visitors'); } # Limitations: usernames may not contain : (separator) and . (hosts) sub DoSurgeProtection { if ($SurgeProtection or $Visitors) { my $name = &GetParam('username',''); $name = $ENV{'REMOTE_ADDR'} if not $name and $SurgeProtection; if ($name) { RequestVisitorsLock(); ReadRecentVisitors(); AddRecentVisitor($name); WriteRecentVisitors(); ReleaseVisitorsLock(); if ($SurgeProtection and &DelayRequired($name)) { &ReportError(Ts('Too many connections by %s',$name)); exit; } } } } sub DelayRequired { my ($name) = @_; my @entries = @{$RecentVisitors{$name}}; my $ts = $entries[$SurgeProtectionViews - 1]; return 0 if not $ts; return 0 if ($Now - $ts) > $SurgeProtectionTime; return 1; } sub AddRecentVisitor { my ($name) = @_; my $value = $RecentVisitors{$name}; my @entries; if ($value) { @entries = @{$value}; unshift(@entries, $Now); } else { @entries = ($Now); } $RecentVisitors{$name} = \@entries; } sub ReadRecentVisitors { my ($status, $data) = &ReadFile($VisitorFile); %RecentVisitors = (); if (!$status) { return; } foreach (split(/\n/,$data)) { my @entries = split /:/; my $name = shift(@entries); $RecentVisitors{$name} = \@entries; } } sub WriteRecentVisitors { my $data = ''; my $limit = $Now - $VisitorTime; foreach my $name (keys %RecentVisitors) { # for performance, we do not check wether $name is a valid page name if ($SurgeProtection or ($Visitors and $name =~ /\./)) { my @entries = @{$RecentVisitors{$name}}; if ($entries[0] >= $limit) { # save the data if ($SurgeProtection) { $data .= $name . ':' . join(':', @entries[0 .. $SurgeProtectionViews - 1]) . "\n"; } else { $data .= $name . ':' . $entries[0] . "\n"; } } } } &WriteStringToFile($VisitorFile, $data); } sub DoShowVisitors { print &GetHeader('', 'Recent Visitors', ''); &ReadRecentVisitors(); print '<p><ul>'; foreach my $name (sort (keys %RecentVisitors)) { my @entries = @{$RecentVisitors{$name}}; my $time = $entries[0]; print '<li>'; if (!$name or ($SurgeProtection and $name =~ /\./)) { print 'Anonymous'; } else { print &GetPageLink($name); } print ', ', $Now - $time, ' seconds ago</li>'; } print '</ul>'; print &GetCommonFooter(); }