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
  */