From 8cbfaed989eb918ff359b8fcfe7ae5fdcfa2a70c Mon Sep 17 00:00:00 2001 From: billz Date: Tue, 28 Nov 2023 10:00:07 +0000 Subject: [PATCH 1/6] Define RASPI_LOG_SIZE_LIMIT, RASPI_HOSTAPD_LOG --- config/config.php | 2 ++ includes/defaults.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/config/config.php b/config/config.php index 1aff2412..bdda1a02 100755 --- a/config/config.php +++ b/config/config.php @@ -8,6 +8,7 @@ define('RASPI_ADMIN_DETAILS', RASPI_CONFIG.'/raspap.auth'); define('RASPI_WIFI_AP_INTERFACE', 'wlan0'); define('RASPI_CACHE_PATH', sys_get_temp_dir() . '/raspap'); define('RASPI_DEBUG_LOG', 'raspap_debug.log'); +define('RASPI_LOG_SIZE_LIMIT', 64); // Constants for configuration file paths. // These are typical for default RPi installs. Modify if needed. @@ -18,6 +19,7 @@ define('RASPI_ADBLOCK_CONFIG', RASPI_DNSMASQ_PREFIX.'adblock.conf'); define('RASPI_HOSTAPD_CONFIG', '/etc/hostapd/hostapd.conf'); define('RASPI_DHCPCD_CONFIG', '/etc/dhcpcd.conf'); define('RASPI_DHCPCD_LOG', '/var/log/dnsmasq.log'); +define('RASPI_HOSTAPD_LOG', '/tmp/hostapd.log'); define('RASPI_WPA_SUPPLICANT_CONFIG', '/etc/wpa_supplicant/wpa_supplicant.conf'); define('RASPI_HOSTAPD_CTRL_INTERFACE', '/var/run/hostapd'); define('RASPI_WPA_CTRL_INTERFACE', '/var/run/wpa_supplicant'); diff --git a/includes/defaults.php b/includes/defaults.php index b7c101b3..b5efb0cb 100755 --- a/includes/defaults.php +++ b/includes/defaults.php @@ -13,6 +13,7 @@ $defaults = [ 'RASPI_WIFI_AP_INTERFACE' => 'wlan0', 'RASPI_CACHE_PATH' => sys_get_temp_dir() . '/raspap', 'RASPI_DEBUG_LOG' => 'raspap_debug.log', + 'RASPI_LOG_SIZE_LIMIT' => 64, // Constants for configuration file paths. // These are typical for default RPi installs. Modify if needed. @@ -23,6 +24,7 @@ $defaults = [ 'RASPI_HOSTAPD_CONFIG' => '/etc/hostapd/hostapd.conf', 'RASPI_DHCPCD_CONFIG' => '/etc/dhcpcd.conf', 'RASPI_DHCPCD_LOG' => '/var/log/dnsmasq.log', + 'RASPI_HOSTAPD_LOG' => '/tmp/hostapd.log', 'RASPI_WPA_SUPPLICANT_CONFIG' => '/etc/wpa_supplicant/wpa_supplicant.conf', 'RASPI_HOSTAPD_CTRL_INTERFACE' => '/var/run/hostapd', 'RASPI_WPA_CTRL_INTERFACE' => '/var/run/wpa_supplicant', From cf53c575ffaa2b6550396f8d7a09ddaaab863b58 Mon Sep 17 00:00:00 2001 From: billz Date: Tue, 28 Nov 2023 10:01:15 +0000 Subject: [PATCH 2/6] Create getLogLimited() --- includes/functions.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/includes/functions.php b/includes/functions.php index f909311d..5d3207e1 100755 --- a/includes/functions.php +++ b/includes/functions.php @@ -924,3 +924,26 @@ function checkReleaseVersion($installed, $latest) { return false; } +/** + * Returns logfile contents up to a maximum defined limit, in kilobytes + * + * @param string $file_path + * @param string $file_data optional + * @return string $log_limited + */ +function getLogLimited($file_path, $file_data = null) { + $limit_in_kb = isset($_SESSION['log_limit']) ? $_SESSION['log_limit'] : RASPI_LOG_SIZE_LIMIT; + $limit = $limit_in_kb * 1024; // convert KB to bytes + + if ($file_data === null) { + $file_size = filesize($file_path); + $start_position = max(0, $file_size - $limit); + $log_limited = file_get_contents($file_path, false, null, $start_position); + } else { + $file_size = strlen($file_data); + $start_position = max(0, $file_size - $limit); + $log_limited = substr($file_data, $start_position); + } + return $log_limited; +} + From 2a5241fb62abb2b3a4dc290474ac9d4580597799 Mon Sep 17 00:00:00 2001 From: billz Date: Tue, 28 Nov 2023 10:03:58 +0000 Subject: [PATCH 3/6] Add log size limit to Advanced tab, validate + set session var --- includes/system.php | 14 +++++++++++++- templates/system/advanced.php | 7 ++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/includes/system.php b/includes/system.php index ddc462c5..e75a8585 100755 --- a/includes/system.php +++ b/includes/system.php @@ -39,6 +39,16 @@ function DisplaySystem(&$extraFooterScripts) $serverBind = escapeshellarg($_POST['serverBind']); } } + // Validate log limit + if (isset($_POST['logLimit'])) { + if ( strlen($_POST['logLimit']) > 4 || !is_numeric($_POST['logLimit']) ) { + $status->addMessage('Invalid value for log size limit', 'danger'); + $good_input = false; + } else { + $_SESSION['log_limit'] = intval($_POST['logLimit']); + $status->addMessage(sprintf(_('Changing log limit size to %s KB'), $_SESSION['log_limit']), 'info'); + } + } // Save settings if ($good_input) { exec("sudo /etc/raspap/lighttpd/configport.sh $serverPort $serverBind " .RASPI_LIGHTTPD_CONFIG. " ".$_SERVER['SERVER_NAME'], $return); @@ -141,6 +151,7 @@ function DisplaySystem(&$extraFooterScripts) $extraFooterScripts[] = array('src'=>'dist/huebee/huebee.pkgd.min.js', 'defer'=>false); $extraFooterScripts[] = array('src'=>'app/js/huebee.js', 'defer'=>false); + $logLimit = isset($_SESSION['log_limit']) ? $_SESSION['log_limit'] : RASPI_LOG_SIZE_LIMIT; echo renderTemplate("system", compact( "arrLocales", @@ -166,6 +177,7 @@ function DisplaySystem(&$extraFooterScripts) "hostapd_status", "hostapd_led", "themes", - "selectedTheme" + "selectedTheme", + "logLimit" )); } diff --git a/templates/system/advanced.php b/templates/system/advanced.php index 0cc66e1e..efab1cc2 100644 --- a/templates/system/advanced.php +++ b/templates/system/advanced.php @@ -16,10 +16,15 @@ +
+
+ + +
+
" /> " /> - From 05210e8967c534f430459748a62e8e38405bbb40 Mon Sep 17 00:00:00 2001 From: billz Date: Tue, 28 Nov 2023 10:04:25 +0000 Subject: [PATCH 4/6] Update en_US locale w/ new messages --- locale/en_US/LC_MESSAGES/messages.po | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/locale/en_US/LC_MESSAGES/messages.po b/locale/en_US/LC_MESSAGES/messages.po index 0ab52d0e..1d99560d 100644 --- a/locale/en_US/LC_MESSAGES/messages.po +++ b/locale/en_US/LC_MESSAGES/messages.po @@ -1,4 +1,4 @@ -# RaspAP Portable Object file + KB# RaspAP Portable Object file # Project home: https://github.com/billz/raspap-webgui # Licensed under the GNU General Public License v3.0 # This file is distributed under the same license as the RaspAP package @@ -911,6 +911,12 @@ msgstr "Generate debug log" msgid "Debug log generation in progress..." msgstr "Debug log generation in progress..." +msgid "Diagnostic log size limit (KB)" +msgstr "Diagnostic log size limit (KB)" + +msgid "Changing log limit size to %s KB" +msgstr "Changing log limit size to %s KB" + #: includes/data_usage.php msgid "Data usage" msgstr "Data usage" From 74656c63ed994a746571275f83c3cea983315c8d Mon Sep 17 00:00:00 2001 From: billz Date: Tue, 28 Nov 2023 10:05:06 +0000 Subject: [PATCH 5/6] Update templates w/ getLogLimited, add $logdata to payload --- includes/adblock.php | 3 ++- includes/dhcp.php | 6 +++++- includes/hostapd.php | 5 ++++- templates/adblock/logging.php | 2 +- templates/dhcp/logging.php | 4 +--- templates/hostapd/logging.php | 4 +--- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/includes/adblock.php b/includes/adblock.php index 0d171e25..ec6fb38d 100755 --- a/includes/adblock.php +++ b/includes/adblock.php @@ -92,6 +92,7 @@ function DisplayAdBlockConfig() } else { $adblock_log = "Unable to open log file"; } + $logdata = getLogLimited(RASPI_DHCPCD_LOG, $adblock_log); echo renderTemplate( "adblock", compact( @@ -101,7 +102,7 @@ function DisplayAdBlockConfig() "enabled", "custom_enabled", "adblock_custom_content", - "adblock_log" + "logdata" ) ); } diff --git a/includes/dhcp.php b/includes/dhcp.php index 1c6b500c..b1167457 100755 --- a/includes/dhcp.php +++ b/includes/dhcp.php @@ -60,6 +60,9 @@ function DisplayDHCPConfig() count($log_dhcp) > 0 ? $conf['log-dhcp'] = true : false ; count($log_queries) > 0 ? $conf['log-queries'] = true : false ; + exec('sudo /bin/chmod o+r '.RASPI_DHCPCD_LOG); + $logdata = getLogLimited(RASPI_DHCPCD_LOG); + echo renderTemplate( "dhcp", compact( "status", @@ -70,7 +73,8 @@ function DisplayDHCPConfig() "hosts", "upstreamServers", "interfaces", - "leases" + "leases", + "logdata" ) ); } diff --git a/includes/hostapd.php b/includes/hostapd.php index 7fe54134..3ca11356 100755 --- a/includes/hostapd.php +++ b/includes/hostapd.php @@ -135,6 +135,8 @@ function DisplayHostAPDConfig() $selectedHwMode = 'w'; } } + exec('sudo /bin/chmod o+r '.RASPI_HOSTAPD_LOG); + $logdata = getLogLimited(RASPI_HOSTAPD_LOG); echo renderTemplate( "hostapd", compact( @@ -153,7 +155,8 @@ function DisplayHostAPDConfig() "arrHostapdConf", "operatingSystem", "selectedHwMode", - "countryCodes" + "countryCodes", + "logdata" ) ); } diff --git a/templates/adblock/logging.php b/templates/adblock/logging.php index 8378e68d..2140ae71 100644 --- a/templates/adblock/logging.php +++ b/templates/adblock/logging.php @@ -3,7 +3,7 @@

