[Home]WikiPatches/SurgeProtector

UseModWiki | WikiPatches | RecentChanges | Preferences

This does a MeatBall:SurgeProtector. Note that to protect against a DenialOfService, you might be better off using a solution for the web server. But many web hosting providers do not offer that. So here we are patching the script.

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.

Discussion

More documentation is availabe at http://www.emacswiki.org/cgi-bin/oddmuse.pl?ConfigOptions

Worse solutions

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.

The importance of username

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.

This could be done if you have visitor cookies, is there a patch to track visitor/guest users.

Code

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();
  }


WikiPatches

UseModWiki | WikiPatches | RecentChanges | Preferences
Edit text of this page | View other revisions | Search MetaWiki
Last edited October 26, 2007 7:11 pm by MarkusLude (diff)
Search: