2008-06-27 06:18:51 +00:00
< ? php
if ( ! defined ( 'MEDIAWIKI' ) )
die ();
class AbuseFilter {
2008-07-15 08:46:17 +00:00
public static $condCount = 0 ;
public static $condCheckCount = array ();
public static $condMatchCount = array ();
2008-07-17 02:43:45 +00:00
public static $statsStoragePeriod = 86400 ;
2008-07-18 02:18:58 +00:00
public static $modifierWords = array ( 'norm' , 'supernorm' , 'lcase' , 'length' , 'specialratio' , 'htmldecode' , 'htmlencode' , 'urlencode' , 'urldecode' , 'htmlfullencode' );
2008-07-17 13:40:45 +00:00
public static $operatorWords = array ( 'eq' , 'neq' , 'gt' , 'lt' , 'regex' , 'contains' );
public static $validJoinConditions = array ( '!' , '|' , '&' );
2008-07-18 02:18:58 +00:00
public static $condLimitEnabled = true ;
public static $tokenCache = array ();
public static $modifyCache = array ();
2008-07-15 08:46:17 +00:00
2008-06-27 06:18:51 +00:00
public static function generateUserVars ( $user ) {
$vars = array ();
// Load all the data we want.
$user -> load ();
$vars [ 'USER_EDITCOUNT' ] = $user -> getEditCount ();
$vars [ 'USER_AGE' ] = time () - wfTimestampOrNull ( TS_UNIX , $user -> getRegistration () );
$vars [ 'USER_NAME' ] = $user -> getName ();
$vars [ 'USER_GROUPS' ] = implode ( ',' , $user -> getEffectiveGroups () );
$vars [ 'USER_EMAILCONFIRM' ] = $user -> getEmailAuthenticationTimestamp ();
// More to come
return $vars ;
}
2008-07-18 02:18:58 +00:00
public static function disableConditionLimit () {
// For use in batch scripts and the like
self :: $condLimitEnabled = false ;
}
2008-06-27 06:18:51 +00:00
public static function generateTitleVars ( $title , $prefix ) {
$vars = array ();
$vars [ $prefix . " _NAMESPACE " ] = $title -> getNamespace ();
$vars [ $prefix . " _TEXT " ] = $title -> getText ();
$vars [ $prefix . " _PREFIXEDTEXT " ] = $title -> getPrefixedText ();
if ( $title -> mRestrictionsLoaded ) {
// Don't bother if they're unloaded
foreach ( $title -> mRestrictions as $action => $rights ) {
$rights = count ( $rights ) ? $rights : array ();
$vars [ $prefix . " _RESTRICTIONS_ " . $action ] = implode ( ',' , $rights );
}
}
return $vars ;
}
public static function checkConditions ( $conds , $vars ) {
2008-07-15 08:46:17 +00:00
$fname = __METHOD__ ;
2008-07-17 02:43:45 +00:00
global $wgAbuseFilterConditionLimit ;
2008-07-18 02:18:58 +00:00
if ( self :: $condCount > $wgAbuseFilterConditionLimit && self :: $condLimitEnabled ) {
2008-07-17 02:43:45 +00:00
return false ;
}
2008-06-27 06:18:51 +00:00
// Remove leading/trailing spaces
$conds = trim ( $conds );
2008-07-15 08:46:17 +00:00
self :: $condCount ++ ;
self :: $condCheckCount [ $conds ] ++ ;
2008-06-27 06:18:51 +00:00
// Is it a set?
if ( substr ( $conds , 0 , 1 ) == '(' && substr ( $conds , - 1 , 1 ) == ')' ) {
// We should have a set here.
$setInternal = substr ( $conds , 1 , - 1 );
// Get the join condition ( &, | or ! )
list ( $setJoinCondition , $conditionList ) = explode ( ':' , $setInternal , 2 );
$setJoinCondition = trim ( $setJoinCondition );
2008-07-17 13:40:45 +00:00
if ( ! in_array ( $setJoinCondition , self :: $validJoinConditions )) {
2008-06-27 06:18:51 +00:00
// Bad join condition
return false ;
}
// Tokenise.
$allConditions = self :: tokeniseList ( $conditionList );
foreach ( $allConditions as $thisCond ) {
if ( trim ( $thisCond ) == '' ) {
// Ignore it
2008-06-27 11:13:07 +00:00
continue ;
2008-06-27 06:18:51 +00:00
} else {
$result = self :: checkConditions ( $thisCond , $vars );
}
2008-07-17 02:43:45 +00:00
// We've hit the limit.
2008-07-18 02:18:58 +00:00
if ( self :: $condCount > $wgAbuseFilterConditionLimit && self :: $condLimitEnabled ) {
2008-07-17 02:43:45 +00:00
return false ;
}
2008-06-27 06:18:51 +00:00
// Need we short-circuit?
if ( $setJoinCondition == '|' && $result ) {
// Definite yes.
2008-07-15 08:46:17 +00:00
// print "Short-circuit YES for condition $conds, as $thisCond is TRUE\n";
self :: $condMatchCount [ $conds ] ++ ;
2008-06-27 06:18:51 +00:00
return true ;
} elseif ( $setJoinCondition == '&' && ! $result ) {
2008-07-15 08:46:17 +00:00
// print "Short-circuit NO for condition $conds, as $thisCond is FALSE\n";
2008-06-27 06:18:51 +00:00
// Definite no.
return false ;
} elseif ( $setJoinCondition == '!' && $result ) {
2008-07-15 08:46:17 +00:00
// print "Short-circuit NO for condition $conds, as $thisCond is TRUE\n";
2008-06-27 06:18:51 +00:00
// Definite no.
return false ;
}
}
2008-07-15 08:46:17 +00:00
if ( $setJoinCondition != '|' )
self :: $condMatchCount [ $conds ] ++ ;
2008-06-27 06:18:51 +00:00
// Return the default result.
return ( $setJoinCondition != '|' ); // Only OR returns false after checking all conditions.
}
2008-07-15 08:46:17 +00:00
wfProfileIn ( " $fname -evaluate " );
2008-06-27 06:18:51 +00:00
// Grab the first word.
list ( $thisWord ) = explode ( ' ' , $conds );
$wordNum = 0 ;
// Check for modifiers
$modifier = '' ;
2008-07-17 13:40:45 +00:00
if ( in_array ( $thisWord , self :: $modifierWords ) ) {
2008-06-27 06:18:51 +00:00
$modifier = $thisWord ;
$wordNum ++ ;
$thisWord = explode ( ' ' , $conds );
$thisWord = $thisWord [ $wordNum ];
}
2008-06-27 11:13:07 +00:00
if ( in_array ( $thisWord , array_keys ( $vars ) ) ) {
2008-06-27 06:18:51 +00:00
$value = $vars [ $thisWord ];
if ( $modifier ) {
$value = self :: modifyValue ( $modifier , $value );
}
// We have a variable. Now read the next word to see what we're doing with it.
$wordNum ++ ;
$thisWord = explode ( ' ' , $conds );
$thisWord = $thisWord [ $wordNum ];
2008-07-17 13:40:45 +00:00
if ( in_array ( $thisWord , self :: $operatorWords ) ) {
2008-06-27 06:18:51 +00:00
// Get the rest of the string after the operator.
$parameters = explode ( ' ' , $conds , $wordNum + 2 );
2008-07-15 08:46:17 +00:00
$parameters = trim ( $parameters [ $wordNum + 1 ]);
2008-07-18 02:18:58 +00:00
list ( $firstWord , $rest ) = explode ( ' ' , $parameters , 2 );
if ( in_array ( $firstWord , self :: $modifierWords ) && in_array ( $rest , array_keys ( $vars ))) {
// Allow the compare target to be modified, too.
$parameters = self :: modifyValue ( $firstWord , $vars [ $rest ] );
} elseif ( in_array ( $parameters , array_keys ( $vars ) )) {
2008-07-15 08:46:17 +00:00
$parameters = $vars [ $parameters ];
}
wfProfileOut ( " $fname -evaluate " );
$result = self :: checkOperator ( $thisWord , $value , $parameters );
2008-06-27 06:18:51 +00:00
2008-07-15 08:46:17 +00:00
if ( $result )
self :: $condMatchCount [ $conds ] ++ ;
return $result ;
2008-06-27 06:18:51 +00:00
}
} else {
2008-07-15 08:46:17 +00:00
// print "Unknown var $thisWord\n";
2008-06-27 06:18:51 +00:00
}
2008-07-15 08:46:17 +00:00
wfProfileOut ( " $fname -evaluate " );
2008-06-27 06:18:51 +00:00
}
public static function tokeniseList ( $list ) {
2008-07-18 02:18:58 +00:00
if ( isset ( self :: $tokenCache [ $list ])) {
return self :: $tokenCache [ $list ];
}
2008-07-18 08:30:25 +00:00
wfProfileIn ( __METHOD__ );
2008-07-18 02:18:58 +00:00
2008-06-27 06:18:51 +00:00
// Parse it, character by character.
$escapeNext = false ;
$listLevel = 0 ;
$thisToken = '' ;
$allTokens = array ();
for ( $i = 0 ; $i < strlen ( $list ); $i ++ ) {
$char = substr ( $list , $i , 1 );
$suppressAdd = false ;
// We don't care about semicolons and so on unless it's
if ( $listLevel == 0 ) {
if ( $char == " \\ " ) {
if ( $escapeNext ) { // Escaped backslash
$escapeNext = false ;
} else {
$escapeNext = true ;
$suppressAdd = true ;
}
} elseif ( $char == ';' ) {
if ( $escapeNext ) {
$escapeNext = false ; // Escaped semicolon
} else { // Next token, plz
$escapeNext = false ;
$allTokens [] = $thisToken ;
$thisToken = '' ;
$suppressAdd = true ;
}
} elseif ( $escapeNext ) {
$escapeNext = false ;
$thisToken .= " \\ " ; // The backslash wasn't intended to escape.
}
}
if ( $char == '(' && $lastChar == ';' ) {
// A list!
$listLevel ++ ;
} elseif ( $char == ')' && ( $lastChar == ';' || $lastChar == ')' || $lastChar = '' ) ) {
$listLevel -- ; // End of a list.
}
if ( ! $suppressAdd ) {
$thisToken .= $char ;
}
// Ignore whitespace.
2008-06-28 04:11:21 +00:00
if ( $char != ' ' && $char != " \n " && $char != " \t " ) {
2008-06-27 06:18:51 +00:00
$lastChar = $char ;
}
}
// Put any leftovers in
$allTokens [] = $thisToken ;
2008-07-18 02:18:58 +00:00
2008-07-18 08:30:25 +00:00
// Don't let it fill up.
if ( count ( self :: $tokenCache ) <= 1000 ) {
self :: $tokenCache [ $list ] = $allTokens ;
}
2008-06-27 06:18:51 +00:00
2008-07-15 08:46:17 +00:00
wfProfileOut ( __METHOD__ );
2008-06-27 06:18:51 +00:00
return $allTokens ;
}
public static function modifyValue ( $modifier , $value ) {
2008-07-18 02:18:58 +00:00
if ( isset ( self :: $modifyCache [ $modifier ][ $value ]))
return self :: $modifyCache [ $modifier ][ $value ];
2008-07-18 08:30:25 +00:00
wfProfileIn ( __METHOD__ );
wfProfileIn ( __METHOD__ . '-' . $modifier );
2008-07-18 02:18:58 +00:00
2008-06-27 06:18:51 +00:00
if ( $modifier == 'norm' ) {
2008-07-18 02:18:58 +00:00
$val = self :: normalise ( $value );
2008-06-27 06:18:51 +00:00
} elseif ( $modifier == 'supernorm' ) {
2008-07-18 02:18:58 +00:00
$val = self :: superNormalise ( $value );
2008-06-27 06:18:51 +00:00
} elseif ( $modifier == 'lcase' ) {
2008-07-18 02:18:58 +00:00
$val = strtolower ( $value );
2008-06-27 06:18:51 +00:00
} elseif ( $modifier == 'length' ) {
2008-07-18 02:18:58 +00:00
$val = strlen ( $value );
2008-06-27 06:18:51 +00:00
} elseif ( $modifier == 'specialratio' ) {
$specialsonly = preg_replace ( '/\w/' , '' , $value );
2008-07-18 02:18:58 +00:00
$val = ( strlen ( $specialsonly ) / strlen ( $value ));
2008-07-17 13:40:45 +00:00
} elseif ( $modifier == 'htmlencode' ) {
2008-07-18 02:18:58 +00:00
$val = htmlspecialchars ( $value );
2008-07-17 13:40:45 +00:00
} elseif ( $modifier == 'htmldecode' ) {
2008-07-18 02:18:58 +00:00
$val = htmlspecialchars_decode ( $value );
2008-07-17 13:40:45 +00:00
} elseif ( $modifier == 'urlencode' ) {
2008-07-18 02:18:58 +00:00
$val = urlencode ( $value );
2008-07-17 13:40:45 +00:00
} elseif ( $modifier == 'urldecode' ) {
2008-07-18 02:18:58 +00:00
$val = urldecode ( $value );
} elseif ( $modifier == 'htmlfullencode' ) {
$val = htmlentities ( $value );
} elseif ( $modifier == 'simplenorm' ) {
$val = preg_replace ( '/[\d\W]+/' , '' , $value );
$val = strtolower ( $value );
}
if ( count ( self :: $modifyCache [ $modifier ][ $value ]) > 1000 ) {
self :: $modifyCache = array ();
2008-06-27 06:18:51 +00:00
}
2008-07-18 08:30:25 +00:00
wfProfileOut ( __METHOD__ . '-' . $modifier );
wfProfileOut ( __METHOD__ );
2008-07-18 02:18:58 +00:00
return self :: $modifyCache [ $modifier ][ $value ] = $val ;
2008-06-27 06:18:51 +00:00
}
2008-07-18 08:30:25 +00:00
public static function checkOperator ( $operator , $value , $parameters ) {
wfProfileIn ( __METHOD__ );
wfProfileIn ( __METHOD__ . '-' . $operator );
2008-06-27 06:18:51 +00:00
if ( $operator == 'eq' ) {
2008-07-18 08:30:25 +00:00
$val = $value == $parameters ;
2008-06-27 06:18:51 +00:00
} elseif ( $operator == 'neq' ) {
2008-07-18 08:30:25 +00:00
$val = $value != $parameters ;
2008-06-27 06:18:51 +00:00
} elseif ( $operator == 'gt' ) {
2008-07-18 08:30:25 +00:00
$val = $value > $parameters ;
2008-06-27 06:18:51 +00:00
} elseif ( $operator == 'lt' ) {
2008-07-18 08:30:25 +00:00
$val = $value < $parameters ;
2008-06-27 06:18:51 +00:00
} elseif ( $operator == 'regex' ) {
2008-07-18 08:30:25 +00:00
$val = preg_match ( $parameters , $value );
2008-06-27 08:50:03 +00:00
} elseif ( $operator == 'contains' ) {
2008-07-18 08:30:25 +00:00
$val = strpos ( $value , $parameters ) !== false ;
2008-06-27 06:18:51 +00:00
} else {
2008-07-18 08:30:25 +00:00
$val = false ;
2008-06-27 06:18:51 +00:00
}
2008-07-18 08:30:25 +00:00
wfProfileOut ( __METHOD__ . '-' . $operator );
wfProfileOut ( __METHOD__ );
return $val ;
2008-06-27 06:18:51 +00:00
}
public static function superNormalise ( $text ) {
2008-07-18 08:30:25 +00:00
wfProfileIn ( __METHOD__ );
2008-06-27 06:18:51 +00:00
$text = self :: normalise ( $text );
2008-07-18 02:18:58 +00:00
$text = AntiSpoof :: stringToList ( $text ); // Split to a char array.
2008-06-27 06:18:51 +00:00
sort ( $text );
$text = array_unique ( $text ); // Remove duplicate characters.
2008-07-18 02:18:58 +00:00
$text = AntiSpoof :: listToString ( $text );
2008-07-18 08:30:25 +00:00
wfProfileOut ( __METHOD__ );
2008-06-27 06:18:51 +00:00
return $text ;
}
public static function normalise ( $text ) {
2008-07-18 08:30:25 +00:00
wfProfileIn ( __METHOD__ );
2008-06-27 06:18:51 +00:00
$old_text = $text ;
$text = strtolower ( $text );
2008-07-18 02:18:58 +00:00
$text = AntiSpoof :: stringToList ( $text );
2008-06-27 06:18:51 +00:00
$text = AntiSpoof :: equivString ( $text ); // Normalise
// Remove repeated characters, but not all duplicates.
$oldText = $text ;
$text = array ( $oldText [ 0 ]);
for ( $i = 1 ; $i < count ( $oldText ); $i ++ ) {
if ( $oldText [ $i ] != $oldText [ $i - 1 ]) {
$text [] = $oldText [ $i ];
}
}
2008-07-18 02:18:58 +00:00
$text = AntiSpoof :: listToString ( $text ); // Sort in alphabetical order, put back as it was.
$text = preg_replace ( '/\W/' , '' , $text ); // Remove any special characters.
2008-06-27 06:18:51 +00:00
2008-07-18 08:30:25 +00:00
wfProfileOut ( __METHOD__ );
2008-06-27 06:18:51 +00:00
return $text ;
}
2008-07-18 08:30:25 +00:00
public static function tokenCacheKey () {
return wfMemcKey ( 'abusefilter' , 'tokencache' );
}
2008-06-27 06:18:51 +00:00
public static function filterAction ( $vars , $title ) {
2008-07-18 08:30:25 +00:00
global $wgUser , $wgMemc ;
// Pick up cached data
if ( ! count ( self :: $tokenCache )) {
self :: $tokenCache = $wgMemc -> get ( self :: tokenCacheKey () );
}
$tokenCacheCount_orig = count ( self :: $tokenCache );
2008-06-27 06:18:51 +00:00
// Fetch from the database.
$dbr = wfGetDB ( DB_SLAVE );
2008-06-27 07:55:13 +00:00
$res = $dbr -> select ( 'abuse_filter' , '*' , array ( 'af_enabled' => 1 ) );
2008-06-27 06:18:51 +00:00
$blocking_filters = array ();
$log_entries = array ();
$log_template = array ( 'afl_user' => $wgUser -> getId (), 'afl_user_text' => $wgUser -> getName (),
'afl_var_dump' => serialize ( $vars ), 'afl_timestamp' => $dbr -> timestamp ( wfTimestampNow ()),
'afl_namespace' => $title -> getNamespace (), 'afl_title' => $title -> getDbKey (), 'afl_ip' => wfGetIp () );
$doneActionsByFilter = array ();
2008-07-17 02:43:45 +00:00
$filter_matched = array ();
2008-06-27 06:18:51 +00:00
while ( $row = $dbr -> fetchObject ( $res ) ) {
if ( self :: checkConditions ( $row -> af_pattern , $vars ) ) {
$blocking_filters [ $row -> af_id ] = $row ;
$newLog = $log_template ;
$newLog [ 'afl_filter' ] = $row -> af_id ;
$newLog [ 'afl_action' ] = $vars [ 'ACTION' ];
$log_entries [] = $newLog ;
$doneActionsByFilter [ $row -> af_id ] = array ();
2008-07-17 02:43:45 +00:00
$filter_matched [ $row -> af_id ] = true ;
} else {
$filter_matched [ $row -> af_id ] = false ;
2008-06-27 06:18:51 +00:00
}
}
2008-07-18 08:30:25 +00:00
//// Clean up from checking all the filters
2008-07-18 02:18:58 +00:00
// Don't store stats if the cond limit is disabled.
2008-07-18 08:30:25 +00:00
// It's probably a batch process or similar.
2008-07-18 02:18:58 +00:00
if ( ! self :: $condLimitEnabled )
self :: recordStats ( $filter_matched );
2008-07-18 08:30:25 +00:00
// Store the token list in memcached for future use.
if ( count ( self :: $tokenCache ) > $tokenCacheCount_orig ) {
if ( count ( self :: $tokenCache ) > 750 ) {
// slice the first quarter off.
// This way, out-of-date stuff eventually goes away.
self :: $tokenCache = array_slice ( self :: $tokenCache , 250 , 500 , true /* preserve keys */ );
}
$wgMemc -> set ( self :: tokenCacheKey (), self :: $tokenCache , 86400 );
}
2008-07-17 02:43:45 +00:00
2008-06-27 06:18:51 +00:00
if ( count ( $blocking_filters ) == 0 ) {
// No problems.
return true ;
}
// Retrieve the consequences.
2008-06-29 14:00:39 +00:00
$res = $dbr -> select ( 'abuse_filter_action' , '*' , array ( 'afa_filter' => array_keys ( $blocking_filters ) ), __METHOD__ , array ( " ORDER BY " => " (afa_consequence in ('throttle','warn'))-(afa_consequence in ('disallow')) desc " ) );
// We want throttles, warnings first, as they have a bit of a special treatment. We want disallow last.
2008-06-27 06:18:51 +00:00
$actions_done = array ();
$throttled_filters = array ();
$display = '' ;
while ( $row = $dbr -> fetchObject ( $res ) ) {
2008-06-29 14:00:39 +00:00
// Don't do the same action-parameters twice
2008-06-27 06:18:51 +00:00
$action_key = md5 ( $row -> afa_consequence . $row -> afa_parameters );
2008-06-29 14:00:39 +00:00
// Skip if we've already done this action-parameter, or a passive action has sufficed.
$skipAction = ( in_array ( $action_key , $actions_done ) || in_array ( $row -> afa_filter , $throttled_filters ) );
// Don't disallow if we've already done something active. It produces two messages, where one would suffice.
if ( $row -> afa_consequence == 'disallow' && ! $skipAction ) {
$doneActiveActions = array_diff ( $doneActionsByFilter [ $row -> afa_filter ], array ( 'throttle' , 'warn' /* passive actions */ ) );
$skipAction = ( bool ) count ( $doneActiveActions );
}
if ( ! $skipAction ) {
// Unpack parameters
2008-06-27 06:18:51 +00:00
$parameters = explode ( " \n " , $row -> afa_parameters );
2008-06-29 14:00:39 +00:00
// Take the action.
$result = self :: takeConsequenceAction ( $row -> afa_consequence , $parameters , $title , $vars , & $display , & $continue , $blocking_filters [ $row -> afa_filter ] -> af_public_comments );
// Don't do it twice.
2008-06-27 06:18:51 +00:00
$doneActionsByFilter [ $row -> afa_filter ][] = $row -> afa_consequence ;
2008-06-29 14:00:39 +00:00
$actions_done [] = $action_key ;
// Only execute other actions for a filter if that filter's rate limiter has been tripped.
2008-06-27 06:18:51 +00:00
if ( ! $result ) {
2008-06-29 14:00:39 +00:00
$throttled_filters [] = $row -> afa_filter ;
2008-06-27 06:18:51 +00:00
}
} else {
// Ignore it, until we hit the rate limit.
}
}
$dbw = wfGetDB ( DB_MASTER );
// Log it
foreach ( $log_entries as $index => $entry ) {
$log_entries [ $index ][ 'afl_actions' ] = implode ( ',' , $doneActionsByFilter [ $entry [ 'afl_filter' ]] );
// Increment the hit counter
$dbw -> update ( 'abuse_filter' , array ( 'af_hit_count=af_hit_count+1' ), array ( 'af_id' => $entry [ 'afl_filter' ] ), __METHOD__ );
}
$dbw -> insert ( 'abuse_filter_log' , $log_entries , __METHOD__ );
return $display ;
}
2008-06-29 14:00:39 +00:00
public static function takeConsequenceAction ( $action , $parameters , $title , $vars , & $display , & $continue , $rule_desc ) {
2008-06-27 06:18:51 +00:00
switch ( $action ) {
case 'warn' :
wfLoadExtensionMessages ( 'AbuseFilter' );
if ( ! $_SESSION [ 'abusefilter-warned' ]) {
$_SESSION [ 'abusefilter-warned' ] = true ;
// Threaten them a little bit
if ( strlen ( $parameters [ 0 ])) {
2008-06-29 20:10:42 +00:00
$display .= call_user_func_array ( 'wfMsgNoTrans' , $parameters ) . " \n " ;
2008-06-27 06:18:51 +00:00
} else {
// Generic message.
2008-06-29 20:10:42 +00:00
$display .= wfMsgNoTrans ( 'abusefilter-warning' , $rule_desc ) . " <br /> \n " ;
2008-06-27 06:18:51 +00:00
}
return false ; // Don't apply the other stuff yet.
} else {
// We already warned them
$_SESSION [ 'abusefilter-warned' ] = false ;
}
break ;
case 'disallow' :
wfLoadExtensionMessages ( 'AbuseFilter' );
// Don't let them do it
if ( strlen ( $parameters [ 0 ])) {
2008-06-29 20:10:42 +00:00
$display .= call_user_func_array ( 'wfMsgNoTrans' , $parameters ) . " \n " ;
2008-06-27 06:18:51 +00:00
} else {
// Generic message.
2008-06-29 20:10:42 +00:00
$display .= wfMsgNoTrans ( 'abusefilter-disallowed' , $rule_desc ) . " <br /> \n " ;
2008-06-27 06:18:51 +00:00
}
break ;
case 'block' :
wfLoadExtensionMessages ( 'AbuseFilter' );
global $wgUser ;
2008-07-09 07:02:13 +00:00
$filterUser = AbuseFilter :: getFilterUser ();
2008-06-27 06:18:51 +00:00
// Create a block.
$block = new Block ;
$block -> mAddress = $wgUser -> getName ();
$block -> mUser = $wgUser -> getId ();
2008-06-27 09:38:54 +00:00
$block -> mBy = User :: idFromName ( wfMsgForContent ( 'abusefilter-blocker' ) ); // Let's say the site owner blocked them
2008-06-27 06:18:51 +00:00
$block -> mByName = wfMsgForContent ( 'abusefilter-blocker' );
2008-06-29 14:00:39 +00:00
$block -> mReason = wfMsgForContent ( 'abusefilter-blockreason' , $rule_desc );
2008-06-27 06:18:51 +00:00
$block -> mTimestamp = wfTimestampNow ();
$block -> mEnableAutoblock = 1 ;
$block -> mAngryAutoblock = 1 ; // Block lots of IPs
$block -> mCreateAccount = 1 ;
$block -> mExpiry = 'infinity' ;
$block -> insert ();
2008-07-09 07:02:13 +00:00
// Log it
# Prepare log parameters
$logParams = array ();
$logParams [] = 'indefinite' ;
$logParams [] = 'nocreate, angry-autoblock' ;
$log = new LogPage ( 'block' );
$log -> addEntry ( 'block' , Title :: makeTitle ( NS_USER , $wgUser -> getName () ),
wfMsgForContent ( 'abusefilter-blockreason' , $rule_desc ), $logParams , self :: getFilterUser () );
2008-06-29 20:10:42 +00:00
$display .= wfMsgNoTrans ( 'abusefilter-blocked-display' , $rule_desc ) . " <br /> \n " ;
2008-06-27 06:18:51 +00:00
break ;
case 'throttle' :
$throttleId = array_shift ( $parameters );
list ( $rateCount , $ratePeriod ) = explode ( ',' , array_shift ( $parameters ) );
$hitThrottle = false ;
// The rest are throttle-types.
foreach ( $parameters as $throttleType ) {
$hitThrottle = $hitThrottle || self :: isThrottled ( $throttleId , $throttleType , $title , $rateCount , $ratePeriod );
}
return $hitThrottle ;
break ;
case 'degroup' :
wfLoadExtensionMessages ( 'AbuseFilter' );
global $wgUser ;
// Remove all groups from the user. Ouch.
$groups = $wgUser -> getGroups ();
foreach ( $groups as $group ) {
$wgUser -> removeGroup ( $group );
}
2008-06-29 20:10:42 +00:00
$display .= wfMsgNoTrans ( 'abusefilter-degrouped' , $rule_desc ) . " <br /> \n " ;
2008-06-27 06:18:51 +00:00
2008-07-09 07:02:13 +00:00
// Log it.
$log = new LogPage ( 'rights' );
$log -> addEntry ( 'rights' ,
$wgUser -> getUserPage (),
wfMsgForContent ( 'abusefilter-degroupreason' , $rule_desc ),
array (
implode ( ', ' , $groups ),
wfMsgForContent ( 'rightsnone' )
)
, self :: getFilterUser () );
2008-06-27 06:18:51 +00:00
break ;
case 'blockautopromote' :
wfLoadExtensionMessages ( 'AbuseFilter' );
global $wgUser , $wgMemc ;
$blockPeriod = ( int ) mt_rand ( 3 * 86400 , 7 * 86400 ); // Block for 3-7 days.
$wgMemc -> set ( self :: autoPromoteBlockKey ( $wgUser ), true , $blockPeriod );
2008-06-29 20:10:42 +00:00
$display .= wfMsgNoTrans ( 'abusefilter-autopromote-blocked' , $rule_desc ) . " <br /> \n " ;
2008-06-27 06:18:51 +00:00
break ;
case 'flag' :
// Do nothing. Here for completeness.
break ;
}
return true ;
}
public static function isThrottled ( $throttleId , $types , $title , $rateCount , $ratePeriod ) {
global $wgMemc ;
$key = self :: throttleKey ( $throttleId , $types , $title );
$count = $wgMemc -> get ( $key );
if ( $count > 0 ) {
$wgMemc -> incr ( $key );
if ( $count > $rateCount ) {
//die( "Hit rate limiter: $count actions, against limit of $rateCount actions in $ratePeriod seconds (key is $key).\n" );
$wgMemc -> delete ( $key );
return true ; // THROTTLED
}
} else {
$wgMemc -> add ( $key , 1 , $ratePeriod );
}
return false ; // NOT THROTTLED
}
public static function throttleIdentifier ( $type , $title ) {
global $wgUser ;
switch ( $type ) {
case 'ip' :
$identifier = wfGetIp ();
break ;
case 'user' :
$identifier = $wgUser -> getId ();
break ;
case 'range' :
$identifier = substr ( IP :: toHex ( wfGetIp ()), 0 , 4 );
break ;
case 'creationdate' :
$reg = $wgUser -> getRegistration ();
$identifier = $reg - ( $reg % 86400 );
break ;
case 'editcount' :
// Hack for detecting different single-purpose accounts.
$identifier = $wgUser -> getEditCount ();
break ;
case 'site' :
return 1 ;
break ;
case 'page' :
return $title -> getPrefixedText ();
break ;
}
return $identifier ;
}
public static function throttleKey ( $throttleId , $type , $title ) {
$identifier = '' ;
$types = explode ( ',' , $type );
$identifiers = array ();
foreach ( $types as $subtype ) {
$identifiers [] = self :: throttleIdentifier ( $subtype , $title );
}
$identifier = implode ( ':' , $identifiers );
return wfMemcKey ( 'abusefilter' , 'throttle' , $throttleId , $type , $identifier );
}
public static function autoPromoteBlockKey ( $user ) {
return wfMemcKey ( 'abusefilter' , 'block-autopromote' , $user -> getId () );
}
2008-07-09 07:02:13 +00:00
2008-07-17 02:43:45 +00:00
public static function recordStats ( $filters ) {
global $wgAbuseFilterConditionLimit , $wgMemc ;
$overflow_triggered = ( self :: $condCount > $wgAbuseFilterConditionLimit );
$filter_triggered = count ( $blocking_filters );
$overflow_key = self :: filterLimitReachedKey ();
$total_key = self :: filterUsedKey ();
$total = $wgMemc -> get ( $total_key );
$storage_period = self :: $statsStoragePeriod ; // One day.
if ( ! $total || $total > 1000 ) {
$wgMemc -> set ( $total_key , 1 , $storage_period );
if ( $overflow_triggered ) {
$wgMemc -> set ( $overflow_key , 1 , $storage_period );
} else {
$wgMemc -> set ( $overflow_key , 0 , $storage_period );
}
$anyMatch = false ;
foreach ( $filters as $filter => $matched ) {
$filter_key = self :: filterMatchesKey ( $filter );
if ( $matched ) {
$anyMatch = true ;
$wgMemc -> set ( $filter_key , 1 , $storage_period );
} else {
$wgMemc -> set ( $filter_key , 0 , $storage_period );
}
}
if ( $anyMatch ) {
$wgMemc -> set ( self :: filterMatchesKey (), 1 , $storage_period );
} else {
$wgMemc -> set ( self :: filterMatchesKey (), 0 , $storage_period );
}
return ;
}
$wgMemc -> incr ( $total_key );
if ( $overflow_triggered ) {
$wgMemc -> incr ( $overflow_key );
}
$anyMatch = false ;
global $wgAbuseFilterEmergencyDisableThreshold , $wgAbuseFilterEmergencyDisableCount ;
foreach ( $filters as $filter => $matched ) {
if ( $matched ) {
$anyMatch = true ;
$match_count = $wgMemc -> get ( self :: filterMatchesKey ( $filter ) );
if ( $match_count > 0 ) {
$wgMemc -> incr ( self :: filterMatchesKey ( $filter ) );
} else {
$wgMemc -> set ( self :: filterMatchesKey ( $filter ), 1 , self :: $statsStoragePeriod );
}
if ( $match_count > $wgAbuseFilterEmergencyDisableCount && ( $match_count / $total ) > $wgAbuseFilterEmergencyDisableThreshold ) {
// More than X matches, constituting more than Y% of last Z edits. Disable it.
$dbw = wfGetDB ( DB_MASTER );
$dbw -> update ( 'abuse_filter' , array ( 'af_enabled' => 0 , 'af_throttled' => 1 ), array ( 'af_id' => $filter ), __METHOD__ );
}
}
}
if ( $anyMatch ) {
$wgMemc -> incr ( self :: filterMatchesKey () );
}
}
public static function filterLimitReachedKey () {
return wfMemcKey ( 'abusefilter' , 'stats' , 'overflow' );
}
public static function filterUsedKey () {
return wfMemcKey ( 'abusefilter' , 'stats' , 'total' );
}
public static function filterMatchesKey ( $filter = null ) {
return wfMemcKey ( 'abusefilter' , 'stats' , 'matches' , $filter );
}
2008-07-09 07:02:13 +00:00
public static function getFilterUser () {
wfLoadExtensionMessages ( 'AbuseFilter' );
$user = User :: newFromName ( wfMsgForContent ( 'abusefilter-blocker' ) );
$user -> load ();
if ( $user -> getId () && $user -> mPassword == '' ) {
// Already set up.
return $user ;
}
// Not set up. Create it.
if ( ! $user -> getId ()) {
$user -> addToDatabase ();
$user -> saveSettings ();
} else {
// Take over the account
$user -> setPassword ( null );
$user -> setEmail ( null );
$user -> saveSettings ();
}
# Promote user so it doesn't look too crazy.
$user -> addGroup ( 'sysop' );
# Increment site_stats.ss_users
$ssu = new SiteStatsUpdate ( 0 , 0 , 0 , 0 , 1 );
$ssu -> doUpdate ();
return $user ;
}
2008-06-27 06:18:51 +00:00
}