- '.htmlspecialchars($adblock_log, ENT_QUOTES).''; ?> + '.htmlspecialchars($logdata, ENT_QUOTES).''; ?>
diff --git a/templates/dhcp/logging.php b/templates/dhcp/logging.php index 6050e102..7243c7a5 100644 --- a/templates/dhcp/logging.php +++ b/templates/dhcp/logging.php @@ -17,9 +17,7 @@
'.htmlspecialchars($log, ENT_QUOTES).''; + echo ''; } else { echo ''; } diff --git a/templates/hostapd/logging.php b/templates/hostapd/logging.php index 0994f88e..f5b646db 100644 --- a/templates/hostapd/logging.php +++ b/templates/hostapd/logging.php @@ -14,9 +14,7 @@
'.htmlspecialchars($log, ENT_QUOTES).''; + echo ''; } else { echo ''; } From 7818462f5677b3cf6bf60762315d92600875504c Mon Sep 17 00:00:00 2001 From: billz Date: Tue, 28 Nov 2023 11:07:25 +0000 Subject: [PATCH 6/6] Update en_US locale + compile .mo --- locale/en_US/LC_MESSAGES/messages.mo | Bin 41335 -> 41495 bytes locale/en_US/LC_MESSAGES/messages.po | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/locale/en_US/LC_MESSAGES/messages.mo b/locale/en_US/LC_MESSAGES/messages.mo index 43c9fc0a3548d7ca6aaa59c93b88cb6f3f033059..0b0865ab2d8620507917a839e476740d6ea90027 100644 GIT binary patch delta 10715 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 QC93THaKM%V{~s6nACS>Wg8%>k delta 10558 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