X-Git-Url: https://git.toastfreeware.priv.at/toast/cookiecaptcha.git/blobdiff_plain/2b89cec777f9becfd199d676e757e2c2064bc554..def5670e9b43c6204d0fc36ea236f3b7910bd820:/ConfirmEdit.php diff --git a/ConfirmEdit.php b/ConfirmEdit.php index be229cf..f6cab65 100644 --- a/ConfirmEdit.php +++ b/ConfirmEdit.php @@ -6,7 +6,7 @@ * can extend the base to produce their fancy images in place of the * text-based test output here. * - * Copyright (C) 2005, 2006 Brion Vibber + * Copyright (C) 2005-2007 Brion Vibber * http://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify @@ -79,6 +79,7 @@ $wgCaptchaTriggers['edit'] = false; // Would check on every edit $wgCaptchaTriggers['create'] = false; // Check on page creation. $wgCaptchaTriggers['addurl'] = true; // Check on edits that add URLs $wgCaptchaTriggers['createaccount'] = true; // Special:Userlogin&type=signup +$wgCaptchaTriggers['badlogin'] = true; // Special:Userlogin after failure /** * You may wish to apply special rules for captcha triggering on some namespaces. @@ -108,7 +109,7 @@ global $wgCaptchaStorageClass; $wgCaptchaStorageClass = 'CaptchaSessionStore'; /** - * Number of sections a captcha session should last in the data cache + * Number of seconds a captcha session should last in the data cache * before expiring when managing through CaptchaCacheStore class. * * Default is a half hour. @@ -116,6 +117,18 @@ $wgCaptchaStorageClass = 'CaptchaSessionStore'; global $wgCaptchaSessionExpiration; $wgCaptchaSessionExpiration = 30 * 60; +/** + * Number of seconds after a bad login that a captcha will be shown to + * that client on the login form to slow down password-guessing bots. + * + * Has no effect if 'badlogin' is disabled in $wgCaptchaTriggers or + * if there is not a caching engine enabled. + * + * Default is five minutes. + */ +global $wgCaptchaBadLoginExpiration; +$wgCaptchaBadLoginExpiration = 5 * 60; + /** * Allow users who have confirmed their e-mail addresses to post * URL links without being harassed by the captcha. @@ -127,7 +140,7 @@ $ceAllowConfirmedEmail = false; * Regex to whitelist URLs to known-good sites... * For instance: * $wgCaptchaWhitelist = '#^https?://([a-z0-9-]+\\.)?(wikimedia|wikipedia)\.org/#i'; - * @fixme Use the 'spam-whitelist' thingy instead? + * Local admins can define a whitelist under [[MediaWiki:captcha-addurl-whitelist]] */ $wgCaptchaWhitelist = false; @@ -162,6 +175,22 @@ function ceSetup() { $wgHooks['UserCreateForm'][] = array( &$wgCaptcha, 'injectUserCreate' ); $wgHooks['AbortNewAccount'][] = array( &$wgCaptcha, 'confirmUserCreate' ); + + $wgHooks['LoginAuthenticateAudit'][] = array( &$wgCaptcha, 'triggerUserLogin' ); + $wgHooks['UserLoginForm'][] = array( &$wgCaptcha, 'injectUserLogin' ); + $wgHooks['AbortLogin'][] = array( &$wgCaptcha, 'confirmUserLogin' ); + + global $wgGroupPermissions, $wgCaptchaTriggers; + if( !$wgGroupPermissions['*']['read'] && $wgCaptchaTriggers['badlogin'] ) { + // We need to ensure that the captcha interface is accessible + // so that unauthenticated users can actually get in after a + // mistaken password typing. + global $wgWhitelistRead; + $image = Title::makeTitle( NS_SPECIAL, 'Captcha/image' ); + $help = Title::makeTitle( NS_SPECIAL, 'Captcha/help' ); + $wgWhitelistRead[] = $image->getPrefixedText(); + $wgWhitelistRead[] = $help->getPrefixedText(); + } } /** @@ -258,6 +287,67 @@ class SimpleCaptcha { return true; } + /** + * Inject a captcha into the user login form after a failed + * password attempt as a speedbump for mass attacks. + * @fixme if multiple thingies insert a header, could break + * @param SimpleTemplate $template + * @return bool true to keep running callbacks + */ + function injectUserLogin( &$template ) { + if( $this->isBadLoginTriggered() ) { + global $wgOut; + $template->set( 'header', + "
" . + $wgOut->parse( $this->getMessage( 'badlogin' ) ) . + $this->getForm() . + "
\n" ); + } + return true; + } + + /** + * When a bad login attempt is made, increment an expiring counter + * in the memcache cloud. Later checks for this may trigger a + * captcha display to prevent too many hits from the same place. + * @param User $user + * @param string $password + * @param int $retval authentication return value + * @return bool true to keep running callbacks + */ + function triggerUserLogin( $user, $password, $retval ) { + global $wgCaptchaTriggers, $wgCaptchaBadLoginExpiration, $wgMemc; + if( $retval == LoginForm::WRONG_PASS && $wgCaptchaTriggers['badlogin'] ) { + $key = $this->badLoginKey(); + $count = $wgMemc->get( $key ); + if( !$count ) { + $wgMemc->add( $key, 0, $wgCaptchaBadLoginExpiration ); + } + $count = $wgMemc->incr( $key ); + } + return true; + } + + /** + * Check if a bad login has already been registered for this + * IP address. If so, require a captcha. + * @return bool + * @access private + */ + function isBadLoginTriggered() { + global $wgMemc; + return intval( $wgMemc->get( $this->badLoginKey() ) ) > 0; + } + + /** + * Internal cache key for badlogin checks. + * @return string + * @access private + */ + function badLoginKey() { + return wfMemcKey( 'captcha', 'badlogin', 'ip', wfGetIP() ); + } + /** * Check if the submitted form matches the captcha session data provided * by the plugin when the form was generated. @@ -390,12 +480,70 @@ class SimpleCaptcha { /** * Filter callback function for URL whitelisting + * @param string url to check * @return bool true if unknown, false if whitelisted * @access private */ function filterLink( $url ) { global $wgCaptchaWhitelist; - return !( $wgCaptchaWhitelist && preg_match( $wgCaptchaWhitelist, $url ) ); + $source = wfMsgForContent( 'captcha-addurl-whitelist' ); + + $whitelist = wfEmptyMsg( 'captcha-addurl-whitelist', $source ) + ? false + : $this->buildRegexes( explode( "\n", $source ) ); + + $cwl = $wgCaptchaWhitelist !== false ? preg_match( $wgCaptchaWhitelist, $url ) : false; + $wl = $whitelist !== false ? preg_match( $whitelist, $url ) : false; + + return !( $cwl || $wl ); + } + + /** + * Build regex from whitelist + * @param string lines from [[MediaWiki:Captcha-addurl-whitelist]] + * @return string Regex or bool false if whitelist is empty + * @access private + */ + function buildRegexes( $lines ) { + # Code duplicated from the SpamBlacklist extension (r19197) + + # Strip comments and whitespace, then remove blanks + $lines = array_filter( array_map( 'trim', preg_replace( '/#.*$/', '', $lines ) ) ); + + # No lines, don't make a regex which will match everything + if ( count( $lines ) == 0 ) { + wfDebug( "No lines\n" ); + return false; + } else { + # Make regex + # It's faster using the S modifier even though it will usually only be run once + //$regex = 'http://+[a-z0-9_\-.]*(' . implode( '|', $lines ) . ')'; + //return '/' . str_replace( '/', '\/', preg_replace('|\\\*/|', '/', $regex) ) . '/Si'; + $regexes = ''; + $regexStart = '/http:\/\/+[a-z0-9_\-.]*('; + $regexEnd = ')/Si'; + $regexMax = 4096; + $build = false; + foreach( $lines as $line ) { + // FIXME: not very robust size check, but should work. :) + if( $build === false ) { + $build = $line; + } elseif( strlen( $build ) + strlen( $line ) > $regexMax ) { + $regexes .= $regexStart . + str_replace( '/', '\/', preg_replace('|\\\*/|', '/', $build) ) . + $regexEnd; + $build = $line; + } else { + $build .= '|' . $line; + } + } + if( $build !== false ) { + $regexes .= $regexStart . + str_replace( '/', '\/', preg_replace('|\\\*/|', '/', $build) ) . + $regexEnd; + } + return $regexes; + } } /** @@ -436,6 +584,25 @@ class SimpleCaptcha { } return true; } + + /** + * Hook for user login form submissions. + * @param User $u + * @param string $message + * @return bool true to continue, false to abort user creation + */ + function confirmUserLogin( $u, $pass, &$retval ) { + if( $this->isBadLoginTriggered() ) { + $this->trigger = "post-badlogin login '" . $u->getName() . "'"; + if( !$this->passCaptcha() ) { + $message = wfMsg( 'captcha-badlogin-fail' ); + // Emulate a bad-password return to confuse the shit out of attackers + $retval = LoginForm::WRONG_PASS; + return false; + } + } + return true; + } /** * Given a required captcha run, test form input for correct @@ -608,4 +775,4 @@ class CaptchaCacheStore { } # End invocation guard -?> +