From d1b817443c5b34e4b564e31e9dc02e54d8db6188 Mon Sep 17 00:00:00 2001 From: Richard Underwood <richard.underwood@digitaslbi.com> Date: Wed, 24 Aug 2016 11:32:43 +0100 Subject: [PATCH 1/8] Initial implementation of log rotation. --- includes/config.inc.php-dist | 5 +++++ includes/misc.inc.php | 31 +++++++++++++++++++++++++++++++ index.php | 29 +++++++++++++++++++++++++++++ logs.php | 13 ++++++++++++- 4 files changed, 77 insertions(+), 1 deletion(-) diff --git a/includes/config.inc.php-dist b/includes/config.inc.php-dist index 51f0bdd..bc00616 100644 --- a/includes/config.inc.php-dist +++ b/includes/config.inc.php-dist @@ -7,7 +7,12 @@ $apiproto = 'http'; # http | https $apisslverify = FALSE; # Verify SSL Certificate if using https for apiproto $allowzoneadd = FALSE; # Allow normal users to add zones $logging = TRUE; +$allowclearlogs = TRUE; # Allow clearing of log entries +$allowrotatelogs = FALSE;# Allow rotation to text file on server +# Log directory - if allowrotatelogs is set, this is where the logs will +# be written. It must be writeable by the web server user. +$logsdirectory = "../etc"; # If you configure this, nsedit will try to authenticate via WeFact too. # Debtors will be added to the sqlitedatabase with their crypted password. diff --git a/includes/misc.inc.php b/includes/misc.inc.php index 7041d0f..4c88c01 100644 --- a/includes/misc.inc.php +++ b/includes/misc.inc.php @@ -277,6 +277,37 @@ function clearlogs() { writelog("Logtable truncated."); } +function rotatelogs() { + global $logging, $logsdirectory; + if ($logging !== TRUE) + return FALSE; + + if(!is_dir($logsdirectory) || !is_writable($logsdirectory)) { + writelog("Logs directory cannot be written to."); + return FALSE; + } + + date_default_timezone_set('UTC'); + $filename = date("Y-m-d-His") . ".json"; + $file = fopen($logsdirectory . "/" . $filename, "x"); + + if($file === FALSE) { + writelog("Can't create file for log rotation."); + return FALSE; + } + + if(fwrite($file,json_encode(getlogs())) === FALSE) { + writelog("Can't write to file for log rotation."); + fclose($file); + return FALSE; + } else { + fclose($file); + clearlogs(); + return $filename; + } + +} + function writelog($line) { global $logging; if ($logging !== TRUE) diff --git a/index.php b/index.php index 78d1ee9..eb4cfbd 100644 --- a/index.php +++ b/index.php @@ -116,6 +116,9 @@ if ($blocklogin === TRUE) { <div id="clearlogs" style="display: none;"> Are you sure you want to clear all the logs? Maybe save them first? </div> + <div id="rotatelogs" style="display: none;"> + Are you sure you want to rotate the logs? + </div> <div id="searchlogs" style="display: none; text-align: right;"> <table border="0"> <tr><td>User:</td><td><input type="text" id ="searchlogs-user"><br></td></tr> @@ -1043,6 +1046,31 @@ $(document).ready(function () { }); } }, + <?php if($allowrotatelogs === TRUE) { ?> + { + icon: 'img/export.png', + text: 'Rotate logs', + click: function() { + $("#rotatelogs").dialog({ + modal: true, + title: "Rotate logs", + width: 'auto', + buttons: { + Ok: function() { + $.get("logs.php?action=rotate"); + $( this ).dialog( "close" ); + $('#Logs').jtable('load'); + }, + Cancel: function() { + $( this ).dialog( "close" ); + return false; + } + } + }); + } + }, + <?php } ?> + <?php if($allowclearlogs === TRUE) { ?> { icon: 'img/delete_inverted.png', text: 'Clear logs', @@ -1065,6 +1093,7 @@ $(document).ready(function () { }); } }, + <?php } ?> { icon: 'img/export.png', text: 'Save logs', diff --git a/logs.php b/logs.php index 1f4d32f..20a227f 100644 --- a/logs.php +++ b/logs.php @@ -61,7 +61,18 @@ case "export": break; case "clear": - clearlogs(); + if($allowclearlogs === TRUE) { + clearlogs(); + } else { + jtable_respond(null, 'error', 'Invalid action'); + } + break; +case "rotate": + if($allowrotatelogs === TRUE) { + rotatelogs(); + } else { + jtable_respond(null, 'error', 'Invalid action'); + } break; default: jtable_respond(null, 'error', 'Invalid action'); From 56c1789b309d27c850cce25e8ae4ab342e2d26e6 Mon Sep 17 00:00:00 2001 From: Richard Underwood <richard.underwood@digitaslbi.com> Date: Wed, 24 Aug 2016 11:42:22 +0100 Subject: [PATCH 2/8] Changed "Save logs" to "Download logs" for clarity. Removed the rotate logs icon. Updated warning text for clearing logs, if rotation is allowed. --- index.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/index.php b/index.php index eb4cfbd..17c5b14 100644 --- a/index.php +++ b/index.php @@ -114,7 +114,9 @@ if ($blocklogin === TRUE) { <div id="dnssecinfo"> </div> <div id="clearlogs" style="display: none;"> - Are you sure you want to clear all the logs? Maybe save them first? + Are you sure you want to clear all the logs? Maybe download them + first<?php if($allowrotatelogs) { ?>, or use "Rotate logs" to save + them on the server<?php } ?>? </div> <div id="rotatelogs" style="display: none;"> Are you sure you want to rotate the logs? @@ -1048,7 +1050,6 @@ $(document).ready(function () { }, <?php if($allowrotatelogs === TRUE) { ?> { - icon: 'img/export.png', text: 'Rotate logs', click: function() { $("#rotatelogs").dialog({ @@ -1096,7 +1097,7 @@ $(document).ready(function () { <?php } ?> { icon: 'img/export.png', - text: 'Save logs', + text: 'Download logs', click: function () { var $zexport = $.get("logs.php?action=export", function(data) { console.log(data); From f081d96b0c52198d78262a9b3096c053e15005cc Mon Sep 17 00:00:00 2001 From: Richard Underwood <richard.underwood@digitaslbi.com> Date: Wed, 24 Aug 2016 14:19:52 +0100 Subject: [PATCH 3/8] Allow viewing of past logs. Add a command-line PHP script for rotation in cron. --- includes/misc.inc.php | 21 ++++++++++++++++++++ index.php | 45 ++++++++++++++++++++++++++++++++++++------- logs.php | 15 +++++++++++++-- rotate-logs.php | 16 +++++++++++++++ 4 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 rotate-logs.php diff --git a/includes/misc.inc.php b/includes/misc.inc.php index 4c88c01..3f77ee6 100644 --- a/includes/misc.inc.php +++ b/includes/misc.inc.php @@ -308,6 +308,27 @@ function rotatelogs() { } +function listrotatedlogs() { + global $logging, $logsdirectory; + if ($logging !== TRUE) + return FALSE; + + $list = scandir($logsdirectory,SCANDIR_SORT_DESCENDING); + + if($list === FALSE) { + writelog("Logs directory cannot read."); + return FALSE; + } + + $list=array_filter($list, + function ($val) { + return(preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{6}\.json/',$val) == 1); + } + ); + + return $list; +} + function writelog($line) { global $logging; if ($logging !== TRUE) diff --git a/index.php b/index.php index 17c5b14..5165946 100644 --- a/index.php +++ b/index.php @@ -189,6 +189,21 @@ if ($blocklogin === TRUE) { </div> <div id="logs"> <div class="tables" id="Logs"></div> + <?php if($allowrotatelogs) { ?> + <br>Log entries being viewed: + <select id="logfile"> + <option value="">(Current logs)</option> + <?php + $logfiles=listrotatedlogs(); + if($logfiles !== FALSE) { + foreach ($logfiles as $filename) { + echo '<option value="' . $filename . '">' . str_replace(".json","",$filename) . "</option>\n"; + } + } + ?></select> + <?php } else { ?> + <input type="hidden" id="logfile" value=""> + <?php } ?> </div> <?php } ?> @@ -925,18 +940,18 @@ $(document).ready(function () { }); <?php if (is_adminuser()) { ?> - $('#Logs').hide(); + $('#logs').hide(); $('#Users').hide(); $('#AboutMe').hide(); $('#aboutme').click(function () { - $('#Logs').hide(); + $('#logs').hide(); $('#Users').hide(); $('#MasterZones').hide(); $('#SlaveZones').hide(); $('#AboutMe').show(); }); $('#useradmin').click(function () { - $('#Logs').hide(); + $('#logs').hide(); $('#MasterZones').hide(); $('#SlaveZones').hide(); $('#AboutMe').hide(); @@ -944,7 +959,7 @@ $(document).ready(function () { $('#Users').show(); }); $('#zoneadmin').click(function () { - $('#Logs').hide(); + $('#logs').hide(); $('#Users').hide(); $('#AboutMe').hide(); $('#MasterZones').show(); @@ -955,8 +970,10 @@ $(document).ready(function () { $('#AboutMe').hide(); $('#MasterZones').hide(); $('#SlaveZones').hide(); - $('#Logs').jtable('load'); - $('#Logs').show(); + $('#Logs').jtable('load', { + logfile: $('#logfile').val() + }); + $('#logs').show(); }); $('#Users').jtable({ title: 'Users', @@ -1032,6 +1049,7 @@ $(document).ready(function () { $( this ).dialog( 'close' ); $('#Logs').find('.jtable-title-text').text('Logs (filtered)'); $('#Logs').jtable('load', { + logfile: $('#logfile').val(), user: $('#searchlogs-user').val(), entry: $('#searchlogs-entry').val() }); @@ -1041,7 +1059,9 @@ $(document).ready(function () { $('#searchlogs-entry').val(''); $( this ).dialog( 'close' ); $('#Logs').find('.jtable-title-text').text('Logs'); - $('#Logs').jtable('load'); + $('#Logs').jtable('load', { + logfile: $('#logfile').val() + }); return false; } } @@ -1060,6 +1080,7 @@ $(document).ready(function () { Ok: function() { $.get("logs.php?action=rotate"); $( this ).dialog( "close" ); + $('#logfile').val(''); $('#Logs').jtable('load'); }, Cancel: function() { @@ -1084,6 +1105,7 @@ $(document).ready(function () { Ok: function() { $.get("logs.php?action=clear"); $( this ).dialog( "close" ); + $('#logfile').val(''); $('#Logs').jtable('load'); }, Cancel: function() { @@ -1141,6 +1163,15 @@ $(document).ready(function () { } } }); + + $('#logfile').change(function () { + $('#Logs').jtable('load', { + logfile: $('#logfile').val(), + user: $('#searchlogs-user').val(), + entry: $('#searchlogs-entry').val() + }); + }); + <?php } ?> $('#MasterZones').jtable('load'); $('#SlaveZones').jtable('load'); diff --git a/logs.php b/logs.php index 20a227f..816ef3e 100644 --- a/logs.php +++ b/logs.php @@ -24,10 +24,21 @@ switch ($_GET['action']) { case "list": global $logging; - if ($logging !== TRUE) + if ($logging !== TRUE) { jtable_respond(null, 'error', 'Logging is disabled'); + break; + } - $entries=getlogs(); + if(!empty($_POST['logfile'])) { + if(preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{6}\.json/',$_POST['logfile']) == 1) { + $entries=json_decode(file_get_contents($logsdirectory . "/" . $_POST['logfile']),true); + } else { + jtable_respond(null, 'error', "Can't find log file"); + break; + } + } else { + $entries=getlogs(); + } if(!empty($_POST['user'])) { $entries=array_filter($entries, diff --git a/rotate-logs.php b/rotate-logs.php new file mode 100644 index 0000000..3ce3793 --- /dev/null +++ b/rotate-logs.php @@ -0,0 +1,16 @@ +<?php + +include_once('includes/config.inc.php'); +include_once('includes/session.inc.php'); +include_once('includes/misc.inc.php'); + +if(php_sapi_name() !== 'cli') { + echo "This script is intended to be run from the command line"; +} else { + if($allowrotatelogs === TRUE) { + $current_user['username']='<system>'; + rotatelogs(); + } else { + jtable_respond(null, 'error', 'Invalid action'); + } +} From 9d8d909c18c486253ddfed8fe29aa3ad0d1a1cae Mon Sep 17 00:00:00 2001 From: Richard Underwood <richard.underwood@digitaslbi.com> Date: Wed, 24 Aug 2016 14:38:03 +0100 Subject: [PATCH 4/8] Remove jtable_respond from the CLI script. --- rotate-logs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rotate-logs.php b/rotate-logs.php index 3ce3793..233136a 100644 --- a/rotate-logs.php +++ b/rotate-logs.php @@ -11,6 +11,6 @@ if(php_sapi_name() !== 'cli') { $current_user['username']='<system>'; rotatelogs(); } else { - jtable_respond(null, 'error', 'Invalid action'); + echo "Rotating logs has been disabled." } } From befb891174d9a8b6bba400241984499221e487e4 Mon Sep 17 00:00:00 2001 From: Richard Underwood <richard.underwood@digitaslbi.com> Date: Thu, 25 Aug 2016 10:23:31 +0100 Subject: [PATCH 5/8] Changed Download logs to download the logs currently being shown, not always the current logs - note, doesn't filter first. Removed "delete" case in logs.php Moved logging check out of case statements to avoid duplication. Changed wording of clear logs warning. Pretty-print the JSON on log export - requires PHP 5.4. --- index.php | 6 +-- logs.php | 131 +++++++++++++++++++++++++++--------------------------- 2 files changed, 69 insertions(+), 68 deletions(-) diff --git a/index.php b/index.php index 5b9c368..87d65fd 100644 --- a/index.php +++ b/index.php @@ -114,7 +114,7 @@ if ($blocklogin === TRUE) { <div id="dnssecinfo"> </div> <div id="clearlogs" style="display: none;"> - Are you sure you want to clear all the logs? Maybe download them + Are you sure you want to clear the current logs? Maybe download them first<?php if($allowrotatelogs) { ?>, or use "Rotate logs" to save them on the server<?php } ?>? </div> @@ -1121,13 +1121,13 @@ $(document).ready(function () { icon: 'img/export.png', text: 'Download logs', click: function () { - var $zexport = $.get("logs.php?action=export", function(data) { + var $zexport = $.get("logs.php?action=export&logfile=" + $('#logfile').val(), function(data) { console.log(data); blob = new Blob([data], { type: 'text/plain' }); var dl = document.createElement('a'); dl.addEventListener('click', function(ev) { dl.href = URL.createObjectURL(blob); - dl.download = 'nseditlogs.txt'; + dl.download = $('#logfile').val() == "" ? 'nseditlogs.txt':$('#logfile').val() + ".txt"; }, false); if (document.createEvent) { diff --git a/logs.php b/logs.php index 816ef3e..1d2a362 100644 --- a/logs.php +++ b/logs.php @@ -20,72 +20,73 @@ if (!isset($_GET['action'])) { jtable_respond(null, 'error', 'No action given'); } -switch ($_GET['action']) { +if ($logging !== TRUE) { + jtable_respond(null, 'error', 'Logging is disabled'); +} else { + switch ($_GET['action']) { + + case "list": + if(!empty($_POST['logfile'])) { + if(preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{6}\.json/',$_POST['logfile']) == 1) { + $entries=json_decode(file_get_contents($logsdirectory . "/" . $_POST['logfile']),true); + } else { + jtable_respond(null, 'error', "Can't find log file"); + break; + } + } else { + $entries=getlogs(); + } -case "list": - global $logging; - if ($logging !== TRUE) { - jtable_respond(null, 'error', 'Logging is disabled'); + if(!empty($_POST['user'])) { + $entries=array_filter($entries, + function ($val) { + return(stripos($val['user'], $_POST['user']) !== FALSE); + } + ); + } + + if(!empty($_POST['entry'])) { + $entries=array_filter($entries, + function ($val) { + return(stripos($val['log'], $_POST['entry']) !== FALSE); + } + ); + } + + jtable_respond($entries); + break; + + case "export": + if(!empty($_GET['logfile'])) { + if(preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{6}\.json/',$_GET['logfile']) == 1) { + $entries=json_decode(file_get_contents($logsdirectory . "/" . $_GET['logfile']),true); + } else { + jtable_respond(null, 'error', "Can't find log file"); + break; + } + } else { + $entries=getlogs(); + } + + print json_encode($entries,JSON_PRETTY_PRINT); + break; + + case "clear": + if($allowclearlogs === TRUE) { + clearlogs(); + } else { + jtable_respond(null, 'error', 'Invalid action'); + } + break; + case "rotate": + if($allowrotatelogs === TRUE) { + rotatelogs(); + } else { + jtable_respond(null, 'error', 'Invalid action'); + } + break; + default: + jtable_respond(null, 'error', 'Invalid action'); break; } - - if(!empty($_POST['logfile'])) { - if(preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{6}\.json/',$_POST['logfile']) == 1) { - $entries=json_decode(file_get_contents($logsdirectory . "/" . $_POST['logfile']),true); - } else { - jtable_respond(null, 'error', "Can't find log file"); - break; - } - } else { - $entries=getlogs(); - } - - if(!empty($_POST['user'])) { - $entries=array_filter($entries, - function ($val) { - return(stripos($val['user'], $_POST['user']) !== FALSE); - } - ); - } - - if(!empty($_POST['entry'])) { - $entries=array_filter($entries, - function ($val) { - return(stripos($val['log'], $_POST['entry']) !== FALSE); - } - ); - } - - jtable_respond($entries); - break; - -case "delete": - if ($emailaddress != '' and delete_user($emailaddress) !== FALSE) { - jtable_respond(null, 'delete'); - } else { - jtable_respond(null, 'error', 'Could not delete user'); - } - break; - -case "export": - print json_encode(getlogs()); - break; - -case "clear": - if($allowclearlogs === TRUE) { - clearlogs(); - } else { - jtable_respond(null, 'error', 'Invalid action'); - } - break; -case "rotate": - if($allowrotatelogs === TRUE) { - rotatelogs(); - } else { - jtable_respond(null, 'error', 'Invalid action'); - } - break; -default: - jtable_respond(null, 'error', 'Invalid action'); - break; } From 2cb95a695932c3c5e34ac1a7e6a3b8a6be80d848 Mon Sep 17 00:00:00 2001 From: Richard Underwood <richard.underwood@digitaslbi.com> Date: Fri, 26 Aug 2016 09:30:56 +0100 Subject: [PATCH 6/8] UNRELATED CHANGE - put test around curl_reset to allow testing on PHP 5.4 --- includes/class/ApiHandler.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/includes/class/ApiHandler.php b/includes/class/ApiHandler.php index 15efef8..aa349b4 100644 --- a/includes/class/ApiHandler.php +++ b/includes/class/ApiHandler.php @@ -44,7 +44,11 @@ class ApiHandler { $this->authheaders(); $this->addheader('Accept', 'application/json'); - curl_reset($this->curlh); + if(defined('curl_reset')) { + curl_reset($this->curlh); + } else { + $this->curlh = curl_init(); + } curl_setopt($this->curlh, CURLOPT_HTTPHEADER, Array()); curl_setopt($this->curlh, CURLOPT_RETURNTRANSFER, 1); From 8d6e8ddf55c61ae88c7469a24b67400c6d0e5c5d Mon Sep 17 00:00:00 2001 From: Richard Underwood <richard.underwood@digitaslbi.com> Date: Fri, 26 Aug 2016 11:45:30 +0100 Subject: [PATCH 7/8] Removed delete button from logs table as the action wasn't implemented and would not be possible on rotated logs. --- index.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.php b/index.php index 87d65fd..9cd5aed 100644 --- a/index.php +++ b/index.php @@ -1026,8 +1026,7 @@ $(document).ready(function () { pageSize: 20, sorting: false, actions: { - listAction: 'logs.php?action=list', - deleteAction: 'logs.php?action=delete', + listAction: 'logs.php?action=list' }, messages: { deleteConfirmation: 'This entry will be deleted. Are you sure?' From badebb99652278a53dde01ad33fe21bf3b45cbe7 Mon Sep 17 00:00:00 2001 From: Richard Underwood <richard.underwood@digitaslbi.com> Date: Fri, 26 Aug 2016 11:59:49 +0100 Subject: [PATCH 8/8] Clarified wording of rotation warning. --- index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.php b/index.php index 9cd5aed..41f23ac 100644 --- a/index.php +++ b/index.php @@ -119,7 +119,7 @@ if ($blocklogin === TRUE) { them on the server<?php } ?>? </div> <div id="rotatelogs" style="display: none;"> - Are you sure you want to rotate the logs? + Are you sure you want to rotate the current logs? </div> <div id="searchlogs" style="display: none; text-align: right;"> <table border="0">