PluginProbe ʕ •ᴥ•ʔ
Wordfence Security – Firewall, Malware Scan, and Login Security / 7.3.2
Wordfence Security – Firewall, Malware Scan, and Login Security v7.3.2
8.2.2 8.2.1 8.2.0 3.7.1 3.7.2 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.8.8 3.8.9 3.9.1 4.0.1 4.0.2 4.0.3 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.0.9 5.1.1 5.1.2 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.2.1 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.1 5.3.10 5.3.11 5.3.12 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.3.8 5.3.9 6.0.1 6.0.10 6.0.11 6.0.12 6.0.14 6.0.15 6.0.16 6.0.17 6.0.18 6.0.19 6.0.2 6.0.20 6.0.21 6.0.22 6.0.23 6.0.24 6.0.25 6.0.3 6.0.4 6.0.5 6.0.6 6.0.7 6.0.8 6.0.9 6.1.1 6.1.10 6.1.11 6.1.12 6.1.14 6.1.15 6.1.16 6.1.17 6.1.2 6.1.3 6.1.4 6.1.5 6.1.6 6.1.7 6.1.8 6.1.9 6.2.0 6.2.1 6.2.10 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.7 6.2.8 6.2.9 6.3.0 6.3.1 6.3.10 6.3.11 6.3.12 6.3.14 6.3.15 6.3.16 6.3.17 6.3.18 6.3.19 6.3.2 6.3.20 6.3.21 6.3.22 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.3.8 6.3.9 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1.0 7.1.1 7.1.10 7.1.11 7.1.12 7.1.14 7.1.15 7.1.16 7.1.17 7.1.18 7.1.19 7.1.2 7.1.20 7.1.3 7.1.4 7.1.5 7.1.6 7.1.7 7.1.8 7.1.9 7.10.0 7.10.1 7.10.2 7.10.3 7.10.4 7.10.5 7.10.6 7.10.7 7.11.0 7.11.1 7.11.2 7.11.3 7.11.4 7.11.5 7.11.6 7.11.7 7.2.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3.1 7.3.2 7.3.3 7.3.4 7.3.5 7.3.6 7.4.0 7.4.1 7.4.10 7.4.11 7.4.12 7.4.14 7.4.2 7.4.3 trunk 7.4.4 1.1 7.4.5 1.2 7.4.6 1.3 7.4.7 1.3.1 7.4.8 1.3.2 7.4.9 1.3.3 7.5.0 1.4.2 7.5.1 1.4.3 7.5.10 1.4.4 7.5.11 1.4.5 7.5.2 1.4.6 7.5.3 1.4.7 7.5.4 1.4.8 7.5.5 1.5.1 7.5.6 1.5.2 7.5.7 1.5.3 7.5.8 1.5.4 7.5.9 1.5.5 7.6.0 1.5.6 7.6.1 2.0.1 7.6.2 2.0.2 7.7.0 2.0.3 7.7.1 2.0.5 7.8.0 2.0.6 7.8.1 2.0.7 7.8.2 2.1.0 7.9.0 2.1.1 7.9.1 2.1.2 7.9.2 2.1.3 7.9.3 2.1.4 8.0.0 2.1.5 8.0.1 3.0.2 8.0.2 3.0.3 8.0.3 3.0.4 8.0.4 3.0.5 8.0.5 3.0.6 8.1.0 3.0.7 8.1.1 3.0.8 8.1.2 3.0.9 8.1.3 3.1.0 8.1.4 3.1.1 v1.4.1 3.1.2 3.1.4 3.1.6 3.2.1 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.1 3.4.4 3.4.5 3.5.1 3.5.2 3.6.1 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.6.9
wordfence / lib / wfLog.php
wordfence / lib Last commit date
Diff 8 years ago dashboard 7 years ago rest-api 7 years ago .htaccess 7 years ago Diff.php 14 years ago GeoLite2-Country.mmdb 7 years ago IPTraf.php 8 years ago IPTrafList.php 7 years ago WFLSPHP52Compatability.php 7 years ago compat.php 8 years ago conntest.php 7 years ago cronview.php 8 years ago dbview.php 8 years ago diffResult.php 8 years ago email_genericAlert.php 7 years ago email_newIssues.php 7 years ago email_unlockRequest.php 8 years ago email_unsubscribeRequest.php 7 years ago flags.php 7 years ago live_activity.php 8 years ago menu_dashboard.php 7 years ago menu_dashboard_options.php 7 years ago menu_firewall.php 7 years ago menu_firewall_blocking.php 7 years ago menu_firewall_blocking_options.php 8 years ago menu_firewall_waf.php 7 years ago menu_firewall_waf_options.php 7 years ago menu_options.php 7 years ago menu_scanner.php 7 years ago menu_scanner_credentials.php 8 years ago menu_scanner_options.php 8 years ago menu_support.php 7 years ago menu_tools.php 7 years ago menu_tools_diagnostic.php 7 years ago menu_tools_importExport.php 7 years ago menu_tools_livetraffic.php 7 years ago menu_tools_twoFactor.php 7 years ago menu_tools_whois.php 8 years ago menu_wordfence_central.php 7 years ago noc1.key 7 years ago sysinfo.php 8 years ago unknownFiles.php 8 years ago viewFullActivityLog.php 8 years ago wf503.php 7 years ago wfAPI.php 7 years ago wfActivityReport.php 7 years ago wfAdminNoticeQueue.php 8 years ago wfArray.php 7 years ago wfBrowscap.php 8 years ago wfBrowscapCache.php 7 years ago wfBulkCountries.php 7 years ago wfCache.php 9 years ago wfCentralAPI.php 7 years ago wfConfig.php 7 years ago wfCrawl.php 8 years ago wfCredentialsController.php 7 years ago wfCrypt.php 7 years ago wfDB.php 7 years ago wfDashboard.php 7 years ago wfDateLocalization.php 8 years ago wfDiagnostic.php 7 years ago wfDict.php 8 years ago wfDirectoryIterator.php 7 years ago wfHelperBin.php 11 years ago wfHelperString.php 11 years ago wfIPWhitelist.php 7 years ago wfImportExportController.php 7 years ago wfIssues.php 7 years ago wfJWT.php 7 years ago wfLockedOut.php 7 years ago wfLog.php 7 years ago wfMD5BloomFilter.php 8 years ago wfModuleController.php 7 years ago wfNotification.php 8 years ago wfOnboardingController.php 7 years ago wfPersistenceController.php 8 years ago wfRESTAPI.php 7 years ago wfScan.php 7 years ago wfScanEngine.php 7 years ago wfSchema.php 7 years ago wfStyle.php 7 years ago wfSupportController.php 7 years ago wfUnlockMsg.php 7 years ago wfUpdateCheck.php 8 years ago wfUtils.php 7 years ago wfVersionCheckController.php 8 years ago wfView.php 10 years ago wfViewResult.php 8 years ago wordfenceClass.php 7 years ago wordfenceConstants.php 7 years ago wordfenceHash.php 7 years ago wordfenceScanner.php 7 years ago wordfenceURLHoover.php 7 years ago
wfLog.php
2050 lines
1 <?php
2 require_once('wfDB.php');
3 require_once('wfUtils.php');
4 require_once('wfBrowscap.php');
5 class wfLog {
6 public $canLogHit = true;
7 private $effectiveUserID = 0;
8 private $hitsTable = '';
9 private $apiKey = '';
10 private $wp_version = '';
11 private $db = false;
12 private $googlePattern = '/\.(?:googlebot\.com|google\.[a-z]{2,3}|google\.[a-z]{2}\.[a-z]{2}|1e100\.net)$/i';
13 private static $gbSafeCache = array();
14
15 /**
16 * @var wfRequestModel
17 */
18 private $currentRequest;
19
20 public static function shared() {
21 static $_shared = null;
22 if ($_shared === null) {
23 $_shared = new wfLog(wfConfig::get('apiKey'), wfUtils::getWPVersion());
24 }
25 return $_shared;
26 }
27
28 /**
29 * Returns whether or not we have a cached record identifying the visitor as human. This is used both by certain
30 * rate limiting features and by Live Traffic.
31 *
32 * @param bool|string $IP
33 * @param bool|string $UA
34 * @return bool
35 */
36 public static function isHumanRequest($IP = false, $UA = false) {
37 global $wpdb;
38
39 if ($IP === false) {
40 $IP = wfUtils::getIP();
41 }
42
43 if ($UA === false) {
44 $UA = (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');
45 }
46
47 $table = wfDB::networkTable('wfLiveTrafficHuman');
48 if ($wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE IP = %s AND identifier = %s AND expiration >= UNIX_TIMESTAMP()", wfUtils::inet_pton($IP), hash('sha256', $UA, true)))) {
49 return true;
50 }
51 return false;
52 }
53
54 /**
55 * Creates a cache record for the requester to tag it as human.
56 *
57 * @param bool|string $IP
58 * @param bool|string $UA
59 * @return bool
60 */
61 public static function cacheHumanRequester($IP = false, $UA = false) {
62 global $wpdb;
63
64 if ($IP === false) {
65 $IP = wfUtils::getIP();
66 }
67
68 if ($UA === false) {
69 $UA = (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');
70 }
71
72 $table = wfDB::networkTable('wfLiveTrafficHuman');
73 if ($wpdb->get_var($wpdb->prepare("INSERT IGNORE INTO {$table} (IP, identifier, expiration) VALUES (%s, %s, UNIX_TIMESTAMP() + 86400)", wfUtils::inet_pton($IP), hash('sha256', $UA, true)))) {
74 return true;
75 }
76 }
77
78 /**
79 * Prunes any expired records from the human cache.
80 */
81 public static function trimHumanCache() {
82 global $wpdb;
83 $table = wfDB::networkTable('wfLiveTrafficHuman');
84 $wpdb->query("DELETE FROM {$table} WHERE `expiration` < UNIX_TIMESTAMP()");
85 }
86
87 public function __construct($apiKey, $wp_version){
88 $this->apiKey = $apiKey;
89 $this->wp_version = $wp_version;
90 $this->hitsTable = wfDB::networkTable('wfHits');
91 $this->loginsTable = wfDB::networkTable('wfLogins');
92 $this->blocksTable = wfBlock::blocksTable();
93 $this->lockOutTable = wfDB::networkTable('wfLockedOut');
94 $this->throttleTable = wfDB::networkTable('wfThrottleLog');
95 $this->statusTable = wfDB::networkTable('wfStatus');
96 $this->ipRangesTable = wfDB::networkTable('wfBlocksAdv');
97
98 add_filter('determine_current_user', array($this, '_userIDDetermined'), 99, 1);
99 }
100
101 public function _userIDDetermined($userID) {
102 //Needed because the REST API will clear the authenticated user if it fails a nonce check on the request
103 $this->effectiveUserID = (int) $userID;
104 return $userID;
105 }
106
107 public function initLogRequest() {
108 if ($this->currentRequest === null) {
109 $this->currentRequest = new wfRequestModel();
110
111 $this->currentRequest->ctime = sprintf('%.6f', microtime(true));
112 $this->currentRequest->statusCode = 200;
113 $this->currentRequest->isGoogle = (wfCrawl::isGoogleCrawler() ? 1 : 0);
114 $this->currentRequest->IP = wfUtils::inet_pton(wfUtils::getIP());
115 $this->currentRequest->userID = $this->getCurrentUserID();
116 $this->currentRequest->URL = wfUtils::getRequestedURL();
117 $this->currentRequest->referer = (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '');
118 $this->currentRequest->UA = (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');
119 $this->currentRequest->jsRun = 0;
120
121 add_action('wp_loaded', array($this, 'actionSetRequestJSEnabled'));
122 add_action('init', array($this, 'actionSetRequestOnInit'), 9999);
123
124 if (function_exists('register_shutdown_function')) {
125 register_shutdown_function(array($this, 'logHit'));
126 }
127 }
128 }
129
130 public function actionSetRequestJSEnabled() {
131 if (get_current_user_id() > 0) {
132 $this->currentRequest->jsRun = true;
133 return;
134 }
135
136 $IP = wfUtils::getIP();
137 $UA = $this->currentRequest->UA;
138 $this->currentRequest->jsRun = wfLog::isHumanRequest($IP, $UA);
139 }
140
141 /**
142 * CloudFlare's plugin changes $_SERVER['REMOTE_ADDR'] on init.
143 */
144 public function actionSetRequestOnInit() {
145 $this->currentRequest->IP = wfUtils::inet_pton(wfUtils::getIP());
146 $this->currentRequest->userID = $this->getCurrentUserID();
147 }
148
149 /**
150 * @return wfRequestModel
151 */
152 public function getCurrentRequest() {
153 return $this->currentRequest;
154 }
155
156 public function logLogin($action, $fail, $username){
157 if(! $username){
158 return;
159 }
160 $user = get_user_by('login', $username);
161 $userID = 0;
162 if($user){
163 $userID = $user->ID;
164 if(! $userID){
165 return;
166 }
167 }
168 else {
169 $user = get_user_by('email', $username);
170 if ($user) {
171 $userID = $user->ID;
172 if (!$userID) {
173 return;
174 }
175 }
176 }
177 // change the action flag here if the user does not exist.
178 if ($action == 'loginFailValidUsername' && $userID == 0) {
179 $action = 'loginFailInvalidUsername';
180 }
181
182 $hitID = 0;
183 if ($this->currentRequest !== null) {
184 $this->currentRequest->userID = $userID;
185 $this->currentRequest->action = $action;
186 $this->currentRequest->save();
187 $hitID = $this->currentRequest->getPrimaryKey();
188 }
189
190 //Else userID stays 0 but we do log this even though the user doesn't exist.
191 $this->getDB()->queryWrite("insert into " . $this->loginsTable . " (hitID, ctime, fail, action, username, userID, IP, UA) values (%d, %f, %d, '%s', '%s', %s, %s, '%s')",
192 $hitID,
193 sprintf('%.6f', microtime(true)),
194 $fail,
195 $action,
196 $username,
197 $userID,
198 wfUtils::inet_pton(wfUtils::getIP()),
199 (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '')
200 );
201 }
202 private function getCurrentUserID(){
203 if (!function_exists('get_current_user_id') || !defined('AUTH_COOKIE')) { //If pluggable.php is loaded early by some other plugin on a multisite installation, it leads to an error because AUTH_COOKIE is undefined and WP doesn't check for it first
204 return 0;
205 }
206 $id = get_current_user_id();
207 return $id ? $id : 0;
208 }
209 public function logLeechAndBlock($type) { //404 or hit
210 if (!wfRateLimit::mightRateLimit($type)) {
211 return;
212 }
213
214 wfRateLimit::countHit($type, wfUtils::getIP());
215
216 if (wfRateLimit::globalRateLimit()->shouldEnforce($type)) {
217 $this->takeBlockingAction('maxGlobalRequests', "Exceeded the maximum global requests per minute for crawlers or humans.");
218 }
219 else if (wfRateLimit::crawlerViewsRateLimit()->shouldEnforce($type)) {
220 $this->takeBlockingAction('maxRequestsCrawlers', "Exceeded the maximum number of requests per minute for crawlers."); //may not exit
221 }
222 else if (wfRateLimit::crawler404sRateLimit()->shouldEnforce($type)) {
223 $this->takeBlockingAction('max404Crawlers', "Exceeded the maximum number of page not found errors per minute for a crawler.");
224 }
225 else if (wfRateLimit::humanViewsRateLimit()->shouldEnforce($type)) {
226 $this->takeBlockingAction('maxRequestsHumans', "Exceeded the maximum number of page requests per minute for humans.");
227 }
228 else if (wfRateLimit::human404sRateLimit()->shouldEnforce($type)) {
229 $this->takeBlockingAction('max404Humans', "Exceeded the maximum number of page not found errors per minute for humans.");
230 }
231 }
232
233 public function tagRequestForBlock($reason, $wfsn = false) {
234 if ($this->currentRequest !== null) {
235 $this->currentRequest->statusCode = 403;
236 $this->currentRequest->action = 'blocked:' . ($wfsn ? 'wfsn' : 'wordfence');
237 $this->currentRequest->actionDescription = $reason;
238 }
239 }
240
241 public function tagRequestForLockout($reason) {
242 if ($this->currentRequest !== null) {
243 $this->currentRequest->statusCode = 503;
244 $this->currentRequest->action = 'lockedOut';
245 $this->currentRequest->actionDescription = $reason;
246 }
247 }
248
249 /**
250 * @return bool|int
251 */
252 public function logHit() {
253 $liveTrafficEnabled = wfConfig::liveTrafficEnabled();
254 $action = $this->currentRequest->action;
255 $logHitOK = $this->logHitOK();
256 if (!$logHitOK) {
257 return false;
258 }
259 if (!$liveTrafficEnabled && !$action) {
260 return false;
261 }
262 if ($this->currentRequest !== null) {
263 if ($this->currentRequest->save()) {
264 return $this->currentRequest->getPrimaryKey();
265 }
266 }
267 return false;
268 }
269
270 public function getHits($hitType /* 'hits' or 'logins' */, $type, $afterTime, $limit = 50, $IP = false){
271 global $wpdb;
272 $IPSQL = "";
273 if($IP){
274 $IPSQL = " and IP=%s ";
275 $sqlArgs = array($afterTime, wfUtils::inet_pton($IP), $limit);
276 } else {
277 $sqlArgs = array($afterTime, $limit);
278 }
279 if($hitType == 'hits'){
280 $securityOnly = !wfConfig::liveTrafficEnabled();
281 $delayedHumanBotFiltering = false;
282
283 if($type == 'hit'){
284 $typeSQL = " ";
285 } else if($type == 'crawler'){
286 if ($securityOnly) {
287 $typeSQL = " ";
288 $delayedHumanBotFiltering = true;
289 }
290 else {
291 $now = time();
292 $typeSQL = " and jsRun = 0 and {$now} - ctime > 30 ";
293 }
294 } else if($type == 'gCrawler'){
295 $typeSQL = " and isGoogle = 1 ";
296 } else if($type == '404'){
297 $typeSQL = " and statusCode = 404 ";
298 } else if($type == 'human'){
299 if ($securityOnly) {
300 $typeSQL = " ";
301 $delayedHumanBotFiltering = true;
302 }
303 else {
304 $typeSQL = " and jsRun = 1 ";
305 }
306 } else if($type == 'ruser'){
307 $typeSQL = " and userID > 0 ";
308 } else {
309 wordfence::status(1, 'error', "Invalid log type to wfLog: $type");
310 return false;
311 }
312 array_unshift($sqlArgs, "select h.*, u.display_name from {$this->hitsTable} h
313 LEFT JOIN {$wpdb->users} u on h.userID = u.ID
314 where ctime > %f $IPSQL $typeSQL order by ctime desc limit %d");
315 $results = call_user_func_array(array($this->getDB(), 'querySelect'), $sqlArgs);
316
317 if ($delayedHumanBotFiltering) {
318 $browscap = wfBrowscap::shared();
319 foreach ($results as $index => $res) {
320 if ($res['UA']) {
321 $b = $browscap->getBrowser($res['UA']);
322 if ($b && $b['Parent'] != 'DefaultProperties') {
323 $jsRun = wfUtils::truthyToBoolean($res['jsRun']);
324 if (!wfConfig::liveTrafficEnabled() && !$jsRun) {
325 $jsRun = !(isset($b['Crawler']) && $b['Crawler']);
326 }
327
328 if ($type == 'crawler' && $jsRun || $type == 'human' && !$jsRun) {
329 unset($results[$index]);
330 }
331 }
332 }
333 }
334 }
335
336 } else if($hitType == 'logins'){
337 array_unshift($sqlArgs, "select l.*, u.display_name from {$this->loginsTable} l
338 LEFT JOIN {$wpdb->users} u on l.userID = u.ID
339 where ctime > %f $IPSQL order by ctime desc limit %d");
340 $results = call_user_func_array(array($this->getDB(), 'querySelect'), $sqlArgs );
341
342 } else {
343 wordfence::status(1, 'error', "getHits got invalid hitType: $hitType");
344 return false;
345 }
346 $this->processGetHitsResults($type, $results);
347 return $results;
348 }
349
350 /**
351 * @param string $type
352 * @param array $results
353 * @throws Exception
354 */
355 public function processGetHitsResults($type, &$results) {
356 $serverTime = $this->getDB()->querySingle("select unix_timestamp()");
357
358 $this->resolveIPs($results);
359 $ourURL = parse_url(site_url());
360 $ourHost = strtolower($ourURL['host']);
361 $ourHost = preg_replace('/^www\./i', '', $ourHost);
362 $browscap = wfBrowscap::shared();
363
364 $patternBlocks = wfBlock::patternBlocks(true);
365
366 foreach($results as &$res){
367 $res['type'] = $type;
368 $res['IP'] = wfUtils::inet_ntop($res['IP']);
369 $res['timeAgo'] = wfUtils::makeTimeAgo($serverTime - $res['ctime']);
370 $res['blocked'] = false;
371 $res['rangeBlocked'] = false;
372 $res['ipRangeID'] = -1;
373
374 $ipBlock = wfBlock::findIPBlock($res['IP']);
375 if ($ipBlock !== false) {
376 $res['blocked'] = true;
377 $res['blockID'] = $ipBlock->id;
378 }
379
380 foreach ($patternBlocks as $b) {
381 if (empty($b->ipRange)) { continue; }
382 $range = new wfUserIPRange($b->ipRange);
383 if ($range->isIPInRange($res['IP'])) {
384 $res['rangeBlocked'] = true;
385 $res['ipRangeID'] = $b->id;
386 break;
387 }
388 }
389
390 $res['extReferer'] = false;
391 if(isset( $res['referer'] ) && $res['referer']){
392 if(wfUtils::hasXSS($res['referer'] )){ //filtering out XSS
393 $res['referer'] = '';
394 }
395 }
396 if( isset( $res['referer'] ) && $res['referer']){
397 $refURL = parse_url($res['referer']);
398 if(is_array($refURL) && isset($refURL['host']) && $refURL['host']){
399 $refHost = strtolower(preg_replace('/^www\./i', '', $refURL['host']));
400 if($refHost != $ourHost){
401 $res['extReferer'] = true;
402 //now extract search terms
403 $q = false;
404 if(preg_match('/(?:google|bing|alltheweb|aol|ask)\./i', $refURL['host'])){
405 $q = 'q';
406 } else if(stristr($refURL['host'], 'yahoo.')){
407 $q = 'p';
408 } else if(stristr($refURL['host'], 'baidu.')){
409 $q = 'wd';
410 }
411 if($q){
412 $queryVars = array();
413 if( isset( $refURL['query'] ) ) {
414 parse_str($refURL['query'], $queryVars);
415 if(isset($queryVars[$q])){
416 $res['searchTerms'] = urlencode($queryVars[$q]);
417 }
418 }
419 }
420 }
421 }
422 if($res['extReferer']){
423 if ( isset( $referringPage ) && stristr( $referringPage['host'], 'google.' ) )
424 {
425 parse_str( $referringPage['query'], $queryVars );
426 // echo $queryVars['q']; // This is the search term used
427 }
428 }
429 }
430 $res['browser'] = false;
431 if($res['UA']){
432 $b = $browscap->getBrowser($res['UA']);
433 if($b && $b['Parent'] != 'DefaultProperties'){
434 $res['browser'] = array(
435 'browser' => !empty($b['Browser']) ? $b['Browser'] : "",
436 'version' => !empty($b['Version']) ? $b['Version'] : "",
437 'platform' => !empty($b['Platform']) ? $b['Platform'] : "",
438 'isMobile' => !empty($b['isMobileDevice']) ? $b['isMobileDevice'] : "",
439 'isCrawler' => !empty($b['Crawler']) ? $b['Crawler'] : "",
440 );
441
442 if (isset($res['jsRun']) && !wfConfig::liveTrafficEnabled() && !wfUtils::truthyToBoolean($res['jsRun'])) {
443 $res['jsRun'] = !(isset($b['Crawler']) && $b['Crawler']) ? '1' : '0';
444 }
445 }
446 else {
447 $IP = wfUtils::getIP();
448 $res['browser'] = array(
449 'isCrawler' => !wfLog::isHumanRequest($IP, $res['UA']) ? 'true' : ''
450 );
451 }
452 }
453
454
455 if($res['userID']){
456 $ud = get_userdata($res['userID']);
457 if($ud){
458 $res['user'] = array(
459 'editLink' => wfUtils::editUserLink($res['userID']),
460 'display_name' => $res['display_name'],
461 'ID' => $res['userID']
462 );
463 }
464 } else {
465 $res['user'] = false;
466 }
467 }
468 }
469
470 public function resolveIPs(&$results){
471 if(sizeof($results) < 1){ return; }
472 $IPs = array();
473 foreach($results as &$res){
474 if($res['IP']){ //Can also be zero in case of non IP events
475 $IPs[] = $res['IP'];
476 }
477 }
478 $IPLocs = wfUtils::getIPsGeo($IPs); //Creates an array with IP as key and data as value
479
480 foreach($results as &$res){
481 $ip_printable = wfUtils::inet_ntop($res['IP']);
482 if(isset($IPLocs[$ip_printable])){
483 $res['loc'] = $IPLocs[$ip_printable];
484 } else {
485 $res['loc'] = false;
486 }
487 }
488 }
489 public function logHitOK(){
490 if (!$this->canLogHit) {
491 return false;
492 }
493 if (is_admin()) { return false; } //Don't log admin pageviews
494 if (isset($_SERVER['HTTP_USER_AGENT'])) {
495 if (preg_match('/WordPress\/' . $this->wp_version . '/i', $_SERVER['HTTP_USER_AGENT'])) { return false; } //Ignore regular requests generated by WP UA.
496 }
497 $userID = get_current_user_id();
498 if (!$userID) {
499 $userID = $this->effectiveUserID;
500 }
501 if ($userID) {
502 $user = new WP_User($userID);
503 if ($user && $user->exists()) {
504 if (wfConfig::get('liveTraf_ignorePublishers') && ($user->has_cap('publish_posts') || $user->has_cap('publish_pages'))) {
505 return false;
506 }
507
508 if (wfConfig::get('liveTraf_ignoreUsers')) {
509 $ignored = explode(',', wfConfig::get('liveTraf_ignoreUsers'));
510 foreach ($ignored as $entry) {
511 if($user->user_login == $entry){
512 return false;
513 }
514 }
515 }
516 }
517 }
518 if(wfConfig::get('liveTraf_ignoreIPs')){
519 $IPs = explode(',', wfConfig::get('liveTraf_ignoreIPs'));
520 $IP = wfUtils::getIP();
521 foreach($IPs as $ignoreIP){
522 if($ignoreIP == $IP){
523 return false;
524 }
525 }
526 }
527 if( isset($_SERVER['HTTP_USER_AGENT']) && wfConfig::get('liveTraf_ignoreUA') ){
528 if($_SERVER['HTTP_USER_AGENT'] == wfConfig::get('liveTraf_ignoreUA')){
529 return false;
530 }
531 }
532
533 return true;
534 }
535 private function getDB(){
536 if(! $this->db){
537 $this->db = new wfDB();
538 }
539 return $this->db;
540 }
541 public function firewallBadIPs() {
542 $IP = wfUtils::getIP();
543 if (wfBlock::isWhitelisted($IP)) {
544 return;
545 }
546
547 //Range and UA pattern blocking
548 $patternBlocks = wfBlock::patternBlocks(true);
549 $userAgent = !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
550 $referrer = !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
551 foreach ($patternBlocks as $b) {
552 if ($b->matchRequest($IP, $userAgent, $referrer) !== wfBlock::MATCH_NONE) {
553 $b->recordBlock();
554 wfActivityReport::logBlockedIP($IP, null, 'advanced');
555 $this->currentRequest->actionDescription = 'UA/Referrer/IP Range not allowed';
556 $this->do503(3600, "Advanced blocking in effect."); //exits
557 }
558 }
559
560 // Country blocking
561 $countryBlocks = wfBlock::countryBlocks(true);
562 foreach ($countryBlocks as $b) {
563 $match = $b->matchRequest($IP, false, false);
564 if ($match === wfBlock::MATCH_COUNTRY_REDIR_BYPASS) {
565 $bypassRedirDest = wfConfig::get('cbl_bypassRedirDest', '');
566
567 $this->initLogRequest();
568 $this->getCurrentRequest()->actionDescription = __('redirected to bypass URL', 'wordfence');
569 $this->getCurrentRequest()->statusCode = 302;
570 $this->currentRequest->action = 'cbl:redirect';
571 $this->logHit();
572
573 wfUtils::doNotCache();
574 wp_redirect($bypassRedirDest, 302);
575 exit();
576 }
577 else if ($match === wfBlock::MATCH_COUNTRY_REDIR) {
578 $b->recordBlock();
579 wfConfig::inc('totalCountryBlocked');
580
581 $this->initLogRequest();
582 $this->getCurrentRequest()->actionDescription = sprintf(__('blocked access via country blocking and redirected to URL (%s)', 'wordfence'), wfConfig::get('cbl_redirURL'));
583 $this->getCurrentRequest()->statusCode = 503;
584 if (!$this->getCurrentRequest()->action) {
585 $this->currentRequest->action = 'blocked:wordfence';
586 }
587 $this->logHit();
588
589 wfActivityReport::logBlockedIP($IP, null, 'country');
590
591 wfUtils::doNotCache();
592 wp_redirect(wfConfig::get('cbl_redirURL'), 302);
593 exit();
594 }
595 else if ($match !== wfBlock::MATCH_NONE) {
596 $b->recordBlock();
597 $this->currentRequest->actionDescription = __('blocked access via country blocking', 'wordfence');
598 wfConfig::inc('totalCountryBlocked');
599 wfActivityReport::logBlockedIP($IP, null, 'country');
600 $this->do503(3600, __('Access from your area has been temporarily limited for security reasons', 'wordfence'));
601 }
602 }
603
604 //Specific IP blocks
605 $ipBlock = wfBlock::findIPBlock($IP);
606 if ($ipBlock !== false) {
607 $ipBlock->recordBlock();
608 $secsToGo = max(0, $ipBlock->expiration - time());
609 if (wfConfig::get('other_WFNet') && self::isAuthRequest()) { //It's an auth request and this IP has been blocked
610 $this->getCurrentRequest()->action = 'blocked:wfsnrepeat';
611 wordfence::wfsnReportBlockedAttempt($IP, 'login');
612 }
613 $reason = $ipBlock->reason;
614 if ($ipBlock->type == wfBlock::TYPE_IP_MANUAL || $ipBlock->type == wfBlock::TYPE_IP_AUTOMATIC_PERMANENT) {
615 $reason = __('Manual block by administrator', 'wordfence');
616 }
617 $this->do503($secsToGo, $reason); //exits
618 }
619 }
620
621 private function takeBlockingAction($configVar, $reason) {
622 if ($this->googleSafetyCheckOK()) {
623 $action = wfConfig::get($configVar . '_action');
624 if (!$action) {
625 return;
626 }
627
628 $IP = wfUtils::getIP();
629 $secsToGo = 0;
630 if ($action == 'block') { //Rate limited - block temporarily
631 $secsToGo = wfBlock::blockDuration();
632 wfBlock::createRateBlock($reason, $IP, $secsToGo);
633 wfActivityReport::logBlockedIP($IP, null, 'throttle');
634 $this->tagRequestForBlock($reason);
635
636 if (wfConfig::get('alertOn_block')) {
637 $message = sprintf(__('Wordfence has blocked IP address %s.', 'wordfence'), $IP) . "\n";
638 $message .= sprintf(__('The reason is: "%s".', 'wordfence'), $reason);
639 if ($secsToGo > 0) {
640 $message .= "\n" . sprintf(__('The duration of the block is %s.', 'wordfence'), wfUtils::makeDuration($secsToGo, true));
641 }
642 wordfence::alert(sprintf(__('Blocking IP %s', 'wordfence'), $IP), $message, $IP);
643 }
644 wordfence::status(2, 'info', sprintf(__('Blocking IP %s. %s', 'wordfence'), $IP, $reason));
645 }
646 else if ($action == 'throttle') { //Rate limited - throttle
647 $secsToGo = wfBlock::rateLimitThrottleDuration();
648 wfBlock::createRateThrottle($reason, $IP, $secsToGo);
649 wfActivityReport::logBlockedIP($IP, null, 'throttle');
650
651 wordfence::status(2, 'info', sprintf(__('Throttling IP %s. %s', 'wordfence'), $IP, $reason));
652 wfConfig::inc('totalIPsThrottled');
653 }
654 $this->do503($secsToGo, $reason);
655 }
656
657 return;
658 }
659
660 /**
661 * Test if the current request is for wp-login.php or xmlrpc.php
662 *
663 * @return boolean
664 */
665 private static function isAuthRequest() {
666 if ((strpos($_SERVER['REQUEST_URI'], '/wp-login.php') !== false)) {
667 return true;
668 }
669 return false;
670 }
671
672 public function do503($secsToGo, $reason){
673 $this->initLogRequest();
674 $this->currentRequest->statusCode = 503;
675 if (!$this->currentRequest->action) {
676 $this->currentRequest->action = 'blocked:wordfence';
677 }
678 if (!$this->currentRequest->actionDescription) {
679 $this->currentRequest->actionDescription = "blocked: " . $reason;
680 }
681
682 $this->logHit();
683
684 wfConfig::inc('total503s');
685 wfUtils::doNotCache();
686 header('HTTP/1.1 503 Service Temporarily Unavailable');
687 header('Status: 503 Service Temporarily Unavailable');
688 if($secsToGo){
689 header('Retry-After: ' . $secsToGo);
690 }
691 $customText = wpautop(wp_strip_all_tags(wfConfig::get('blockCustomText', '')));
692 require_once('wf503.php');
693 exit();
694 }
695 private function redirect($URL){
696 wfUtils::doNotCache();
697 wp_redirect($URL, 302);
698 exit();
699 }
700 private function googleSafetyCheckOK(){ //returns true if OK to block. Returns false if we must not block.
701 $cacheKey = md5( (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '') . ' ' . wfUtils::getIP());
702 //Cache so we can call this multiple times in one request
703 if(! isset(self::$gbSafeCache[$cacheKey])){
704 $nb = wfConfig::get('neverBlockBG');
705 if($nb == 'treatAsOtherCrawlers'){
706 self::$gbSafeCache[$cacheKey] = true; //OK to block because we're treating google like everyone else
707 } else if($nb == 'neverBlockUA' || $nb == 'neverBlockVerified'){
708 if(wfCrawl::isGoogleCrawler()){ //Check the UA using regex
709 if($nb == 'neverBlockVerified'){
710 if(wfCrawl::isVerifiedGoogleCrawler(wfUtils::getIP())){ //UA check passed, now verify using PTR if configured to
711 self::$gbSafeCache[$cacheKey] = false; //This is a verified Google crawler, so no we can't block it
712 } else {
713 self::$gbSafeCache[$cacheKey] = true; //This is a crawler claiming to be Google but it did not verify
714 }
715 } else { //neverBlockUA
716 self::$gbSafeCache[$cacheKey] = false; //User configured us to only do a UA check and this claims to be google so don't block
717 }
718 } else {
719 self::$gbSafeCache[$cacheKey] = true; //This isn't a Google UA, so it's OK to block
720 }
721 } else {
722 //error_log("Wordfence error: neverBlockBG option is not set.");
723 self::$gbSafeCache[$cacheKey] = false; //Oops the config option is not set. This should never happen because it's set on install. So we return false to indicate it's not OK to block just for safety.
724 }
725 }
726 if(! isset(self::$gbSafeCache[$cacheKey])){
727 //error_log("Wordfence assertion fail in googleSafetyCheckOK: cached value is not set.");
728 return false; //for safety
729 }
730 return self::$gbSafeCache[$cacheKey]; //return cached value
731 }
732 public function addStatus($level, $type, $msg){
733 //$msg = '[' . sprintf('%.2f', memory_get_usage(true) / (1024 * 1024)) . '] ' . $msg;
734 $this->getDB()->queryWrite("insert into " . $this->statusTable . " (ctime, level, type, msg) values (%s, %d, '%s', '%s')", sprintf('%.6f', microtime(true)), $level, $type, $msg);
735 }
736 public function getStatusEvents($lastCtime){
737 if($lastCtime < 1){
738 $lastCtime = $this->getDB()->querySingle("select ctime from " . $this->statusTable . " order by ctime desc limit 1000,1");
739 if(! $lastCtime){
740 $lastCtime = 0;
741 }
742 }
743 $results = $this->getDB()->querySelect("select ctime, level, type, msg from " . $this->statusTable . " where ctime > %f order by ctime asc", $lastCtime);
744 $timeOffset = 3600 * get_option('gmt_offset');
745 foreach($results as &$rec){
746 //$rec['timeAgo'] = wfUtils::makeTimeAgo(time() - $rec['ctime']);
747 $rec['date'] = date('M d H:i:s', $rec['ctime'] + $timeOffset);
748 $rec['msg'] = wp_kses_data( (string) $rec['msg']);
749 }
750 return $results;
751 }
752 public function getSummaryEvents(){
753 $results = $this->getDB()->querySelect("select ctime, level, type, msg from " . $this->statusTable . " where level = 10 order by ctime desc limit 100");
754 $timeOffset = 3600 * get_option('gmt_offset');
755 foreach($results as &$rec){
756 $rec['date'] = date('M d H:i:s', $rec['ctime'] + $timeOffset);
757 if(strpos($rec['msg'], 'SUM_PREP:') === 0){
758 break;
759 }
760 }
761 return array_reverse($results);
762 }
763
764 /**
765 * @return string
766 */
767 public function getGooglePattern() {
768 return $this->googlePattern;
769 }
770
771 }
772
773 /**
774 *
775 */
776 class wfUserIPRange {
777
778 /**
779 * @var string|null
780 */
781 private $ip_string;
782
783 /**
784 * @param string|null $ip_string
785 */
786 public function __construct($ip_string = null) {
787 $this->setIPString($ip_string);
788 }
789
790 /**
791 * Check if the supplied IP address is within the user supplied range.
792 *
793 * @param string $ip
794 * @return bool
795 */
796 public function isIPInRange($ip) {
797 $ip_string = $this->getIPString();
798
799 if (strpos($ip_string, '/') !== false) { //CIDR range -- 127.0.0.1/24
800 return wfUtils::subnetContainsIP($ip_string, $ip);
801 }
802 else if (strpos($ip_string, '[') !== false) //Bracketed range -- 127.0.0.[1-100]
803 {
804 // IPv4 range
805 if (strpos($ip_string, '.') !== false && strpos($ip, '.') !== false) {
806 // IPv4-mapped-IPv6
807 if (preg_match('/:ffff:([^:]+)$/i', $ip_string, $matches)) {
808 $ip_string = $matches[1];
809 }
810 if (preg_match('/:ffff:([^:]+)$/i', $ip, $matches)) {
811 $ip = $matches[1];
812 }
813
814 // Range check
815 if (preg_match('/\[\d+\-\d+\]/', $ip_string)) {
816 $IPparts = explode('.', $ip);
817 $whiteParts = explode('.', $ip_string);
818 $mismatch = false;
819 if (count($whiteParts) != 4 || count($IPparts) != 4) {
820 return false;
821 }
822
823 for ($i = 0; $i <= 3; $i++) {
824 if (preg_match('/^\[(\d+)\-(\d+)\]$/', $whiteParts[$i], $m)) {
825 if ($IPparts[$i] < $m[1] || $IPparts[$i] > $m[2]) {
826 $mismatch = true;
827 }
828 }
829 else if ($whiteParts[$i] != $IPparts[$i]) {
830 $mismatch = true;
831 }
832 }
833 if ($mismatch === false) {
834 return true; // Is whitelisted because we did not get a mismatch
835 }
836 }
837 else if ($ip_string == $ip) {
838 return true;
839 }
840
841 // IPv6 range
842 }
843 else if (strpos($ip_string, ':') !== false && strpos($ip, ':') !== false) {
844 $ip = strtolower(wfUtils::expandIPv6Address($ip));
845 $ip_string = strtolower(self::expandIPv6Range($ip_string));
846 if (preg_match('/\[[a-f0-9]+\-[a-f0-9]+\]/i', $ip_string)) {
847 $IPparts = explode(':', $ip);
848 $whiteParts = explode(':', $ip_string);
849 $mismatch = false;
850 if (count($whiteParts) != 8 || count($IPparts) != 8) {
851 return false;
852 }
853
854 for ($i = 0; $i <= 7; $i++) {
855 if (preg_match('/^\[([a-f0-9]+)\-([a-f0-9]+)\]$/i', $whiteParts[$i], $m)) {
856 $ip_group = hexdec($IPparts[$i]);
857 $range_group_from = hexdec($m[1]);
858 $range_group_to = hexdec($m[2]);
859 if ($ip_group < $range_group_from || $ip_group > $range_group_to) {
860 $mismatch = true;
861 break;
862 }
863 }
864 else if ($whiteParts[$i] != $IPparts[$i]) {
865 $mismatch = true;
866 break;
867 }
868 }
869 if ($mismatch === false) {
870 return true; // Is whitelisted because we did not get a mismatch
871 }
872 }
873 else if ($ip_string == $ip) {
874 return true;
875 }
876 }
877 }
878 else if (strpos($ip_string, '-') !== false) { //Linear range -- 127.0.0.1 - 127.0.1.100
879 list($ip1, $ip2) = explode('-', $ip_string);
880 $ip1N = wfUtils::inet_pton($ip1);
881 $ip2N = wfUtils::inet_pton($ip2);
882 $ipN = wfUtils::inet_pton($ip);
883 return (strcmp($ip1N, $ipN) <= 0 && strcmp($ip2N, $ipN) >= 0);
884 }
885 else { //Treat as a literal IP
886 $ip1 = @wfUtils::inet_pton($ip_string);
887 $ip2 = @wfUtils::inet_pton($ip);
888 if ($ip1 !== false && $ip1 == $ip2) {
889 return true;
890 }
891 }
892
893 return false;
894 }
895
896 /**
897 * Expand a compressed printable range representation of an IPv6 address.
898 *
899 * @todo Hook up exceptions for better error handling.
900 * @todo Allow IPv4 mapped IPv6 addresses (::ffff:192.168.1.1).
901 * @param string $ip_range
902 * @return string
903 */
904 public static function expandIPv6Range($ip_range) {
905 $colon_count = substr_count($ip_range, ':');
906 $dbl_colon_count = substr_count($ip_range, '::');
907 if ($dbl_colon_count > 1) {
908 return false;
909 }
910 $dbl_colon_pos = strpos($ip_range, '::');
911 if ($dbl_colon_pos !== false) {
912 $ip_range = str_replace('::', str_repeat(':0000',
913 (($dbl_colon_pos === 0 || $dbl_colon_pos === strlen($ip_range) - 2) ? 9 : 8) - $colon_count) . ':', $ip_range);
914 $ip_range = trim($ip_range, ':');
915 }
916 $colon_count = substr_count($ip_range, ':');
917 if ($colon_count != 7) {
918 return false;
919 }
920
921 $groups = explode(':', $ip_range);
922 $expanded = '';
923 foreach ($groups as $group) {
924 if (preg_match('/\[([a-f0-9]{1,4})\-([a-f0-9]{1,4})\]/i', $group, $matches)) {
925 $expanded .= sprintf('[%s-%s]', str_pad(strtolower($matches[1]), 4, '0', STR_PAD_LEFT), str_pad(strtolower($matches[2]), 4, '0', STR_PAD_LEFT)) . ':';
926 } else if (preg_match('/[a-f0-9]{1,4}/i', $group)) {
927 $expanded .= str_pad(strtolower($group), 4, '0', STR_PAD_LEFT) . ':';
928 } else {
929 return false;
930 }
931 }
932 return trim($expanded, ':');
933 }
934
935 /**
936 * @return bool
937 */
938 public function isValidRange() {
939 return $this->isValidCIDRRange() || $this->isValidBracketedRange() || $this->isValidLinearRange() || wfUtils::isValidIP($this->getIPString());
940 }
941
942 public function isValidCIDRRange() { //e.g., 192.0.2.1/24
943 $ip_string = $this->getIPString();
944 if (preg_match('/[^0-9a-f:\/\.]/i', $ip_string)) { return false; }
945 return wfUtils::isValidCIDRRange($ip_string);
946 }
947
948 public function isValidBracketedRange() { //e.g., 192.0.2.[1-10]
949 $ip_string = $this->getIPString();
950 if (preg_match('/[^0-9a-f:\.\[\]\-]/i', $ip_string)) { return false; }
951 if (strpos($ip_string, '.') !== false) { //IPv4
952 if (preg_match_all('/(\d+)/', $ip_string, $matches) > 0) {
953 foreach ($matches[1] as $match) {
954 $group = (int) $match;
955 if ($group > 255 || $group < 0) {
956 return false;
957 }
958 }
959 }
960
961 $group_regex = '([0-9]{1,3}|\[[0-9]{1,3}\-[0-9]{1,3}\])';
962 return preg_match('/^' . str_repeat("{$group_regex}\\.", 3) . $group_regex . '$/i', $ip_string) > 0;
963 }
964
965 //IPv6
966 if (strpos($ip_string, '::') !== false) {
967 $ip_string = self::expandIPv6Range($ip_string);
968 }
969 if (!$ip_string) {
970 return false;
971 }
972 $group_regex = '([a-f0-9]{1,4}|\[[a-f0-9]{1,4}\-[a-f0-9]{1,4}\])';
973 return preg_match('/^' . str_repeat("$group_regex:", 7) . $group_regex . '$/i', $ip_string) > 0;
974 }
975
976 public function isValidLinearRange() { //e.g., 192.0.2.1-192.0.2.100
977 $ip_string = $this->getIPString();
978 if (preg_match('/[^0-9a-f:\.\-]/i', $ip_string)) { return false; }
979 list($ip1, $ip2) = explode("-", $ip_string);
980 $ip1N = @wfUtils::inet_pton($ip1);
981 $ip2N = @wfUtils::inet_pton($ip2);
982
983 if ($ip1N === false || !wfUtils::isValidIP($ip1) || $ip2N === false || !wfUtils::isValidIP($ip2)) {
984 return false;
985 }
986
987 return strcmp($ip1N, $ip2N) <= 0;
988 }
989
990 public function isMixedRange() { //e.g., 192.0.2.1-2001:db8::ffff
991 $ip_string = $this->getIPString();
992 if (preg_match('/[^0-9a-f:\.\-]/i', $ip_string)) { return false; }
993 list($ip1, $ip2) = explode("-", $ip_string);
994
995 $ipv4Count = 0;
996 $ipv4Count += filter_var($ip1, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false ? 1 : 0;
997 $ipv4Count += filter_var($ip2, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false ? 1 : 0;
998
999 $ipv6Count = 0;
1000 $ipv6Count += filter_var($ip1, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false ? 1 : 0;
1001 $ipv6Count += filter_var($ip2, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false ? 1 : 0;
1002
1003 if ($ipv4Count != 2 && $ipv6Count != 2) {
1004 return true;
1005 }
1006
1007 return false;
1008 }
1009
1010 protected function _sanitizeIPRange($ip_string) {
1011 $ip_string = preg_replace('/\s/', '', $ip_string); //Strip whitespace
1012 $ip_string = preg_replace('/[\\x{2013}-\\x{2015}]/u', '-', $ip_string); //Non-hyphen dashes to hyphen
1013 $ip_string = strtolower($ip_string);
1014
1015 if (preg_match('/^\d+-\d+$/', $ip_string)) { //v5 32 bit int style format
1016 list($start, $end) = explode('-', $ip_string);
1017 $start = long2ip($start);
1018 $end = long2ip($end);
1019 $ip_string = "{$start}-{$end}";
1020 }
1021
1022 return $ip_string;
1023 }
1024
1025 /**
1026 * @return string|null
1027 */
1028 public function getIPString() {
1029 return $this->ip_string;
1030 }
1031
1032 /**
1033 * @param string|null $ip_string
1034 */
1035 public function setIPString($ip_string) {
1036 $this->ip_string = $this->_sanitizeIPRange($ip_string);
1037 }
1038 }
1039
1040 /**
1041 * The function of this class is to detect admin users created via direct access to the database (in other words, not
1042 * through WordPress).
1043 */
1044 class wfAdminUserMonitor {
1045
1046 public function isEnabled() {
1047 $options = wfScanner::shared()->scanOptions();
1048 $enabled = $options['scansEnabled_suspiciousAdminUsers'];
1049 if ($enabled && is_multisite()) {
1050 if (!function_exists('wp_is_large_network')) {
1051 require_once ABSPATH . WPINC . '/ms-functions.php';
1052 }
1053 $enabled = !wp_is_large_network('sites') && !wp_is_large_network('users');
1054 }
1055 return $enabled;
1056 }
1057
1058 /**
1059 *
1060 */
1061 public function createInitialList() {
1062 $admins = $this->getCurrentAdmins();
1063 wfConfig::set_ser('adminUserList', $admins);
1064 }
1065
1066 /**
1067 * @param int $userID
1068 */
1069 public function grantSuperAdmin($userID = null) {
1070 if ($userID) {
1071 $this->addAdmin($userID);
1072 }
1073 }
1074
1075 /**
1076 * @param int $userID
1077 */
1078 public function revokeSuperAdmin($userID = null) {
1079 if ($userID) {
1080 $this->removeAdmin($userID);
1081 }
1082 }
1083
1084 /**
1085 * @param int $ID
1086 * @param mixed $role
1087 * @param mixed $old_roles
1088 */
1089 public function updateToUserRole($ID = null, $role = null, $old_roles = null) {
1090 $admins = $this->getLoggedAdmins();
1091 if ($role !== 'administrator' && array_key_exists($ID, $admins)) {
1092 $this->removeAdmin($ID);
1093 } else if ($role === 'administrator') {
1094 $this->addAdmin($ID);
1095 }
1096 }
1097
1098 /**
1099 * @return array|bool
1100 */
1101 public function checkNewAdmins() {
1102 $loggedAdmins = $this->getLoggedAdmins();
1103 $admins = $this->getCurrentAdmins();
1104 $suspiciousAdmins = array();
1105 foreach ($admins as $adminID => $v) {
1106 if (!array_key_exists($adminID, $loggedAdmins)) {
1107 $suspiciousAdmins[] = $adminID;
1108 }
1109 }
1110 return $suspiciousAdmins ? $suspiciousAdmins : false;
1111 }
1112
1113 /**
1114 * Checks if the supplied user ID is suspicious.
1115 *
1116 * @param int $userID
1117 * @return bool
1118 */
1119 public function isAdminUserLogged($userID) {
1120 $loggedAdmins = $this->getLoggedAdmins();
1121 return array_key_exists($userID, $loggedAdmins);
1122 }
1123
1124 /**
1125 * @return array
1126 */
1127 public function getCurrentAdmins() {
1128 require_once ABSPATH . WPINC . '/user.php';
1129 if (is_multisite()) {
1130 if (function_exists("get_sites")) {
1131 $sites = get_sites(array(
1132 'network_id' => null,
1133 ));
1134 }
1135 else {
1136 $sites = wp_get_sites(array(
1137 'network_id' => null,
1138 ));
1139 }
1140 } else {
1141 $sites = array(array(
1142 'blog_id' => get_current_blog_id(),
1143 ));
1144 }
1145
1146 // not very efficient, but the WordPress API doesn't provide a good way to do this.
1147 $admins = array();
1148 foreach ($sites as $siteRow) {
1149 $siteRowArray = (array) $siteRow;
1150 $user_query = new WP_User_Query(array(
1151 'blog_id' => $siteRowArray['blog_id'],
1152 'role' => 'administrator',
1153 ));
1154 $users = $user_query->get_results();
1155 if (is_array($users)) {
1156 /** @var WP_User $user */
1157 foreach ($users as $user) {
1158 $admins[$user->ID] = 1;
1159 }
1160 }
1161 }
1162
1163 // Add any super admins that aren't also admins on a network
1164 $superAdmins = get_super_admins();
1165 foreach ($superAdmins as $userLogin) {
1166 $user = get_user_by('login', $userLogin);
1167 if ($user) {
1168 $admins[$user->ID] = 1;
1169 }
1170 }
1171 return $admins;
1172 }
1173
1174 public function getLoggedAdmins() {
1175 $loggedAdmins = wfConfig::get_ser('adminUserList', false);
1176 if (!is_array($loggedAdmins)) {
1177 $this->createInitialList();
1178 $loggedAdmins = wfConfig::get_ser('adminUserList', false);
1179 }
1180 if (!is_array($loggedAdmins)) {
1181 $loggedAdmins = array();
1182 }
1183 return $loggedAdmins;
1184 }
1185
1186 /**
1187 * @param int $userID
1188 */
1189 public function addAdmin($userID) {
1190 $loggedAdmins = $this->getLoggedAdmins();
1191 if (!array_key_exists($userID, $loggedAdmins)) {
1192 $loggedAdmins[$userID] = 1;
1193 wfConfig::set_ser('adminUserList', $loggedAdmins);
1194 }
1195 }
1196
1197 /**
1198 * @param int $userID
1199 */
1200 public function removeAdmin($userID) {
1201 $loggedAdmins = $this->getLoggedAdmins();
1202 if (array_key_exists($userID, $loggedAdmins) && !array_key_exists($userID, $this->getCurrentAdmins())) {
1203 unset($loggedAdmins[$userID]);
1204 wfConfig::set_ser('adminUserList', $loggedAdmins);
1205 }
1206 }
1207 }
1208
1209 /**
1210 * Represents a request record
1211 *
1212 * @property int $id
1213 * @property float $attackLogTime
1214 * @property float $ctime
1215 * @property string $IP
1216 * @property bool $jsRun
1217 * @property int $statusCode
1218 * @property bool $isGoogle
1219 * @property int $userID
1220 * @property string $URL
1221 * @property string $referer
1222 * @property string $UA
1223 * @property string $action
1224 * @property string $actionDescription
1225 * @property string $actionData
1226 */
1227 class wfRequestModel extends wfModel {
1228
1229 private static $actionDataEncodedParams = array(
1230 'paramKey',
1231 'paramValue',
1232 'path',
1233 );
1234
1235 /**
1236 * @param $actionData
1237 * @return mixed|string|void
1238 */
1239 public static function serializeActionData($actionData) {
1240 if (is_array($actionData)) {
1241 foreach (self::$actionDataEncodedParams as $key) {
1242 if (array_key_exists($key, $actionData)) {
1243 $actionData[$key] = base64_encode($actionData[$key]);
1244 }
1245 }
1246 }
1247 return json_encode($actionData);
1248 }
1249
1250 /**
1251 * @param $actionDataJSON
1252 * @return mixed|string|void
1253 */
1254 public static function unserializeActionData($actionDataJSON) {
1255 $actionData = json_decode($actionDataJSON, true);
1256 if (is_array($actionData)) {
1257 foreach (self::$actionDataEncodedParams as $key) {
1258 if (array_key_exists($key, $actionData)) {
1259 $actionData[$key] = base64_decode($actionData[$key]);
1260 }
1261 }
1262 }
1263 return $actionData;
1264 }
1265
1266 private $columns = array(
1267 'id',
1268 'attackLogTime',
1269 'ctime',
1270 'IP',
1271 'jsRun',
1272 'statusCode',
1273 'isGoogle',
1274 'userID',
1275 'URL',
1276 'referer',
1277 'UA',
1278 'action',
1279 'actionDescription',
1280 'actionData',
1281 );
1282
1283 public function getIDColumn() {
1284 return 'id';
1285 }
1286
1287 public function getTable() {
1288 return wfDB::networkTable('wfHits');
1289 }
1290
1291 public function hasColumn($column) {
1292 return in_array($column, $this->columns);
1293 }
1294
1295 public function save() {
1296 $sapi = @php_sapi_name();
1297 if ($sapi == "cli") {
1298 return false;
1299 }
1300
1301 return parent::save();
1302 }
1303 }
1304
1305
1306 class wfLiveTrafficQuery {
1307
1308 protected $validParams = array(
1309 'id' => 'h.id',
1310 'ctime' => 'h.ctime',
1311 'ip' => 'h.ip',
1312 'jsrun' => 'h.jsrun',
1313 'statuscode' => 'h.statuscode',
1314 'isgoogle' => 'h.isgoogle',
1315 'userid' => 'h.userid',
1316 'url' => 'h.url',
1317 'referer' => 'h.referer',
1318 'ua' => 'h.ua',
1319 'action' => 'h.action',
1320 'actiondescription' => 'h.actiondescription',
1321 'actiondata' => 'h.actiondata',
1322
1323 // wfLogins
1324 'user_login' => 'u.user_login',
1325 'username' => 'l.username',
1326 );
1327
1328 /** @var wfLiveTrafficQueryFilterCollection */
1329 private $filters = array();
1330
1331 /** @var wfLiveTrafficQueryGroupBy */
1332 private $groupBy;
1333 /**
1334 * @var float|null
1335 */
1336 private $startDate;
1337 /**
1338 * @var float|null
1339 */
1340 private $endDate;
1341 /**
1342 * @var int
1343 */
1344 private $limit;
1345 /**
1346 * @var int
1347 */
1348 private $offset;
1349
1350 private $tableName;
1351
1352 /** @var wfLog */
1353 private $wfLog;
1354
1355 /**
1356 * wfLiveTrafficQuery constructor.
1357 *
1358 * @param wfLog $wfLog
1359 * @param wfLiveTrafficQueryFilterCollection $filters
1360 * @param wfLiveTrafficQueryGroupBy $groupBy
1361 * @param float $startDate
1362 * @param float $endDate
1363 * @param int $limit
1364 * @param int $offset
1365 */
1366 public function __construct($wfLog, $filters = null, $groupBy = null, $startDate = null, $endDate = null, $limit = 20, $offset = 0) {
1367 $this->wfLog = $wfLog;
1368 $this->filters = $filters;
1369 $this->groupBy = $groupBy;
1370 $this->startDate = $startDate;
1371 $this->endDate = $endDate;
1372 $this->limit = $limit;
1373 $this->offset = $offset;
1374 }
1375
1376 /**
1377 * @return array|null|object
1378 */
1379 public function execute() {
1380 global $wpdb;
1381 $delayedHumanBotFiltering = false;
1382 $humanOnly = false;
1383 $sql = $this->buildQuery($delayedHumanBotFiltering, $humanOnly);
1384 $results = $wpdb->get_results($sql, ARRAY_A);
1385
1386 if ($delayedHumanBotFiltering) {
1387 $browscap = wfBrowscap::shared();
1388 foreach ($results as $index => $res) {
1389 if ($res['UA']) {
1390 $b = $browscap->getBrowser($res['UA']);
1391 $jsRun = wfUtils::truthyToBoolean($res['jsRun']);
1392 if ($b && $b['Parent'] != 'DefaultProperties') {
1393 $jsRun = wfUtils::truthyToBoolean($res['jsRun']);
1394 if (!wfConfig::liveTrafficEnabled() && !$jsRun) {
1395 $jsRun = !(isset($b['Crawler']) && $b['Crawler']);
1396 }
1397 }
1398
1399 if (!$humanOnly && $jsRun || $humanOnly && !$jsRun) {
1400 unset($results[$index]);
1401 }
1402 }
1403 }
1404 }
1405
1406 $this->getWFLog()->processGetHitsResults('', $results);
1407
1408 $verifyCrawlers = false;
1409 if ($this->filters !== null && count($this->filters->getFilters()) > 0) {
1410 $filters = $this->filters->getFilters();
1411 foreach ($filters as $f) {
1412 if (strtolower($f->getParam()) == "isgoogle") {
1413 $verifyCrawlers = true;
1414 break;
1415 }
1416 }
1417 }
1418
1419 foreach ($results as $key => &$row) {
1420 if ($row['isGoogle'] && $verifyCrawlers) {
1421 if (!wfCrawl::isVerifiedGoogleCrawler($row['IP'], $row['UA'])) {
1422 unset($results[$key]); //foreach copies $results and iterates on the copy, so it is safe to mutate $results within the loop
1423 continue;
1424 }
1425 }
1426
1427 $row['actionData'] = (array) json_decode($row['actionData'], true);
1428 }
1429 return array_values($results);
1430 }
1431
1432 /**
1433 * @param mixed $delayedHumanBotFiltering Whether or not human/bot filtering should be applied in PHP rather than SQL.
1434 * @param mixed $humanOnly When using delayed filtering, whether to show only humans or only bots.
1435 *
1436 * @return string
1437 * @throws wfLiveTrafficQueryException
1438 */
1439 public function buildQuery(&$delayedHumanBotFiltering = null, &$humanOnly) {
1440 global $wpdb;
1441 $filters = $this->getFilters();
1442 $groupBy = $this->getGroupBy();
1443 $startDate = $this->getStartDate();
1444 $endDate = $this->getEndDate();
1445 $limit = absint($this->getLimit());
1446 $offset = absint($this->getOffset());
1447
1448 $wheres = array("h.action != 'logged:waf'", "h.action != 'scan:detectproxy'");
1449 if ($startDate) {
1450 $wheres[] = $wpdb->prepare('h.ctime > %f', $startDate);
1451 }
1452 if ($endDate) {
1453 $wheres[] = $wpdb->prepare('h.ctime < %f', $endDate);
1454 }
1455
1456 if ($filters instanceof wfLiveTrafficQueryFilterCollection) {
1457 if (!wfConfig::liveTrafficEnabled()) {
1458 $individualFilters = $filters->getFilters();
1459 foreach ($individualFilters as $index => $f) {
1460 if ($f->getParam() == 'jsRun' && $delayedHumanBotFiltering !== null && $humanOnly !== null) {
1461 $humanOnly = wfUtils::truthyToBoolean($f->getValue());
1462 if ($f->getOperator() == '!=') {
1463 $humanOnly = !$humanOnly;
1464 }
1465 $delayedHumanBotFiltering = true;
1466 unset($individualFilters[$index]);
1467 }
1468 }
1469 $filters->setFilters($individualFilters);
1470 }
1471
1472 $filtersSQL = $filters->toSQL();
1473 if ($filtersSQL) {
1474 $wheres[] = $filtersSQL;
1475 }
1476 }
1477
1478 $orderBy = 'ORDER BY h.ctime DESC';
1479 $select = ', l.username';
1480 $groupBySQL = '';
1481 if ($groupBy && $groupBy->validate()) {
1482 $groupBySQL = "GROUP BY {$groupBy->getParam()}";
1483 $orderBy = 'ORDER BY hitCount DESC';
1484 $select .= ', COUNT(h.id) as hitCount, MAX(h.ctime) AS lastHit, u.user_login AS username';
1485
1486 if ($groupBy->getParam() == 'user_login') {
1487 $wheres[] = 'user_login IS NOT NULL';
1488 }
1489 else if ($groupBy->getParam() == 'action') {
1490 $wheres[] = '(statusCode = 403 OR statusCode = 503)';
1491 }
1492 }
1493
1494 $where = join(' AND ', $wheres);
1495 if ($where) {
1496 $where = 'WHERE ' . $where;
1497 }
1498 if (!$limit || $limit > 1000) {
1499 $limit = 20;
1500 }
1501 $limitSQL = $wpdb->prepare('LIMIT %d, %d', $offset, $limit);
1502
1503 $table_wfLogins = wfDB::networkTable('wfLogins');
1504 $sql = <<<SQL
1505 SELECT h.*, u.display_name{$select} FROM {$this->getTableName()} h
1506 LEFT JOIN {$wpdb->users} u on h.userID = u.ID
1507 LEFT JOIN {$table_wfLogins} l on h.id = l.hitID
1508 $where
1509 $groupBySQL
1510 $orderBy
1511 $limitSQL
1512 SQL;
1513
1514 return $sql;
1515 }
1516
1517 /**
1518 * @param $param
1519 * @return bool
1520 */
1521 public function isValidParam($param) {
1522 return array_key_exists(strtolower($param), $this->validParams);
1523 }
1524
1525 /**
1526 * @param $getParam
1527 * @return bool|string
1528 */
1529 public function getColumnFromParam($getParam) {
1530 $getParam = strtolower($getParam);
1531 if (array_key_exists($getParam, $this->validParams)) {
1532 return $this->validParams[$getParam];
1533 }
1534 return false;
1535 }
1536
1537 /**
1538 * @return wfLiveTrafficQueryFilterCollection
1539 */
1540 public function getFilters() {
1541 return $this->filters;
1542 }
1543
1544 /**
1545 * @param wfLiveTrafficQueryFilterCollection $filters
1546 */
1547 public function setFilters($filters) {
1548 $this->filters = $filters;
1549 }
1550
1551 /**
1552 * @return float|null
1553 */
1554 public function getStartDate() {
1555 return $this->startDate;
1556 }
1557
1558 /**
1559 * @param float|null $startDate
1560 */
1561 public function setStartDate($startDate) {
1562 $this->startDate = $startDate;
1563 }
1564
1565 /**
1566 * @return float|null
1567 */
1568 public function getEndDate() {
1569 return $this->endDate;
1570 }
1571
1572 /**
1573 * @param float|null $endDate
1574 */
1575 public function setEndDate($endDate) {
1576 $this->endDate = $endDate;
1577 }
1578
1579 /**
1580 * @return wfLiveTrafficQueryGroupBy
1581 */
1582 public function getGroupBy() {
1583 return $this->groupBy;
1584 }
1585
1586 /**
1587 * @param wfLiveTrafficQueryGroupBy $groupBy
1588 */
1589 public function setGroupBy($groupBy) {
1590 $this->groupBy = $groupBy;
1591 }
1592
1593 /**
1594 * @return int
1595 */
1596 public function getLimit() {
1597 return $this->limit;
1598 }
1599
1600 /**
1601 * @param int $limit
1602 */
1603 public function setLimit($limit) {
1604 $this->limit = $limit;
1605 }
1606
1607 /**
1608 * @return int
1609 */
1610 public function getOffset() {
1611 return $this->offset;
1612 }
1613
1614 /**
1615 * @param int $offset
1616 */
1617 public function setOffset($offset) {
1618 $this->offset = $offset;
1619 }
1620
1621 /**
1622 * @return string
1623 */
1624 public function getTableName() {
1625 if ($this->tableName === null) {
1626 $this->tableName = wfDB::networkTable('wfHits');
1627 }
1628 return $this->tableName;
1629 }
1630
1631 /**
1632 * @param string $tableName
1633 */
1634 public function setTableName($tableName) {
1635 $this->tableName = $tableName;
1636 }
1637
1638 /**
1639 * @return wfLog
1640 */
1641 public function getWFLog() {
1642 return $this->wfLog;
1643 }
1644
1645 /**
1646 * @param wfLog $wfLog
1647 */
1648 public function setWFLog($wfLog) {
1649 $this->wfLog = $wfLog;
1650 }
1651 }
1652
1653 class wfLiveTrafficQueryFilterCollection {
1654
1655 private $filters = array();
1656
1657 /**
1658 * wfLiveTrafficQueryFilterCollection constructor.
1659 *
1660 * @param array $filters
1661 */
1662 public function __construct($filters = array()) {
1663 $this->filters = $filters;
1664 }
1665
1666 public function toSQL() {
1667 $params = array();
1668 $sql = '';
1669 $filters = $this->getFilters();
1670 if ($filters) {
1671 /** @var wfLiveTrafficQueryFilter $filter */
1672 foreach ($filters as $filter) {
1673 $params[$filter->getParam()][] = $filter;
1674 }
1675 }
1676
1677 foreach ($params as $param => $filters) {
1678 // $sql .= '(';
1679 $filtersSQL = '';
1680 foreach ($filters as $filter) {
1681 $filterSQL = $filter->toSQL();
1682 if ($filterSQL) {
1683 $filtersSQL .= $filterSQL . ' OR ';
1684 }
1685 }
1686 if ($filtersSQL) {
1687 $sql .= '(' . substr($filtersSQL, 0, -4) . ') AND ';
1688 }
1689 }
1690 if ($sql) {
1691 $sql = substr($sql, 0, -5);
1692 }
1693 return $sql;
1694 }
1695
1696 public function addFilter($filter) {
1697 $this->filters[] = $filter;
1698 }
1699
1700 /**
1701 * @return array
1702 */
1703 public function getFilters() {
1704 return $this->filters;
1705 }
1706
1707 /**
1708 * @param array $filters
1709 */
1710 public function setFilters($filters) {
1711 $this->filters = $filters;
1712 }
1713 }
1714
1715 class wfLiveTrafficQueryFilter {
1716
1717 private $param;
1718 private $operator;
1719 private $value;
1720
1721 protected $validOperators = array(
1722 '=',
1723 '!=',
1724 'contains',
1725 'match',
1726 'hregexp',
1727 'hnotregexp',
1728 );
1729
1730 /**
1731 * @var wfLiveTrafficQuery
1732 */
1733 private $query;
1734
1735 /**
1736 * wfLiveTrafficQueryFilter constructor.
1737 *
1738 * @param wfLiveTrafficQuery $query
1739 * @param string $param
1740 * @param string $operator
1741 * @param string $value
1742 */
1743 public function __construct($query, $param, $operator, $value) {
1744 $this->query = $query;
1745 $this->param = $param;
1746 $this->operator = $operator;
1747 $this->value = $value;
1748 }
1749
1750 /**
1751 * @return string|void
1752 */
1753 public function toSQL() {
1754 $sql = '';
1755 if ($this->validate()) {
1756 /** @var wpdb $wpdb */
1757 global $wpdb;
1758 $operator = $this->getOperator();
1759 $param = $this->getQuery()->getColumnFromParam($this->getParam());
1760 if (!$param) {
1761 return $sql;
1762 }
1763 $value = $this->getValue();
1764 switch ($operator) {
1765 case 'contains':
1766 $like = addcslashes($value, '_%\\');
1767 $sql = $wpdb->prepare("$param LIKE %s", "%$like%");
1768 break;
1769
1770 case 'match':
1771 $sql = $wpdb->prepare("$param LIKE %s", $value);
1772 break;
1773
1774 case 'hregexp':
1775 $sql = $wpdb->prepare("HEX($param) REGEXP %s", $value);
1776 break;
1777
1778 case 'hnotregexp':
1779 $sql = $wpdb->prepare("HEX($param) NOT REGEXP %s", $value);
1780 break;
1781
1782 default:
1783 $sql = $wpdb->prepare("$param $operator %s", $value);
1784 break;
1785 }
1786 }
1787 return $sql;
1788 }
1789
1790 /**
1791 * @return bool
1792 */
1793 public function validate() {
1794 $valid = $this->isValidParam($this->getParam()) && $this->isValidOperator($this->getOperator());
1795 if (defined('WP_DEBUG') && WP_DEBUG) {
1796 if (!$valid) {
1797 throw new wfLiveTrafficQueryException("Invalid param/operator [{$this->getParam()}]/[{$this->getOperator()}] passed to " . get_class($this));
1798 }
1799 return true;
1800 }
1801 return $valid;
1802 }
1803
1804 /**
1805 * @param string $param
1806 * @return bool
1807 */
1808 public function isValidParam($param) {
1809 return $this->getQuery() && $this->getQuery()->isValidParam($param);
1810 }
1811
1812 /**
1813 * @param string $operator
1814 * @return bool
1815 */
1816 public function isValidOperator($operator) {
1817 return in_array($operator, $this->validOperators);
1818 }
1819
1820 /**
1821 * @return mixed
1822 */
1823 public function getParam() {
1824 return $this->param;
1825 }
1826
1827 /**
1828 * @param mixed $param
1829 */
1830 public function setParam($param) {
1831 $this->param = $param;
1832 }
1833
1834 /**
1835 * @return mixed
1836 */
1837 public function getOperator() {
1838 return $this->operator;
1839 }
1840
1841 /**
1842 * @param mixed $operator
1843 */
1844 public function setOperator($operator) {
1845 $this->operator = $operator;
1846 }
1847
1848 /**
1849 * @return mixed
1850 */
1851 public function getValue() {
1852 return $this->value;
1853 }
1854
1855 /**
1856 * @param mixed $value
1857 */
1858 public function setValue($value) {
1859 $this->value = $value;
1860 }
1861
1862 /**
1863 * @return wfLiveTrafficQuery
1864 */
1865 public function getQuery() {
1866 return $this->query;
1867 }
1868
1869 /**
1870 * @param wfLiveTrafficQuery $query
1871 */
1872 public function setQuery($query) {
1873 $this->query = $query;
1874 }
1875 }
1876
1877 class wfLiveTrafficQueryGroupBy {
1878
1879 private $param;
1880
1881 /**
1882 * @var wfLiveTrafficQuery
1883 */
1884 private $query;
1885
1886 /**
1887 * wfLiveTrafficQueryGroupBy constructor.
1888 *
1889 * @param wfLiveTrafficQuery $query
1890 * @param string $param
1891 */
1892 public function __construct($query, $param) {
1893 $this->query = $query;
1894 $this->param = $param;
1895 }
1896
1897 /**
1898 * @return bool
1899 * @throws wfLiveTrafficQueryException
1900 */
1901 public function validate() {
1902 $valid = $this->isValidParam($this->getParam());
1903 if (defined('WP_DEBUG') && WP_DEBUG) {
1904 if (!$valid) {
1905 throw new wfLiveTrafficQueryException("Invalid param [{$this->getParam()}] passed to " . get_class($this));
1906 }
1907 return true;
1908 }
1909 return $valid;
1910 }
1911
1912 /**
1913 * @param string $param
1914 * @return bool
1915 */
1916 public function isValidParam($param) {
1917 return $this->getQuery() && $this->getQuery()->isValidParam($param);
1918 }
1919
1920 /**
1921 * @return wfLiveTrafficQuery
1922 */
1923 public function getQuery() {
1924 return $this->query;
1925 }
1926
1927 /**
1928 * @param wfLiveTrafficQuery $query
1929 */
1930 public function setQuery($query) {
1931 $this->query = $query;
1932 }
1933
1934 /**
1935 * @return mixed
1936 */
1937 public function getParam() {
1938 return $this->param;
1939 }
1940
1941 /**
1942 * @param mixed $param
1943 */
1944 public function setParam($param) {
1945 $this->param = $param;
1946 }
1947
1948 }
1949
1950
1951 class wfLiveTrafficQueryException extends Exception {
1952
1953 }
1954
1955 class wfErrorLogHandler {
1956 public static function getErrorLogs($deepSearch = false) {
1957 static $errorLogs = null;
1958
1959 if ($errorLogs === null) {
1960 $searchPaths = array(ABSPATH, ABSPATH . 'wp-admin', ABSPATH . 'wp-content');
1961
1962 $homePath = get_home_path();
1963 if (!in_array($homePath, $searchPaths)) {
1964 $searchPaths[] = $homePath;
1965 }
1966
1967 $errorLogPath = ini_get('error_log');
1968 if (!empty($errorLogPath) && !in_array($errorLogPath, $searchPaths)) {
1969 $searchPaths[] = $errorLogPath;
1970 }
1971
1972 $errorLogs = array();
1973 foreach ($searchPaths as $s) {
1974 $errorLogs = array_merge($errorLogs, self::_scanForLogs($s, $deepSearch));
1975 }
1976 }
1977 return $errorLogs;
1978 }
1979
1980 private static function _scanForLogs($path, $deepSearch = false) {
1981 static $processedFolders = array(); //Protection for endless loops caused by symlinks
1982 if (is_file($path)) {
1983 $file = basename($path);
1984 if (preg_match('#(?:error_log(\-\d+)?$|\.log$)#i', $file)) {
1985 return array($path => is_readable($path));
1986 }
1987 return array();
1988 }
1989
1990 $path = untrailingslashit($path);
1991 $contents = @scandir($path);
1992 if (!is_array($contents)) {
1993 return array();
1994 }
1995
1996 $processedFolders[$path] = true;
1997 $errorLogs = array();
1998 foreach ($contents as $name) {
1999 if ($name == '.' || $name == '..') { continue; }
2000 $testPath = $path . DIRECTORY_SEPARATOR . $name;
2001 if (!array_key_exists($testPath, $processedFolders)) {
2002 if ((is_dir($testPath) && $deepSearch) || !is_dir($testPath)) {
2003 $errorLogs = array_merge($errorLogs, self::_scanForLogs($testPath, $deepSearch));
2004 }
2005 }
2006 }
2007 return $errorLogs;
2008 }
2009
2010 public static function outputErrorLog($path) {
2011 $errorLogs = self::getErrorLogs();
2012 if (!isset($errorLogs[$path])) { //Only allow error logs we've identified
2013 status_header(404);
2014 nocache_headers();
2015
2016 $template = get_404_template();
2017 if ($template && file_exists($template)) {
2018 include($template);
2019 }
2020 exit;
2021 }
2022
2023 $fh = @fopen($path, 'r');
2024 if (!$fh) {
2025 status_header(503);
2026 nocache_headers();
2027 echo "503 Service Unavailable";
2028 exit;
2029 }
2030
2031 $headersOutputted = false;
2032 while (!feof($fh)) {
2033 $data = fread($fh, 1 * 1024 * 1024); //read 1 megs max per chunk
2034 if ($data === false) { //Handle the error where the file was reported readable but we can't actually read it
2035 status_header(503);
2036 nocache_headers();
2037 echo "503 Service Unavailable";
2038 exit;
2039 }
2040
2041 if (!$headersOutputted) {
2042 header('Content-Type: text/plain');
2043 header('Content-Disposition: attachment; filename="' . basename($path));
2044 $headersOutputted = true;
2045 }
2046 echo $data;
2047 }
2048 exit;
2049 }
2050 }