*/
$wgEnableSorbs = false;
+/**
+ * Use opm.blitzed.org to check for open proxies.
+ * Not yet actually used.
+ */
+$wgEnableOpm = false;
+
+/**
+ * Simple rate limiter options to brake edit floods.
+ * Maximum number actions allowed in the given number of seconds;
+ * after that the violating client receives HTTP 500 error pages
+ * until the period elapses.
+ *
+ * array( 4, 60 ) for a maximum of 4 hits in 60 seconds.
+ *
+ * This option set is experimental and likely to change.
+ * Requires memcached.
+ */
+$wgRateLimits = array(
+ 'edit' => array(
+ 'anon' => null, // for any and all anonymous edits (aggregate)
+ 'user' => null, // for each logged-in user
+ 'newbie' => null, // for each recent account; overrides 'user'
+ 'ip' => null, // for each anon and recent account
+ 'subnet' => null, // ... with final octet removed
+ ),
+ 'move' => array(
+ 'user' => null,
+ 'newbie' => null,
+ 'ip' => null,
+ 'subnet' => null,
+ ),
+ );
+
+/**
+ * Set to a filename to log rate limiter hits.
+ */
+$wgRateLimitLog = null;
+
/**
* On Special:Unusedimages, consider images "used", if they are put
* into a category. Default (false) is not to count those as used.
}
function inSorbsBlacklist( $ip ) {
- $fname = 'User::inSorbsBlacklist';
+ global $wgEnableSorbs;
+ return $wgEnableSorbs &&
+ $this->inDnsBlacklist( $ip, 'http.dnsbl.sorbs.net.' );
+ }
+
+ function inOpmBlacklist( $ip ) {
+ global $wgEnableOpm;
+ return $wgEnableOpm &&
+ $this->inDnsBlacklist( $ip, 'opm.blitzed.org.' );
+ }
+
+ function inDnsBlacklist( $ip, $base ) {
+ $fname = 'User::inDnsBlacklist';
wfProfileIn( $fname );
$found = false;
for ( $i=4; $i>=1; $i-- ) {
$host .= $m[$i] . '.';
}
- $host .= 'http.dnsbl.sorbs.net.';
+ $host .= $base;
# Send query
$ipList = gethostbynamel( $host );
if ( $ipList ) {
- wfDebug( "Hostname $host is {$ipList[0]}, it's a proxy!\n" );
+ wfDebug( "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" );
$found = true;
} else {
- wfDebug( "Requested $host, not found.\n" );
+ wfDebug( "Requested $host, not found in $base.\n" );
}
}
return $found;
}
+ /**
+ * Primitive rate limits: enforce maximum actions per time period
+ * to put a brake on flooding.
+ *
+ * Note: when using a shared cache like memcached, IP-address
+ * last-hit counters will be shared across wikis.
+ *
+ * @return bool true if a rate limiter was tripped
+ * @access public
+ */
+ function pingLimiter( $action='edit' ) {
+ global $wgRateLimits;
+ if( !isset( $wgRateLimits[$action] ) ) {
+ return false;
+ }
+ if( $this->isAllowed( 'delete' ) ) {
+ // goddam cabal
+ return false;
+ }
+
+ global $wgMemc, $wgIP, $wgDBname, $wgRateLimitLog;
+ $fname = 'User::pingLimiter';
+ $limits = $wgRateLimits[$action];
+ $keys = array();
+ $id = $this->getId();
+
+ if( isset( $limits['anon'] ) && $id == 0 ) {
+ $keys["$wgDBname:limiter:$action:anon"] = $limits['anon'];
+ }
+
+ if( isset( $limits['user'] ) && $id != 0 ) {
+ $keys["$wgDBname:limiter:$action:user:$id"] = $limits['user'];
+ }
+ if( $this->isNewbie() ) {
+ if( isset( $limits['newbie'] ) && $id != 0 ) {
+ $keys["$wgDBname:limiter:$action:user:$id"] = $limits['newbie'];
+ }
+ if( isset( $limits['ip'] ) ) {
+ $keys["mediawiki:limiter:$action:ip:$wgIP"] = $limits['ip'];
+ }
+ if( isset( $limits['subnet'] ) && preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $wgIP, $matches ) ) {
+ $subnet = $matches[1];
+ $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
+ }
+ }
+
+ $triggered = false;
+ foreach( $keys as $key => $limit ) {
+ list( $max, $period ) = $limit;
+ $summary = "(limit $max in {$period}s)";
+ $count = $wgMemc->get( $key );
+ if( $count ) {
+ if( $count > $max ) {
+ wfDebug( "$fname: tripped! $key at $count $summary\n" );
+ if( $wgRateLimitLog ) {
+ @error_log( $this->getName() . ": tripped $key at $count $summary\n", 3, $wgRateLimitLog );
+ }
+ $triggered = true;
+ } else {
+ wfDebug( "$fname: ok. $key at $count $summary\n" );
+ }
+ } else {
+ wfDebug( "$fname: adding record for $key $summary\n" );
+ $wgMemc->add( $key, 1, IntVal( $period ) );
+ }
+ $wgMemc->incr( $key );
+ }
+
+ return $triggered;
+ }
+
/**
* Check if user is blocked
* @return bool True if blocked, false otherwise
* @return bool True if it is a newbie.
*/
function isNewbie() {
- return $this->mId > User::getMaxID() * 0.99 && !$this->isAllowed( 'delete' ) && !$this->isBot() || $this->getID() == 0;
+ return $this->isAnon() || $this->mId > User::getMaxID() * 0.99 && !$this->isAllowed( 'delete' ) && !$this->isBot();
}
/**