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">