Copyright (C) 2015 ESF, LLC All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ require_once('globals.inc'); require_once('config.inc'); require_once('util.inc'); require_once('pfsense-utils.inc'); require_once('pkg-utils.inc'); require_once('filter.inc'); require_once('service-utils.inc'); if (file_exists('/usr/local/pkg/squid.inc')) { require_once('/usr/local/pkg/squid.inc'); } else { echo "No squid.inc found. You must have Squid/Squid3 package installed to use LightSquid."; } global $pfs_version; $pfs_version = substr(trim(file_get_contents("/etc/version")), 0, 3); switch ($pfs_version) { case "2.1": define('LIGHTSQUID_BASE', '/usr/pbi/lightsquid-' . php_uname("m")); break; case "2.2": define('LIGHTSQUID_BASE', '/usr/pbi/lightsquid-' . php_uname("m") . '/local'); break; default: define('LIGHTSQUID_BASE', '/usr/local'); break; } // configuration settings !-- CHECK THIS --! define('LS_CONFIGPATH', LIGHTSQUID_BASE . '/etc/lightsquid'); define('LS_CONFIGFILE', 'lightsquid.cfg'); define('LS_CONFIGFILE_DIST', 'lightsquid.cfg.dist'); define('LS_WWWPATH', LIGHTSQUID_BASE . '/www/lightsquid'); define('LS_TEMPLATEPATH', LS_WWWPATH . '/tpl'); define('LS_LANGPATH', LIGHTSQUID_BASE . '/share/lightsquid/lang'); define('LS_REPORTPATH', '/var/lightsquid/report'); global $config; if (is_array($config['installedpackages']['squid']['config'][0]) && $config['installedpackages']['squid']['config'][0]['log_dir'] != "") { define('LS_SQUIDLOGPATH', $config['installedpackages']['squid']['config'][0]['log_dir']); } else { define('LS_SQUIDLOGPATH', '/var/squid/logs'); } define('LS_SQUIDLOG', 'access.log'); define('LS_IP2NAMEPATH', LIGHTSQUID_BASE . '/libexec/lightsquid'); define('CRONTAB_LS_TEMPLATE', '/usr/bin/perl ' . LIGHTSQUID_BASE . '/www/lightsquid/lightparser.pl'); // default values define('LS_DEF_IP2NAME', 'dns'); define('LS_DEF_SQUIDLOGTYPE', '0'); define('LS_DEF_SKIPURL', 'zzz\.zzz'); define('LS_DEF_LANG', 'eng'); define('LS_DEF_TEMPLATE', 'base'); define('LS_DEF_BARCOLOR', 'orange'); // variable names define('LS_VAR_CFGPATH', 'cfgpath'); define('LS_VAR_LOGPATH', 'logpath'); define('LS_VAR_TPLPATH', 'tplpath'); define('LS_VAR_LANGPATH', 'langpath'); define('LS_VAR_LANG', 'lang'); define('LS_VAR_REPORTPATH', 'reportpath'); define('LS_VAR_SQUIDLOGTYPE', 'squidlogtype'); define('LS_VAR_SKIPURL', 'skipurl'); define('LS_VAR_IP2NAMEPATH', 'ip2namepath'); define('LS_VAR_IP2NAME', 'ip2name'); define('LS_VAR_TEMPLATE', 'templatename'); define('LS_VAR_BARCOLOR', 'barcolor'); // XML GUI variables define('LS_XML_LANG', 'lightsquid_lang'); define('LS_XML_SKIPURL', 'lightsquid_skipurl'); define('LS_XML_IP2NAME', 'lightsquid_ip2name'); define('LS_XML_TEMPLATE', 'lightsquid_template'); define('LS_XML_BARCOLOR', 'lightsquid_barcolor'); define('LS_XML_SHEDULERTIME', 'lightsquid_refreshsheduler_time'); /* * Package install/uninstall */ function lightsquid_install() { // create lightsquid reports directory lightsquid_create_reportdir(); // ugly PBI hacks if (LIGHTSQUID_BASE != '/usr/local') { // check and fix perl paths lightsquid_fix_perl(); if (!is_dir('/usr/local/etc/lightsquid') && is_dir(LS_CONFIGPATH)) { symlink(LS_CONFIGPATH, '/usr/local/etc/lightsquid'); } if (is_dir('/usr/local/www/lightsquid')) $_gc = exec('rm -rf /usr/local/www/lightsquid'); if (is_dir(LS_WWWPATH)) { symlink(LS_WWWPATH, '/usr/local/www/lightsquid'); } } // template symlinks foreach (array('novopf', 'novosea') as $tpl) { if (is_dir(LS_TEMPLATEPATH . '/' . $tpl)) { $_gc = exec('rm -rf ' . LS_TEMPLATEPATH . '/' . $tpl); } symlink('/usr/local/share/lightsquid/tpl/' . $tpl, LS_TEMPLATEPATH . '/' . $tpl); } symlink("/usr/local/www/themes", "/usr/local/www/sqstat/themes"); } function lightsquid_deinstall() { // remove cronjobs lightsquid_setup_cron(false); // undo PBI hacks lightsquid_unfix_perl(); if (is_dir("/usr/local/www/sqstat/")) { mwexec("/bin/rm -rf /usr/local/www/sqstat/"); } } /* * Ugly PBI hacks around perl paths; only needed for pfSense <2.3 */ /* Create perl links on package install */ function lightsquid_fix_perl() { if (LIGHTSQUID_BASE != '/usr/local') { /* Clean up a broken perl link first if needed. */ $perl_path = '/usr/bin/perl'; if (file_exists($perl_path) && is_link($perl_path)) { $target = readlink($perl_path); if (!file_exists($target) || !is_executable($target)) { unlink($target); } } /* Find usable perl and create perl symlink to $perl_path */ if (!file_exists($perl_path)) { if (is_executable("/usr/local/bin/perl")) { symlink("/usr/local/bin/perl", "{$perl_path}"); } elseif (is_executable(LIGHTSQUID_BASE . "/bin/perl")) { symlink(LIGHTSQUID_BASE . "/bin/perl", "{$perl_path}"); } } if (!is_dir("/usr/local/lib/perl5") && is_dir(LIGHTSQUID_BASE . "/lib/perl5")) { symlink(LIGHTSQUID_BASE . "/lib/perl5", "/usr/local/lib/perl5"); } } } /* Remove perl links on package uninstall */ function lightsquid_unfix_perl() { if (LIGHTSQUID_BASE != '/usr/local') { $perl_path = '/usr/bin/perl'; if (file_exists($perl_path) && is_link($perl_path)) { $target = readlink($perl_path); $ls_target = LIGHTSQUID_BASE . "/bin/perl"; if ($target === $ls_target) { unlink($perl_path); } } $perl_libpath = "/usr/local/lib/perl5"; if (is_dir($perl_libpath) && is_link($perl_libpath)) { $target = readlink($perl_libpath); $ls_target = LIGHTSQUID_BASE . "/lib/perl5"; if ($target === $ls_target) { unlink($perl_libpath); } } } } /* * Package configuration routines */ function lightsquid_resync() { global $config, $pfs_version; // Ugly PBI hacks if (LIGHTSQUID_BASE != '/usr/local') { // check perl paths if (!file_exists("/usr/bin/perl")) { lightsquid_fix_perl(); } // Fixup library path so GD can find its libraries for graphs. mwexec("/sbin/ldconfig -m " . LIGHTSQUID_BASE . "/lib/"); } lightsquid_create_reportdir(); mwexec("/bin/chmod -R u+w " . LIGHTSQUID_BASE . "/etc/lightsquid"); // Set up variables for configuration update $lsconf_var = array(); $lsconf_var[LS_VAR_CFGPATH] = "\"" . LS_CONFIGPATH . "\""; $lsconf_var[LS_VAR_LOGPATH] = "\"" . LS_SQUIDLOGPATH . "\""; $lsconf_var[LS_VAR_TPLPATH] = "\"" . LS_TEMPLATEPATH . "\""; $lsconf_var[LS_VAR_LANGPATH] = "\"" . LS_LANGPATH . "\""; $lsconf_var[LS_VAR_REPORTPATH] = "\"" . LS_REPORTPATH . "\""; $lsconf_var[LS_VAR_IP2NAMEPATH] = "\"" . LS_IP2NAMEPATH . "\""; $lsconf_var[LS_VAR_LANG] = "\"" . LS_DEF_LANG . "\""; $lsconf_var[LS_VAR_TEMPLATE] = "\"" . LS_DEF_TEMPLATE . "\""; $lsconf_var[LS_VAR_IP2NAME] = "\"" . LS_DEF_IP2NAME . "\""; $lsconf_var[LS_VAR_SKIPURL] = "'" . LS_DEF_SKIPURL . "'"; $lsconf_var[LS_VAR_SQUIDLOGTYPE]= LS_DEF_SQUIDLOGTYPE; // Update variables from package GUI config if (is_array($config['installedpackages']['lightsquid']['config'][0])) { $lightsquid_config = $config['installedpackages']['lightsquid']['config'][0]; if (isset($lightsquid_config[LS_XML_LANG]) and !empty($lightsquid_config[LS_XML_LANG])) { $lsconf_var[LS_VAR_LANG] = "\"" . $lightsquid_config[LS_XML_LANG] . "\""; } if (isset($lightsquid_config[LS_XML_SKIPURL]) and !empty($lightsquid_config[LS_XML_SKIPURL])) { $lsconf_var[LS_VAR_SKIPURL] = "'" . str_replace(".", "\\.", $lightsquid_config[LS_XML_SKIPURL]) . "'"; } if (isset($lightsquid_config[LS_XML_IP2NAME]) and !empty($lightsquid_config[LS_XML_IP2NAME] )) { $lsconf_var[LS_VAR_IP2NAME] = "\"" . $lightsquid_config[LS_XML_IP2NAME] . "\""; } if (isset($lightsquid_config[LS_XML_TEMPLATE]) and !empty($lightsquid_config[LS_XML_TEMPLATE])) { $tpl_val = $lightsquid_config[LS_XML_TEMPLATE]; // check template path if (!file_exists(LS_TEMPLATEPATH."/$tpl_val")) { $tpl_val = 'base'; } $lsconf_var[LS_VAR_TEMPLATE] = "\"" . $tpl_val . "\""; } if (isset($lightsquid_config[LS_XML_BARCOLOR]) and !empty($lightsquid_config[LS_XML_BARCOLOR])) { $lsconf_var[LS_VAR_BARCOLOR] = "\"" . $lightsquid_config[LS_XML_BARCOLOR] . "\""; } } // Create or update lightsquid.cfg $lsconf = ""; $lsconf_file = LS_CONFIGPATH . "/" . LS_CONFIGFILE; // Always use the lightsquid.cfg.dist template to avoid issues with GUI values reconfiguration $lsconf_dist_file = LS_CONFIGPATH . "/" . LS_CONFIGFILE_DIST; if (file_exists($lsconf_dist_file)) { $lsconf = file_get_contents($lsconf_dist_file); log_error("[lightsquid] Loaded default '{$lsconf_dist_file}' configuration file."); } else { log_error("[lightsquid] Error: Could not load default '{$lsconf_dist_file}' configuration file."); } // Update lightsquid.cfg if (!empty($lsconf)) { $lsconf = explode("\n", $lsconf); foreach ($lsconf_var as $key => $val) { for ($i = 0; $i < count($lsconf); $i++) { $s = trim($lsconf[$i]); $e_key = "/^[$]" . $key . "[ ]*[=]+/i"; if (preg_match($e_key, $s)) { $lsconf[$i] = '$' . "$key = $val;"; } } } $lsconf = implode("\n", $lsconf); if (file_put_contents($lsconf_file, $lsconf)) { log_error("[lightsquid] Successfully created '{$lsconf_file}' configuration file."); } else { log_error("[lightsquid] Error: Could not create '{$lsconf_file}' configuration file."); } } else { log_error("[lightsquid] Error: Could not create '{$lsconf_file}' configuration file."); } // Set up scheduled reports updates lightsquid_setup_cron(true); } /* * Reports */ /* Configure scheduled reports updates via cron */ function lightsquid_setup_cron($active=false) { global $config; if (is_array($config['installedpackages']['lightsquid']['config'][0])) { $cron_schedule = $config['installedpackages']['lightsquid']['config'][0][LS_XML_SHEDULERTIME]; } else { $cron_schedule = ''; } $lightsquid_parser_today = CRONTAB_LS_TEMPLATE . " today"; $lightsquid_parser_yesterday = CRONTAB_LS_TEMPLATE . " yesterday"; if ($active && $cron_schedule) { $on = false; $opt = array("*", "*", "*", "*", "*", "root"); // remove old cronjobs first ... log_error("[lightsquid] Removing old cronjobs..."); install_cron_job($lightsquid_parser_today, false); install_cron_job($lightsquid_parser_yesterday, false); // ... and configure updated cronjobs if needed switch($cron_schedule) { case 'lhp_none': $on = false; break; case 'lhp_10m': $on = true; $opt[0]= "*/10"; break; case 'lhp_20m': $on = true; $opt[0]= "*/20"; break; case 'lhp_30m': $on = true; $opt[0]= "*/30"; break; case 'lhp_40m': $on = true; $opt[0]= "*/40"; break; case 'lhp_50m': $on = true; $opt[0]= "*/50"; break; case 'lhp_60m': $on = true; $opt[0]= "*/60"; break; case 'lhp_2h': $on = true; $opt[0]= "0"; $opt[1]= "*/2"; break; case 'lhp_3h': $on = true; $opt[0]= "0"; $opt[1]= "*/3"; break; case 'lhp_4h': $on = true; $opt[0]= "0"; $opt[1]= "*/4"; break; case 'lhp_6h': $on = true; $opt[0]= "0"; $opt[1]= "*/6"; break; case 'lhp_8h': $on = true; $opt[0]= "0"; $opt[1]= "*/8"; break; case 'lhp_12h': $on = true; $opt[0]= "0"; $opt[1]= "*/12"; break; case 'lhp_24h': $on = true; $opt[0]= "45"; $opt[1]= "23"; break; // daily at 23:45 } if ($on) { log_error("[lightsquid] Updating cronjobs..."); install_cron_job($lightsquid_parser_today, $on, $opt[0], $opt[1], $opt[2], $opt[3], $opt[4], $opt[5]); // fix possible data lost with 00:00 script start - rescan yesterday install_cron_job($lightsquid_parser_yesterday, true, "15", "0", "*", "*", "*", "root"); } } else { log_error("[lightsquid] Removing all cronjobs..."); install_cron_job($lightsquid_parser_today, false); install_cron_job($lightsquid_parser_yesterday, false); } } /* Parse today's entires only in access.log via the GUI button */ function lightsquid_refresh_now() { $cmd = CRONTAB_LS_TEMPLATE . " today"; $lg = LS_SQUIDLOG; lightsquid_create_reportdir(); if (file_exists(LS_SQUIDLOGPATH . "/{$lg}")) { log_error("[lightsquid] Parsing today's entries in access.log using '{$cmd}'"); mwexec_bg($cmd); } else { log_error("[lightsquid] Could not parse '{$lg}' - log does not exist!"); } } /* Parse all entries in all access logs, including the rotated ones via the GUI button */ function lightsquid_refresh_full() { $cmd = CRONTAB_LS_TEMPLATE; lightsquid_create_reportdir(); log_error("[lightsquid] Parsing all access log(s) entries using '{$cmd}'..."); // parse access.log $lg = LS_SQUIDLOG; if (file_exists(LS_SQUIDLOGPATH . "/{$lg}")) { log_error("[lightsquid] Parsing log entries in '{$lg}'..."); mwexec_bg("{$cmd} {$lg}"); } else { log_error("[lightsquid] Could not parse '{$lg}' - log does not exist!"); } // parse access.log.x; if user configured some insane amount of rotated logs, only parse the first 100 of them for ($i = 0; $i < 100; $i++) { $lg = LS_SQUIDLOG . ".{$i}"; if (file_exists(LS_SQUIDLOGPATH . "/{$lg}")) { log_error("[lightsquid] Parsing log entries in '{$lg}'..."); mwexec_bg("{$cmd} {$lg}"); } else { break; } } } /* Helper function to create lightsquid reports directory if needed */ function lightsquid_create_reportdir() { if (!is_dir(LS_REPORTPATH)) { log_error("[lightsquid] Creating report dir " . LS_REPORTPATH); safe_mkdir(LS_REPORTPATH); } } /* * Input validation * Check required Squid configuration and provide instructions to users, * instead of trying to mess with Squid's settings directly. */ function lightsquid_validate_input($post, &$input_errors) { global $config; /* Manual reports refresh; only allow to run if the configuration file exists already! */ if ($post['refreshnow'] == 'Refresh now' || $post['refreshfull'] == 'Refresh full') { $lsconf_file = LS_CONFIGPATH . "/" . LS_CONFIGFILE; if (file_exists($lsconf_file)) { if ($post['refreshnow'] == 'Refresh now') { lightsquid_refresh_now(); return; } elseif ($post['refreshfull'] == 'Refresh full') { lightsquid_refresh_full(); return; } } else { $input_errors[] = "Please, configure LightSquid package first and Save settings before trying to manually refresh reports."; } } /* Check whether Squid is configured at all */ if (is_array($config['installedpackages']['squid']['config'][0])) { $squid_settings = $config['installedpackages']['squid']['config'][0]; } else { $input_errors[] = "Please, configure Squid package 'General' settings first."; $squid_settings = array(); } /* Check whether logging is enabled in Squid */ if ($squid_settings['log_enabled'] != "on") { $input_errors[] = "Please, enable Access Logging in Squid package 'General' settings first."; } /* Check whether log directory is configured in Squid */ if ($squid_settings['log_dir'] == "") { $input_errors[] = "Please, configure Log Store Directory in Squid package 'General' settings first."; } /* SqStat - check external cache managers * This check is only needed for Squid 2.7 packages, any Squid 3.x package allows localhost as cache manager by default */ $ifmgr = "127.0.0.1"; if (get_pkg_id("squid") !== -1) { if (is_array($config['installedpackages']['squidnac']['config'])) { $ext_cachemanager = ($config['installedpackages']['squidnac']['config'][0]['ext_cachemanager'] ?: ""); } else { $ext_cachemanager = ""; } if (strpos($ext_cachemanager, $ifmgr) === FALSE) { $input_errors[] = "Please, add '{$ifmgr}' to Squid - Access Control - External Cache-Managers first."; } } /* SqStat - check that Squid listens on loopback */ if (is_array($config['installedpackages']['squid']['config'])) { $active_interfaces = ($config['installedpackages']['squid']['config'][0]['active_interface'] ?: ""); } else { $active_interfaces = ""; } if (strpos($active_interfaces, "lo0") === FALSE) { $input_errors[] = "Please, configure Squid - General - Proxy Interface(s) to include 'loopback' interface."; } /* 'Skip URL(s)' validation */ if ($post['lightsquid_skipurl'] != "") { $hosts = explode("|", $post['lightsquid_skipurl']); foreach ($hosts as $host) { $host = trim($host); if (!is_ipaddr($host) && !is_hostname($host) && !is_domain($host)) { $input_errors[] = "'Skip URL(s)' entry '{$host}' is not a valid IP address, hostname, domain or subnet."; } } } } ?>