/** * Hook function for RecentChange_save * Saves user data into the cu_changes table */ function efUpdateCheckUserData($rc) { global $wgUser; // Extract params extract($rc->mAttribs); // Get IP $ip = wfGetIP(); // Get XFF header $xff = wfGetForwardedFor(); list($xff_ip, $trusted) = efGetClientIPfromXFF($xff); // Our squid XFFs can flood this up sometimes $isSquidOnly = efXFFChainIsSquid($xff); // Get agent $agent = wfGetAgent(); // Store the log action text for log events // $rc_comment should just be the log_comment // BC: check if log_type and log_action exists // If not, then $rc_comment is the actiontext and comment if (isset($rc_log_type) && $rc_type == RC_LOG) { $target = Title::makeTitle($rc_namespace, $rc_title); $actionText = LogPage::actionText($rc_log_type, $rc_log_action, $target, NULL, explode('\\n', $rc_params)); } else { $actionText = ''; } $dbw = wfGetDB(DB_MASTER); $cuc_id = $dbw->nextSequenceValue('cu_changes_cu_id_seq'); $rcRow = array('cuc_id' => $cuc_id, 'cuc_namespace' => $rc_namespace, 'cuc_title' => $rc_title, 'cuc_minor' => $rc_minor, 'cuc_user' => $rc_user, 'cuc_user_text' => $rc_user_text, 'cuc_actiontext' => $actionText, 'cuc_comment' => $rc_comment, 'cuc_this_oldid' => $rc_this_oldid, 'cuc_last_oldid' => $rc_last_oldid, 'cuc_type' => $rc_type, 'cuc_timestamp' => $rc_timestamp, 'cuc_ip' => IP::sanitizeIP($ip), 'cuc_ip_hex' => $ip ? IP::toHex($ip) : null, 'cuc_xff' => !$isSquidOnly ? $xff : '', 'cuc_xff_hex' => $xff_ip && !$isSquidOnly ? IP::toHex($xff_ip) : null, 'cuc_agent' => $agent); ## On PG, MW unsets cur_id due to schema incompatibilites. So it may not be set! if (isset($rc_cur_id)) { $rcRow['cuc_page_id'] = $rc_cur_id; } $dbw->insert('cu_changes', $rcRow, __METHOD__); # Every 100th edit, prune the checkuser changes table. wfSeedRandom(); if (0 == mt_rand(0, 99)) { # Periodically flush old entries from the recentchanges table. global $wgCUDMaxAge; $cutoff = $dbw->timestamp(time() - $wgCUDMaxAge); $recentchanges = $dbw->tableName('cu_changes'); $sql = "DELETE FROM {$recentchanges} WHERE cuc_timestamp < '{$cutoff}'"; $dbw->query($sql); } return true; }
/** * @param string $ip * @param bool $xfor * @param string $reason * Lists all users in recent changes who used an IP, newest to oldest down * Outputs usernames, latest and earliest found edit date, and count * List unique IPs used for each user in time order, list corresponding user agent */ function doIPUsersRequest($ip, $xfor = false, $reason = '') { global $wgUser, $wgOut, $wgLang, $wgTitle, $wgDBname; $fname = 'CheckUser::doIPUsersRequest'; #invalid IPs are passed in as a blank string if (!$ip) { $s = wfMsgHtml('badipaddress'); $wgOut->addHTML($s); return; } $logType = 'ipusers'; if ($xfor) { $logType .= '-xff'; } if (!$this->addLogEntry($logType, 'ip', $ip, $reason)) { $wgOut->addHTML('<p>' . wfMsgHtml('checkuser-log-fail') . '</p>'); } $dbr = wfGetDB(DB_SLAVE); $ip_conds = $dbr->makeList($this->getIpConds($dbr, $ip, $xfor), LIST_AND); $cu_changes = $dbr->tableName('cu_changes'); $index = $xfor ? 'cuc_xff_hex_time' : 'cuc_ip_hex_time'; # Ordered in descent by timestamp. Can cause large filesorts on range scans. # Check how many rows will need sorting ahead of time to see if this is too big. if (strpos($ip, '/') !== false) { $rangecount = $dbr->estimateRowCount('cu_changes', '*', array($ip_conds), __METHOD__, array('USE INDEX' => $index)); } if (isset($rangecount) && $rangecount > 5000) { $use_index = $dbr->useIndexClause($index); $sql = "SELECT cuc_ip_hex, COUNT(*) AS count,\n\t\t\t\tMIN(cuc_timestamp) AS first, MAX(cuc_timestamp) AS last \n\t\t\t\tFROM {$cu_changes} {$use_index} WHERE {$ip_conds} \n\t\t\t\tGROUP BY cuc_ip_hex ORDER BY cuc_ip_hex LIMIT 5000"; $ret = $dbr->query($sql, __METHOD__); # List out each IP that has edits $s = '<h5>' . wfMsg('checkuser-too-many') . '</h5>'; $s .= '<ol>'; while ($row = $ret->fetchObject()) { # Convert the IP hexes into normal form if (strpos($row->cuc_ip_hex, 'v6-') !== false) { $ip = substr($row->cuc_ip_hex, 3); // Seperate into 8 octets $ip_oct = substr($ip, 0, 4); for ($n = 1; $n < 8; $n++) { $ip_oct .= ':' . substr($ip, 4 * $n, 4); } // NO leading zeroes $ip = preg_replace('/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip_oct); } else { $ip = long2ip(wfBaseConvert($row->cuc_ip_hex, 16, 10, 8)); } $s .= '<li><a href="' . $wgTitle->escapeLocalURL('user='******'&reason=' . urlencode($reason) . '&checktype=subipusers') . '">' . $ip . '</a>'; if ($row->first == $row->last) { $s .= ' (' . $wgLang->timeanddate($row->first, true) . ') '; } else { $s .= ' (' . $wgLang->timeanddate($row->first, true) . ' -- ' . $wgLang->timeanddate($row->last, true) . ') '; } $s .= " [<strong>" . $row->count . "</strong>]</li>\n"; } $s .= '</ol>'; $dbr->freeResult($ret); $wgOut->addHTML($s); return; } else { if (isset($rangecount) && !$rangecount) { $s = wfMsgHtml("checkuser-nomatch") . "\n"; $wgOut->addHTML($s); return; } } # OK, do the real query... $use_index = $dbr->useIndexClause($index); $sql = "SELECT cuc_user_text, cuc_timestamp, cuc_user, cuc_ip, cuc_agent, cuc_xff \n\t\t\tFROM {$cu_changes} {$use_index} WHERE {$ip_conds} \n\t\t\tORDER BY cuc_timestamp DESC LIMIT 5000"; $ret = $dbr->query($sql, __METHOD__); $users_first = $users_last = $users_edits = $users_ids = array(); if (!$dbr->numRows($ret)) { $s = wfMsgHtml("checkuser-nomatch") . "\n"; } else { while (($row = $dbr->fetchObject($ret)) != false) { if (!array_key_exists($row->cuc_user_text, $users_edits)) { $users_last[$row->cuc_user_text] = $row->cuc_timestamp; $users_edits[$row->cuc_user_text] = 0; $users_ids[$row->cuc_user_text] = $row->cuc_user; $users_infosets[$row->cuc_user_text] = array(); $users_agentsets[$row->cuc_user_text] = array(); } $users_edits[$row->cuc_user_text] += 1; $users_first[$row->cuc_user_text] = $row->cuc_timestamp; # Treat blank or NULL xffs as empty strings $xff = empty($row->cuc_xff) ? null : $row->cuc_xff; $xff_ip_combo = array($row->cuc_ip, $xff); # Add this IP/XFF combo for this username if it's not already there if (!in_array($xff_ip_combo, $users_infosets[$row->cuc_user_text])) { $users_infosets[$row->cuc_user_text][] = $xff_ip_combo; } # Add this agent string if it's not already there; 10 max. if (count($users_agentsets[$row->cuc_user_text]) < 10) { if (!in_array($row->cuc_agent, $users_agentsets[$row->cuc_user_text])) { $users_agentsets[$row->cuc_user_text][] = $row->cuc_agent; } } } $dbr->freeResult($ret); $logs = SpecialPage::getTitleFor('Log'); $blocklist = SpecialPage::getTitleFor('Ipblocklist'); $s = '<ul>'; foreach ($users_edits as $name => $count) { $s .= '<li>'; $s .= $this->sk->userLink(-1, $name) . $this->sk->userToolLinks(-1, $name); $s .= ' (<a href="' . $wgTitle->escapeLocalURL('user='******'&reason=' . urlencode($reason)) . '">' . wfMsgHtml('checkuser-check') . '</a>)'; if ($users_first[$name] == $users_last[$name]) { $s .= ' (' . $wgLang->timeanddate($users_first[$name], true) . ') '; } else { $s .= ' (' . $wgLang->timeanddate($users_first[$name], true) . ' -- ' . $wgLang->timeanddate($users_last[$name], true) . ') '; } $s .= ' [<strong>' . $count . '</strong>]<br />'; # Check if this user or IP is blocked # If so, give a link to the block log $block = new Block(); $block->fromMaster(false); // use slaves $ip = IP::isIPAddress($name) ? $name : ''; // only check IP blocks if we have an IP if ($block->load($ip, $users_ids[$name])) { if (IP::isIPAddress($block->mAddress) && strpos($block->mAddress, '/')) { $userpage = Title::makeTitle(NS_USER, $block->mAddress); $blocklog = $this->sk->makeKnownLinkObj($logs, wfMsgHtml('checkuser-blocked'), 'type=block&page=' . urlencode($userpage->getPrefixedText())); $s .= ' <strong>(' . $blocklog . ' - ' . $block->mAddress . ')</strong>'; } else { if ($block->mAuto) { $blocklog = $this->sk->makeKnownLinkObj($blocklist, wfMsgHtml('checkuser-blocked'), 'ip=' . urlencode("#{$block->mId}")); $s .= ' <strong>(' . $blocklog . ')</strong>'; } else { $userpage = Title::makeTitle(NS_USER, $name); $blocklog = $this->sk->makeKnownLinkObj($logs, wfMsgHtml('checkuser-blocked'), 'type=block&page=' . urlencode($userpage->getPrefixedText())); $s .= '<strong>(' . $blocklog . ')</strong>'; } } } $s .= '<ol>'; # List out each IP/XFF combo for this username for ($i = count($users_infosets[$name]) - 1; $i >= 0; $i--) { $set = $users_infosets[$name][$i]; # IP link $s .= '<li>'; $s .= '<a href="' . $wgTitle->escapeLocalURL('user='******'">' . htmlspecialchars($set[0]) . '</a>'; # XFF string, link to /xff search if ($set[1]) { # Flag our trusted proxies list($client, $trusted) = efGetClientIPfromXFF($set[1], $set[0]); $c = $trusted ? '#F0FFF0' : '#FFFFCC'; $s .= ' <span style="background-color: ' . $c . '"><strong>XFF</strong>: '; $s .= $this->sk->makeKnownLinkObj($wgTitle, htmlspecialchars($set[1]), "user="******"/xff") . "</span>"; } $s .= "</li>\n"; } $s .= '</ol><br /><ol>'; # List out each agent for this username for ($i = count($users_agentsets[$name]) - 1; $i >= 0; $i--) { $agent = $users_agentsets[$name][$i]; # IP link $s .= "<li><i>" . htmlspecialchars($agent) . "</i></li>\n"; } $s .= '</ol>'; $s .= '</li>'; } $s .= '</ul>'; } $wgOut->addHTML($s); }