From ff0f56bc8cd1aebbcc32e6dd33bb2eb421e55ebb Mon Sep 17 00:00:00 2001 From: Tim Starling Date: Sun, 7 Sep 2003 13:56:25 +0000 Subject: [PATCH] Autoblocker privacy protection --- LocalSettings.sample | 5 ++ Version.php | 2 +- docs/schema.doc | 15 ++++-- includes/Block.php | 87 +++++++++++++++++---------------- includes/DefaultSettings.php | 1 - includes/SpecialBlockip.php | 14 ++---- includes/SpecialIpblocklist.php | 40 +++++++++------ includes/User.php | 12 +++-- install.php | 3 +- maintenance/tables.sql | 8 ++- update.php | 59 ++++++++++++++++++++-- wiki.phtml | 8 +++ 12 files changed, 173 insertions(+), 81 deletions(-) diff --git a/LocalSettings.sample b/LocalSettings.sample index 0e13281edd..41e6d5d5b2 100644 --- a/LocalSettings.sample +++ b/LocalSettings.sample @@ -37,6 +37,11 @@ $wgDBsqlpassword = "sqlpass"; $wgDBminWordLen = 3; # Match this to your MySQL fulltext $wgDBtransactions = false; # Set to true if using InnoDB tables +# Change this key to different sequence of 8 bytes, so that sysops cannot +# obtain the IP addresses of logged-in users. +$wgIPBlockKey = implode( "", array_map( "chr", + array( 57, 100, 182, 241, 93, 122, 40, 195 ) ) ); + # Turn this on during database maintenance # $wgReadOnly = true; diff --git a/Version.php b/Version.php index e03fcc4679..2ba6842d41 100644 --- a/Version.php +++ b/Version.php @@ -9,5 +9,5 @@ # update.php script. # -$wgSoftwareRevision = 1001; +$wgSoftwareRevision = 1002; ?> diff --git a/docs/schema.doc b/docs/schema.doc index d28af2d653..11d6ba5d17 100644 --- a/docs/schema.doc +++ b/docs/schema.doc @@ -165,16 +165,23 @@ oldimage (Old versions of images stored for potential revert) ipblocks (IP addresses and users blocked from editing) - + ipb_id + Primary key, introduced for privacy. ipb_address - Blocked IP address in dotted-quad form or "" + Blocked IP address in dotted-quad form or user name. ipb_user - Blocked user ID or 0. + Blocked user ID or 0 for IP blocks. ipb_by User ID who made the block. ipb_reason Text comment made by blocker. - + ipb_timestamp + Creation (or refresh) date in standard YMDHMS form. IP + blocks expire automatically. + ipb_auto + Indicates that the IP address was banned because a banned + user accessed a page through it. If this is 1, ipb_address + will be hidden. random (Random page queue) diff --git a/includes/Block.php b/includes/Block.php index faffc641ac..2aeff388f3 100644 --- a/includes/Block.php +++ b/includes/Block.php @@ -1,52 +1,51 @@ mAddress = $address; $this->mUser = $user; $this->mBy = $by; $this->mReason = $reason; $this->mTimestamp = $timestamp; + $this->mAuto = $auto; } - /*static*/ function newFromDB( $address, $user = 0, $killExpired = true ) - { + /*static*/ function newFromDB( $address, $user = 0, $killExpired = true ) { $ban = new Block(); $ban->load( $address, $user, $killExpired ); return $ban; } - function clear() - { + function clear() { $mAddress = $mReason = $mTimestamp = ""; $mUser = $mBy = 0; } # Get a ban from the DB, with either the given address or the given username - function load( $address, $user = 0, $killExpired = true ) - { + function load( $address, $user = 0, $killExpired = true ) { $fname = "Block::load"; $ret = false; $killed = false; if ( 0 == $user ) { - $sql = "SELECT * FROM ipblocks WHERE ipb_address='$address'"; + $sql = "SELECT * FROM ipblocks WHERE ipb_address='" . wfStrencode( $address ) . "'"; } else { - $sql = "SELECT * FROM ipblocks WHERE (ipb_address='$address' OR ipb_user={$user})"; + $sql = "SELECT * FROM ipblocks WHERE (ipb_address='" . wfStrencode( $address ) . + "' OR ipb_user={$user})"; } - $res = wfQuery( $sql, $fname ); if ( 0 == wfNumRows( $res ) ) { @@ -58,15 +57,19 @@ class Block $this->initFromRow( $row ); if ( $killExpired ) { - # If requested, delete expired rows do { $killed = $this->deleteIfExpired(); - $row = wfFetchObject( $res ); + if ( $killed ) { + $row = wfFetchObject( $res ); + if ( $row ) { + $this->initFromRow( $row ); + } + } } while ( $killed && $row ); # If there were any left after the killing finished, return true - if ( $row == false ) { + if ( !$row ) { $ret = false; $this->clear(); } else { @@ -80,18 +83,18 @@ class Block return $ret; } - function initFromRow( $row ) - { + function initFromRow( $row ) { $this->mAddress = $row->ipb_address; $this->mReason = $row->ipb_reason; $this->mTimestamp = $row->ipb_timestamp; $this->mUser = $row->ipb_user; $this->mBy = $row->ipb_by; + $this->mAuto = $row->ipb_auto; + $this->mId = $row->ipb_id; } # Callback with a Block object for every block - /*static*/ function enumBlocks( $callback, $tag, $killExpired = true ) - { + /*static*/ function enumBlocks( $callback, $tag, $killExpired = true ) { $sql = "SELECT * FROM ipblocks ORDER BY ipb_timestamp"; $res = wfQuery( $sql, "Block::enumBans" ); $block = new Block(); @@ -109,22 +112,26 @@ class Block wfFreeResult( $res ); } - function delete() - { - wfQuery( "DELETE FROM ipblocks WHERE ipb_address='{$this->mAddress}'", - "Block::delete" ); + function delete() { + $fname = "Block::delete"; + if ( $this->mAddress == "" ) { + $sql = "DELETE FROM ipblocks WHERE ipb_id={$this->mId}"; + } else { + $sql = "DELETE FROM ipblocks WHERE ipb_address='" . + wfStrencode( $this->mAddress ) . "'"; + } + wfQuery( $sql, "Block::delete" ); } - function insert() - { - $sql = "INSERT INTO ipblocks (ipb_address, ipb_user, ipb_by, " . - "ipb_reason, ipb_timestamp ) VALUES ('{$this->mAddress}', {$this->mUser}, " . - "{$this->mBy}, '" . wfStrencode( $this->mReason ) . "','{$this->mTimestamp}')"; + function insert() { + $sql = "INSERT INTO ipblocks + (ipb_address, ipb_user, ipb_by, ipb_reason, ipb_timestamp, ipb_auto ) + VALUES ('" . wfStrencode( $this->mAddress ) . "', {$this->mUser}, {$this->mBy}, '" . + wfStrencode( $this->mReason ) . "','{$this->mTimestamp}', {$this->mAuto})"; wfQuery( $sql, "Block::insert" ); } - function deleteIfExpired() - { + function deleteIfExpired() { if ( $this->isExpired() ) { $this->delete(); return true; @@ -133,8 +140,7 @@ class Block } } - function isExpired() - { + function isExpired() { global $wgIPBlockExpiration, $wgUserBlockExpiration; $period = $this->mUser ? $wgUserBlockExpiration : $wgIPBlockExpiration; @@ -152,17 +158,14 @@ class Block } } - function isValid() - { + function isValid() { return $this->mAddress != ""; } - - function updateTimestamp() - { - $sql = "UPDATE ipblocks SET ipb_timestamp='" . wfTimestampNow() . "' WHERE ipb_address='{$this->mAddress}'"; + function updateTimestamp() { wfQuery( "UPDATE ipblocks SET ipb_timestamp='" . wfTimestampNow() . - "' WHERE ipb_address='{$this->mAddress}'", "Block::updateTimestamp" ); + "' WHERE ipb_address='" . wfStrencode( $this->mAddress ) . "'", "Block::updateTimestamp" ); } + } ?> diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 8df71bf4a9..de102686a8 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -23,7 +23,6 @@ $wgEmergencyContact = "wikiadmin@" . getenv( "SERVER_NAME" ); #$wgPasswordSender = "Wikipedia Mail "; $wgPasswordSender = "Wikipedia Mail \r\nReply-To: webmaster@www.wikipedia.org"; - # MySQL settings # $wgDBserver = "localhost"; diff --git a/includes/SpecialBlockip.php b/includes/SpecialBlockip.php index 744ceee13c..b56b1d2dd6 100644 --- a/includes/SpecialBlockip.php +++ b/includes/SpecialBlockip.php @@ -59,14 +59,13 @@ class IPBlockForm { function doSubmit() { global $wgOut, $wgUser, $wgLang; - global $ip, $wpBlockAddress, $wpBlockReason, $wgSysopUserBlocks; - $fname = "IPBlockForm::doSubmit"; + global $ip, $wpBlockAddress, $wpBlockReason, $wgSysopUserBans; $userId = 0; if ( ! preg_match( "/\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}/", $wpBlockAddress ) ) { - if ( $wgSysopUserBlocks ) { + if ( $wgSysopUserBans ) { $userId = User::idFromName( $wpBlockAddress ); if ( $userId == 0 ) { $this->showForm( wfMsg( "badipaddress" ) ); @@ -83,12 +82,9 @@ class IPBlockForm { } # Note: for a user block, ipb_address is only for display purposes - - $sql = "INSERT INTO ipblocks (ipb_address, ipb_user, ipb_by, " . - "ipb_reason, ipb_timestamp ) VALUES ('{$wpBlockAddress}', {$userId}, " . - $wgUser->getID() . ", '" . wfStrencode( $wpBlockReason ) . "','" . - wfTimestampNow() . "')"; - wfQuery( $sql, $fname ); + $ban = new Block( $wpBlockAddress, $userId, $wgUser->getID(), + wfStrencode( $wpBlockReason ), wfTimestampNow(), 0 ); + $ban->insert(); $success = wfLocalUrl( $wgLang->specialPage( "Blockip" ), "action=success&ip={$wpBlockAddress}" ); diff --git a/includes/SpecialIpblocklist.php b/includes/SpecialIpblocklist.php index f98098d401..37c4974140 100644 --- a/includes/SpecialIpblocklist.php +++ b/includes/SpecialIpblocklist.php @@ -57,18 +57,24 @@ class IPUnblockForm { \n" ); } - + function doSubmit() { global $wgOut, $wgUser, $wgLang; - global $ip, $wpUnblockAddress; - $fname = "IPUnblockForm::doSubmit"; + global $wpUnblockAddress; - $sql = "DELETE FROM ipblocks WHERE ipb_address='{$wpUnblockAddress}'"; - wfQuery( $sql, $fname ); + $block = new Block(); + + if ( $wpUnblockAddress{0} == "#" ) { + $block->mId = substr( $wpUnblockAddress, 1 ); + } else { + $block->mAddress = $wpUnblockAddress; + } + + $block->delete(); $success = wfLocalUrl( $wgLang->specialPage( "Ipblocklist" ), - "action=success&ip={$wpUnblockAddress}" ); + "action=success&ip=" . urlencode($wpUnblockAddress) ); $wgOut->redirect( $success ); } @@ -86,29 +92,35 @@ class IPUnblockForm { } } -# Callback function +# Callback function to output a block function wfAddRow( $block, $tag ) { global $wgOut, $wgUser, $wgLang, $ip; $sk = $wgUser->getSkin(); - $addr = $block->mAddress; + + # Hide addresses blocked by User::spreadBlocks, for privacy + $addr = $block->mAuto ? "#{$block->mId}" : $block->mAddress; + $name = User::whoIs( $block->mBy ); $ulink = $sk->makeKnownLink( $wgLang->getNsText( Namespace::getUser() ). ":{$name}", $name ); $d = $wgLang->timeanddate( $block->mTimestamp, true ); $line = str_replace( "$1", $d, wfMsg( "blocklistline" ) ); $line = str_replace( "$2", $ulink, $line ); - $line = str_replace( "$3", $block->mAddress, $line ); + $line = str_replace( "$3", $addr, $line ); $wgOut->addHTML( "
  • {$line}" ); - $clink = "specialPage( - "Contributions" ), "target={$addr}" ) . "\">" . - wfMsg( "contribslink" ) . ""; - $wgOut->addHTML( " ({$clink})" ); + + if ( !$block->mAuto ) { + $clink = "specialPage( + "Contributions" ), "target={$block->mAddress}" ) . "\">" . + wfMsg( "contribslink" ) . ""; + $wgOut->addHTML( " ({$clink})" ); + } if ( $wgUser->isSysop() ) { $ublink = "specialPage( - "Ipblocklist" ), "action=unblock&ip={$addr}" ) . "\">" . + "Ipblocklist" ), "action=unblock&ip=" . urlencode( $addr ) ) . "\">" . wfMsg( "unblocklink" ) . ""; $wgOut->addHTML( " ({$ublink})" ); } diff --git a/includes/User.php b/includes/User.php index 0b527370b8..157348bac5 100644 --- a/includes/User.php +++ b/includes/User.php @@ -94,14 +94,15 @@ class User { { if ( -1 != $this->mBlockedby ) { return; } - $ban = new Ban(); - if ( $ban->load( getenv( "REMOTE_ADDR" ), $this->mId ) ) { + $block = new Block(); + if ( !$block->load( getenv( "REMOTE_ADDR" ), $this->mId ) ) { + wfDebug( getenv( "REMOTE_ADDR" ) ." is not blocked\n" ); $this->mBlockedby = 0; return; } - $this->mBlockedby = $ban->by; - $this->mBlockreason = $ban->reason; + $this->mBlockedby = $block->mBy; + $this->mBlockreason = $block->mReason; } function isBlocked() @@ -588,7 +589,7 @@ class User { return; } - # Make a new ban object with the desired properties + # Make a new block object with the desired properties wfDebug( "Autoblocking {$this->mUserName}@{$addr}\n" ); $ipblock->mAddress = $addr; $ipblock->mUser = 0; @@ -596,6 +597,7 @@ class User { $ipblock->mReason = str_replace( "$1", $this->getName(), wfMsg( "autoblocker" ) ); $ipblock->mReason = str_replace( "$2", $userblock->mReason, $ipblock->mReason ); $ipblock->mTimestamp = wfTimestampNow(); + $ipblock->mAuto = 1; # Insert it $ipblock->insert(); diff --git a/install.php b/install.php index 71d27ec420..724ec24125 100644 --- a/install.php +++ b/install.php @@ -170,7 +170,8 @@ function copydirectory( $source, $dest ) { $handle = opendir( $source ); while ( false !== ( $f = readdir( $handle ) ) ) { if ( "." == $f{0} ) continue; - if ( "CVS" == $f ) continue; + # Something made all my "CVSs" go lowercase :( + if ( !strcasecmp( "CVS", $f ) ) continue; copyfile( $source, $f, $dest ); } } diff --git a/maintenance/tables.sql b/maintenance/tables.sql index 388bbfe052..43c2a2736e 100644 --- a/maintenance/tables.sql +++ b/maintenance/tables.sql @@ -5,6 +5,9 @@ -- Only UNIQUE keys are defined here; the rest are added by -- indexes.sql. -- +-- If you change the main development branch version of this +-- file, please add an appropriate ALTER TABLE to update.php, +-- and increment the version number in Version.php. DROP TABLE IF EXISTS user; CREATE TABLE user ( @@ -104,11 +107,14 @@ CREATE TABLE site_stats ( DROP TABLE IF EXISTS ipblocks; CREATE TABLE ipblocks ( + ipb_id int(8) NOT NULL auto_increment, ipb_address varchar(40) binary NOT NULL default '', ipb_user int(8) unsigned NOT NULL default '0', ipb_by int(8) unsigned NOT NULL default '0', ipb_reason tinyblob NOT NULL default '', - ipb_timestamp char(14) binary NOT NULL default '' + ipb_timestamp char(14) binary NOT NULL default '', + ipb_auto tinyint(1) NOT NULL default '0', + UNIQUE KEY ipb_id ) TYPE=MyISAM PACK_KEYS=1; DROP TABLE IF EXISTS image; diff --git a/update.php b/update.php index 66ae6f0600..4683e72d8f 100644 --- a/update.php +++ b/update.php @@ -27,6 +27,7 @@ include_once( "Version.php" ); include_once( "{$IP}/Setup.php" ); $wgTitle = Title::newFromText( "Update script" ); $wgCommandLineMode = true; +$wgAlterSpecs = array(); do_revision_updates(); @@ -89,7 +90,8 @@ function copydirectory( $source, $dest ) { $handle = opendir( $source ); while ( false !== ( $f = readdir( $handle ) ) ) { if ( "." == $f{0} ) continue; - if ( "CVS" == $f ) continue; + # Windows turned all my CVS->cvs :( + if ( !strcasecmp ( "CVS", $f ) ) continue; copyfile( $source, $f, $dest ); } } @@ -102,13 +104,32 @@ function readconsole() { } function do_revision_updates() { - global $wgSoftwareRevision; + global $wgSoftwareRevision, $wgAlterSpecs, $wgDBserver, $wgDBadminuser; + global $wgDBadminpassword, $wgDBname; if ( $wgSoftwareRevision < 1001 ) { update_passwords(); } + if ( $wgSoftwareRevision < 1002 ) { alter_ipblocks(); } + + # Run ALTER TABLE queries. + + if ( count( $wgAlterSpecs ) ) { + $rconn = mysql_connect( $wgDBserver, $wgDBadminuser, $wgDBadminpassword ); + mysql_select_db( $wgDBname ); + print "\n"; + foreach ( $wgAlterSpecs as $table => $specs ) { + $sql = "ALTER TABLE $table $specs"; + print "$sql;\n"; + $res = mysql_query( $sql, $rconn ); + if ( $res === false ) { + print "MySQL error: " . mysql_error( $rconn ) . "\n"; + } + } + mysql_close( $rconn ); + } } function update_passwords() { - $fname = "Update scripte: update_passwords()"; + $fname = "Update script: update_passwords()"; print "\nIt appears that you need to update the user passwords in your\n" . "database. If you have already done this (if you've run this update\n" . "script once before, for example), doing so again will make all your\n" . @@ -132,4 +153,36 @@ function update_passwords() { } } +function alter_ipblocks() { + global $wgAlterSpecs; + $fname = "Update script: alter_ipblocks"; + + if ( field_exists( "ipblocks", "ipb_id" ) ) { + return; + } + + if ( array_key_exists( "ipblocks", $wgAlterSpecs ) ) { + $wgAlterSpecs["ipblocks"] .= ","; + } + + $wgAlterSpecs["ipblocks"] .= + "ADD ipb_auto tinyint(1) NOT NULL default '0', ". + "ADD ipb_id int(8) NOT NULL auto_increment,". + "ADD PRIMARY KEY (ipb_id)"; +} + +function field_exists( $table, $field ) { + $fname = "Update script: field_exists"; + $res = wfQuery( "DESCRIBE $table", $fname ); + $found = false; + + while ( $row = wfFetchObject( $res ) ) { + if ( $row->Field == $field ) { + $found = true; + break; + } + } + return $found; +} + ?> diff --git a/wiki.phtml b/wiki.phtml index d79da00f17..215bb64d22 100644 --- a/wiki.phtml +++ b/wiki.phtml @@ -16,6 +16,14 @@ include_once( "$IP/Setup.php" ); wfProfileIn( "main-misc-setup" ); OutputPage::setEncodings(); # Not really used yet +# Useful debug output +wfDebug( "\nStart request\n" ); +wfDebug( "$REQUEST_METHOD $REQUEST_URI\n" ); +$headers = getallheaders(); +foreach ($headers as $name => $value) { + wfDebug( "$name: $value\n" ); +} + # Query string fields # global $action, $title, $search, $go, $target, $printable; -- 2.20.1