Autoblocker privacy protection
authorTim Starling <tstarling@users.mediawiki.org>
Sun, 7 Sep 2003 13:56:25 +0000 (13:56 +0000)
committerTim Starling <tstarling@users.mediawiki.org>
Sun, 7 Sep 2003 13:56:25 +0000 (13:56 +0000)
12 files changed:
LocalSettings.sample
Version.php
docs/schema.doc
includes/Block.php
includes/DefaultSettings.php
includes/SpecialBlockip.php
includes/SpecialIpblocklist.php
includes/User.php
install.php
maintenance/tables.sql
update.php
wiki.phtml

index 0e13281..41e6d5d 100644 (file)
@@ -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;
 
index e03fcc4..2ba6842 100644 (file)
@@ -9,5 +9,5 @@
 # update.php script.
 #
 
-$wgSoftwareRevision = 1001;
+$wgSoftwareRevision = 1002;
 ?>
index d28af2d..11d6ba5 100644 (file)
@@ -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)
index faffc64..2aeff38 100644 (file)
@@ -1,52 +1,51 @@
 <?
 # Blocks and bans object
-
+#
 #TODO: This could be used everywhere, but it isn't.
-
+#
 # All the functions in this class assume the object is either explicitly 
-# loaded or filled. It is not load-on-demand.
-
+# loaded or filled. It is not load-on-demand. There are no accessors.
+#
 # To use delete(), you only need to fill $mAddress
 
 class Block
 {
-       var $mAddress, $mUser, $mBy, $mReason, $mTimestamp;
+       var $mAddress, $mUser, $mBy, $mReason, $mTimestamp, $mAuto, $mId;
        
-       function Block( $address = "", $user = "", $by = 0, $reason = "", $timestamp = "" ) 
+       function Block( $address = "", $user = "", $by = 0, $reason = "", 
+               $timestamp = "" , $auto = 0) 
        {
                $this->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" );
        }
+
 }
 ?>
index 8df71bf..de10268 100644 (file)
@@ -23,7 +23,6 @@ $wgEmergencyContact = "wikiadmin@" . getenv( "SERVER_NAME" );
 #$wgPasswordSender     = "Wikipedia Mail <apache@www.wikipedia.org>";
 $wgPasswordSender      = "Wikipedia Mail <apache@www.wikipedia.org>\r\nReply-To: webmaster@www.wikipedia.org";
 
-
 # MySQL settings
 #
 $wgDBserver         = "localhost";
index 744ceee..b56b1d2 100644 (file)
@@ -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}" );
index f98098d..37c4974 100644 (file)
@@ -57,18 +57,24 @@ class IPUnblockForm {
 </form>\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( "<li>{$line}" );
-       $clink = "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
-         "Contributions" ), "target={$addr}" ) . "\">" .
-         wfMsg( "contribslink" ) . "</a>";
-       $wgOut->addHTML( " ({$clink})" );
+       
+       if ( !$block->mAuto ) {
+               $clink = "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
+                 "Contributions" ), "target={$block->mAddress}" ) . "\">" .
+                 wfMsg( "contribslink" ) . "</a>";
+               $wgOut->addHTML( " ({$clink})" );
+       }
 
        if ( $wgUser->isSysop() ) {
                $ublink = "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
-                 "Ipblocklist" ), "action=unblock&ip={$addr}" ) . "\">" .
+                 "Ipblocklist" ), "action=unblock&ip=" . urlencode( $addr ) ) . "\">" .
                  wfMsg( "unblocklink" ) . "</a>";
                $wgOut->addHTML( " ({$ublink})" );
        }
index 0b52737..157348b 100644 (file)
@@ -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();
index 71d27ec..724ec24 100644 (file)
@@ -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 );
        }
 }
index 388bbfe..43c2a27 100644 (file)
@@ -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;
index 66ae6f0..4683e72 100644 (file)
@@ -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;
+}
+
 ?>
index d79da00..215bb64 100644 (file)
@@ -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;