Sucuri Security – Auditing, Malware Scanner and Security Hardening - Version 1.8.7

Version Description

This version adds support for the latest version of WordPress. Introduces new features and fixes some bugs reported by the WordPress community as well as bugs found by our automated testing system.

=

Download this release

Release Info

Developer yorman
Plugin Icon 128x128 Sucuri Security – Auditing, Malware Scanner and Security Hardening
Version 1.8.7
Comparing to
See all releases

Code changes from version 1.8.6 to 1.8.7

inc/css/styles.css CHANGED
@@ -753,9 +753,6 @@ body.sucuri-security_page_sucuriscan_hardening {
753
  .sucuriscan-tag-blue {
754
  background-color: #3922f2;
755
  }
756
- .sucuriscan-auditlog-response {
757
- margin-bottom: 30px;
758
- }
759
  .sucuriscan-auditlog-date {
760
  color: #808080;
761
  padding: 30px 0;
@@ -816,6 +813,10 @@ body.sucuri-security_page_sucuriscan_hardening {
816
  line-height: 32px;
817
  margin-left: 10px;
818
  }
 
 
 
 
819
  .sucuriscan-hardening-option {
820
  margin-bottom: 0;
821
  }
753
  .sucuriscan-tag-blue {
754
  background-color: #3922f2;
755
  }
 
 
 
756
  .sucuriscan-auditlog-date {
757
  color: #808080;
758
  padding: 30px 0;
813
  line-height: 32px;
814
  margin-left: 10px;
815
  }
816
+ .sucuriscan-pagination-panel,
817
+ .sucuriscan-auditlog-footer {
818
+ margin-top: 30px;
819
+ }
820
  .sucuriscan-hardening-option {
821
  margin-bottom: 0;
822
  }
inc/tpl/auditlogs.html.tpl CHANGED
@@ -3,6 +3,18 @@
3
  /* global jQuery */
4
  /* jshint camelcase:false */
5
  jQuery(function ($) {
 
 
 
 
 
 
 
 
 
 
 
 
6
  var sucuriscanLoadAuditLogs = function (page, reset) {
7
  var url = '%%SUCURI.AjaxURL.Dashboard%%';
8
 
@@ -14,7 +26,10 @@ jQuery(function ($) {
14
  $('.sucuriscan-auditlog-response').html('<em>@@SUCURI.Loading@@</em>');
15
  }
16
 
17
- $('.sucuriscan-pagination-loading').html('@@SUCURI.Loading@@');
 
 
 
18
 
19
  $.post(url, {
20
  action: 'sucuriscan_ajax',
@@ -23,14 +38,16 @@ jQuery(function ($) {
23
  }, function (data) {
24
  $('.sucuriscan-pagination-loading').html('');
25
 
 
 
 
 
 
26
  if (data.content !== undefined) {
27
  $('.sucuriscan-auditlog-response').html(data.content);
28
 
29
- if (data.selfhosting) {
30
- $('#sucuriscan-auditlog-selfhosting').removeClass('sucuriscan-hidden');
31
- }
32
-
33
  if (data.pagination !== '') {
 
34
  $('.sucuriscan-auditlog-table .sucuriscan-pagination').html(data.pagination);
35
  }
36
  } else if (typeof data === 'object') {
@@ -54,14 +71,14 @@ jQuery(function ($) {
54
  sucuriscanLoadAuditLogs($(this).attr('data-page'));
55
  });
56
 
57
- $('.sucuriscan-auditlog-table').on('click', '.sucuriscan-reset-auditlogs', function (event) {
58
  event.preventDefault();
 
59
  $.post('%%SUCURI.AjaxURL.Dashboard%%', {
60
  action: 'sucuriscan_ajax',
61
  sucuriscan_page_nonce: '%%SUCURI.PageNonce%%',
62
- form_action: 'reset_auditlogs_cache',
63
- }, function (data) {
64
- console.log(data);
65
  sucuriscanLoadAuditLogs(0, true);
66
  });
67
  });
@@ -69,19 +86,11 @@ jQuery(function ($) {
69
  </script>
70
 
71
  <div class="sucuriscan-auditlog-table">
72
- <div id="sucuriscan-auditlog-selfhosting" class="sucuriscan-inline-alert-info sucuriscan-hidden">
73
- <p>@@SUCURI.SelfHostingFallback@@</p>
74
- </div>
75
-
76
  <div class="sucuriscan-auditlog-response">
77
  <em>@@SUCURI.Loading@@</em>
78
  </div>
79
 
80
- <div>
81
- <small>@@SUCURI.AuditLogsCache@@ &mdash; <a href="#" class="sucuriscan-reset-auditlogs">@@SUCURI.Refresh@@</a></small>
82
- </div>
83
-
84
- <div class="sucuriscan-clearfix">
85
  <ul class="sucuriscan-pull-left sucuriscan-pagination">
86
  <!-- Populated via JavaScript -->
87
  </ul>
@@ -90,4 +99,15 @@ jQuery(function ($) {
90
  <!-- Populated via JavaScript -->
91
  </div>
92
  </div>
 
 
 
 
 
 
 
 
 
 
 
93
  </div>
3
  /* global jQuery */
4
  /* jshint camelcase:false */
5
  jQuery(function ($) {
6
+ var writeQueueSize = function (queueSize)
7
+ {
8
+ if (queueSize === 0) {
9
+ $('.sucuriscan-auditlogs-sendlogs-response').html('');
10
+ $('.sucuriscan-sendlogs-panel').addClass('sucuriscan-hidden');
11
+ } else {
12
+ var msg = '\x20@@SUCURI.AuditLogsQueue@@\x20&mdash;\x20';
13
+ $('.sucuriscan-auditlogs-sendlogs-response').html((queueSize).toString() + msg);
14
+ $('.sucuriscan-sendlogs-panel').removeClass('sucuriscan-hidden');
15
+ }
16
+ };
17
+
18
  var sucuriscanLoadAuditLogs = function (page, reset) {
19
  var url = '%%SUCURI.AjaxURL.Dashboard%%';
20
 
26
  $('.sucuriscan-auditlog-response').html('<em>@@SUCURI.Loading@@</em>');
27
  }
28
 
29
+ $('.sucuriscan-auditlog-status').html('');
30
+ $('.sucuriscan-pagination-loading').html('');
31
+ $('.sucuriscan-pagination-panel').addClass('sucuriscan-hidden');
32
+ $('.sucuriscan-auditlog-footer').addClass('sucuriscan-hidden');
33
 
34
  $.post(url, {
35
  action: 'sucuriscan_ajax',
38
  }, function (data) {
39
  $('.sucuriscan-pagination-loading').html('');
40
 
41
+ writeQueueSize(data.queueSize);
42
+
43
+ $('.sucuriscan-auditlog-status').html(data.status);
44
+ $('.sucuriscan-auditlog-footer').removeClass('sucuriscan-hidden');
45
+
46
  if (data.content !== undefined) {
47
  $('.sucuriscan-auditlog-response').html(data.content);
48
 
 
 
 
 
49
  if (data.pagination !== '') {
50
+ $('.sucuriscan-pagination-panel').removeClass('sucuriscan-hidden');
51
  $('.sucuriscan-auditlog-table .sucuriscan-pagination').html(data.pagination);
52
  }
53
  } else if (typeof data === 'object') {
71
  sucuriscanLoadAuditLogs($(this).attr('data-page'));
72
  });
73
 
74
+ $('.sucuriscan-auditlog-table').on('click', '.sucuriscan-auditlogs-sendlogs', function (event) {
75
  event.preventDefault();
76
+ $('.sucuriscan-auditlogs-sendlogs-response').html('@@SUCURI.Loading@@');
77
  $.post('%%SUCURI.AjaxURL.Dashboard%%', {
78
  action: 'sucuriscan_ajax',
79
  sucuriscan_page_nonce: '%%SUCURI.PageNonce%%',
80
+ form_action: 'auditlogs_send_logs',
81
+ }, function () {
 
82
  sucuriscanLoadAuditLogs(0, true);
83
  });
84
  });
86
  </script>
87
 
88
  <div class="sucuriscan-auditlog-table">
 
 
 
 
89
  <div class="sucuriscan-auditlog-response">
90
  <em>@@SUCURI.Loading@@</em>
91
  </div>
92
 
93
+ <div class="sucuriscan-clearfix sucuriscan-pagination-panel">
 
 
 
 
94
  <ul class="sucuriscan-pull-left sucuriscan-pagination">
95
  <!-- Populated via JavaScript -->
96
  </ul>
99
  <!-- Populated via JavaScript -->
100
  </div>
101
  </div>
102
+
103
+ <div class="sucuriscan-clearfix sucuriscan-auditlog-footer">
104
+ <div class="sucuriscan-pull-left sucuriscan-hidden sucuriscan-sendlogs-panel">
105
+ <small class="sucuriscan-auditlogs-sendlogs-response"></small>
106
+ <small><a href="#" class="sucuriscan-auditlogs-sendlogs">@@SUCURI.SendLogs@@</a></small>
107
+ </div>
108
+
109
+ <div class="sucuriscan-pull-right">
110
+ <small class="sucuriscan-auditlog-status"></small>
111
+ </div>
112
+ </div>
113
  </div>
inc/tpl/lastlogins-loggedin.snippet.tpl CHANGED
@@ -8,7 +8,7 @@
8
 
9
  <td class="sucuriscan-monospace">%%SUCURI.LoggedInUsers.Registered%%</td>
10
 
11
- <td class="sucuriscan-monospace">%%SUCURI.LoggedInUsers.RemoveAddr%%</td>
12
 
13
  <td><a href="%%SUCURI.LoggedInUsers.UserURL%%" target="_blank">@@SUCURI.Edit@@</a></td>
14
  </tr>
8
 
9
  <td class="sucuriscan-monospace">%%SUCURI.LoggedInUsers.Registered%%</td>
10
 
11
+ <td class="sucuriscan-monospace">%%SUCURI.LoggedInUsers.RemoteAddr%%</td>
12
 
13
  <td><a href="%%SUCURI.LoggedInUsers.UserURL%%" target="_blank">@@SUCURI.Edit@@</a></td>
14
  </tr>
inc/tpl/notification-simple.html.tpl CHANGED
@@ -1,7 +1,7 @@
1
 
2
  @@SUCURI.Event@@: %%SUCURI.Subject%%
3
  @@SUCURI.Website@@: http://%%SUCURI.Website%%
4
- @@SUCURI.RemoveAddr@@: %%SUCURI.RemoteAddress%%
5
  @@SUCURI.Datetime@@: %%SUCURI.Time%%
6
  %%SUCURI.User%%
7
 
1
 
2
  @@SUCURI.Event@@: %%SUCURI.Subject%%
3
  @@SUCURI.Website@@: http://%%SUCURI.Website%%
4
+ @@SUCURI.RemoteAddr@@: %%SUCURI.RemoteAddress%%
5
  @@SUCURI.Datetime@@: %%SUCURI.Time%%
6
  %%SUCURI.User%%
7
 
inc/tpl/settings-apiservice-timeout.html.tpl DELETED
@@ -1,21 +0,0 @@
1
-
2
- <div class="sucuriscan-panel">
3
- <h3 class="sucuriscan-title">@@SUCURI.APITimeout@@</h3>
4
-
5
- <div class="inside">
6
- <p>@@SUCURI.APITimeoutInfo@@</p>
7
-
8
- <div class="sucuriscan-hstatus sucuriscan-hstatus-2">
9
- <span>@@SUCURI.APITimeoutValue@@</span>
10
- </div>
11
-
12
- <form action="%%SUCURI.URL.Settings%%#apiservice" method="post">
13
- <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
14
- <fieldset class="sucuriscan-clearfix">
15
- <label>@@SUCURI.APITimeoutLabel@@</label>
16
- <input type="text" name="sucuriscan_request_timeout" />
17
- <button type="submit" class="button button-primary">@@SUCURI.Submit@@</button>
18
- </fieldset>
19
- </form>
20
- </div>
21
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/tpl/settings-general-commentmonitor.html.tpl DELETED
@@ -1,18 +0,0 @@
1
-
2
- <div class="sucuriscan-panel">
3
- <h3 class="sucuriscan-title">@@SUCURI.CommentMonitor@@</h3>
4
-
5
- <div class="inside">
6
- <p>@@SUCURI.CommentMonitorInfo@@</p>
7
-
8
- <div class="sucuriscan-hstatus sucuriscan-hstatus-2">
9
- <span>@@SUCURI.CommentMonitor@@ &mdash; %%SUCURI.CommentMonitorStatus%%</span>
10
-
11
- <form action="%%SUCURI.URL.Settings%%" method="post">
12
- <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
13
- <input type="hidden" name="sucuriscan_comment_monitor" value="%%SUCURI.CommentMonitorSwitchValue%%" />
14
- <button type="submit" class="button button-primary">%%SUCURI.CommentMonitorSwitchText%%</button>
15
- </form>
16
- </div>
17
- </div>
18
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/tpl/{settings-general-cronjobs.html.tpl → settings-scanner-cronjobs.html.tpl} RENAMED
@@ -3,9 +3,15 @@
3
  <h3 class="sucuriscan-title">@@SUCURI.Cronjobs@@</h3>
4
 
5
  <div class="inside">
 
 
 
 
 
 
6
  <p>@@SUCURI.CronjobsInfo@@</p>
7
 
8
- <form action="%%SUCURI.URL.Settings%%#general" method="post">
9
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
10
 
11
  <table class="wp-list-table widefat sucuriscan-table sucuriscan-wpcron-list">
3
  <h3 class="sucuriscan-title">@@SUCURI.Cronjobs@@</h3>
4
 
5
  <div class="inside">
6
+ <p>@@SUCURI.ScannerDescription@@</p>
7
+
8
+ <div class="sucuriscan-inline-alert-error sucuriscan-%%SUCURI.NoSPL.Visibility%%">
9
+ <p>@@SUCURI.ScannerWithoutSPL@@</p>
10
+ </div>
11
+
12
  <p>@@SUCURI.CronjobsInfo@@</p>
13
 
14
+ <form action="%%SUCURI.URL.Settings%%#scanner" method="post">
15
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
16
 
17
  <table class="wp-list-table widefat sucuriscan-table sucuriscan-wpcron-list">
inc/tpl/{settings-general-cronjobs.snippet.tpl → settings-scanner-cronjobs.snippet.tpl} RENAMED
@@ -8,7 +8,7 @@
8
 
9
  <td>%%SUCURI.Cronjob.Schedule%%</td>
10
 
11
- <td>%%SUCURI.Cronjob.NextTime%%</td>
12
 
13
  <td>%%SUCURI.Cronjob.Arguments%%</td>
14
  </tr>
8
 
9
  <td>%%SUCURI.Cronjob.Schedule%%</td>
10
 
11
+ <td>%%SUCURI.Cronjob.NextTime%% <em>(%%SUCURI.Cronjob.NextTimeHuman%%)</em></td>
12
 
13
  <td>%%SUCURI.Cronjob.Arguments%%</td>
14
  </tr>
inc/tpl/settings-scanner-options.html.tpl DELETED
@@ -1,24 +0,0 @@
1
-
2
- <div class="sucuriscan-panel">
3
- <h3 class="sucuriscan-title">@@SUCURI.ScannerTitle@@</h3>
4
-
5
- <div class="inside">
6
- <p>@@SUCURI.ScannerDescription@@</p>
7
-
8
- <div class="sucuriscan-inline-alert-error sucuriscan-%%SUCURI.NoSPL.Visibility%%">
9
- <p>@@SUCURI.ScannerWithoutSPL@@</p>
10
- </div>
11
-
12
- <form action="%%SUCURI.URL.Settings%%#scanner" method="post">
13
- <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
14
-
15
- <fieldset class="sucuriscan-clearfix">
16
- <label>@@SUCURI.ScannerFrequency@@</label>
17
- <select name="sucuriscan_scan_frequency">
18
- %%%SUCURI.ScanningFrequencyOptions%%%
19
- </select>
20
- <button type="submit" class="button button-primary">@@SUCURI.Submit@@</button>
21
- </fieldset>
22
- </form>
23
- </div>
24
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/tpl/settings.html.tpl CHANGED
@@ -18,14 +18,10 @@
18
 
19
  %%%SUCURI.Settings.General.SelfHosting%%%
20
 
21
- %%%SUCURI.Settings.General.Cronjobs%%%
22
-
23
  %%%SUCURI.Settings.General.ReverseProxy%%%
24
 
25
  %%%SUCURI.Settings.General.IPDiscoverer%%%
26
 
27
- %%%SUCURI.Settings.General.CommentMonitor%%%
28
-
29
  %%%SUCURI.Settings.General.AuditLogStats%%%
30
 
31
  %%%SUCURI.Settings.General.ImportExport%%%
@@ -34,7 +30,7 @@
34
  </div>
35
 
36
  <div id="sucuriscan-tabs-scanner">
37
- %%%SUCURI.Settings.Scanner.Options%%%
38
 
39
  %%%SUCURI.Settings.Scanner.IntegrityDiffUtility%%%
40
 
@@ -106,8 +102,6 @@
106
  <div id="sucuriscan-tabs-apiservice">
107
  %%%SUCURI.Settings.APIService.Status%%%
108
 
109
- %%%SUCURI.Settings.APIService.Timeout%%%
110
-
111
  %%%SUCURI.Settings.APIService.Proxy%%%
112
  </div>
113
 
18
 
19
  %%%SUCURI.Settings.General.SelfHosting%%%
20
 
 
 
21
  %%%SUCURI.Settings.General.ReverseProxy%%%
22
 
23
  %%%SUCURI.Settings.General.IPDiscoverer%%%
24
 
 
 
25
  %%%SUCURI.Settings.General.AuditLogStats%%%
26
 
27
  %%%SUCURI.Settings.General.ImportExport%%%
30
  </div>
31
 
32
  <div id="sucuriscan-tabs-scanner">
33
+ %%%SUCURI.Settings.Scanner.Cronjobs%%%
34
 
35
  %%%SUCURI.Settings.Scanner.IntegrityDiffUtility%%%
36
 
102
  <div id="sucuriscan-tabs-apiservice">
103
  %%%SUCURI.Settings.APIService.Status%%%
104
 
 
 
105
  %%%SUCURI.Settings.APIService.Proxy%%%
106
  </div>
107
 
languages/sucuri-scanner-en_US.mo CHANGED
Binary file
languages/sucuri-scanner-en_US.po CHANGED
@@ -194,6 +194,15 @@ msgstr "This data is cached for %%SUCURI.AuditLogs.Lifetime%% seconds"
194
  msgid "Refresh"
195
  msgstr "Refresh"
196
 
 
 
 
 
 
 
 
 
 
197
  msgid "UnsupportedWordPress"
198
  msgstr "WordPress version is not supported."
199
 
@@ -764,12 +773,6 @@ msgstr "The post-type is invalid or it may be already ignored."
764
  msgid "APIServiceChanged"
765
  msgstr "The status of the API service has been changed"
766
 
767
- msgid "HTTPTimeoutChange"
768
- msgstr "The timeout for the HTTP requests has been changed"
769
-
770
- msgid "HTTPTimeoutFailure"
771
- msgstr "API request timeout in seconds is too high."
772
-
773
  msgid "PluginResetSuccess"
774
  msgstr "Local security logs, hardening and settings were deleted"
775
 
@@ -792,7 +795,7 @@ msgid "Cronjobs"
792
  msgstr "Scheduled Tasks"
793
 
794
  msgid "CronjobsInfo"
795
- msgstr "<b>Scheduled Tasks</b> are rules registered in your database by a plugin, theme, or the base system itself; they are used to automatically execute actions defined in the code every certain amount of time. A good use of these rules is to generate backup files of your site, execute a security scanner, or remove unused elements like drafts."
796
 
797
  msgid "CronjobRunNow"
798
  msgstr "Execute Now (in +10 seconds)"
@@ -1016,9 +1019,6 @@ msgstr "Newest WordPress"
1016
  msgid "NoUpdates"
1017
  msgstr "There are no updates available."
1018
 
1019
- msgid "ScannerFreqStatus"
1020
- msgstr "The file system scanner frequency has been changed"
1021
-
1022
  msgid "SiteClean"
1023
  msgstr "Site is Clean"
1024
 
@@ -1346,14 +1346,8 @@ msgstr "Your website has no <code>.htaccess</code> file or it was not found in t
1346
  msgid "HTAccessStandard"
1347
  msgstr "The main <code>.htaccess</code> file in your site has the standard rules for a WordPress installation. You can customize it to improve the performance and change the behaviour of the redirections for pages and posts in your site. To get more information visit the official documentation at <a target=\"_blank\" rel=\"noopener\" href=\"https://codex.wordpress.org/Using_Permalinks#Creating_and_editing_.28.htaccess.29\"> Codex WordPress - Creating and editing (.htaccess)</a>"
1348
 
1349
- msgid "ScannerTitle"
1350
- msgstr "Scanner Settings"
1351
-
1352
- msgid "ScannerFrequency"
1353
- msgstr "Scanning Frequency"
1354
-
1355
  msgid "ScannerDescription"
1356
- msgstr "The plugin scans your entire website looking for changes which are later reported via the API in the audit logs page. This scanner runs twice-daily by default but you can change the frequency to meet your own requirements. Notice that scanning your project files too frequently will affect the performance of your website. Be sure to have enough server resources before changing this option. The memory limit and maximum execution time are two of the PHP options that your server will set to stop your website from consuming too much resources."
1357
 
1358
  msgid "ScannerWithoutSPL"
1359
  msgstr "The scanner uses the <a href=\"http://php.net/manual/en/class.splfileobject.php\" target=\"_blank\" rel=\"noopener\">PHP SPL library</a> and the <a target=\"_blank\" href=\"http://php.net/manual/en/class.filesystemiterator.php\" rel=\"noopener\">Filesystem Iterator</a> class to scan the directory tree where your website is located in the server. This library is only available on PHP 5 >= 5.3.0 &mdash; OR &mdash; PHP 7; if you have an older version of PHP the plugin will not work as expected. Please ask your hosting provider to advice you on this matter."
@@ -1466,15 +1460,6 @@ msgstr "Data Storage"
1466
  msgid "DataStorageInfo"
1467
  msgstr "This is the directory where the plugin will store the security logs, the list of files marked as fixed in the core integrity tool, the cache for the malware scanner and 3rd-party plugin metadata. The plugin requires write permissions in this directory as well as the files contained in it. If you prefer to keep these files in a non-public directory <em>(one level up the document root) </em> please define a constant in the <em>\"wp-config.php\"</em> file named <em>\"SUCURI_DATA_STORAGE\"</em> with the absolute path to the new directory."
1468
 
1469
- msgid "CommentMonitor"
1470
- msgstr "Comment Monitor"
1471
-
1472
- msgid "CommentMonitorStatus"
1473
- msgstr "The status of the comment monitor has been changed"
1474
-
1475
- msgid "CommentMonitorInfo"
1476
- msgstr "User comments are the main source of spam in WordPress websites, this option enables the monitoring of data sent via the comment forms loaded in every page and post. We also use this information in an anonymous way to generate <a target=\"_blank\" href=\"https://sucuri.net/security-reports/brute-force/\">statistics</a> of usage that help us improve our service."
1477
-
1478
  msgid "LogsReport"
1479
  msgstr "Audit Log Statistics"
1480
 
194
  msgid "Refresh"
195
  msgstr "Refresh"
196
 
197
+ msgid "AuditLogsNoAPI"
198
+ msgstr "The API service is not available; logs are coming from the local server"
199
+
200
+ msgid "AuditLogsQueue"
201
+ msgstr "logs in the queue"
202
+
203
+ msgid "SendLogs"
204
+ msgstr "Send Logs"
205
+
206
  msgid "UnsupportedWordPress"
207
  msgstr "WordPress version is not supported."
208
 
773
  msgid "APIServiceChanged"
774
  msgstr "The status of the API service has been changed"
775
 
 
 
 
 
 
 
776
  msgid "PluginResetSuccess"
777
  msgstr "Local security logs, hardening and settings were deleted"
778
 
795
  msgstr "Scheduled Tasks"
796
 
797
  msgid "CronjobsInfo"
798
+ msgstr "Scheduled tasks are rules registered in your database by a plugin, theme, or the base system itself; they are used to automatically execute actions defined in the code every certain amount of time. A good use of these rules is to generate backup files of your site, execute a security scanner, or remove unused elements like drafts. <b>Note:</b> Scheduled tasks can be re-installed by any plugin/theme automatically, consider to deactivate the plugin entirely if you want to get rid of the scheduled tasks."
799
 
800
  msgid "CronjobRunNow"
801
  msgstr "Execute Now (in +10 seconds)"
1019
  msgid "NoUpdates"
1020
  msgstr "There are no updates available."
1021
 
 
 
 
1022
  msgid "SiteClean"
1023
  msgstr "Site is Clean"
1024
 
1346
  msgid "HTAccessStandard"
1347
  msgstr "The main <code>.htaccess</code> file in your site has the standard rules for a WordPress installation. You can customize it to improve the performance and change the behaviour of the redirections for pages and posts in your site. To get more information visit the official documentation at <a target=\"_blank\" rel=\"noopener\" href=\"https://codex.wordpress.org/Using_Permalinks#Creating_and_editing_.28.htaccess.29\"> Codex WordPress - Creating and editing (.htaccess)</a>"
1348
 
 
 
 
 
 
 
1349
  msgid "ScannerDescription"
1350
+ msgstr "The plugin scans your entire website looking for changes which are later reported via the API in the audit logs page. This scanner runs daily but you can change the frequency to meet your own requirements. Notice that scanning your project files too frequently will affect the performance of your website. Be sure to have enough server resources before changing this option. The memory limit and maximum execution time are two of the PHP options that your server will set to stop your website from consuming too much resources."
1351
 
1352
  msgid "ScannerWithoutSPL"
1353
  msgstr "The scanner uses the <a href=\"http://php.net/manual/en/class.splfileobject.php\" target=\"_blank\" rel=\"noopener\">PHP SPL library</a> and the <a target=\"_blank\" href=\"http://php.net/manual/en/class.filesystemiterator.php\" rel=\"noopener\">Filesystem Iterator</a> class to scan the directory tree where your website is located in the server. This library is only available on PHP 5 >= 5.3.0 &mdash; OR &mdash; PHP 7; if you have an older version of PHP the plugin will not work as expected. Please ask your hosting provider to advice you on this matter."
1460
  msgid "DataStorageInfo"
1461
  msgstr "This is the directory where the plugin will store the security logs, the list of files marked as fixed in the core integrity tool, the cache for the malware scanner and 3rd-party plugin metadata. The plugin requires write permissions in this directory as well as the files contained in it. If you prefer to keep these files in a non-public directory <em>(one level up the document root) </em> please define a constant in the <em>\"wp-config.php\"</em> file named <em>\"SUCURI_DATA_STORAGE\"</em> with the absolute path to the new directory."
1462
 
 
 
 
 
 
 
 
 
 
1463
  msgid "LogsReport"
1464
  msgstr "Audit Log Statistics"
1465
 
languages/sucuri-scanner-es_ES.mo CHANGED
Binary file
languages/sucuri-scanner-es_ES.po CHANGED
@@ -194,6 +194,15 @@ msgstr "Los registros son guardados por %%SUCURI.AuditLogs.Lifetime%% segundos"
194
  msgid "Refresh"
195
  msgstr "Actualizar"
196
 
 
 
 
 
 
 
 
 
 
197
  msgid "UnsupportedWordPress"
198
  msgstr "esta versión de WordPress no tiene soporte."
199
 
@@ -764,12 +773,6 @@ msgstr "Este tipo de publicaciones no es válido o ya han sido ignoradas"
764
  msgid "APIServiceChanged"
765
  msgstr "El estado de comunicación con la API ha sido cambiado"
766
 
767
- msgid "HTTPTimeoutChange"
768
- msgstr "El tiempo de espera para las conexiones a través de HTTP ha sido cambiado"
769
-
770
- msgid "HTTPTimeoutFailure"
771
- msgstr "El tiempo de espera para las conexiones a través de HTTP es muy alto"
772
-
773
  msgid "PluginResetSuccess"
774
  msgstr "Los registros de seguridad, configuraciones y cambios adicionales hechos por el plugin han sido eliminados"
775
 
@@ -792,7 +795,7 @@ msgid "Cronjobs"
792
  msgstr "Acciones Automatizadas"
793
 
794
  msgid "CronjobsInfo"
795
- msgstr "<b>Acciones Automatizadas</b> son reglas registradas en su base de datos por un plugin, plantilla o WordPress; estas reglas son usadas para ejecutar automáticamente acciones definidas en el código con un intervalo de tiempo definido. Un buen uso de estas reglas es la generación de copias de seguridad, la ejecución de escáneres de seguridad o la remoción de elementos innecesarios de la base de datos como borradores de publicaciones."
796
 
797
  msgid "CronjobRunNow"
798
  msgstr "Ejecutar Ahora (en +10 segundos)"
@@ -1016,9 +1019,6 @@ msgstr "Última Versión"
1016
  msgid "NoUpdates"
1017
  msgstr "No existen actualizaciones."
1018
 
1019
- msgid "ScannerFreqStatus"
1020
- msgstr "La frecuencia para la ejecución del escáner de archivos ha sido cambiada"
1021
-
1022
  msgid "SiteClean"
1023
  msgstr "Sitio Limpio"
1024
 
@@ -1346,14 +1346,8 @@ msgstr "El sitio web no tiene un archivo <code>.htaccess</code> o no se encuentr
1346
  msgid "HTAccessStandard"
1347
  msgstr "El archivo <code>.htaccess</code> principal contiene las reglas estándares de WordPress generadas durante la instalación del sitio web. Usted puede modificar estas reglas para aumentar el rendimiento del servidor o para generar redirecciones a otras páginas. Para obtener más información al respecto visite la página oficial con la documentación en <a target=\"_blank\" rel=\"noopener\" href=\"https://codex.wordpress.org/Using_Permalinks#Creating_and_editing_.28.htaccess.29\"> Codex WordPress - Creating and editing (.htaccess)</a>"
1348
 
1349
- msgid "ScannerTitle"
1350
- msgstr "Ajustes para el Escáner"
1351
-
1352
- msgid "ScannerFrequency"
1353
- msgstr "Frecuencia de Ejecución"
1354
-
1355
  msgid "ScannerDescription"
1356
- msgstr "El plugin escanea su sitio web en busca de cambios que son eventualmente reportados a la API y visualizados a través de los registros de seguridad. Este escáner es ejecutado automáticamente cada doce horas pero usted puede cambiar la frecuencia dependiendo de sus necesidades. Tenga en cuenta que si su sitio contiene una gran cantidad de archivos es posible que el escáner sea bloqueado por su proveedor de hosting si este excede el límite de ejecución que ellos hayan impuesto sobre su cuenta."
1357
 
1358
  msgid "ScannerWithoutSPL"
1359
  msgstr "El escáner hace uso de las librerías <a href=\"http://php.net/manual/en/class.splfileobject.php\" target=\"_blank\" rel=\"noopener\">SPL</a> y <a target=\"_blank\" href=\"http://php.net/manual/en/class.filesystemiterator.php\" rel=\"noopener\">Filesystem Iterator</a> de PHP para escanear el árbol de directorios de su sitio web. Esta librería solo está disponible en PHP 5 >= 5.3.0 &mdash; y &mdash; PHP 7; si su servidor hace uso de una versión antigua el plugin no funcionará como se espera. Por favor, contacte a su proveedor de hosting para obtener ayuda al respecto."
@@ -1466,15 +1460,6 @@ msgstr "Almacenamiento de Datos"
1466
  msgid "DataStorageInfo"
1467
  msgstr "Este es el directorio donde el plugin guardará los registros de seguridad, el archivo con las configuraciones, la cache y datos adicionales de otras herramientas. El plugin require de permisos de escritura en este directorio así como en los archivos contenidos allí. Si usted prefiere mantener estos archivos en un directorio diferente al especificado, por favor defina una constante llamada <em>\"SUCURI_DATA_STORAGE\"</em> en el archivo de configuración de WordPress con la ruta absoluta del nuevo directorio."
1468
 
1469
- msgid "CommentMonitor"
1470
- msgstr "Monitor de Comentarios"
1471
-
1472
- msgid "CommentMonitorStatus"
1473
- msgstr "El estado del monitor de comentarios ha sido cambiado"
1474
-
1475
- msgid "CommentMonitorInfo"
1476
- msgstr "Los comentarios de usuario son el origen principal de spam en sitios que usan WordPress, esta opción activa el monitoreo de los datos enviados a través de formularios de comentario disponibles en cada página. Nosotros también usamos esta información de forma anónima para generar <a target=\"_blank\" href=\"https://sucuri.net/security-reports/brute-force/\">estadísticas</a> de uso para ayudarnos a mejorar el servicio."
1477
-
1478
  msgid "LogsReport"
1479
  msgstr "Estadísticas De Seguridad"
1480
 
194
  msgid "Refresh"
195
  msgstr "Actualizar"
196
 
197
+ msgid "AuditLogsNoAPI"
198
+ msgstr "La API no está disponible; la información viene del servidor local"
199
+
200
+ msgid "AuditLogsQueue"
201
+ msgstr "registros en cola"
202
+
203
+ msgid "SendLogs"
204
+ msgstr "Enviar"
205
+
206
  msgid "UnsupportedWordPress"
207
  msgstr "esta versión de WordPress no tiene soporte."
208
 
773
  msgid "APIServiceChanged"
774
  msgstr "El estado de comunicación con la API ha sido cambiado"
775
 
 
 
 
 
 
 
776
  msgid "PluginResetSuccess"
777
  msgstr "Los registros de seguridad, configuraciones y cambios adicionales hechos por el plugin han sido eliminados"
778
 
795
  msgstr "Acciones Automatizadas"
796
 
797
  msgid "CronjobsInfo"
798
+ msgstr "Acciones automatizadas son reglas registradas en su base de datos por un plugin, plantilla o WordPress; estas reglas son usadas para ejecutar automáticamente acciones definidas en el código con un intervalo de tiempo definido. Un buen uso de estas reglas es la generación de copias de seguridad, la ejecución de escáneres de seguridad o la remoción de elementos innecesarios de la base de datos como borradores de publicaciones. <b>Nota:</b> Las acciones automatizadas pueden ser re-instaladas por cualquier plugin/plantilla automáticamente, considere desactivar el plugin completamente si quiere eliminar una de estas acciones."
799
 
800
  msgid "CronjobRunNow"
801
  msgstr "Ejecutar Ahora (en +10 segundos)"
1019
  msgid "NoUpdates"
1020
  msgstr "No existen actualizaciones."
1021
 
 
 
 
1022
  msgid "SiteClean"
1023
  msgstr "Sitio Limpio"
1024
 
1346
  msgid "HTAccessStandard"
1347
  msgstr "El archivo <code>.htaccess</code> principal contiene las reglas estándares de WordPress generadas durante la instalación del sitio web. Usted puede modificar estas reglas para aumentar el rendimiento del servidor o para generar redirecciones a otras páginas. Para obtener más información al respecto visite la página oficial con la documentación en <a target=\"_blank\" rel=\"noopener\" href=\"https://codex.wordpress.org/Using_Permalinks#Creating_and_editing_.28.htaccess.29\"> Codex WordPress - Creating and editing (.htaccess)</a>"
1348
 
 
 
 
 
 
 
1349
  msgid "ScannerDescription"
1350
+ msgstr "El plugin escanea su sitio web en busca de cambios que son eventualmente reportados a la API y visualizados a través de los registros de seguridad. Este escáner es ejecutado automáticamente cada día pero usted puede cambiar la frecuencia dependiendo de sus necesidades. Tenga en cuenta que si su sitio contiene una gran cantidad de archivos es posible que el escáner sea bloqueado por su proveedor de hosting si este excede el límite de ejecución que ellos hayan impuesto sobre su cuenta."
1351
 
1352
  msgid "ScannerWithoutSPL"
1353
  msgstr "El escáner hace uso de las librerías <a href=\"http://php.net/manual/en/class.splfileobject.php\" target=\"_blank\" rel=\"noopener\">SPL</a> y <a target=\"_blank\" href=\"http://php.net/manual/en/class.filesystemiterator.php\" rel=\"noopener\">Filesystem Iterator</a> de PHP para escanear el árbol de directorios de su sitio web. Esta librería solo está disponible en PHP 5 >= 5.3.0 &mdash; y &mdash; PHP 7; si su servidor hace uso de una versión antigua el plugin no funcionará como se espera. Por favor, contacte a su proveedor de hosting para obtener ayuda al respecto."
1460
  msgid "DataStorageInfo"
1461
  msgstr "Este es el directorio donde el plugin guardará los registros de seguridad, el archivo con las configuraciones, la cache y datos adicionales de otras herramientas. El plugin require de permisos de escritura en este directorio así como en los archivos contenidos allí. Si usted prefiere mantener estos archivos en un directorio diferente al especificado, por favor defina una constante llamada <em>\"SUCURI_DATA_STORAGE\"</em> en el archivo de configuración de WordPress con la ruta absoluta del nuevo directorio."
1462
 
 
 
 
 
 
 
 
 
 
1463
  msgid "LogsReport"
1464
  msgstr "Estadísticas De Seguridad"
1465
 
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: dd@sucuri.net
3
  Donate Link: https://sucuri.net/
4
  Tags: malware, security, firewall, scan, spam, virus, sucuri, protection, WordPress Security, Login Security, Security Auditing, File Integrity, htaccess, phishing, backdoors, SQL Injection, RFI, LFI, XSS, CSRF, website firewall, Website Security, Performance Optimization, Zero Day, Software Vulnerability, Exploits, Hacks, Attackers, Bad Actors, Reverse Proxy, Two Factor Security, Two Factor Authentication, Security Logs, HeatBleed Vulnerability, Website Protection, Bash Vulnerability, RevSlider Vulnerability, MailPoet Vulnerability, Malware Prevention, Website Security, Website Firewall, Website AntiVirus, Security Response, Security Detection, Security Prevention
5
  Requires at least: 3.6
6
- Stable tag: 1.8.6
7
  Tested up to: 4.8.0
8
 
9
  The Sucuri WordPress Security plugin is a security toolset for security integrity monitoring, malware detection and security hardening.
@@ -216,11 +216,23 @@ No, it is not required. The Website Firewall runs in the cloud without the need
216
 
217
  == Upgrade Notice ==
218
 
219
- = 1.8.6 =
220
  This version adds support for the latest version of WordPress. Introduces new features and fixes some bugs reported by the WordPress community as well as bugs found by our automated testing system.
221
 
222
  == Changelog ==
223
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  = 1.8.6 =
225
  * Add default language for internationalization fallback
226
 
3
  Donate Link: https://sucuri.net/
4
  Tags: malware, security, firewall, scan, spam, virus, sucuri, protection, WordPress Security, Login Security, Security Auditing, File Integrity, htaccess, phishing, backdoors, SQL Injection, RFI, LFI, XSS, CSRF, website firewall, Website Security, Performance Optimization, Zero Day, Software Vulnerability, Exploits, Hacks, Attackers, Bad Actors, Reverse Proxy, Two Factor Security, Two Factor Authentication, Security Logs, HeatBleed Vulnerability, Website Protection, Bash Vulnerability, RevSlider Vulnerability, MailPoet Vulnerability, Malware Prevention, Website Security, Website Firewall, Website AntiVirus, Security Response, Security Detection, Security Prevention
5
  Requires at least: 3.6
6
+ Stable tag: 1.8.7
7
  Tested up to: 4.8.0
8
 
9
  The Sucuri WordPress Security plugin is a security toolset for security integrity monitoring, malware detection and security hardening.
216
 
217
  == Upgrade Notice ==
218
 
219
+ = 1.8.7 =
220
  This version adds support for the latest version of WordPress. Introduces new features and fixes some bugs reported by the WordPress community as well as bugs found by our automated testing system.
221
 
222
  == Changelog ==
223
 
224
+ = 1.8.7 =
225
+ * Fix multiple issues with the API calls
226
+ * Add queue system to fix website performance
227
+ * Fix non-dismissable newsletter invitation message
228
+ * Fix performance of the audit log parser without regexp
229
+ * Add conditional to check for the availability of SPL
230
+ * Add cache for the audit logs to make dashboard responsive
231
+ * Modify frequency of the file system scans to run daily
232
+ * Remove option to configure the maximum API timeout
233
+ * Modify location of the scanner options and scheduled tasks
234
+ * Add button to send the logs from the queue to the API
235
+
236
  = 1.8.6 =
237
  * Add default language for internationalization fallback
238
 
src/api.lib.php CHANGED
@@ -37,31 +37,6 @@ if (!defined('SUCURISCAN_INIT') || SUCURISCAN_INIT !== true) {
37
  */
38
  class SucuriScanAPI extends SucuriScanOption
39
  {
40
- /**
41
- * Seconds before consider a HTTP request as timeout.
42
- *
43
- * As for the 01/Jan/2016 if the number of seconds before a timeout is greater
44
- * than sixty (which is one minute) the method will reset the option to its
45
- * default value to keep the latency of the HTTP requests in a minimum to
46
- * minimize the interruptions in the admins workflow. The normal connection
47
- * timeout should be in the range of ten seconds, or fifteen if the DNS lookups
48
- * are slow.
49
- *
50
- * @return int Seconds to consider a HTTP request timeout.
51
- */
52
- public static function requestTimeout()
53
- {
54
- $timeout = (int) self::getOption(':request_timeout');
55
-
56
- if ($timeout > SUCURISCAN_MAX_REQUEST_TIMEOUT) {
57
- self::deleteOption(':request_timeout');
58
-
59
- return self::requestTimeout();
60
- }
61
-
62
- return $timeout;
63
- }
64
-
65
  /**
66
  * Alternative to the built-in PHP method http_build_query.
67
  *
@@ -108,7 +83,7 @@ class SucuriScanAPI extends SucuriScanOption
108
  }
109
 
110
  $response = null;
111
- $timeout = self::requestTimeout();
112
  $args = is_array($args) ? $args : array();
113
 
114
  if (isset($args['timeout'])) {
@@ -259,10 +234,9 @@ class SucuriScanAPI extends SucuriScanOption
259
  * for the first time.
260
  *
261
  * @param array $response HTTP response after API endpoint execution.
262
- * @param bool $enqueue Add the log to the local queue on a failure.
263
  * @return bool False if the API call failed, true otherwise.
264
  */
265
- public static function handleResponse($response = array(), $enqueue = true)
266
  {
267
  if (!$response || getenv('SUCURISCAN_NO_API_HANDLE')) {
268
  return false;
@@ -324,7 +298,7 @@ class SucuriScanAPI extends SucuriScanOption
324
  $msg = __('ErrorInvalidEmail', SUCURISCAN_TEXTDOMAIN);
325
  }
326
 
327
- return SucuriScanInterface::error($enqueue ? $msg : '');
328
  }
329
 
330
  /**
@@ -348,7 +322,7 @@ class SucuriScanAPI extends SucuriScanOption
348
  if (self::handleResponse($response)) {
349
  self::setPluginKey($response['output']['api_key']);
350
 
351
- SucuriScanEvent::scheduleTask(); /* install scheduled tasks */
352
  SucuriScanEvent::notifyEvent('plugin_change', 'API key was generated and set');
353
  return SucuriScanInterface::info(__('AlertAPIKeySet', SUCURISCAN_TEXTDOMAIN));
354
  }
@@ -379,58 +353,6 @@ class SucuriScanAPI extends SucuriScanOption
379
  return false;
380
  }
381
 
382
- /**
383
- * Send a request to the API to store and analyze the events of the site. An
384
- * event can be anything from a simple request, an internal modification of the
385
- * settings or files in the administrator panel, or a notification generated by
386
- * this plugin.
387
- *
388
- * @param string $event Event triggered by the core system functions.
389
- * @param int $time Timestamp when the event was originally triggered.
390
- * @param bool $enqueue Add the log to the local queue on a failure.
391
- * @return bool True if the event was logged, false otherwise.
392
- */
393
- public static function sendLog($event = '', $time = 0, $enqueue = true)
394
- {
395
- if (empty($event)) {
396
- return self::throwException('Event identifier cannot be empty');
397
- }
398
-
399
- $params = array();
400
- $params['a'] = 'send_log';
401
- $params['m'] = $event;
402
-
403
- if (intval($time) > 0) {
404
- $params['time'] = (int) $time;
405
- }
406
-
407
- $response = self::apiCallWordpress('POST', $params, true);
408
-
409
- return self::handleResponse($response, $enqueue);
410
- }
411
-
412
- /**
413
- * Send all logs from the queue.
414
- *
415
- * Retry the HTTP calls for the logs that were not sent to the API service
416
- * because of a connection failure or misconfiguration. Each successful call
417
- * will remove the log from the queue and the failures will keep them until the
418
- * next method call is executed.
419
- */
420
- public static function sendLogsFromQueue()
421
- {
422
- $cache = new SucuriScanCache('auditqueue');
423
- $entries = $cache->getAll();
424
-
425
- if (is_array($entries) && !empty($entries)) {
426
- foreach ($entries as $key => $entry) {
427
- if (self::sendLog($entry->message, $entry->created_at, false)) {
428
- $cache->delete($key);
429
- }
430
- }
431
- }
432
- }
433
-
434
  /**
435
  * Retrieve the event logs registered by the API service.
436
  *
@@ -452,36 +374,27 @@ class SucuriScanAPI extends SucuriScanOption
452
  }
453
 
454
  /**
455
- * Gets the security logs from the local server (if enabled).
456
  *
457
- * @param int $limit Maximum number of logs to return.
458
- * @return array|bool The data structure with the logs.
459
  */
460
- public static function getSelfHostingLogs($limit = 0)
461
  {
462
- if (SucuriScanOption::isDisabled(':selfhosting_monitor')) {
463
- return self::throwException('Self-hosting monitor is disabled');
464
- }
465
-
466
  $auditlogs = array();
467
- $fpath = SucuriScanOption::getOption(':selfhosting_fpath');
468
- $category = "\x20WordPressAudit\x20" . SucuriScan::getDomain(true);
469
-
470
- if (class_exists('SplFileObject') && is_readable($fpath)) {
471
- $file = new SplFileObject($fpath);
472
- $file->seek(PHP_INT_MAX);
473
- $total = $file->key();
474
- $offset = $total - $limit;
475
- $file->seek($offset>0 ? $offset : 0);
476
-
477
- while (!$file->eof()) {
478
- $line = $file->current();
479
- $line = str_replace($category, '', $line);
480
- $line = trim($line); /* remove new line */
481
- if (!empty($line)) {
482
- $auditlogs[] = $line;
483
- }
484
- $file->next();
485
  }
486
  }
487
 
@@ -505,94 +418,116 @@ class SucuriScanAPI extends SucuriScanOption
505
  {
506
  $response = is_array($response) ? $response : array();
507
  $response['output_data'] = array();
508
- $log_pattern = '/^([0-9\-]+) ([0-9:]+) (\S+) : (.+)/';
509
- $extra_pattern = '/(.+ \(multiple entries\):) (.+)/';
510
- $generic_pattern = '/^@?([A-Z][a-z]{3,7}): ([^;]+; )?(.+)/';
511
- $auth_pattern = '/^User authentication (succeeded|failed): ([^<;]+)/';
512
 
513
  foreach ((array) @$response['output'] as $log) {
514
- if (@preg_match($log_pattern, $log, $log_match)) {
515
- $log_data = array(
516
- 'event' => 'notice',
517
- 'date' => '',
518
- 'time' => '',
519
- 'datetime' => '',
520
- 'timestamp' => 0,
521
- 'account' => $log_match[3],
522
- 'username' => 'system',
523
- 'remote_addr' => '127.0.0.1',
524
- 'message' => $log_match[4],
525
- 'file_list' => false,
526
- 'file_list_count' => 0,
527
- );
528
 
529
- /* extract and fix the date and time using the Eastern time zone */
530
- $datetime = sprintf('%s %s EDT', $log_match[1], $log_match[2]);
531
- $log_data['timestamp'] = strtotime($datetime);
532
- $log_data['datetime'] = date('Y-m-d H:i:s', $log_data['timestamp']);
533
- $log_data['date'] = date('Y-m-d', $log_data['timestamp']);
534
- $log_data['time'] = date('H:i:s', $log_data['timestamp']);
535
-
536
- /* extract more information from the generic audit logs */
537
- $log_data['message'] = str_replace('<br>', '; ', $log_data['message']);
538
-
539
- if (@preg_match($generic_pattern, $log_data['message'], $log_extra)) {
540
- $log_data['event'] = strtolower($log_extra[1]);
541
- $log_data['message'] = trim($log_extra[3]);
542
-
543
- /* extract the username and remote address from the log */
544
- if (!empty($log_extra[2])) {
545
- $username_address = rtrim($log_extra[2], ";\x20");
546
- $log_data['remote_addr'] = $username_address;
547
-
548
- /* separate the username from the remote address */
549
- if (strpos($username_address, ",\x20") !== false) {
550
- $usip_parts = explode(",\x20", $username_address, 2);
551
-
552
- if (count($usip_parts) == 2) {
553
- /* separate the username from the display name */
554
- $log_data['username'] = @preg_replace('/^.+ \((.+)\)$/', '$1', $usip_parts[0]);
555
- $log_data['remote_addr'] = $usip_parts[1];
556
- }
557
- }
558
- }
559
 
560
- /* fix old user authentication logs for backward compatibility */
561
- $log_data['message'] = str_replace(
562
- 'logged in',
563
- 'authentication succeeded',
564
- $log_data['message']
565
- );
566
 
567
- if (@preg_match($auth_pattern, $log_data['message'], $user_match)) {
568
- $log_data['username'] = $user_match[2];
569
- }
570
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
571
 
572
- /* extract more information from the special formatted logs */
573
- if (@preg_match($extra_pattern, $log_data['message'], $log_extra)) {
574
- $log_data['message'] = $log_extra[1];
575
- $log_extra[2] = str_replace(', new size', '; new size', $log_extra[2]);
576
- $log_extra[2] = str_replace(",\x20", ";\x20", $log_extra[2]);
577
- $log_data['file_list'] = explode(',', $log_extra[2]);
578
- $log_data['file_list_count'] = count($log_data['file_list']);
579
  }
580
 
581
- /* extract additional details from the message */
582
- if (strpos($log_data['message'], '; details:')) {
583
- $idx = strpos($log_data['message'], '; details:');
584
- $message = substr($log_data['message'], 0, $idx);
585
- $details = substr($log_data['message'], $idx + 11);
586
 
587
- $log_data['message'] = $message . ' (details):';
588
- $log_data['file_list'] = explode(',', $details);
589
- $log_data['file_list_count'] = count($log_data['file_list']);
 
 
590
  }
591
 
592
- if ($log_data = self::getLogsHotfix($log_data)) {
593
- $response['output_data'][] = $log_data;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
594
  }
595
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
596
  }
597
 
598
  return $response;
@@ -617,14 +552,18 @@ class SucuriScanAPI extends SucuriScanOption
617
  *
618
  * @see https://wordpress.org/plugins/php-compatibility-checker/
619
  */
620
- if (isset($data['message'])
621
- && strpos($data['message'], 'Wpephpcompat_jobs') === 0
622
- && preg_match('/ID: ([0-9]+); name: (.+)/', $data['message'], $match)
623
- ) {
 
 
 
 
624
  $data['message'] = sprintf(
625
  'WP Engine PHP Compatibility Checker: %s (created post #%d as cache)',
626
- $match[2], /* plugin or theme name */
627
- $match[1] /* unique post or page identifier */
628
  );
629
  }
630
 
@@ -725,16 +664,22 @@ class SucuriScanAPI extends SucuriScanOption
725
  $report['end_timestamp'] = $event['timestamp'];
726
  }
727
 
728
- // Detect successful and failed user authentications.
729
- if (@preg_match('/^User authentication (succeeded|failed):/', $event['message'], $match)) {
730
- if ($match[1] == 'succeeded') {
731
- $report['events_per_login']['successful']++;
732
- } else {
733
- $report['events_per_login']['failed']++;
734
- }
735
- } elseif (@preg_match('/^User logged in:/', $event['message'])) {
736
- // Backward compatibility for previous user login messages.
737
  $report['events_per_login']['successful']++;
 
 
 
 
 
 
 
738
  }
739
  }
740
 
@@ -854,11 +799,18 @@ class SucuriScanAPI extends SucuriScanOption
854
  * distributed by an independent developer.
855
  */
856
  if (isset($plugin_data['PluginURI'])
857
- && preg_match($pattern, $plugin_data['PluginURI'], $match)
 
858
  ) {
859
- $repository = $match[0];
860
- $repository_name = $match[2];
861
  $is_free_plugin = true;
 
 
 
 
 
 
 
 
862
  } else {
863
  $delimiter = strpos($path, '/') ? '/' : '.';
864
  $parts = explode($delimiter, $path, 2);
37
  */
38
  class SucuriScanAPI extends SucuriScanOption
39
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  /**
41
  * Alternative to the built-in PHP method http_build_query.
42
  *
83
  }
84
 
85
  $response = null;
86
+ $timeout = SUCURISCAN_MAX_REQUEST_TIMEOUT;
87
  $args = is_array($args) ? $args : array();
88
 
89
  if (isset($args['timeout'])) {
234
  * for the first time.
235
  *
236
  * @param array $response HTTP response after API endpoint execution.
 
237
  * @return bool False if the API call failed, true otherwise.
238
  */
239
+ public static function handleResponse($response = array())
240
  {
241
  if (!$response || getenv('SUCURISCAN_NO_API_HANDLE')) {
242
  return false;
298
  $msg = __('ErrorInvalidEmail', SUCURISCAN_TEXTDOMAIN);
299
  }
300
 
301
+ return SucuriScanInterface::error($msg);
302
  }
303
 
304
  /**
322
  if (self::handleResponse($response)) {
323
  self::setPluginKey($response['output']['api_key']);
324
 
325
+ SucuriScanEvent::installScheduledTask();
326
  SucuriScanEvent::notifyEvent('plugin_change', 'API key was generated and set');
327
  return SucuriScanInterface::info(__('AlertAPIKeySet', SUCURISCAN_TEXTDOMAIN));
328
  }
353
  return false;
354
  }
355
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  /**
357
  * Retrieve the event logs registered by the API service.
358
  *
374
  }
375
 
376
  /**
377
+ * Returns the security logs from the system queue.
378
  *
379
+ * @return array The data structure with the logs.
 
380
  */
381
+ public static function getAuditLogsFromQueue()
382
  {
 
 
 
 
383
  $auditlogs = array();
384
+ $cache = new SucuriScanCache('auditqueue');
385
+
386
+ if ($events = $cache->getAll()) {
387
+ $events = array_reverse($events);
388
+
389
+ foreach ($events as $micro => $message) {
390
+ $offset = strpos($micro, '_');
391
+ $time = substr($micro, 0, $offset);
392
+ $auditlogs[] = sprintf(
393
+ '%s %s : %s',
394
+ date('Y-m-d H:i:s', intval($time)),
395
+ SucuriScan::getSiteEmail(),
396
+ $message
397
+ );
 
 
 
 
398
  }
399
  }
400
 
418
  {
419
  $response = is_array($response) ? $response : array();
420
  $response['output_data'] = array();
 
 
 
 
421
 
422
  foreach ((array) @$response['output'] as $log) {
423
+ /* YYYY-MM-dd HH:ii:ss EMAIL : MESSAGE: (multiple entries): a,b,c */
424
+ if (strpos($log, "\x20:\x20") === false) {
425
+ continue; /* ignore; invalid format */
426
+ }
 
 
 
 
 
 
 
 
 
 
427
 
428
+ $log_data = array(
429
+ 'event' => 'notice',
430
+ 'date' => '',
431
+ 'time' => '',
432
+ 'datetime' => '',
433
+ 'timestamp' => 0,
434
+ 'account' => '',
435
+ 'username' => 'system',
436
+ 'remote_addr' => '127.0.0.1',
437
+ 'message' => '',
438
+ 'file_list' => false,
439
+ 'file_list_count' => 0,
440
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
 
442
+ list($left, $right) = explode("\x20:\x20", $log, 2);
443
+ $dateAndEmail = explode("\x20", $left, 3);
 
 
 
 
444
 
445
+ /* set basic information */
446
+ $log_data['message'] = $right;
447
+ $log_data['account'] = $dateAndEmail[2];
448
+
449
+ /* extract and fix the date and time using the Eastern time zone */
450
+ $datetime = sprintf('%s %s EDT', $dateAndEmail[0], $dateAndEmail[1]);
451
+ $log_data['timestamp'] = strtotime($datetime);
452
+ $log_data['datetime'] = date('Y-m-d H:i:s', $log_data['timestamp']);
453
+ $log_data['date'] = date('Y-m-d', $log_data['timestamp']);
454
+ $log_data['time'] = date('H:i:s', $log_data['timestamp']);
455
+
456
+ /* extract more information from the generic audit logs */
457
+ $log_data['message'] = str_replace('<br>', ";\x20", $log_data['message']);
458
+
459
+ $eventTypes = self::getAuditEventTypes();
460
+ $eventTypes = array_keys($eventTypes);
461
+
462
+ /* LEVEL: USERNAME, IP; MESSAGE */
463
+ if (strpos($log_data['message'], ":\x20") && strpos($log_data['message'], ";\x20")) {
464
+ $offset = strpos($log_data['message'], ":\x20");
465
+ $level = substr($log_data['message'], 0, $offset);
466
+ $log_data['event'] = strtolower($level);
467
 
468
+ /* ignore; invalid event type */
469
+ if (!in_array($log_data['event'], $eventTypes)) {
470
+ continue;
 
 
 
 
471
  }
472
 
473
+ /* extract the IP address */
474
+ $log_data['message'] = substr($log_data['message'], $offset+2);
475
+ $offset = strpos($log_data['message'], ";\x20");
476
+ $log_data['remote_addr'] = substr($log_data['message'], 0, $offset);
 
477
 
478
+ /* extract the username */
479
+ if (strpos($log_data['remote_addr'], ",\x20")) {
480
+ $index = strpos($log_data['remote_addr'], ",\x20");
481
+ $log_data['username'] = substr($log_data['remote_addr'], 0, $index);
482
+ $log_data['remote_addr'] = substr($log_data['remote_addr'], $index+2);
483
  }
484
 
485
+ /* fix old user authentication logs for backward compatibility */
486
+ $log_data['message'] = substr($log_data['message'], $offset+2);
487
+ $log_data['message'] = str_replace(
488
+ 'logged in',
489
+ 'authentication succeeded',
490
+ $log_data['message']
491
+ );
492
+
493
+ /* extract the username of a successful/failed login */
494
+ if (strpos($log_data['message'], "User authentication\x20") === 0) {
495
+ $offset = strpos($log_data['message'], ":\x20");
496
+ $username = substr($log_data['message'], $offset+2);
497
+ if (strpos($username, ';') !== false) {
498
+ $username = substr($username, 0, strpos($username, ';'));
499
+ }
500
+ $log_data['username'] = $username;
501
  }
502
  }
503
+
504
+ /* extract more information from the special formatted logs */
505
+ if (strpos($log_data['message'], "(multiple entries):\x20")) {
506
+ $offset = strpos($log_data['message'], "(multiple entries):\x20");
507
+ $message = substr($log_data['message'], 0, $offset+19);
508
+ $entries = substr($log_data['message'], $offset+20);
509
+
510
+ $log_data['message'] = $message;
511
+ $entries = str_replace(', new size', '; new size', $entries);
512
+ $entries = str_replace(",\x20", ";\x20", $entries);
513
+ $log_data['file_list'] = explode(',', $entries);
514
+ $log_data['file_list_count'] = count($log_data['file_list']);
515
+ }
516
+
517
+ /* extract additional details from the message */
518
+ if (strpos($log_data['message'], '; details:')) {
519
+ $idx = strpos($log_data['message'], '; details:');
520
+ $message = substr($log_data['message'], 0, $idx);
521
+ $details = substr($log_data['message'], $idx + 11);
522
+
523
+ $log_data['message'] = $message . ' (details):';
524
+ $log_data['file_list'] = explode(',', $details);
525
+ $log_data['file_list_count'] = count($log_data['file_list']);
526
+ }
527
+
528
+ if ($log_data = self::getLogsHotfix($log_data)) {
529
+ $response['output_data'][] = $log_data;
530
+ }
531
  }
532
 
533
  return $response;
552
  *
553
  * @see https://wordpress.org/plugins/php-compatibility-checker/
554
  */
555
+ if (isset($data['message']) && strpos($data['message'], 'Wpephpcompat_jobs') === 0) {
556
+ $offset = strpos($data['message'], "ID:\x20");
557
+ $id = substr($data['message'], $offset+4);
558
+ $id = substr($id, 0, strpos($id, ';'));
559
+
560
+ $offset = strpos($data['message'], "name:\x20");
561
+ $name = substr($data['message'], $offset+6);
562
+
563
  $data['message'] = sprintf(
564
  'WP Engine PHP Compatibility Checker: %s (created post #%d as cache)',
565
+ $name, /* plugin or theme name */
566
+ $id /* unique post or page identifier */
567
  );
568
  }
569
 
664
  $report['end_timestamp'] = $event['timestamp'];
665
  }
666
 
667
+ /* backward compatibility for previous user login messages */
668
+ if (strpos($event['message'], 'User logged in:') === 0) {
669
+ $report['events_per_login']['successful']++;
670
+ continue;
671
+ }
672
+
673
+ /* detect successful user authentications */
674
+ if (strpos($event['message'], 'User authentication succeeded:') === 0) {
 
675
  $report['events_per_login']['successful']++;
676
+ continue;
677
+ }
678
+
679
+ /* detect failed user authentications */
680
+ if (strpos($event['message'], 'User authentication failed:') === 0) {
681
+ $report['events_per_login']['failed']++;
682
+ continue;
683
  }
684
  }
685
 
799
  * distributed by an independent developer.
800
  */
801
  if (isset($plugin_data['PluginURI'])
802
+ && strpos($plugin_data['PluginURI'], '.org/plugins/')
803
+ && strpos($plugin_data['PluginURI'], '://wordpress.org/')
804
  ) {
 
 
805
  $is_free_plugin = true;
806
+ $repository = $plugin_data['PluginURI'];
807
+ $offset = strpos($plugin_data['PluginURI'], '/plugins/');
808
+ $repository_name = substr($plugin_data['PluginURI'], $offset+9);
809
+
810
+ if (strpos($repository_name, '/') !== false) {
811
+ $offset = strpos($repository_name, '/');
812
+ $repository_name = substr($repository_name, 0, $offset);
813
+ }
814
  } else {
815
  $delimiter = strpos($path, '/') ? '/' : '.';
816
  $parts = explode($delimiter, $path, 2);
src/auditlogs.lib.php CHANGED
@@ -64,10 +64,13 @@ class SucuriScanAuditLogs
64
 
65
  $response = array();
66
  $response['count'] = 0;
 
67
  $response['content'] = '';
 
 
68
  $response['selfhosting'] = false;
69
 
70
- /* Initialize the values for the pagination. */
71
  $maxPerPage = SUCURISCAN_AUDITLOGS_PER_PAGE;
72
  $pageNumber = SucuriScanTemplate::pageNumber();
73
  $logsLimit = ($pageNumber * $maxPerPage);
@@ -81,17 +84,22 @@ class SucuriScanAuditLogs
81
  /* API call if cache is invalid. */
82
  if (!$auditlogs || $pageNumber !== 1) {
83
  ob_start();
 
84
  $cacheTheResponse = true;
85
  $auditlogs = SucuriScanAPI::getAuditLogs($logsLimit);
86
- $errors = ob_get_contents();
 
87
  ob_end_clean();
 
 
 
 
 
88
  }
89
 
90
- /* Stop everything and report errors. */
91
  if (!empty($errors)) {
92
- header('Content-Type: text/html; charset=UTF-8');
93
- print($errors);
94
- exit(0);
95
  }
96
 
97
  /* Cache the data for sometime. */
@@ -99,10 +107,21 @@ class SucuriScanAuditLogs
99
  $cache->add('response', $auditlogs);
100
  }
101
 
102
- /* Fallback; get the logs from the local server */
103
- if (!$auditlogs && !SucuriScanOption::getOption(':api_key')) {
104
- $auditlogs = SucuriScanAPI::getSelfHostingLogs(SUCURISCAN_AUDITLOGS_PER_PAGE);
105
- $response['selfhosting'] = (bool) ($auditlogs !== false);
 
 
 
 
 
 
 
 
 
 
 
106
  }
107
 
108
  if ($auditlogs) {
@@ -197,6 +216,11 @@ class SucuriScanAuditLogs
197
  $response['content'] = __('NoLogs', SUCURISCAN_TEXTDOMAIN);
198
  }
199
 
 
 
 
 
 
200
  wp_send_json($response, 200);
201
  }
202
 
@@ -290,7 +314,7 @@ class SucuriScanAuditLogs
290
  }
291
 
292
  /**
293
- * Deletes the cache file with the auditlogs data.
294
  *
295
  * @codeCoverageIgnore - Notice that there is a test case that covers this
296
  * code, but since the WP-Send-JSON method uses die() to stop any further
@@ -298,27 +322,15 @@ class SucuriScanAuditLogs
298
  * with a missing line in the coverage. Since the test case takes care of
299
  * the functionality of this code we will assume that it is fully covered.
300
  */
301
- public static function ajaxAuditLogsResetCache()
302
  {
303
- if (SucuriScanRequest::post('form_action') !== 'reset_auditlogs_cache') {
304
  return;
305
  }
306
 
307
- $response = array();
308
- $cache = new SucuriScanCache('auditlogs');
309
- $cacheInfo = $cache->getDatastoreInfo();
310
- $filename = $cacheInfo['fpath'];
311
- $response['path'] = basename($filename);
312
- $response['ok'] = false;
313
-
314
- if (!file_exists($filename)) {
315
- $response['error'] = 'cache does not exists';
316
- } elseif (!is_writable($filename)) {
317
- $response['error'] = 'cache is not resetable';
318
- } else {
319
- $response['ok'] = (bool) @unlink($filename);
320
- }
321
 
322
- wp_send_json($response, 200);
323
  }
324
  }
64
 
65
  $response = array();
66
  $response['count'] = 0;
67
+ $response['status'] = '';
68
  $response['content'] = '';
69
+ $response['queueSize'] = 0;
70
+ $response['pagination'] = '';
71
  $response['selfhosting'] = false;
72
 
73
+ /* initialize the values for the pagination */
74
  $maxPerPage = SUCURISCAN_AUDITLOGS_PER_PAGE;
75
  $pageNumber = SucuriScanTemplate::pageNumber();
76
  $logsLimit = ($pageNumber * $maxPerPage);
84
  /* API call if cache is invalid. */
85
  if (!$auditlogs || $pageNumber !== 1) {
86
  ob_start();
87
+ $start = microtime(true);
88
  $cacheTheResponse = true;
89
  $auditlogs = SucuriScanAPI::getAuditLogs($logsLimit);
90
+ $errors = ob_get_contents(); /* capture errors */
91
+ $duration = microtime(true) - $start;
92
  ob_end_clean();
93
+
94
+ /* report latency in the API calls */
95
+ $response['status'] = !is_array($auditlogs)
96
+ ? __('AuditLogsNoAPI', SUCURISCAN_TEXTDOMAIN)
97
+ : sprintf('API %s secs', round($duration, 4));
98
  }
99
 
100
+ /* stop everything and report errors */
101
  if (!empty($errors)) {
102
+ $response['content'] .= $errors;
 
 
103
  }
104
 
105
  /* Cache the data for sometime. */
107
  $cache->add('response', $auditlogs);
108
  }
109
 
110
+ /* merge the logs from the queue system */
111
+ if ($queuelogs = SucuriScanAPI::getAuditLogsFromQueue()) {
112
+ if (!$auditlogs) {
113
+ $auditlogs = $queuelogs;
114
+ } else {
115
+ $auditlogs['total_entries'] += $queuelogs['total_entries'];
116
+ $auditlogs['output'] = array_merge(
117
+ $queuelogs['output'],
118
+ $auditlogs['output']
119
+ );
120
+ $auditlogs['output_data'] = array_merge(
121
+ $queuelogs['output_data'],
122
+ $auditlogs['output_data']
123
+ );
124
+ }
125
  }
126
 
127
  if ($auditlogs) {
216
  $response['content'] = __('NoLogs', SUCURISCAN_TEXTDOMAIN);
217
  }
218
 
219
+ $cache = new SucuriScanCache('auditqueue');
220
+ $finfo = $cache->getDatastoreInfo();
221
+ $events = $cache->getAll();
222
+ $response['queueSize'] = count($events);
223
+
224
  wp_send_json($response, 200);
225
  }
226
 
314
  }
315
 
316
  /**
317
+ * Send the logs from the queue to the API.
318
  *
319
  * @codeCoverageIgnore - Notice that there is a test case that covers this
320
  * code, but since the WP-Send-JSON method uses die() to stop any further
322
  * with a missing line in the coverage. Since the test case takes care of
323
  * the functionality of this code we will assume that it is fully covered.
324
  */
325
+ public static function ajaxAuditLogsSendLogs()
326
  {
327
+ if (SucuriScanRequest::post('form_action') !== 'auditlogs_send_logs') {
328
  return;
329
  }
330
 
331
+ /* blocking; might take a while */
332
+ SucuriScanEvent::sendLogsFromQueue();
 
 
 
 
 
 
 
 
 
 
 
 
333
 
334
+ wp_send_json(array('ok' => true), 200);
335
  }
336
  }
src/cache.lib.php CHANGED
@@ -81,7 +81,7 @@ class SucuriScanCache extends SucuriScan
81
  *
82
  * @return array Default attributes for every datastore file.
83
  */
84
- private function datastoreDefaultInfo()
85
  {
86
  $attrs = array(
87
  'datastore' => $this->datastore,
@@ -135,32 +135,53 @@ class SucuriScanCache extends SucuriScan
135
  return false;
136
  }
137
 
138
- $filename = 'sucuri-' . $this->datastore . '.php';
139
- $file_path = $this->dataStorePath($filename);
140
- $folder_path = dirname($file_path);
141
 
142
- /* create the datastore parent directory. */
143
- if (!file_exists($folder_path)) {
144
- @mkdir($folder_path, 0755, true);
145
  }
146
 
147
- /* create the cache file if necessary and the folder is writable. */
148
- if (!file_exists($file_path) && is_writable($folder_path) && $auto_create) {
149
- @file_put_contents($file_path, $this->datastoreInfo());
150
  }
151
 
152
- return $file_path;
153
  }
154
 
155
  /**
156
  * Check whether a key has a valid name or not.
157
  *
 
 
 
 
158
  * @param string $key Unique name for the data.
159
- * @return bool TRUE if the format of the key name is valid, FALSE otherwise.
160
  */
161
  private function validKeyName($key = '')
162
  {
163
- return (bool) @preg_match('/^([0-9a-zA-Z_]+)$/', $key);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  }
165
 
166
  /**
@@ -171,18 +192,20 @@ class SucuriScanCache extends SucuriScan
171
  */
172
  private function saveNewEntries($finfo = array())
173
  {
174
- $data_string = $this->datastoreInfo($finfo);
 
 
175
 
176
- if (!empty($finfo)) {
 
 
177
  foreach ($finfo['entries'] as $key => $data) {
178
- if ($this->validKeyName($key)) {
179
- $data = json_encode($data);
180
- $data_string .= sprintf("%s:%s\n", $key, $data);
181
- }
182
  }
183
  }
184
 
185
- return (bool) @file_put_contents($this->datastore_path, $data_string);
186
  }
187
 
188
  /**
@@ -191,28 +214,35 @@ class SucuriScanCache extends SucuriScan
191
  * be merged automatically.
192
  *
193
  * @param bool $assoc Force object to array conversion.
 
194
  * @return array Rainbow table with the key names and decoded values.
195
  */
196
- private function getDatastoreContent($assoc = false)
197
  {
198
  $object = array();
199
  $object['info'] = array();
200
  $object['entries'] = array();
201
- $header = '/^\/\/ ([a-z_]+)=(.*);$/';
202
- $content = '/^([0-9a-zA-Z_]+):(.+)/';
203
 
204
  if ($lines = SucuriScanFileInfo::fileLines($this->datastore_path)) {
205
  foreach ($lines as $line) {
206
- if (preg_match($header, $line, $match)) {
207
- $object['info'][ $match[1] ] = $match[2];
 
 
 
 
 
 
 
 
 
 
208
  continue;
209
  }
210
 
211
- if (preg_match($content, $line, $match)) {
212
- if ($this->validKeyName($match[1])) {
213
- $object['entries'][$match[1]] =
214
- @json_decode($match[2], $assoc);
215
- }
216
  }
217
  }
218
  }
@@ -234,7 +264,7 @@ class SucuriScanCache extends SucuriScan
234
  */
235
  public function getDatastoreInfo()
236
  {
237
- $finfo = $this->getDatastoreContent();
238
 
239
  if (empty($finfo['info'])) {
240
  return false;
@@ -273,7 +303,8 @@ class SucuriScanCache extends SucuriScan
273
  public function dataHasExpired($lifetime = 0, $finfo = null)
274
  {
275
  if (is_null($finfo)) {
276
- $finfo = $this->getDatastoreContent();
 
277
  }
278
 
279
  if ($lifetime > 0 && !empty($finfo['info'])) {
@@ -315,11 +346,10 @@ class SucuriScanCache extends SucuriScan
315
  return self::throwException('Invalid cache key name');
316
  }
317
 
318
- $finfo = $this->getDatastoreContent(true);
319
-
320
- $finfo['entries'][$key] = $data;
321
 
322
- return $this->saveNewEntries($finfo);
323
  }
324
 
325
  /**
@@ -403,6 +433,20 @@ class SucuriScanCache extends SucuriScan
403
  return $this->saveNewEntries($finfo);
404
  }
405
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
  /**
407
  * Remove all the entries from the datastore file.
408
  *
@@ -410,12 +454,8 @@ class SucuriScanCache extends SucuriScan
410
  */
411
  public function flush()
412
  {
413
- $finfo = $this->getDatastoreContent();
414
-
415
- if (array_key_exists('entries', $finfo)) {
416
- $finfo['entries'] = array();
417
- }
418
 
419
- return $this->saveNewEntries($finfo);
420
  }
421
  }
81
  *
82
  * @return array Default attributes for every datastore file.
83
  */
84
+ public function datastoreDefaultInfo()
85
  {
86
  $attrs = array(
87
  'datastore' => $this->datastore,
135
  return false;
136
  }
137
 
138
+ $filename = $this->dataStorePath('sucuri-' . $this->datastore . '.php');
139
+ $directory = dirname($filename); /* create directory if necessary */
 
140
 
141
+ if (!file_exists($directory)) {
142
+ @mkdir($directory, 0755, true);
 
143
  }
144
 
145
+ if (!file_exists($filename) && is_writable($directory) && $auto_create) {
146
+ @file_put_contents($filename, $this->datastoreInfo());
 
147
  }
148
 
149
+ return $filename;
150
  }
151
 
152
  /**
153
  * Check whether a key has a valid name or not.
154
  *
155
+ * WARNING: Instead of using a regular expression to match the format of the
156
+ * key we will use a primitive string transformation technique to reduce the
157
+ * execution time, regular expressions are significantly slow.
158
+ *
159
  * @param string $key Unique name for the data.
160
+ * @return bool True if the key is valid, false otherwise.
161
  */
162
  private function validKeyName($key = '')
163
  {
164
+ $result = true;
165
+ $length = strlen($key);
166
+ $allowed = array(
167
+ /* preg_match('/^([0-9a-zA-Z_]+)$/', $key) */
168
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
169
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
170
+ 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
171
+ 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
172
+ 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
173
+ 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
174
+ 'Y', 'Z', '_',
175
+ );
176
+
177
+ for ($i = 0; $i < $length; $i++) {
178
+ if (!in_array($key[$i], $allowed)) {
179
+ $result = false;
180
+ break;
181
+ }
182
+ }
183
+
184
+ return $result;
185
  }
186
 
187
  /**
192
  */
193
  private function saveNewEntries($finfo = array())
194
  {
195
+ if (!$finfo) {
196
+ return false;
197
+ }
198
 
199
+ $metadata = $this->datastoreInfo($finfo);
200
+
201
+ if (@file_put_contents($this->datastore_path, $metadata)) {
202
  foreach ($finfo['entries'] as $key => $data) {
203
+ $line = sprintf("%s:%s\n", $key, json_encode($data));
204
+ @file_put_contents($this->datastore_path, $line, FILE_APPEND);
 
 
205
  }
206
  }
207
 
208
+ return true;
209
  }
210
 
211
  /**
214
  * be merged automatically.
215
  *
216
  * @param bool $assoc Force object to array conversion.
217
+ * @param bool $onlyInfo Returns the cache headers and no content.
218
  * @return array Rainbow table with the key names and decoded values.
219
  */
220
+ private function getDatastoreContent($assoc = false, $onlyInfo = false)
221
  {
222
  $object = array();
223
  $object['info'] = array();
224
  $object['entries'] = array();
 
 
225
 
226
  if ($lines = SucuriScanFileInfo::fileLines($this->datastore_path)) {
227
  foreach ($lines as $line) {
228
+ if (strpos($line, "//\x20") === 0
229
+ && strpos($line, '=') !== false
230
+ && $line[strlen($line)-1] === ';'
231
+ ) {
232
+ $section = substr($line, 3, strlen($line)-4);
233
+ list($header, $value) = explode('=', $section, 2);
234
+ $object['info'][$header] = $value;
235
+ continue;
236
+ }
237
+
238
+ /* skip content */
239
+ if ($onlyInfo) {
240
  continue;
241
  }
242
 
243
+ if (strpos($line, ':') !== false) {
244
+ list($keyname, $value) = explode(':', $line, 2);
245
+ $object['entries'][$keyname] = @json_decode($value, $assoc);
 
 
246
  }
247
  }
248
  }
264
  */
265
  public function getDatastoreInfo()
266
  {
267
+ $finfo = $this->getDatastoreContent(false, true);
268
 
269
  if (empty($finfo['info'])) {
270
  return false;
303
  public function dataHasExpired($lifetime = 0, $finfo = null)
304
  {
305
  if (is_null($finfo)) {
306
+ $meta = $this->getDatastoreInfo();
307
+ $finfo = array('info' => $meta);
308
  }
309
 
310
  if ($lifetime > 0 && !empty($finfo['info'])) {
346
  return self::throwException('Invalid cache key name');
347
  }
348
 
349
+ $finfo = $this->getDatastoreInfo();
350
+ $line = sprintf("%s:%s\n", $key, json_encode($data));
 
351
 
352
+ return (bool) @file_put_contents($finfo['fpath'], $line, FILE_APPEND);
353
  }
354
 
355
  /**
433
  return $this->saveNewEntries($finfo);
434
  }
435
 
436
+ /**
437
+ * Replaces the entire content of the cache file.
438
+ *
439
+ * @param array $entries New data for the cache.
440
+ * @return bool True if the cache was replaced.
441
+ */
442
+ public function override($entries = array())
443
+ {
444
+ return $this->saveNewEntries(array(
445
+ 'info' => $this->getDatastoreInfo(),
446
+ 'entries' => $entries,
447
+ ));
448
+ }
449
+
450
  /**
451
  * Remove all the entries from the datastore file.
452
  *
454
  */
455
  public function flush()
456
  {
457
+ $filename = 'sucuri-' . $this->datastore . '.php';
 
 
 
 
458
 
459
+ return @unlink($this->dataStorePath($filename));
460
  }
461
  }
src/event.lib.php CHANGED
@@ -34,7 +34,7 @@ if (!defined('SUCURISCAN_INIT') || SUCURISCAN_INIT !== true) {
34
  class SucuriScanEvent extends SucuriScan
35
  {
36
  /**
37
- * Creates a cronjob to run the file system scanner twice-daily.
38
  *
39
  * Right after a fresh installation of the plugin, it will create a cronjob
40
  * that will execute the first scan in the next five minutes. This scan will
@@ -43,23 +43,13 @@ class SucuriScanEvent extends SucuriScan
43
  * list with the checksum of the new file list, if there are differences we
44
  * will assume that someone or something modified one or more files and send
45
  * an email alsert about the incident.
46
- *
47
- * @param bool $run_now Forces the execute of the scanner right now.
48
  */
49
- public static function scheduleTask($run_now = true)
50
  {
51
- /* stop if the user has disabled the cronjobs. */
52
- if (SucuriScanOption::getOption(':scan_frequency') !== '_oneoff') {
53
- $task_name = 'sucuriscan_scheduled_scan';
54
-
55
- if (!wp_next_scheduled($task_name)) {
56
- wp_schedule_event(time() + 10, 'twicedaily', $task_name);
57
- }
58
 
59
- if ($run_now === true) {
60
- // Execute scheduled task after five minutes.
61
- wp_schedule_single_event(time() + 300, $task_name);
62
- }
63
  }
64
  }
65
 
@@ -179,10 +169,38 @@ class SucuriScanEvent extends SucuriScan
179
  * in a failure. However, this procedure depends on the ability of the plugin
180
  * to write the log into the queue when the previous request failed.
181
  *
182
- * @param string $event_message Information about the security event.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  * @return bool True if the operation succeeded, false otherwise.
184
  */
185
- public static function sendEventLog($event_message = '')
186
  {
187
  /* create storage directory if necessary */
188
  SucuriScanInterface::createStorageFolder();
@@ -191,9 +209,9 @@ class SucuriScanEvent extends SucuriScan
191
  * Self-hosted Monitor.
192
  *
193
  * Send a copy of the event log to a local file, this will allow the
194
- * administrator of the server to integrate the events monitored by the plugin
195
- * with a 3rd-party service like OSSEC or similar. More information in the Self-
196
- * Hosting panel located in the plugin' settings page.
197
  */
198
  if (function_exists('sucuriscan_selfhosting_fpath')) {
199
  $monitor_fpath = sucuriscan_selfhosting_fpath();
@@ -204,7 +222,7 @@ class SucuriScanEvent extends SucuriScan
204
  date('Y-m-d H:i:s'),
205
  SucuriScan::getTopLevelDomain(),
206
  SucuriScanOption::getOption(':account'),
207
- $event_message
208
  );
209
  @file_put_contents(
210
  $monitor_fpath,
@@ -214,24 +232,69 @@ class SucuriScanEvent extends SucuriScan
214
  }
215
  }
216
 
 
217
  if (SucuriScanOption::isEnabled(':api_service')) {
218
- SucuriScanAPI::sendLogsFromQueue();
219
-
220
- return SucuriScanAPI::sendLog($event_message);
221
  }
222
 
223
  return true;
224
  }
225
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  /**
227
  * Generates an audit event log (to be sent later).
228
  *
229
  * @param int $severity Importance of the event that will be reported, values from one to five.
230
  * @param string $message The explanation of the event.
231
- * @param bool $internal Whether the event will be publicly visible or not.
232
  * @return bool True if the event was logged in the monitoring service, false otherwise.
233
  */
234
- private static function reportEvent($severity = 0, $message = '', $internal = false)
235
  {
236
  $user = wp_get_current_user();
237
  $remote_ip = self::getRemoteAddr();
@@ -260,18 +323,13 @@ class SucuriScanEvent extends SucuriScan
260
  $severity_name = $severities[$severity];
261
  }
262
 
263
- if ($internal === true) {
264
- /* mark the event as internal if necessary. */
265
- $severity_name = '@' . $severity_name;
266
- }
267
-
268
  /* remove unnecessary characters */
269
  $message = strip_tags($message);
270
  $message = str_replace("\r", '', $message);
271
  $message = str_replace("\n", '', $message);
272
  $message = str_replace("\t", '', $message);
273
 
274
- return self::sendEventLog(sprintf(
275
  '%s:%s %s; %s',
276
  $severity_name,
277
  $username,
@@ -284,72 +342,66 @@ class SucuriScanEvent extends SucuriScan
284
  * Reports a debug event on the website.
285
  *
286
  * @param string $message Text witht the explanation of the event or action performed.
287
- * @param bool $internal Whether the event will be publicly visible or not.
288
  * @return bool Either true or false depending on the success of the operation.
289
  */
290
- public static function reportDebugEvent($message = '', $internal = false)
291
  {
292
- return self::reportEvent(0, $message, $internal);
293
  }
294
 
295
  /**
296
  * Reports a notice event on the website.
297
  *
298
  * @param string $message Text witht the explanation of the event or action performed.
299
- * @param bool $internal Whether the event will be publicly visible or not.
300
  * @return bool Either true or false depending on the success of the operation.
301
  */
302
- public static function reportNoticeEvent($message = '', $internal = false)
303
  {
304
- return self::reportEvent(1, $message, $internal);
305
  }
306
 
307
  /**
308
  * Reports a info event on the website.
309
  *
310
  * @param string $message Text witht the explanation of the event or action performed.
311
- * @param bool $internal Whether the event will be publicly visible or not.
312
  * @return bool Either true or false depending on the success of the operation.
313
  */
314
- public static function reportInfoEvent($message = '', $internal = false)
315
  {
316
- return self::reportEvent(2, $message, $internal);
317
  }
318
 
319
  /**
320
  * Reports a warning event on the website.
321
  *
322
  * @param string $message Text witht the explanation of the event or action performed.
323
- * @param bool $internal Whether the event will be publicly visible or not.
324
  * @return bool Either true or false depending on the success of the operation.
325
  */
326
- public static function reportWarningEvent($message = '', $internal = false)
327
  {
328
- return self::reportEvent(3, $message, $internal);
329
  }
330
 
331
  /**
332
  * Reports a error event on the website.
333
  *
334
  * @param string $message Text witht the explanation of the event or action performed.
335
- * @param bool $internal Whether the event will be publicly visible or not.
336
  * @return bool Either true or false depending on the success of the operation.
337
  */
338
- public static function reportErrorEvent($message = '', $internal = false)
339
  {
340
- return self::reportEvent(4, $message, $internal);
341
  }
342
 
343
  /**
344
  * Reports a critical event on the website.
345
  *
346
  * @param string $message Text witht the explanation of the event or action performed.
347
- * @param bool $internal Whether the event will be publicly visible or not.
348
  * @return bool Either true or false depending on the success of the operation.
349
  */
350
- public static function reportCriticalEvent($message = '', $internal = false)
351
  {
352
- return self::reportEvent(5, $message, $internal);
353
  }
354
 
355
  /**
@@ -357,10 +409,9 @@ class SucuriScanEvent extends SucuriScan
357
  *
358
  * @param string $message Text witht the explanation of the event or action performed.
359
  * @param string $action An optional text, hopefully either enabled or disabled.
360
- * @param bool $internal Whether the event will be publicly visible or not.
361
  * @return bool Either true or false depending on the success of the operation.
362
  */
363
- public static function reportAutoEvent($message = '', $action = '', $internal = false)
364
  {
365
  $message = strip_tags($message);
366
 
@@ -370,14 +421,14 @@ class SucuriScanEvent extends SucuriScan
370
  }
371
 
372
  if ($action === 'enabled') {
373
- return self::reportNoticeEvent($message, $internal);
374
  }
375
 
376
  if ($action === 'disabled') {
377
- return self::reportErrorEvent($message, $internal);
378
  }
379
 
380
- return self::reportInfoEvent($message, $internal);
381
  }
382
 
383
  /**
@@ -647,7 +698,7 @@ class SucuriScanEvent extends SucuriScan
647
  }
648
  }
649
 
650
- $response = array(
651
  'updated' => is_writable($config_path),
652
  'old_keys' => $old_keys,
653
  'old_keys_string' => $old_keys_string,
@@ -656,10 +707,10 @@ class SucuriScanEvent extends SucuriScan
656
  'new_wpconfig' => $new_wpconfig,
657
  );
658
 
659
- if ($response['updated']) {
660
  @file_put_contents($config_path, $new_wpconfig, LOCK_EX);
661
  }
662
 
663
- return $response;
664
  }
665
  }
34
  class SucuriScanEvent extends SucuriScan
35
  {
36
  /**
37
+ * Creates a cronjob to run the file system scanner.
38
  *
39
  * Right after a fresh installation of the plugin, it will create a cronjob
40
  * that will execute the first scan in the next five minutes. This scan will
43
  * list with the checksum of the new file list, if there are differences we
44
  * will assume that someone or something modified one or more files and send
45
  * an email alsert about the incident.
 
 
46
  */
47
+ public static function installScheduledTask()
48
  {
49
+ $task_name = 'sucuriscan_scheduled_scan';
 
 
 
 
 
 
50
 
51
+ if (!wp_next_scheduled($task_name)) {
52
+ wp_schedule_event(time() + 10, 'daily', $task_name);
 
 
53
  }
54
  }
55
 
169
  * in a failure. However, this procedure depends on the ability of the plugin
170
  * to write the log into the queue when the previous request failed.
171
  *
172
+ * @param string $message Information about the event.
173
+ * @param string $timestamp Time when the event was triggered.
174
+ * @return bool True if the event was logged, false otherwise.
175
+ */
176
+ private static function sendLogToAPI($message = '', $timestamp = '')
177
+ {
178
+ if (empty($message)) {
179
+ return self::throwException('Event identifier cannot be empty');
180
+ }
181
+
182
+ $params = array();
183
+ $params['a'] = 'send_log';
184
+ $params['m'] = $message;
185
+ $params['time'] = $timestamp;
186
+ $args = array('timeout' => 5 /* seconds */);
187
+
188
+ $resp = SucuriScanAPI::apiCallWordpress('POST', $params, true, $args);
189
+
190
+ return (bool) (
191
+ is_array($resp)
192
+ && array_key_exists('status', $resp)
193
+ && intval($resp['status']) === 1
194
+ );
195
+ }
196
+
197
+ /**
198
+ * Sends the event message to a local queue system.
199
+ *
200
+ * @param string $message Information about the security event.
201
  * @return bool True if the operation succeeded, false otherwise.
202
  */
203
+ public static function sendLogToQueue($message = '')
204
  {
205
  /* create storage directory if necessary */
206
  SucuriScanInterface::createStorageFolder();
209
  * Self-hosted Monitor.
210
  *
211
  * Send a copy of the event log to a local file, this will allow the
212
+ * administrator of the server to integrate the events monitored by the
213
+ * plugin with a 3rd-party service like OSSEC or similar. More info in
214
+ * the Self-Hosting panel located in the plugin' settings page.
215
  */
216
  if (function_exists('sucuriscan_selfhosting_fpath')) {
217
  $monitor_fpath = sucuriscan_selfhosting_fpath();
222
  date('Y-m-d H:i:s'),
223
  SucuriScan::getTopLevelDomain(),
224
  SucuriScanOption::getOption(':account'),
225
+ $message
226
  );
227
  @file_put_contents(
228
  $monitor_fpath,
232
  }
233
  }
234
 
235
+ /* enqueue the event if the API is enabled */
236
  if (SucuriScanOption::isEnabled(':api_service')) {
237
+ $cache = new SucuriScanCache('auditqueue');
238
+ $key = str_replace('.', '_', microtime(true));
239
+ $written = $cache->add($key, $message);
240
  }
241
 
242
  return true;
243
  }
244
 
245
+ /**
246
+ * Sends all the events from the queue to the API.
247
+ */
248
+ public static function sendLogsFromQueue()
249
+ {
250
+ if (SucuriScanOption::isDisabled(':api_service')) {
251
+ return;
252
+ }
253
+
254
+ $cache = new SucuriScanCache('auditqueue');
255
+ $finfo = $cache->getDatastoreInfo();
256
+ $events = $cache->getAll();
257
+ $counter = 0;
258
+
259
+ if (!$events) {
260
+ return;
261
+ }
262
+
263
+ /* Send around 15,000 logs for maximum 30 seconds */
264
+ $maxtime = (int) SucuriScan::iniGet('max_execution_time');
265
+ $maxreqs = ($maxtime > 1) ? (500 * $maxtime) : 5000;
266
+
267
+ foreach ($events as $keyname => $message) {
268
+ $offset = strpos($keyname, '_');
269
+ $timestamp = substr($keyname, 0, $offset);
270
+ $status = self::sendLogToAPI($message, $timestamp);
271
+
272
+ if ($status !== true) {
273
+ /* API is down */
274
+ break;
275
+ }
276
+
277
+ /* dequeue event message */
278
+ unset($events[$keyname]);
279
+ $counter++;
280
+
281
+ /* avoid memory limit */
282
+ if ($counter >= $maxreqs) {
283
+ break;
284
+ }
285
+ }
286
+
287
+ $cache->override($events);
288
+ }
289
+
290
  /**
291
  * Generates an audit event log (to be sent later).
292
  *
293
  * @param int $severity Importance of the event that will be reported, values from one to five.
294
  * @param string $message The explanation of the event.
 
295
  * @return bool True if the event was logged in the monitoring service, false otherwise.
296
  */
297
+ private static function reportEvent($severity = 0, $message = '')
298
  {
299
  $user = wp_get_current_user();
300
  $remote_ip = self::getRemoteAddr();
323
  $severity_name = $severities[$severity];
324
  }
325
 
 
 
 
 
 
326
  /* remove unnecessary characters */
327
  $message = strip_tags($message);
328
  $message = str_replace("\r", '', $message);
329
  $message = str_replace("\n", '', $message);
330
  $message = str_replace("\t", '', $message);
331
 
332
+ return self::sendLogToQueue(sprintf(
333
  '%s:%s %s; %s',
334
  $severity_name,
335
  $username,
342
  * Reports a debug event on the website.
343
  *
344
  * @param string $message Text witht the explanation of the event or action performed.
 
345
  * @return bool Either true or false depending on the success of the operation.
346
  */
347
+ public static function reportDebugEvent($message = '')
348
  {
349
+ return self::reportEvent(0, $message);
350
  }
351
 
352
  /**
353
  * Reports a notice event on the website.
354
  *
355
  * @param string $message Text witht the explanation of the event or action performed.
 
356
  * @return bool Either true or false depending on the success of the operation.
357
  */
358
+ public static function reportNoticeEvent($message = '')
359
  {
360
+ return self::reportEvent(1, $message);
361
  }
362
 
363
  /**
364
  * Reports a info event on the website.
365
  *
366
  * @param string $message Text witht the explanation of the event or action performed.
 
367
  * @return bool Either true or false depending on the success of the operation.
368
  */
369
+ public static function reportInfoEvent($message = '')
370
  {
371
+ return self::reportEvent(2, $message);
372
  }
373
 
374
  /**
375
  * Reports a warning event on the website.
376
  *
377
  * @param string $message Text witht the explanation of the event or action performed.
 
378
  * @return bool Either true or false depending on the success of the operation.
379
  */
380
+ public static function reportWarningEvent($message = '')
381
  {
382
+ return self::reportEvent(3, $message);
383
  }
384
 
385
  /**
386
  * Reports a error event on the website.
387
  *
388
  * @param string $message Text witht the explanation of the event or action performed.
 
389
  * @return bool Either true or false depending on the success of the operation.
390
  */
391
+ public static function reportErrorEvent($message = '')
392
  {
393
+ return self::reportEvent(4, $message);
394
  }
395
 
396
  /**
397
  * Reports a critical event on the website.
398
  *
399
  * @param string $message Text witht the explanation of the event or action performed.
 
400
  * @return bool Either true or false depending on the success of the operation.
401
  */
402
+ public static function reportCriticalEvent($message = '')
403
  {
404
+ return self::reportEvent(5, $message);
405
  }
406
 
407
  /**
409
  *
410
  * @param string $message Text witht the explanation of the event or action performed.
411
  * @param string $action An optional text, hopefully either enabled or disabled.
 
412
  * @return bool Either true or false depending on the success of the operation.
413
  */
414
+ public static function reportAutoEvent($message = '', $action = '')
415
  {
416
  $message = strip_tags($message);
417
 
421
  }
422
 
423
  if ($action === 'enabled') {
424
+ return self::reportNoticeEvent($message);
425
  }
426
 
427
  if ($action === 'disabled') {
428
+ return self::reportErrorEvent($message);
429
  }
430
 
431
+ return self::reportInfoEvent($message);
432
  }
433
 
434
  /**
698
  }
699
  }
700
 
701
+ $resp = array(
702
  'updated' => is_writable($config_path),
703
  'old_keys' => $old_keys,
704
  'old_keys_string' => $old_keys_string,
707
  'new_wpconfig' => $new_wpconfig,
708
  );
709
 
710
+ if ($resp['updated']) {
711
  @file_put_contents($config_path, $new_wpconfig, LOCK_EX);
712
  }
713
 
714
+ return $resp;
715
  }
716
  }
src/fileinfo.lib.php CHANGED
@@ -95,7 +95,12 @@ class SucuriScanFileInfo extends SucuriScan
95
  */
96
  public static function isSplAvailable()
97
  {
98
- return (bool) (class_exists('SplFileObject') && class_exists('FilesystemIterator'));
 
 
 
 
 
99
  }
100
 
101
  /**
@@ -180,7 +185,7 @@ class SucuriScanFileInfo extends SucuriScan
180
  {
181
  $files = array();
182
 
183
- if (is_dir($directory)) {
184
  $objects = array();
185
 
186
  $this->ignored_directories = SucuriScanFSScanner::getIgnoredDirectories();
95
  */
96
  public static function isSplAvailable()
97
  {
98
+ return (bool) (
99
+ class_exists('SplFileObject')
100
+ && class_exists('FilesystemIterator')
101
+ && class_exists('RecursiveIteratorIterator')
102
+ && class_exists('RecursiveDirectoryIterator')
103
+ );
104
  }
105
 
106
  /**
185
  {
186
  $files = array();
187
 
188
+ if (is_dir($directory) && self::isSplAvailable()) {
189
  $objects = array();
190
 
191
  $this->ignored_directories = SucuriScanFSScanner::getIgnoredDirectories();
src/firewall.lib.php CHANGED
@@ -297,9 +297,11 @@ class SucuriScanFirewall extends SucuriScanAPI
297
  */
298
  public static function auditlogsPage()
299
  {
300
- $date = date('Y-m-d');
301
  $params = array();
302
 
 
 
 
303
  $params['AuditLogs.DateYears'] = self::dates('years', $date);
304
  $params['AuditLogs.DateMonths'] = self::dates('months', $date);
305
  $params['AuditLogs.DateDays'] = self::dates('days', $date);
297
  */
298
  public static function auditlogsPage()
299
  {
 
300
  $params = array();
301
 
302
+ /* logs are available after 24 hours */
303
+ $date = date('Y-m-d', strtotime('-1 day'));
304
+
305
  $params['AuditLogs.DateYears'] = self::dates('years', $date);
306
  $params['AuditLogs.DateMonths'] = self::dates('months', $date);
307
  $params['AuditLogs.DateDays'] = self::dates('days', $date);
src/globals.php CHANGED
@@ -111,14 +111,38 @@ if (defined('SUCURISCAN')) {
111
  {
112
  global $locale;
113
 
114
- $filename = sprintf(
115
  '%s/languages/%s-%s.po',
116
  SUCURISCAN_PLUGIN_PATH,
117
  SUCURISCAN_TEXTDOMAIN,
118
  $locale
119
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
- if (!file_exists($filename)) {
 
122
  $locale = 'en_US';
123
  setlocale(LC_ALL, 'en_US');
124
  }
@@ -203,7 +227,6 @@ if (defined('SUCURISCAN')) {
203
  add_action('switch_theme', 'SucuriScanHook::hookThemeSwitch', 50, 5);
204
  add_action('transition_post_status', 'SucuriScanHook::hookPostStatus', 50, 3);
205
  add_action('user_register', 'SucuriScanHook::hookUserRegister', 50, 5);
206
- add_action('wp_insert_comment', 'SucuriScanHook::hookCommentInsert', 50, 5);
207
  add_action('wp_login', 'SucuriScanHook::hookLoginSuccess', 50, 5);
208
  add_action('wp_login_failed', 'SucuriScanHook::hookLoginFailure', 50, 5);
209
  add_action('wp_trash_post', 'SucuriScanHook::hookPostTrash', 50, 5);
111
  {
112
  global $locale;
113
 
114
+ $pofile = sprintf(
115
  '%s/languages/%s-%s.po',
116
  SUCURISCAN_PLUGIN_PATH,
117
  SUCURISCAN_TEXTDOMAIN,
118
  $locale
119
  );
120
+ $mofile = sprintf(
121
+ '%s/languages/%s-%s.mo',
122
+ SUCURISCAN_PLUGIN_PATH,
123
+ SUCURISCAN_TEXTDOMAIN,
124
+ $locale
125
+ );
126
+
127
+ /* attempt to import the English POT file into LOCALE */
128
+ if (!file_exists($pofile) || !file_exists($mofile)) {
129
+ $en_pofile = sprintf(
130
+ '%s/languages/%s-en_US.po',
131
+ SUCURISCAN_PLUGIN_PATH,
132
+ SUCURISCAN_TEXTDOMAIN
133
+ );
134
+ $en_mofile = sprintf(
135
+ '%s/languages/%s-en_US.mo',
136
+ SUCURISCAN_PLUGIN_PATH,
137
+ SUCURISCAN_TEXTDOMAIN
138
+ );
139
+
140
+ @copy($en_pofile, $pofile);
141
+ @copy($en_mofile, $mofile);
142
+ }
143
 
144
+ /* fallback to English on language import failure */
145
+ if (!file_exists($pofile) || !file_exists($mofile)) {
146
  $locale = 'en_US';
147
  setlocale(LC_ALL, 'en_US');
148
  }
227
  add_action('switch_theme', 'SucuriScanHook::hookThemeSwitch', 50, 5);
228
  add_action('transition_post_status', 'SucuriScanHook::hookPostStatus', 50, 3);
229
  add_action('user_register', 'SucuriScanHook::hookUserRegister', 50, 5);
 
230
  add_action('wp_login', 'SucuriScanHook::hookLoginSuccess', 50, 5);
231
  add_action('wp_login_failed', 'SucuriScanHook::hookLoginFailure', 50, 5);
232
  add_action('wp_trash_post', 'SucuriScanHook::hookPostTrash', 50, 5);
src/hook.lib.php CHANGED
@@ -67,51 +67,6 @@ class SucuriScanHook extends SucuriScanEvent
67
  self::notifyEvent('post_publication', $message);
68
  }
69
 
70
- /**
71
- * Fires immediately after a comment is inserted into the database.
72
- *
73
- * The action comment-post can also be used to track the insertion of data in
74
- * the comments table, but this only returns the identifier of the new entry in
75
- * the database and the status (approved, not approved, spam). The WP-Insert-
76
- * Comment action returns the same identifier and additionally the full data set
77
- * with the comment information.
78
- *
79
- * @see https://codex.wordpress.org/Plugin_API/Action_Reference/wp_insert_comment
80
- * @see https://codex.wordpress.org/Plugin_API/Action_Reference/comment_post
81
- *
82
- * @param int $id The comment identifier.
83
- * @param object $comment The comment object.
84
- */
85
- public static function hookCommentInsert($id = 0, $comment = null)
86
- {
87
- if (is_object($comment)
88
- && property_exists($comment, 'comment_ID')
89
- && property_exists($comment, 'comment_agent')
90
- && property_exists($comment, 'comment_author_IP')
91
- && SucuriScanOption::isEnabled(':comment_monitor')
92
- ) {
93
- $data_set = array(
94
- 'id' => $comment->comment_ID,
95
- 'post_id' => $comment->comment_post_ID,
96
- 'user_id' => $comment->user_id,
97
- 'parent' => $comment->comment_parent,
98
- 'approved' => $comment->comment_approved,
99
- 'remote_addr' => $comment->comment_author_IP,
100
- 'author_email' => $comment->comment_author_email,
101
- 'date' => $comment->comment_date,
102
- 'content' => $comment->comment_content,
103
- 'user_agent' => $comment->comment_agent,
104
- );
105
- $message = base64_encode(json_encode($data_set));
106
- self::reportNoticeEvent('Base64:' . $message, true);
107
- }
108
- }
109
-
110
- // TODO: Log when the comment status is modified: wp_set_comment_status
111
- // TODO: Log when the comment data is modified: edit_comment
112
- // TODO: Log when the comment is going to be deleted: delete_comment, trash_comment
113
- // TODO: Log when the comment is finally deleted: deleted_comment, trashed_comment
114
- // TODO: Log when the comment is closed: comment_closed
115
  // TODO: Detect auto updates in core, themes, and plugin files.
116
 
117
  /**
@@ -620,6 +575,11 @@ class SucuriScanHook extends SucuriScanEvent
620
  return self::throwException('Ignore corrupted post data');
621
  }
622
 
 
 
 
 
 
623
  $post_type = 'post'; /* either post or page */
624
 
625
  if (property_exists($post, 'post_type')) {
67
  self::notifyEvent('post_publication', $message);
68
  }
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  // TODO: Detect auto updates in core, themes, and plugin files.
71
 
72
  /**
575
  return self::throwException('Ignore corrupted post data');
576
  }
577
 
578
+ /* ignore; the same */
579
+ if ($old === $new) {
580
+ return;
581
+ }
582
+
583
  $post_type = 'post'; /* either post or page */
584
 
585
  if (property_exists($post, 'post_type')) {
src/integrity.lib.php CHANGED
@@ -119,45 +119,54 @@ class SucuriScanIntegrity
119
 
120
  foreach ((array) $core_files as $file_meta) {
121
  if (strpos($file_meta, '@')) {
122
- @list($status_type, $file_path) =
123
- explode('@', $file_meta, 2);
124
-
125
- if ($file_path && $status_type) {
126
- $full_path = ABSPATH . '/' . $file_path;
127
-
128
- switch ($action) {
129
- case 'restore':
130
- if ($content = SucuriScanAPI::getOriginalCoreFile($file_path)) {
131
- $basedir = dirname($full_path);
132
- @mkdir($basedir, 0755, true);
133
-
134
- if (file_exists($basedir)) {
135
- $restored = @file_put_contents($full_path, $content);
136
- $files_processed += ($restored ? 1 : 0);
137
- $files_affected[] = $full_path;
138
- }
139
- }
140
- break;
141
-
142
- case 'fixed':
143
- $cache_key = md5($file_path);
144
- $cache_value = array(
145
- 'file_path' => $file_path,
146
- 'file_status' => $status_type,
147
- 'ignored_at' => time(),
148
- );
149
- $cached = $cache->add($cache_key, $cache_value);
150
- $files_processed += ($cached ? 1 : 0);
 
 
 
 
 
 
 
151
  $files_affected[] = $full_path;
152
- break;
 
 
 
153
 
154
- case 'delete':
155
- if (@unlink($full_path)) {
156
- $files_processed += 1;
157
- $files_affected[] = $full_path;
158
- }
159
- break;
160
  }
 
161
  }
162
  }
163
  }
119
 
120
  foreach ((array) $core_files as $file_meta) {
121
  if (strpos($file_meta, '@')) {
122
+ @list($status_type, $file_path) = explode('@', $file_meta, 2);
123
+
124
+ if (!$file_path || !$status_type) {
125
+ continue;
126
+ }
127
+
128
+ $full_path = ABSPATH . '/' . $file_path;
129
+
130
+ if ($action === 'fixed' && (
131
+ $status_type === 'added'
132
+ || $status_type === 'removed'
133
+ || $status_type === 'modified'
134
+ )) {
135
+ $cache_key = md5($file_path);
136
+ $cache_value = array(
137
+ 'file_path' => $file_path,
138
+ 'file_status' => $status_type,
139
+ 'ignored_at' => time(),
140
+ );
141
+ $cached = $cache->add($cache_key, $cache_value);
142
+ $files_processed += ($cached ? 1 : 0);
143
+ $files_affected[] = $full_path;
144
+ continue;
145
+ }
146
+
147
+ if ($action === 'restore' && (
148
+ $status_type === 'removed'
149
+ || $status_type === 'modified'
150
+ )) {
151
+ if ($content = SucuriScanAPI::getOriginalCoreFile($file_path)) {
152
+ $basedir = dirname($full_path);
153
+ @mkdir($basedir, 0755, true);
154
+
155
+ if (file_exists($basedir)) {
156
+ $restored = @file_put_contents($full_path, $content);
157
+ $files_processed += ($restored ? 1 : 0);
158
  $files_affected[] = $full_path;
159
+ }
160
+ }
161
+ continue;
162
+ }
163
 
164
+ if ($action === 'delete' && $status_type === 'added') {
165
+ if (@unlink($full_path)) {
166
+ $files_processed += 1;
167
+ $files_affected[] = $full_path;
 
 
168
  }
169
+ continue;
170
  }
171
  }
172
  }
src/interface.lib.php CHANGED
@@ -37,14 +37,12 @@ class SucuriScanInterface
37
  */
38
  public static function initialize()
39
  {
40
- if (SucuriScan::supportReverseProxy()
41
- || SucuriScan::isBehindFirewall()
42
- ) {
43
  $_SERVER['SUCURIREAL_REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
44
  $_SERVER['REMOTE_ADDR'] = SucuriScan::getRemoteAddr();
45
  }
46
-
47
- SucuriScanEvent::scheduleTask(false);
48
  }
49
 
50
  /**
@@ -189,11 +187,30 @@ class SucuriScanInterface
189
  */
190
  public static function noticeAfterUpdate()
191
  {
 
 
 
192
  /* use simple comparison to force type cast. */
193
- if (SucuriScanOption::getOption(':plugin_version') == SUCURISCAN_VERSION) {
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  return;
195
  }
196
 
 
 
 
197
  /**
198
  * Suggest re-activation of the API communication.
199
  *
@@ -218,9 +235,6 @@ class SucuriScanInterface
218
  * @date Featured added at - May 01, 2017
219
  */
220
  self::info(__('NewsletterInvitation', SUCURISCAN_TEXTDOMAIN));
221
-
222
- /* update the version number in the plugin settings. */
223
- SucuriScanOption::updateOption(':plugin_version', SUCURISCAN_VERSION);
224
  }
225
 
226
  /**
37
  */
38
  public static function initialize()
39
  {
40
+ SucuriScanEvent::installScheduledTask();
41
+
42
+ if (SucuriScan::supportReverseProxy() || SucuriScan::isBehindFirewall()) {
43
  $_SERVER['SUCURIREAL_REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
44
  $_SERVER['REMOTE_ADDR'] = SucuriScan::getRemoteAddr();
45
  }
 
 
46
  }
47
 
48
  /**
187
  */
188
  public static function noticeAfterUpdate()
189
  {
190
+ /* get version of the plugin that was previously installed */
191
+ $version = SucuriScanOption::getOption(':plugin_version');
192
+
193
  /* use simple comparison to force type cast. */
194
+ if (headers_sent() || $version == SUCURISCAN_VERSION) {
195
+ return;
196
+ }
197
+
198
+ if (!is_writable(SucuriScanOption::optionsFilePath())) {
199
+ /**
200
+ * Stop if the settings file is not writable.
201
+ *
202
+ * In some cases where the settings file is not writable, or for
203
+ * some reason the option cannot be updated, the alerts below will
204
+ * be rendered all the time, to avoid unnecessary complains from
205
+ * the website owners we will not display the alerts if the option
206
+ * cannot be updated.
207
+ */
208
  return;
209
  }
210
 
211
+ /* update the version number in the plugin settings. */
212
+ SucuriScanOption::updateOption(':plugin_version', SUCURISCAN_VERSION);
213
+
214
  /**
215
  * Suggest re-activation of the API communication.
216
  *
235
  * @date Featured added at - May 01, 2017
236
  */
237
  self::info(__('NewsletterInvitation', SUCURISCAN_TEXTDOMAIN));
 
 
 
238
  }
239
 
240
  /**
src/lastlogins-loggedin.php CHANGED
@@ -45,7 +45,7 @@ function sucuriscan_loggedin_users_panel()
45
  'LoggedInUsers.UserEmail' => $logged_in_user['user_email'],
46
  'LoggedInUsers.LastActivity' => $logged_in_user['last_activity_datetime'],
47
  'LoggedInUsers.Registered' => $logged_in_user['user_registered_datetime'],
48
- 'LoggedInUsers.RemoveAddr' => $logged_in_user['remote_addr'],
49
  ));
50
  }
51
  }
45
  'LoggedInUsers.UserEmail' => $logged_in_user['user_email'],
46
  'LoggedInUsers.LastActivity' => $logged_in_user['last_activity_datetime'],
47
  'LoggedInUsers.Registered' => $logged_in_user['user_registered_datetime'],
48
+ 'LoggedInUsers.RemoteAddr' => $logged_in_user['remote_addr'],
49
  ));
50
  }
51
  }
src/lastlogins.php CHANGED
@@ -232,9 +232,7 @@ if (!function_exists('sucuri_set_lastlogin')) {
232
  */
233
  function sucuriscan_set_lastlogin($user_login = '')
234
  {
235
- $datastore_filepath = sucuriscan_lastlogins_datastore_is_writable();
236
-
237
- if ($datastore_filepath) {
238
  $current_user = get_user_by('login', $user_login);
239
  $remote_addr = SucuriScan::getRemoteAddr();
240
 
@@ -247,7 +245,7 @@ if (!function_exists('sucuri_set_lastlogin')) {
247
  );
248
 
249
  @file_put_contents(
250
- $datastore_filepath,
251
  json_encode($login_info) . "\n",
252
  FILE_APPEND
253
  );
232
  */
233
  function sucuriscan_set_lastlogin($user_login = '')
234
  {
235
+ if ($filename = sucuriscan_lastlogins_datastore_is_writable()) {
 
 
236
  $current_user = get_user_by('login', $user_login);
237
  $remote_addr = SucuriScan::getRemoteAddr();
238
 
245
  );
246
 
247
  @file_put_contents(
248
+ $filename,
249
  json_encode($login_info) . "\n",
250
  FILE_APPEND
251
  );
src/option.lib.php CHANGED
@@ -55,7 +55,6 @@ class SucuriScanOption extends SucuriScanRequest
55
  'sucuriscan_api_protocol' => 'https',
56
  'sucuriscan_api_service' => 'enabled',
57
  'sucuriscan_cloudproxy_apikey' => '',
58
- 'sucuriscan_comment_monitor' => 'disabled',
59
  'sucuriscan_diff_utility' => 'disabled',
60
  'sucuriscan_dns_lookups' => 'enabled',
61
  'sucuriscan_email_subject' => '',
@@ -93,10 +92,8 @@ class SucuriScanOption extends SucuriScanRequest
93
  'sucuriscan_notify_widget_deleted' => 'disabled',
94
  'sucuriscan_plugin_version' => '0.0',
95
  'sucuriscan_prettify_mails' => 'disabled',
96
- 'sucuriscan_request_timeout' => 15,
97
  'sucuriscan_revproxy' => 'disabled',
98
  'sucuriscan_runtime' => 0,
99
- 'sucuriscan_scan_frequency' => 'twicedaily',
100
  'sucuriscan_selfhosting_fpath' => '',
101
  'sucuriscan_selfhosting_monitor' => 'disabled',
102
  'sucuriscan_site_version' => '0.0',
55
  'sucuriscan_api_protocol' => 'https',
56
  'sucuriscan_api_service' => 'enabled',
57
  'sucuriscan_cloudproxy_apikey' => '',
 
58
  'sucuriscan_diff_utility' => 'disabled',
59
  'sucuriscan_dns_lookups' => 'enabled',
60
  'sucuriscan_email_subject' => '',
92
  'sucuriscan_notify_widget_deleted' => 'disabled',
93
  'sucuriscan_plugin_version' => '0.0',
94
  'sucuriscan_prettify_mails' => 'disabled',
 
95
  'sucuriscan_revproxy' => 'disabled',
96
  'sucuriscan_runtime' => 0,
 
97
  'sucuriscan_selfhosting_fpath' => '',
98
  'sucuriscan_selfhosting_monitor' => 'disabled',
99
  'sucuriscan_site_version' => '0.0',
src/pagehandler.php CHANGED
@@ -117,15 +117,13 @@ function sucuriscan_settings_page()
117
  $params['Settings.General.ApiKey'] = sucuriscan_settings_general_apikey($nonce);
118
  $params['Settings.General.DataStorage'] = sucuriscan_settings_general_datastorage();
119
  $params['Settings.General.SelfHosting'] = sucuriscan_settings_general_selfhosting($nonce);
120
- $params['Settings.General.Cronjobs'] = sucuriscan_settings_general_cronjobs();
121
  $params['Settings.General.ReverseProxy'] = sucuriscan_settings_general_reverseproxy($nonce);
122
  $params['Settings.General.IPDiscoverer'] = sucuriscan_settings_general_ipdiscoverer($nonce);
123
- $params['Settings.General.CommentMonitor'] = sucuriscan_settings_general_commentmonitor($nonce);
124
  $params['Settings.General.AuditLogStats'] = sucuriscan_settings_general_auditlogstats($nonce);
125
  $params['Settings.General.ImportExport'] = sucuriscan_settings_general_importexport($nonce);
126
 
127
  /* settings - scanner */
128
- $params['Settings.Scanner.Options'] = SucuriScanSettingsScanner::options();
129
  $params['Settings.Scanner.IntegrityDiffUtility'] = SucuriScanSettingsIntegrity::diffUtility($nonce);
130
  $params['Settings.Scanner.IntegrityLanguage'] = SucuriScanSettingsIntegrity::language($nonce);
131
  $params['Settings.Scanner.IntegrityCache'] = SucuriScanSettingsIntegrity::cache($nonce);
@@ -163,7 +161,6 @@ function sucuriscan_settings_page()
163
  /* settings - api service */
164
  $params['Settings.APIService.Status'] = sucuriscan_settings_apiservice_status($nonce);
165
  $params['Settings.APIService.Proxy'] = sucuriscan_settings_apiservice_proxy();
166
- $params['Settings.APIService.Timeout'] = sucuriscan_settings_apiservice_timeout($nonce);
167
 
168
  /* settings - website info */
169
  $params['Settings.Webinfo.Details'] = sucuriscan_settings_webinfo_details();
@@ -183,7 +180,7 @@ function sucuriscan_ajax()
183
  if (SucuriScanInterface::checkNonce()) {
184
  SucuriScanAuditLogs::ajaxAuditLogs();
185
  SucuriScanAuditLogs::ajaxAuditLogsReport();
186
- SucuriScanAuditLogs::ajaxAuditLogsResetCache();
187
  SucuriScanSiteCheck::ajaxMalwareScan();
188
  SucuriScanFirewall::auditlogsAjax();
189
  SucuriScanIntegrity::ajaxIntegrity();
117
  $params['Settings.General.ApiKey'] = sucuriscan_settings_general_apikey($nonce);
118
  $params['Settings.General.DataStorage'] = sucuriscan_settings_general_datastorage();
119
  $params['Settings.General.SelfHosting'] = sucuriscan_settings_general_selfhosting($nonce);
 
120
  $params['Settings.General.ReverseProxy'] = sucuriscan_settings_general_reverseproxy($nonce);
121
  $params['Settings.General.IPDiscoverer'] = sucuriscan_settings_general_ipdiscoverer($nonce);
 
122
  $params['Settings.General.AuditLogStats'] = sucuriscan_settings_general_auditlogstats($nonce);
123
  $params['Settings.General.ImportExport'] = sucuriscan_settings_general_importexport($nonce);
124
 
125
  /* settings - scanner */
126
+ $params['Settings.Scanner.Cronjobs'] = SucuriScanSettingsScanner::cronjobs();
127
  $params['Settings.Scanner.IntegrityDiffUtility'] = SucuriScanSettingsIntegrity::diffUtility($nonce);
128
  $params['Settings.Scanner.IntegrityLanguage'] = SucuriScanSettingsIntegrity::language($nonce);
129
  $params['Settings.Scanner.IntegrityCache'] = SucuriScanSettingsIntegrity::cache($nonce);
161
  /* settings - api service */
162
  $params['Settings.APIService.Status'] = sucuriscan_settings_apiservice_status($nonce);
163
  $params['Settings.APIService.Proxy'] = sucuriscan_settings_apiservice_proxy();
 
164
 
165
  /* settings - website info */
166
  $params['Settings.Webinfo.Details'] = sucuriscan_settings_webinfo_details();
180
  if (SucuriScanInterface::checkNonce()) {
181
  SucuriScanAuditLogs::ajaxAuditLogs();
182
  SucuriScanAuditLogs::ajaxAuditLogsReport();
183
+ SucuriScanAuditLogs::ajaxAuditLogsSendLogs();
184
  SucuriScanSiteCheck::ajaxMalwareScan();
185
  SucuriScanFirewall::auditlogsAjax();
186
  SucuriScanIntegrity::ajaxIntegrity();
src/settings-apiservice.php CHANGED
@@ -61,40 +61,6 @@ function sucuriscan_settings_apiservice_status($nonce)
61
  return SucuriScanTemplate::getSection('settings-apiservice-status', $params);
62
  }
63
 
64
- /**
65
- * Returns the HTML to configure the API service timeout.
66
- *
67
- * @param bool $nonce True if the CSRF protection worked, false otherwise.
68
- * @return string HTML for the API service timeout option.
69
- */
70
- function sucuriscan_settings_apiservice_timeout($nonce)
71
- {
72
- $params = array();
73
-
74
- // Update the API request timeout.
75
- if ($nonce) {
76
- $timeout = (int) SucuriScanRequest::post(':request_timeout', '[0-9]+');
77
-
78
- if ($timeout > 0) {
79
- if ($timeout <= SUCURISCAN_MAX_REQUEST_TIMEOUT) {
80
- $message = 'API request timeout set to <code>' . $timeout . '</code> seconds.';
81
-
82
- SucuriScanOption::updateOption(':request_timeout', $timeout);
83
- SucuriScanEvent::reportInfoEvent($message);
84
- SucuriScanEvent::notifyEvent('plugin_change', $message);
85
- SucuriScanInterface::info(__('HTTPTimeoutChange', SUCURISCAN_TEXTDOMAIN));
86
- } else {
87
- SucuriScanInterface::error(__('HTTPTimeoutFailure', SUCURISCAN_TEXTDOMAIN));
88
- }
89
- }
90
- }
91
-
92
- $params['MaxRequestTimeout'] = SUCURISCAN_MAX_REQUEST_TIMEOUT;
93
- $params['RequestTimeout'] = SucuriScanOption::getOption(':request_timeout') . ' seconds';
94
-
95
- return SucuriScanTemplate::getSection('settings-apiservice-timeout', $params);
96
- }
97
-
98
  /**
99
  * Returns the HTML to configure the API service proxy.
100
  *
61
  return SucuriScanTemplate::getSection('settings-apiservice-status', $params);
62
  }
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  /**
65
  * Returns the HTML to configure the API service proxy.
66
  *
src/settings-general.php CHANGED
@@ -83,7 +83,7 @@ function sucuriscan_settings_general_apikey($nonce)
83
  // Save API key after it was recovered by the administrator.
84
  if ($api_key = SucuriScanRequest::post(':manual_api_key')) {
85
  SucuriScanAPI::setPluginKey($api_key, true);
86
- SucuriScanEvent::scheduleTask();
87
  SucuriScanEvent::reportInfoEvent('Sucuri API key was added manually.');
88
  }
89
 
@@ -338,124 +338,6 @@ function sucuriscan_settings_general_selfhosting($nonce)
338
  return SucuriScanTemplate::getSection('settings-general-selfhosting', $params);
339
  }
340
 
341
- /**
342
- * Renders a page with information about the cronjobs feature.
343
- *
344
- * @param bool $nonce True if the CSRF protection worked.
345
- * @return string Page with information about the cronjobs.
346
- */
347
- function sucuriscan_settings_general_cronjobs()
348
- {
349
- $params = array(
350
- 'Cronjobs.List' => '',
351
- 'Cronjobs.Total' => 0,
352
- 'Cronjob.Schedules' => '',
353
- );
354
-
355
- $schedule_allowed = SucuriScanEvent::availableSchedules();
356
-
357
- if (SucuriScanInterface::checkNonce()) {
358
- // Modify the scheduled tasks (run now, remove, re-schedule).
359
- $available = ($schedule_allowed === null)
360
- ? SucuriScanEvent::availableSchedules()
361
- : $schedule_allowed;
362
- $allowed_actions = array_keys($available);
363
- $allowed_actions[] = 'runnow';
364
- $allowed_actions[] = 'remove';
365
- $allowed_actions = sprintf('(%s)', implode('|', $allowed_actions));
366
-
367
- if ($cronjob_action = SucuriScanRequest::post(':cronjob_action', $allowed_actions)) {
368
- $cronjobs = SucuriScanRequest::post(':cronjobs', '_array');
369
-
370
- if (!empty($cronjobs)) {
371
- $total_tasks = count($cronjobs);
372
-
373
- if ($cronjob_action == 'runnow') {
374
- /* Force execution of the selected scheduled tasks. */
375
- SucuriScanInterface::info(sprintf(
376
- __('CronjobsWillRunSoon', SUCURISCAN_TEXTDOMAIN),
377
- $total_tasks /* some cronjobs will be ignored */
378
- ));
379
- SucuriScanEvent::reportNoticeEvent(sprintf(
380
- 'Force execution of scheduled tasks: (multiple entries): %s',
381
- @implode(',', $cronjobs)
382
- ));
383
-
384
- foreach ($cronjobs as $task_name) {
385
- wp_schedule_single_event(time() + 10, $task_name);
386
- }
387
- } elseif ($cronjob_action == 'remove' || $cronjob_action == '_oneoff') {
388
- /* Force deletion of the selected scheduled tasks. */
389
- SucuriScanInterface::info(sprintf(
390
- __('CronjobsWereDeleted', SUCURISCAN_TEXTDOMAIN),
391
- $total_tasks /* some cronjobs will be ignored */
392
- ));
393
- SucuriScanEvent::reportNoticeEvent(sprintf(
394
- 'Delete scheduled tasks: (multiple entries): %s',
395
- @implode(',', $cronjobs)
396
- ));
397
-
398
- foreach ($cronjobs as $task_name) {
399
- wp_clear_scheduled_hook($task_name);
400
- }
401
- } else {
402
- SucuriScanInterface::info(sprintf(
403
- __('CronjobsWereReinstalled', SUCURISCAN_TEXTDOMAIN),
404
- $total_tasks, /* some cronjobs will be ignored */
405
- $cronjob_action /* frequency to run cronjob */
406
- ));
407
- SucuriScanEvent::reportNoticeEvent(sprintf(
408
- 'Re-configure scheduled tasks %s: (multiple entries): %s',
409
- $cronjob_action,
410
- @implode(',', $cronjobs)
411
- ));
412
-
413
- foreach ($cronjobs as $task_name) {
414
- $next_due = wp_next_scheduled($task_name);
415
- wp_schedule_event($next_due, $cronjob_action, $task_name);
416
- }
417
- }
418
- } else {
419
- SucuriScanInterface::error(__('CronjobsWereNotSelected', SUCURISCAN_TEXTDOMAIN));
420
- }
421
- }
422
- }
423
-
424
- $cronjobs = _get_cron_array();
425
- $available = ($schedule_allowed === null)
426
- ? SucuriScanEvent::availableSchedules()
427
- : $schedule_allowed;
428
-
429
- /* Hardcode the first one to allow the immediate execution of the cronjob(s) */
430
- $params['Cronjob.Schedules'] .= '<option value="runnow">'
431
- . __('CronjobRunNow', SUCURISCAN_TEXTDOMAIN) . '</option>';
432
-
433
- foreach ($available as $freq => $name) {
434
- $params['Cronjob.Schedules'] .= sprintf('<option value="%s">%s</option>', $freq, $name);
435
- }
436
-
437
- foreach ($cronjobs as $timestamp => $cronhooks) {
438
- foreach ((array) $cronhooks as $hook => $events) {
439
- foreach ((array) $events as $key => $event) {
440
- if (empty($event['args'])) {
441
- $event['args'] = array('[]');
442
- }
443
-
444
- $params['Cronjobs.Total'] += 1;
445
- $params['Cronjobs.List'] .=
446
- SucuriScanTemplate::getSnippet('settings-general-cronjobs', array(
447
- 'Cronjob.Hook' => $hook,
448
- 'Cronjob.Schedule' => $event['schedule'],
449
- 'Cronjob.NextTime' => SucuriScan::datetime($timestamp),
450
- 'Cronjob.Arguments' => SucuriScan::implode(', ', $event['args']),
451
- ));
452
- }
453
- }
454
- }
455
-
456
- return SucuriScanTemplate::getSection('settings-general-cronjobs', $params);
457
- }
458
-
459
  /**
460
  * Renders a page with information about the reverse proxy feature.
461
  *
@@ -576,44 +458,6 @@ function sucuriscan_settings_general_ipdiscoverer($nonce)
576
  return SucuriScanTemplate::getSection('settings-general-ipdiscoverer', $params);
577
  }
578
 
579
- /**
580
- * Renders a page with information about the comment monitor feature.
581
- *
582
- * @param bool $nonce True if the CSRF protection worked.
583
- * @return string Page with information about the comment monitor.
584
- */
585
- function sucuriscan_settings_general_commentmonitor($nonce)
586
- {
587
- $params = array(
588
- 'CommentMonitorStatus' => __('Enabled', SUCURISCAN_TEXTDOMAIN),
589
- 'CommentMonitorSwitchText' => __('Disable', SUCURISCAN_TEXTDOMAIN),
590
- 'CommentMonitorSwitchValue' => 'disable',
591
- );
592
-
593
- // Configure the comment monitor option.
594
- if ($nonce) {
595
- $monitor = SucuriScanRequest::post(':comment_monitor', '(en|dis)able');
596
-
597
- if ($monitor) {
598
- $action_d = $monitor . 'd';
599
- $message = 'Comment monitor was <code>' . $action_d . '</code>';
600
-
601
- SucuriScanOption::updateOption(':comment_monitor', $action_d);
602
- SucuriScanEvent::reportInfoEvent($message);
603
- SucuriScanEvent::notifyEvent('plugin_change', $message);
604
- SucuriScanInterface::info(__('CommentMonitorStatus', SUCURISCAN_TEXTDOMAIN));
605
- }
606
- }
607
-
608
- if (SucuriScanOption::isDisabled(':comment_monitor')) {
609
- $params['CommentMonitorStatus'] = __('Disabled', SUCURISCAN_TEXTDOMAIN);
610
- $params['CommentMonitorSwitchText'] = __('Enable', SUCURISCAN_TEXTDOMAIN);
611
- $params['CommentMonitorSwitchValue'] = 'enable';
612
- }
613
-
614
- return SucuriScanTemplate::getSection('settings-general-commentmonitor', $params);
615
- }
616
-
617
  /**
618
  * Renders a page with information about the auditlog stats feature.
619
  *
@@ -658,7 +502,6 @@ function sucuriscan_settings_general_importexport($nonce)
658
  ':api_protocol',
659
  ':api_service',
660
  ':cloudproxy_apikey',
661
- ':comment_monitor',
662
  ':diff_utility',
663
  ':dns_lookups',
664
  ':email_subject',
@@ -692,9 +535,7 @@ function sucuriscan_settings_general_importexport($nonce)
692
  ':notify_widget_added',
693
  ':notify_widget_deleted',
694
  ':prettify_mails',
695
- ':request_timeout',
696
  ':revproxy',
697
- ':scan_frequency',
698
  ':selfhosting_fpath',
699
  ':selfhosting_monitor',
700
  ':use_wpmail',
83
  // Save API key after it was recovered by the administrator.
84
  if ($api_key = SucuriScanRequest::post(':manual_api_key')) {
85
  SucuriScanAPI::setPluginKey($api_key, true);
86
+ SucuriScanEvent::installScheduledTask();
87
  SucuriScanEvent::reportInfoEvent('Sucuri API key was added manually.');
88
  }
89
 
338
  return SucuriScanTemplate::getSection('settings-general-selfhosting', $params);
339
  }
340
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
  /**
342
  * Renders a page with information about the reverse proxy feature.
343
  *
458
  return SucuriScanTemplate::getSection('settings-general-ipdiscoverer', $params);
459
  }
460
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
  /**
462
  * Renders a page with information about the auditlog stats feature.
463
  *
502
  ':api_protocol',
503
  ':api_service',
504
  ':cloudproxy_apikey',
 
505
  ':diff_utility',
506
  ':dns_lookups',
507
  ':email_subject',
535
  ':notify_widget_added',
536
  ':notify_widget_deleted',
537
  ':prettify_mails',
 
538
  ':revproxy',
 
539
  ':selfhosting_fpath',
540
  ':selfhosting_monitor',
541
  ':use_wpmail',
src/settings-scanner.php CHANGED
@@ -22,61 +22,125 @@ if (!defined('SUCURISCAN_INIT') || SUCURISCAN_INIT !== true) {
22
  class SucuriScanSettingsScanner extends SucuriScanSettings
23
  {
24
  /**
25
- * Returns the HTML for the project scanner options.
26
  *
27
- * This method renders a section in the settings page that allows the website
28
- * owner to configure the functionality of the main file system scanner. This
29
- * includes the frequency in which such scanner will run and information about
30
- * the availability of the required dependencies.
31
- *
32
- * @return string HTML for the project scanner options.
33
  */
34
- public static function options()
35
  {
36
- $params = array();
 
 
 
 
37
 
38
  $schedule_allowed = SucuriScanEvent::availableSchedules();
39
 
40
  if (SucuriScanInterface::checkNonce()) {
41
- // Modify the schedule of the filesystem scanner.
42
- if ($frequency = SucuriScanRequest::post(':scan_frequency')) {
43
- if (array_key_exists($frequency, $schedule_allowed)) {
44
- SucuriScanOption::updateOption(':scan_frequency', $frequency);
 
 
 
 
45
 
46
- // Remove all the scheduled tasks associated to the plugin.
47
- wp_clear_scheduled_hook('sucuriscan_scheduled_scan');
48
 
49
- // Install new cronjob unless the user has selected "Never".
50
- if ($frequency !== '_oneoff') {
51
- wp_schedule_event(time() + 10, $frequency, 'sucuriscan_scheduled_scan');
52
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- $frequency_title = strtolower($schedule_allowed[ $frequency ]);
55
- $message = 'Scanner frequency set to <code>' . $frequency_title . '</code>';
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
- SucuriScanEvent::reportInfoEvent($message);
58
- SucuriScanEvent::notifyEvent('plugin_change', $message);
59
- SucuriScanInterface::info(__('ScannerFreqStatus', SUCURISCAN_TEXTDOMAIN));
 
 
 
 
60
  }
61
  }
62
  }
63
 
64
- $scan_freq = SucuriScanOption::getOption(':scan_frequency');
 
 
 
65
 
66
- // Generate the HTML code for the option list in the form select fields.
67
- $freq_options = SucuriScanTemplate::selectOptions($schedule_allowed, $scan_freq);
 
68
 
69
- $params['ScanningFrequency'] = __('Undefined', SUCURISCAN_TEXTDOMAIN);
70
- $params['ScanningFrequencyOptions'] = $freq_options;
 
71
 
72
- $hasSPL = SucuriScanFileInfo::isSplAvailable();
73
- $params['NoSPL.Visibility'] = SucuriScanTemplate::visibility(!$hasSPL);
 
 
 
 
74
 
75
- if (array_key_exists($scan_freq, $schedule_allowed)) {
76
- $params['ScanningFrequency'] = $schedule_allowed[ $scan_freq ];
 
 
 
 
 
 
 
 
 
77
  }
78
 
79
- return SucuriScanTemplate::getSection('settings-scanner-options', $params);
 
 
 
80
  }
81
 
82
  /**
22
  class SucuriScanSettingsScanner extends SucuriScanSettings
23
  {
24
  /**
25
+ * Renders a page with information about the cronjobs feature.
26
  *
27
+ * @param bool $nonce True if the CSRF protection worked.
28
+ * @return string Page with information about the cronjobs.
 
 
 
 
29
  */
30
+ public static function cronjobs()
31
  {
32
+ $params = array(
33
+ 'Cronjobs.List' => '',
34
+ 'Cronjobs.Total' => 0,
35
+ 'Cronjob.Schedules' => '',
36
+ );
37
 
38
  $schedule_allowed = SucuriScanEvent::availableSchedules();
39
 
40
  if (SucuriScanInterface::checkNonce()) {
41
+ // Modify the scheduled tasks (run now, remove, re-schedule).
42
+ $available = ($schedule_allowed === null)
43
+ ? SucuriScanEvent::availableSchedules()
44
+ : $schedule_allowed;
45
+ $allowed_actions = array_keys($available);
46
+ $allowed_actions[] = 'runnow'; /* execute in the next 10 seconds */
47
+ $allowed_actions[] = 'remove'; /* can be reinstalled automatically */
48
+ $allowed_actions = sprintf('(%s)', implode('|', $allowed_actions));
49
 
50
+ if ($cronjob_action = SucuriScanRequest::post(':cronjob_action', $allowed_actions)) {
51
+ $cronjobs = SucuriScanRequest::post(':cronjobs', '_array');
52
 
53
+ if (!empty($cronjobs)) {
54
+ $total_tasks = count($cronjobs);
55
+
56
+ if ($cronjob_action == 'runnow') {
57
+ /* Force execution of the selected scheduled tasks. */
58
+ SucuriScanInterface::info(sprintf(
59
+ __('CronjobsWillRunSoon', SUCURISCAN_TEXTDOMAIN),
60
+ $total_tasks /* some cronjobs will be ignored */
61
+ ));
62
+ SucuriScanEvent::reportNoticeEvent(sprintf(
63
+ 'Force execution of scheduled tasks: (multiple entries): %s',
64
+ @implode(',', $cronjobs)
65
+ ));
66
+
67
+ foreach ($cronjobs as $task_name) {
68
+ wp_schedule_single_event(time() + 10, $task_name);
69
+ }
70
+ } elseif ($cronjob_action == 'remove' || $cronjob_action == '_oneoff') {
71
+ /* Force deletion of the selected scheduled tasks. */
72
+ SucuriScanInterface::info(sprintf(
73
+ __('CronjobsWereDeleted', SUCURISCAN_TEXTDOMAIN),
74
+ $total_tasks /* some cronjobs will be ignored */
75
+ ));
76
+ SucuriScanEvent::reportNoticeEvent(sprintf(
77
+ 'Delete scheduled tasks: (multiple entries): %s',
78
+ @implode(',', $cronjobs)
79
+ ));
80
 
81
+ foreach ($cronjobs as $task_name) {
82
+ wp_clear_scheduled_hook($task_name);
83
+ }
84
+ } else {
85
+ SucuriScanInterface::info(sprintf(
86
+ __('CronjobsWereReinstalled', SUCURISCAN_TEXTDOMAIN),
87
+ $total_tasks, /* some cronjobs will be ignored */
88
+ $cronjob_action /* frequency to run cronjob */
89
+ ));
90
+ SucuriScanEvent::reportNoticeEvent(sprintf(
91
+ 'Re-configure scheduled tasks %s: (multiple entries): %s',
92
+ $cronjob_action,
93
+ @implode(',', $cronjobs)
94
+ ));
95
 
96
+ foreach ($cronjobs as $task_name) {
97
+ $next_due = wp_next_scheduled($task_name);
98
+ wp_schedule_event($next_due, $cronjob_action, $task_name);
99
+ }
100
+ }
101
+ } else {
102
+ SucuriScanInterface::error(__('CronjobsWereNotSelected', SUCURISCAN_TEXTDOMAIN));
103
  }
104
  }
105
  }
106
 
107
+ $cronjobs = _get_cron_array();
108
+ $available = ($schedule_allowed === null)
109
+ ? SucuriScanEvent::availableSchedules()
110
+ : $schedule_allowed;
111
 
112
+ /* Hardcode the first one to allow the immediate execution of the cronjob(s) */
113
+ $params['Cronjob.Schedules'] .= '<option value="runnow">'
114
+ . __('CronjobRunNow', SUCURISCAN_TEXTDOMAIN) . '</option>';
115
 
116
+ foreach ($available as $freq => $name) {
117
+ $params['Cronjob.Schedules'] .= sprintf('<option value="%s">%s</option>', $freq, $name);
118
+ }
119
 
120
+ foreach ($cronjobs as $timestamp => $cronhooks) {
121
+ foreach ((array) $cronhooks as $hook => $events) {
122
+ foreach ((array) $events as $key => $event) {
123
+ if (empty($event['args'])) {
124
+ $event['args'] = array('[]');
125
+ }
126
 
127
+ $params['Cronjobs.Total'] += 1;
128
+ $params['Cronjobs.List'] .=
129
+ SucuriScanTemplate::getSnippet('settings-scanner-cronjobs', array(
130
+ 'Cronjob.Hook' => $hook,
131
+ 'Cronjob.Schedule' => $event['schedule'],
132
+ 'Cronjob.NextTime' => SucuriScan::datetime($timestamp),
133
+ 'Cronjob.NextTimeHuman' => SucuriScan::humanTime($timestamp),
134
+ 'Cronjob.Arguments' => SucuriScan::implode(', ', $event['args']),
135
+ ));
136
+ }
137
+ }
138
  }
139
 
140
+ $hasSPL = SucuriScanFileInfo::isSplAvailable();
141
+ $params['NoSPL.Visibility'] = SucuriScanTemplate::visibility(!$hasSPL);
142
+
143
+ return SucuriScanTemplate::getSection('settings-scanner-cronjobs', $params);
144
  }
145
 
146
  /**
src/sitecheck.lib.php CHANGED
@@ -32,8 +32,6 @@ if (!defined('SUCURISCAN_INIT') || SUCURISCAN_INIT !== true) {
32
  */
33
  class SucuriScanSiteCheck extends SucuriScanAPI
34
  {
35
- private static $scanRequests;
36
-
37
  /**
38
  * Executes a malware scan against the specified website.
39
  *
@@ -92,32 +90,25 @@ class SucuriScanSiteCheck extends SucuriScanAPI
92
  $cache = new SucuriScanCache('sitecheck');
93
  $results = $cache->get('scan_results', SUCURISCAN_SITECHECK_LIFETIME, 'array');
94
 
95
- /* return cached malware scan results. */
96
- if ($results && !empty($results)) {
97
- return $results;
98
- }
99
-
100
  /**
101
- * Do not overflow the API service.
102
  *
103
- * If we are scanning the website for the first time or after the cache
104
- * has expired, it makes no sense to let the script connect to the API
105
- * more than once. If there is a failure with the first request we can
106
- * assume that the same error will appear in the subsequent request, so
107
- * we will return false every time a secondary scan is requested.
108
  *
109
- * The first scan is requested and executed normally.
110
- *
111
- * The second and subsequent scan requests (from the other methods) are
112
- * expected to return the data from the cache, hence the position of the
113
- * conditional in this specific line, right after the cache lifetime is
114
- * checked. If the cache is invalid (because the first scan failed) or
115
- * if the cache has expired (and the new request fails) we will assume
116
- * that the other requests (around ten or so) will fail too.
117
  */
118
- self::$scanRequests++;
119
- if (self::$scanRequests > 1) {
120
- return false;
 
 
 
 
 
121
  }
122
 
123
  /* send HTTP request to SiteCheck's API service. */
@@ -545,19 +536,17 @@ class SucuriScanSiteCheck extends SucuriScanAPI
545
  * with a missing line in the coverage. Since the test case takes care of
546
  * the functionality of this code we will assume that it is fully covered.
547
  */
548
- function ajaxMalwareScan()
549
  {
550
  if (SucuriScanRequest::post('form_action') !== 'malware_scan') {
551
  return;
552
  }
553
 
554
- $response = array();
555
-
556
  ob_start();
557
- $result = SucuriScanSiteCheck::malware();
558
- $errors = ob_get_clean(); /* capture possible errors */
559
- $response['malware'] = empty($errors) ? $result : '';
560
 
 
 
 
561
  $response['blacklist'] = SucuriScanSiteCheck::blacklist();
562
  $response['recommendations'] = SucuriScanSiteCheck::recommendations();
563
 
@@ -574,6 +563,14 @@ class SucuriScanSiteCheck extends SucuriScanAPI
574
  'content' => SucuriScanSiteCheck::scriptsContent(),
575
  );
576
 
 
 
 
 
 
 
 
 
577
  wp_send_json($response, 200);
578
  }
579
  }
32
  */
33
  class SucuriScanSiteCheck extends SucuriScanAPI
34
  {
 
 
35
  /**
36
  * Executes a malware scan against the specified website.
37
  *
90
  $cache = new SucuriScanCache('sitecheck');
91
  $results = $cache->get('scan_results', SUCURISCAN_SITECHECK_LIFETIME, 'array');
92
 
 
 
 
 
 
93
  /**
94
+ * Allow the user to scan foreign domains.
95
  *
96
+ * This condition allows for the execution of the malware scanner on a
97
+ * website different than the one where the plugin is installed. This is
98
+ * basically the same as scanning any domain on the SiteCheck website.
99
+ * In this case, this is mostly used to allow the development execute
100
+ * tests and to troubleshoot issues reported by other users.
101
  *
102
+ * @var boolean
 
 
 
 
 
 
 
103
  */
104
+ if ($custom = SucuriScanRequest::get('s')) {
105
+ $tld = SucuriScan::escape($custom);
106
+ $results = false /* invalid cache */;
107
+ }
108
+
109
+ /* return cached malware scan results. */
110
+ if ($results && !empty($results)) {
111
+ return $results;
112
  }
113
 
114
  /* send HTTP request to SiteCheck's API service. */
536
  * with a missing line in the coverage. Since the test case takes care of
537
  * the functionality of this code we will assume that it is fully covered.
538
  */
539
+ public static function ajaxMalwareScan()
540
  {
541
  if (SucuriScanRequest::post('form_action') !== 'malware_scan') {
542
  return;
543
  }
544
 
 
 
545
  ob_start();
 
 
 
546
 
547
+ $response = array();
548
+
549
+ $response['malware'] = SucuriScanSiteCheck::malware();
550
  $response['blacklist'] = SucuriScanSiteCheck::blacklist();
551
  $response['recommendations'] = SucuriScanSiteCheck::recommendations();
552
 
563
  'content' => SucuriScanSiteCheck::scriptsContent(),
564
  );
565
 
566
+ $errors = ob_get_clean(); /* capture possible errors */
567
+
568
+ if (!empty($errors)) {
569
+ $response['malware'] = '';
570
+ $response['blacklist'] = '';
571
+ $response['recommendations'] = '';
572
+ }
573
+
574
  wp_send_json($response, 200);
575
  }
576
  }
src/sucuriscan.lib.php CHANGED
@@ -152,6 +152,61 @@ class SucuriScan
152
  return $result;
153
  }
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  /**
156
  * Check if the admin init hook must not be intercepted.
157
  *
@@ -338,6 +393,7 @@ class SucuriScan
338
  SucuriScanEvent::reportSiteVersion();
339
  SucuriScanIntegrity::getIntegrityStatus(true);
340
  SucuriScanSettingsPosthack::availableUpdatesContent(true);
 
341
  }
342
 
343
  /**
152
  return $result;
153
  }
154
 
155
+ /**
156
+ * Returns the human version of the time difference.
157
+ *
158
+ * If the timestamp is in the past in comparison with the current time, it
159
+ * will return a string in the form of "X time ago". If the timestamp is in
160
+ * the future in comparison with the current time, it will return a string
161
+ * in the form of "in X time". If the timestamp is the same as the current
162
+ * time it will return "right now".
163
+ *
164
+ * @param integer $time Unix timestamp.
165
+ * @return string Different between timestamp and current time.
166
+ */
167
+ public static function humanTime($time = 0)
168
+ {
169
+ $now = time();
170
+
171
+ if ($time === $now) {
172
+ return 'right now';
173
+ }
174
+
175
+ $result = '';
176
+ $template = '';
177
+ $diff = $now - $time;
178
+ $groups = array(
179
+ 31536000 => 'year',
180
+ 2592000 => 'month',
181
+ 86400 => 'day',
182
+ 3600 => 'hour',
183
+ 60 => 'minute',
184
+ 1 => 'second',
185
+ );
186
+
187
+ if ($time < $now) {
188
+ $template = '%d %s ago';
189
+ } else {
190
+ $template = 'in %d %s';
191
+ }
192
+
193
+ foreach ($groups as $secs => $label) {
194
+ $distance = abs($diff / $secs);
195
+
196
+ if ($distance >= 1) {
197
+ $plural = (round($distance) == 1) ? '' : 's';
198
+ $result = sprintf(
199
+ $template,
200
+ round($distance),
201
+ $label . $plural
202
+ );
203
+ break;
204
+ }
205
+ }
206
+
207
+ return $result;
208
+ }
209
+
210
  /**
211
  * Check if the admin init hook must not be intercepted.
212
  *
393
  SucuriScanEvent::reportSiteVersion();
394
  SucuriScanIntegrity::getIntegrityStatus(true);
395
  SucuriScanSettingsPosthack::availableUpdatesContent(true);
396
+ SucuriScanEvent::sendLogsFromQueue(); /* blocking; keep at the end */
397
  }
398
 
399
  /**
sucuri.php CHANGED
@@ -7,7 +7,7 @@
7
  * Author URI: https://sucuri.net/
8
  * Text Domain: sucuri-scanner
9
  * Author: Sucuri Inc.
10
- * Version: 1.8.6
11
  */
12
 
13
 
@@ -83,7 +83,7 @@ define('SUCURISCAN', 'sucuriscan');
83
  /**
84
  * Current version of the plugin's code.
85
  */
86
- define('SUCURISCAN_VERSION', '1.8.6');
87
 
88
  /**
89
  * The name of the Sucuri plugin main file.
@@ -166,7 +166,7 @@ define('SUCURISCAN_AUDITLOGS_PER_PAGE', 50);
166
  /**
167
  * The maximum quantity of buttons in the paginations.
168
  */
169
- define('SUCURISCAN_MAX_PAGINATION_BUTTONS', 20);
170
 
171
  /**
172
  * Frequency of the file system scans in seconds.
@@ -186,7 +186,7 @@ define('SUCURISCAN_GET_PLUGINS_LIFETIME', 1800);
186
  /**
187
  * The maximum execution time of a HTTP request before timeout.
188
  */
189
- define('SUCURISCAN_MAX_REQUEST_TIMEOUT', 60);
190
 
191
  /**
192
  * Sets the text that will preceed the admin notices.
7
  * Author URI: https://sucuri.net/
8
  * Text Domain: sucuri-scanner
9
  * Author: Sucuri Inc.
10
+ * Version: 1.8.7
11
  */
12
 
13
 
83
  /**
84
  * Current version of the plugin's code.
85
  */
86
+ define('SUCURISCAN_VERSION', '1.8.7');
87
 
88
  /**
89
  * The name of the Sucuri plugin main file.
166
  /**
167
  * The maximum quantity of buttons in the paginations.
168
  */
169
+ define('SUCURISCAN_MAX_PAGINATION_BUTTONS', 16);
170
 
171
  /**
172
  * Frequency of the file system scans in seconds.
186
  /**
187
  * The maximum execution time of a HTTP request before timeout.
188
  */
189
+ define('SUCURISCAN_MAX_REQUEST_TIMEOUT', 5);
190
 
191
  /**
192
  * Sets the text that will preceed the admin notices.