Index: trunk/extensions/AbuseFilter/AbuseFilter.php |
— | — | @@ -27,10 +27,10 @@ |
28 | 28 | $wgExtensionMessagesFiles['AbuseFilter'] = "$dir/AbuseFilter.i18n.php"; |
29 | 29 | $wgExtensionAliasesFiles['AbuseFilter'] = "$dir/AbuseFilter.alias.php"; |
30 | 30 | |
31 | | -$wgAutoloadClasses[ 'AbuseFilter' ] = "$dir/AbuseFilter.class.php"; |
32 | | -$wgAutoloadClasses[ 'AbuseFilterParser' ] = "$dir/AbuseFilter.parser.php"; |
33 | | -$wgAutoloadClasses[ 'AbuseFilterParserNative' ] = "$dir/AbuseFilter.nativeparser.php"; |
34 | | -$wgAutoloadClasses[ 'AbuseFilterHooks' ] = "$dir/AbuseFilter.hooks.php"; |
| 31 | +$wgAutoloadClasses['AbuseFilter'] = "$dir/AbuseFilter.class.php"; |
| 32 | +$wgAutoloadClasses['AbuseFilterParser'] = "$dir/AbuseFilter.parser.php"; |
| 33 | +$wgAutoloadClasses['AbuseFilterParserNative'] = "$dir/AbuseFilter.nativeparser.php"; |
| 34 | +$wgAutoloadClasses['AbuseFilterHooks'] = "$dir/AbuseFilter.hooks.php"; |
35 | 35 | $wgAutoloadClasses['SpecialAbuseLog'] = "$dir/SpecialAbuseLog.php"; |
36 | 36 | $wgAutoloadClasses['SpecialAbuseFilter'] = "$dir/SpecialAbuseFilter.php"; |
37 | 37 | |
— | — | @@ -50,17 +50,18 @@ |
51 | 51 | $wgAvailableRights[] = 'abusefilter-log'; |
52 | 52 | $wgAvailableRights[] = 'abusefilter-private'; |
53 | 53 | |
54 | | -$wgAbuseFilterAvailableActions = array( 'flag', 'throttle', 'warn', 'disallow', 'blockautopromote', 'block', 'degroup' ); |
| 54 | +$wgAbuseFilterAvailableActions = array( 'flag', 'throttle', 'warn', 'disallow', 'blockautopromote', 'block', 'degroup', 'rangeblock' ); |
55 | 55 | |
56 | 56 | // Conditions take about 4ms to check, so 100 conditions would take 400ms |
57 | 57 | $wgAbuseFilterConditionLimit = 1000; |
58 | 58 | |
59 | | -// Disable filters if they match more than X edits, constituting more than Y% of the last Z edits |
60 | | -$wgAbuseFilterEmergencyDisableThreshold = 0.05; |
61 | | -$wgAbuseFilterEmergencyDisableCount = 5; |
| 59 | +// Disable filters if they match more than X edits, constituting more than Y% of the last Z edits, if they have been changed in the last S seconds |
| 60 | +$wgAbuseFilterEmergencyDisableThreshold = 0.50; |
| 61 | +$wgAbuseFilterEmergencyDisableCount = 2; |
| 62 | +$wgAbuseFilterEmergencyDisableAge = 86400; // One day. |
62 | 63 | |
63 | 64 | // Abuse filter parser class |
64 | | -$wgAbuseFilterParserClass = 'AbuseFilterParserNative'; |
| 65 | +$wgAbuseFilterParserClass = 'AbuseFilterParser'; |
65 | 66 | $wgAbuseFilterNativeParser = "$dir/parser_native/af_parser"; |
66 | 67 | $wgAbuseFilterNativeSyntaxCheck = "$dir/parser_native/syntax_check"; |
67 | 68 | $wgAbuseFilterNativeExpressionEvaluator = "$dir/parser_native/af_expr"; |
Index: trunk/extensions/AbuseFilter/SpecialAbuseFilter.php |
— | — | @@ -397,7 +397,7 @@ |
398 | 398 | 'op-arithmetic' => array('+' => 'addition', '-' => 'subtraction', '*' => 'multiplication', '/' => 'divide', '%' => 'modulo', '**' => 'pow'), |
399 | 399 | 'op-comparison' => array('==' => 'equal', '!=' => 'notequal', '<' => 'lt', '>' => 'gt', '<=' => 'lte', '>=' => 'gte'), |
400 | 400 | 'op-bool' => array( '!' => 'not', '&' => 'and', '|' => 'or', '^' => 'xor' ), |
401 | | - 'misc' => array( 'val1 ? iftrue : iffalse' => 'ternary', 'in' => 'in', 'like' => 'like' ), |
| 401 | + 'misc' => array( 'val1 ? iftrue : iffalse' => 'ternary', 'in' => 'in', 'like' => 'like', '""' => 'stringlit', ), |
402 | 402 | 'funcs' => array( 'length(string)' => 'length', 'lcase(string)' => 'lcase', 'ccnorm(string)' => 'ccnorm', 'rmdoubles(string)' => 'rmdoubles', 'specialratio(string)' => 'specialratio', 'norm(string)' => 'norm', 'count(needle,haystack)' => 'count' ), |
403 | 403 | 'vars' => array( 'ACCOUNTNAME' => 'accountname', 'ACTION' => 'action', 'ADDED_LINES' => 'addedlines', 'EDIT_DELTA' => 'delta', 'EDIT_DIFF' => 'diff', 'NEW_SIZE' => 'newsize', 'OLD_SIZE' => 'oldsize', 'REMOVED_LINES' => 'removedlines', 'SUMMARY' => 'summary', 'ARTICLE_ARTICLEID' => 'article-id', 'ARTICLE_NAMESPACE' => 'article-ns', 'ARTICLE_TEXT' => 'article-text', 'ARTICLE_PREFIXEDTEXT' => 'article-prefixedtext', 'MOVED_FROM_ARTICLEID' => 'movedfrom-id', 'MOVED_FROM_NAMESPACE' => 'movedfrom-ns', 'MOVED_FROM_TEXT' => 'movedfrom-text', 'MOVED_FROM_PREFIXEDTEXT' => 'movedfrom-prefixedtext', 'MOVED_TO_ARTICLEID' => 'movedto-id', 'MOVED_TO_NAMESPACE' => 'movedto-ns', 'MOVED_TO_TEXT' => 'movedto-text', 'MOVED_TO_PREFIXEDTEXT' => 'movedto-prefixedtext', 'USER_EDITCOUNT' => 'user-editcount', 'USER_AGE' => 'user-age', 'USER_NAME' => 'user-name', 'USER_GROUPS' => 'user-groups', 'USER_EMAILCONFIRM' => 'user-emailconfirm'), |
404 | 404 | ); |
Index: trunk/extensions/AbuseFilter/AbuseFilter.class.php |
— | — | @@ -9,6 +9,7 @@ |
10 | 10 | public static $modifyCache = array(); |
11 | 11 | public static $condLimitEnabled = true; |
12 | 12 | public static $condCount = 0; |
| 13 | + public static $filters = array(); |
13 | 14 | |
14 | 15 | public static function generateUserVars( $user ) { |
15 | 16 | $vars = array(); |
— | — | @@ -130,6 +131,7 @@ |
131 | 132 | $filter_matched = array(); |
132 | 133 | |
133 | 134 | while ( $row = $dbr->fetchObject( $res ) ) { |
| 135 | + self::$filters[$row->af_id] = $row; |
134 | 136 | if ( self::checkConditions( $row->af_pattern, $vars ) ) { |
135 | 137 | $blocking_filters[$row->af_id] = $row; |
136 | 138 | |
— | — | @@ -219,7 +221,7 @@ |
220 | 222 | case 'warn': |
221 | 223 | wfLoadExtensionMessages( 'AbuseFilter' ); |
222 | 224 | |
223 | | - if (!$_SESSION['abusefilter-warned']) { |
| 225 | + if (!isset($_SESSION['abusefilter-warned']) || !$_SESSION['abusefilter-warned']) { |
224 | 226 | $_SESSION['abusefilter-warned'] = true; |
225 | 227 | |
226 | 228 | // Threaten them a little bit |
— | — | @@ -259,12 +261,11 @@ |
260 | 262 | $block = new Block; |
261 | 263 | $block->mAddress = $wgUser->getName(); |
262 | 264 | $block->mUser = $wgUser->getId(); |
263 | | - $block->mBy = User::idFromName( wfMsgForContent( 'abusefilter-blocker' ) ); // Let's say the site owner blocked them |
264 | | - $block->mByName = wfMsgForContent( 'abusefilter-blocker' ); |
| 265 | + $block->mBy = $filterUser->getId(); |
| 266 | + $block->mByName = $filterUser->getName(); |
265 | 267 | $block->mReason = wfMsgForContent( 'abusefilter-blockreason', $rule_desc ); |
266 | 268 | $block->mTimestamp = wfTimestampNow(); |
267 | | - $block->mEnableAutoblock = 1; |
268 | | - $block->mAngryAutoblock = 1; // Block lots of IPs |
| 269 | + $block->mAnonOnly = 1; |
269 | 270 | $block->mCreateAccount = 1; |
270 | 271 | $block->mExpiry = 'infinity'; |
271 | 272 | |
— | — | @@ -282,6 +283,47 @@ |
283 | 284 | |
284 | 285 | $display .= wfMsgNoTrans( 'abusefilter-blocked-display', $rule_desc ) ."<br />\n"; |
285 | 286 | break; |
| 287 | + case 'rangeblock': |
| 288 | + wfLoadExtensionMessages( 'AbuseFilter' ); |
| 289 | + |
| 290 | + global $wgUser; |
| 291 | + $filterUser = AbuseFilter::getFilterUser(); |
| 292 | + |
| 293 | + $range = IP::toHex( wfGetIP() ); |
| 294 | + $range = substr( $range, 0, 4 ) . '0000'; |
| 295 | + $range = long2ip( hexdec( $range ) ); |
| 296 | + $range .= "/16"; |
| 297 | + $range = Block::normaliseRange( $range ); |
| 298 | + |
| 299 | + // Create a block. |
| 300 | + $block = new Block; |
| 301 | + $block->mAddress = $range; |
| 302 | + $block->mUser = 0; |
| 303 | + $block->mBy = $filterUser->getId(); |
| 304 | + $block->mByName = $filterUser->getName(); |
| 305 | + $block->mReason = wfMsgForContent( 'abusefilter-blockreason', $rule_desc ); |
| 306 | + $block->mTimestamp = wfTimestampNow(); |
| 307 | + $block->mAnonOnly = 0; |
| 308 | + $block->mCreateAccount = 1; |
| 309 | + $block->mExpiry = Block::parseExpiryInput( '1 week' ); |
| 310 | + |
| 311 | + $block->initialiseRange(); |
| 312 | + |
| 313 | + $block->insert(); |
| 314 | + |
| 315 | + // Log it |
| 316 | + # Prepare log parameters |
| 317 | + $logParams = array(); |
| 318 | + $logParams[] = 'indefinite'; |
| 319 | + $logParams[] = 'nocreate, angry-autoblock'; |
| 320 | + |
| 321 | + $log = new LogPage( 'block' ); |
| 322 | + $log->addEntry( 'block', SpecialPage::getTitleFor( 'Contributions', $range ), |
| 323 | + wfMsgForContent( 'abusefilter-blockreason', $rule_desc ), $logParams, self::getFilterUser() ); |
| 324 | + |
| 325 | + $display .= wfMsgNoTrans( 'abusefilter-blocked-display', $rule_desc ) ."<br />\n"; |
| 326 | + break; |
| 327 | + |
286 | 328 | case 'throttle': |
287 | 329 | $throttleId = array_shift( $parameters ); |
288 | 330 | list( $rateCount, $ratePeriod ) = explode( ',', array_shift( $parameters ) ); |
— | — | @@ -466,7 +508,7 @@ |
467 | 509 | |
468 | 510 | $anyMatch = false; |
469 | 511 | |
470 | | - global $wgAbuseFilterEmergencyDisableThreshold, $wgAbuseFilterEmergencyDisableCount; |
| 512 | + global $wgAbuseFilterEmergencyDisableThreshold, $wgAbuseFilterEmergencyDisableCount, $wgAbuseFilterEmergencyDisableAge; |
471 | 513 | |
472 | 514 | foreach( $filters as $filter => $matched ) { |
473 | 515 | if ($matched) { |
— | — | @@ -479,7 +521,10 @@ |
480 | 522 | $wgMemc->set( self::filterMatchesKey( $filter ), 1, self::$statsStoragePeriod ); |
481 | 523 | } |
482 | 524 | |
483 | | - if ($match_count > $wgAbuseFilterEmergencyDisableCount && ($match_count / $total) > $wgAbuseFilterEmergencyDisableThreshold) { |
| 525 | + $filter_age = wfTimestamp( TS_UNIX, self::$filters[$filter]['af_timestamp'] ); |
| 526 | + $throttle_exempt_time = $filter_age + $wgAbuseFilterEmergencyDisableAge; |
| 527 | + |
| 528 | + if ($throttle_exempt_time > time() && $match_count > $wgAbuseFilterEmergencyDisableCount && ($match_count / $total) > $wgAbuseFilterEmergencyDisableThreshold) { |
484 | 529 | // More than X matches, constituting more than Y% of last Z edits. Disable it. |
485 | 530 | $dbw = wfGetDB( DB_MASTER ); |
486 | 531 | $dbw->update( 'abuse_filter', array( 'af_enabled' => 0, 'af_throttled' => 1 ), array( 'af_id' => $filter ), __METHOD__ ); |
Index: trunk/extensions/AbuseFilter/AbuseFilter.i18n.php |
— | — | @@ -42,7 +42,7 @@ |
43 | 43 | A brief description of the abuse rule which your action matched is: $1", |
44 | 44 | |
45 | 45 | 'abusefilter-blocker' => 'Abuse filter', |
46 | | - 'abusefilter-blockreason' => 'Automatically blocked by abuse filter. Rule description: $1', |
| 46 | + 'abusefilter-blockreason' => 'Automatically blocked by abuse filter. Description of matched rule: $1', |
47 | 47 | 'abusefilter-degroupreason' => 'Rights automatically stripped by abuse filter. Rule description: $1', |
48 | 48 | |
49 | 49 | 'abusefilter-accountreserved' => 'This account name is reserved for use by the abuse filter.', |
— | — | @@ -133,6 +133,7 @@ |
134 | 134 | 'abusefilter-edit-action-degroup' => 'Remove the user from all privileged groups', |
135 | 135 | 'abusefilter-edit-action-block' => 'Block the user and/or IP address from editing', |
136 | 136 | 'abusefilter-edit-action-throttle' => 'Trigger actions only if the user trips a rate limit', |
| 137 | + 'abusefilter-edit-action-rangeblock' => 'Block the /16 range from which the user originates.', |
137 | 138 | 'abusefilter-edit-throttle-count' => 'Number of actions to allow:', |
138 | 139 | 'abusefilter-edit-throttle-period' => 'Period of time:', |
139 | 140 | 'abusefilter-edit-throttle-seconds' => '$1 {{PLURAL:$1|second|seconds}}', |
— | — | @@ -171,6 +172,7 @@ |
172 | 173 | 'abusefilter-edit-builder-misc-ternary' => 'Ternary operator (1 ? 2 : 3)', |
173 | 174 | 'abusefilter-edit-builder-misc-in' => 'contained in string (in)', |
174 | 175 | 'abusefilter-edit-builder-misc-like' => 'Matches regex (like)', |
| 176 | + 'abusefilter-edit-builder-misc-stringlit' => 'String literal ("")', |
175 | 177 | 'abusefilter-edit-builder-group-funcs' => 'Functions', |
176 | 178 | 'abusefilter-edit-builder-funcs-length' => 'String length (length)', |
177 | 179 | 'abusefilter-edit-builder-funcs-lcase' => 'To lower case (lcase)', |