mirror of
https://github.com/Cacti/rrdproxy.git
synced 2026-10-25 20:46:44 +09:00
* Harden RRDtool proxy request handling * Address PR review feedback: encrypt oversized-request errors, fix env inheritance, spacing, and symlink test Addresses review comments on PR #17: - lib/client.php: encrypt the 'Request too large' error frame once the client is authenticated, matching the existing encrypted-error pattern - lib/client.php: build the removespikes child-process environment from getenv() instead of $_ENV so the full current environment (e.g. TEMP on Windows) is inherited, with RRDP_RRDTOOL_PATH merged on top - lib/client.php: add the missing space in the RRDTOOL_PIPE_UNAVAILABLE error frame so it matches the other 'ERROR: ...' responses - tests/run.php: check the symlink() fixture return value and skip the symlink-escape assertion (instead of producing a false positive) when symlink() is unavailable * Upgrade phpseclib 3.x to phpseclib/phpseclib ^4.0 - composer.json/composer.lock: require phpseclib/phpseclib ^4.0 (was ^3.0) - Update phpseclib3\\... namespace usages to phpseclib4\\... across rrdtool-proxy.php, lib/client.php, lib/functions.php, lib/wizard.php, and tests/run.php - lib/functions.php: phpseclib4 removed the Random class, so Random::string(32) is replaced with random_bytes(32) - Fix lib/__master__.php and lib/replicator.php, which PR #17 never migrated off the old pre-Composer phpseclib API (new \\phpseclib\\Crypt\\RSA(), ->loadKey(), ->getPublicKeyFingerprint() -- none of which exist in phpseclib 3.x or 4.x, so these MSR/replicator peer-authentication code paths would fatal at runtime). Both now use RSA::loadPublicKey()->getFingerprint('md5') wrapped in try/catch, matching the pattern already used in lib/client.php, plus a constant-time hash_equals() fingerprint comparison - tests/run.php: relabel the phpseclib round-trip check from 'phpseclib 3' to 'phpseclib 4' * Bump minimum PHP version to 8.2 and add CHANGELOG.md - composer.json: require php ^8.2 (was ^8.1) - include/global.php: bump RRDP_PHP_VERSION_REQUIRED to 80200 to match - README.md: update minimum/recommended PHP version to 8.2 - composer.lock: refreshed for the new php platform constraint - Add CHANGELOG.md tracking changes prefixed security:/issue:/feature: * Add PHPDoc blocks to all functions Adds a PHPDoc block above every function in rrdtool-proxy.php, lib/functions.php, lib/client.php, lib/__master__.php, lib/replicator.php, lib/wizard.php, cli/removespikes.php, tests/run.php, and .phpstan-bootstrap.php, each with a brief description and inferred @param/@return types. Function signatures themselves are unchanged; PHPStan will flag any inferred types that turn out to be inaccurate. * Add .github/copilot-instructions.md Generated from README.md and this session's learnings: project architecture (daemon entrypoint, shared lib helpers, client/master/replicator roles, wizard, removespikes CLI), build/lint/test commands (composer scripts, php tests/run.php, Windows platform-req caveats), and conventions (tabs, GPL header block, PHPDoc-only typing, phpseclib4 gotchas, CHANGELOG entry prefixes). Also fix .gitignore: the '.git*' pattern was unintentionally also matching and ignoring the .github/ directory; added a '!.github/' exception so files under .github/ (like this one) are tracked. * Add .gitattributes enforcing LF line endings; normalize existing CRLF files - Add .gitattributes with '* text=auto eol=lf' so Git normalizes line endings to LF on checkout/commit regardless of local core.autocrlf settings. - .gitignore: add '!.gitattributes' alongside the existing '!.github/' exception, since '.git*' was also unintentionally ignoring .gitattributes itself. - Normalize the two tracked files that had CRLF line endings (.github/copilot-instructions.md, CHANGELOG.md) to LF; no other tracked files were affected. * Manual PSR coding-style adjustments Manual formatting/coding-style cleanup in cli/removespikes.php, lib/__master__.php, lib/client.php, and lib/replicator.php. * Address automated review feedback: symlink confinement, key-pair validation, and misc fixes Addresses 10 unresolved copilot-pull-request-reviewer comments on PR #20: - lib/functions.php: rewrite rrdp_resolve_path_within() to walk the path one segment at a time (instead of only checking the nearest existing ancestor via file_exists()), so a symlink anywhere along a not-yet-fully-existing path is resolved and confined even if the symlink itself is dangling. - lib/functions.php: add rrdp_resolve_command_paths(), which resolves every RRD file path operand referenced by an RRDtool command (leading bare file arguments and DEF:/SDEF: clauses) against the RRA root and rewrites the command to use the canonical resolved paths, closing the symlink-escape gap left by the lexical-only rrdp_command_has_unsafe_path() check. - lib/client.php: wire rrdp_resolve_command_paths() into the RRDtool command dispatch path, rejecting/rewriting unsafe operands before they reach the RRDtool pipe. - lib/client.php: enforce the request-size limit on each completed transaction and the trailing unfinished remainder, instead of on the aggregate receive buffer before splitting on _EOT_, so multiple legitimate transactions in one read no longer trip a false 'Request too large'. - rrdtool-proxy.php: validate at startup that the loaded public/private key files actually form a matching pair (comparing derived fingerprints) instead of only deriving a fingerprint from the public key, failing startup instead of accepting requests that will all fail to decrypt. - lib/__master__.php, lib/replicator.php: resolve peer-supplied FULLSCAN folder/file names (and RRDLAST/RRDDUMP request filenames) through rrdp_resolve_rra_path() before using them in mkdir()/touch()/ file_put_contents()/RRDtool restore-dump-last-flushcached operations, instead of concatenating raw peer-supplied text; reject and skip entries that fail confinement. - lib/replicator.php, lib/__master__.php: guard rrdtool_pipe_init() failures in the main replication loop the same way lib/client.php already does, instead of indexing into a false return value and never retrying. - cli/removespikes.php: make createRRDFileFromXML() return whether the RRDtool restore actually succeeded, and exit non-zero when it fails instead of silently exiting 0. - .phpstan.neon: restore level 6 (was lowered to 2, contradicting .github/copilot-instructions.md) and add .phpstan-baseline.neon to capture the 42 pre-existing findings surfaced at level 6 without hiding future regressions. - tests/run.php: add the standard Cacti Group GPL header block required for every PHP file. --------- Co-authored-by: Thomas Vincent <thomasvincent@gmail.com>
1031 lines
43 KiB
PHP
1031 lines
43 KiB
PHP
<?php
|
|
/*
|
|
+-------------------------------------------------------------------------+
|
|
| Copyright (C) 2004-2026 The Cacti Group |
|
|
| |
|
|
| This program is free software; you can redistribute it and/or |
|
|
| modify it under the terms of the GNU General Public License |
|
|
| as published by the Free Software Foundation; either version 2 |
|
|
| of the License, or (at your option) any later version. |
|
|
| |
|
|
| This program is distributed in the hope that it will be useful, |
|
|
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
| GNU General Public License for more details. |
|
|
+-------------------------------------------------------------------------+
|
|
| Cacti: The Complete RRDTool-based Graphing Solution |
|
|
+-------------------------------------------------------------------------+
|
|
| This code is designed, written, and maintained by the Cacti Group. See |
|
|
| about.php and/or the AUTHORS file for specific developer information. |
|
|
+-------------------------------------------------------------------------+
|
|
| http://www.cacti.net/ |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
global $active_config;
|
|
|
|
include('../vendor/autoload.php');
|
|
|
|
use phpseclib4\Crypt\RSA;
|
|
|
|
$active_config = [
|
|
'version' => RRDP_VERSION,
|
|
'name' => 'rrdp',
|
|
'ip_version' => 4,
|
|
'address_4' => '0.0.0.0',
|
|
'address_6' => '::',
|
|
'port_client' => 40301,
|
|
'port_server' => 40302,
|
|
'port_admin' => 40303,
|
|
'max_admin_cnn' => 5,
|
|
'remote_cnn_timeout' => 30,
|
|
'logging_size_buffered' => 10000,
|
|
'logging_size_snmp' => 10000,
|
|
'logging_severity_buffered' => 5,
|
|
'logging_severity_snmp' => 5,
|
|
'logging_severity_terminal' => 0,
|
|
'logging_severity_console' => 0,
|
|
'logging_category_console' => 'all',
|
|
'logging_category_terminal' => 'all',
|
|
'path_rra' => realpath('./rra'),
|
|
'path_rra_archive' => realpath('./archive'),
|
|
'path_rrdtool' => '/usr/bin/rrdtool',
|
|
'path_rrdcached' => '/usr/bin/rrdcached',
|
|
'rrdcache_update_cycle' => 600,
|
|
'rrdcache_update_delay' => 0,
|
|
'rrdcache_life_cycle' => 7200,
|
|
'rrdcache_write_threads' => 4,
|
|
'enable_password' => '',
|
|
];
|
|
$rrdp_config = [];
|
|
$rrdp_config_tmp = [];
|
|
$rrdp_remote_clients = [];
|
|
$rrdp_remote_proxies = [];
|
|
|
|
wizard();
|
|
|
|
/**
|
|
* Runs the interactive command-line configuration wizard: checks system
|
|
* requirements, prompts for and validates each configuration setting, generates
|
|
* or reuses the RSA key pair, and writes out the resulting configuration file.
|
|
*
|
|
* @return void
|
|
*/
|
|
function wizard() {
|
|
global $microtime_start, $active_config, $rrdp_config, $rrdp_config_tmp;
|
|
global $rrdp_remote_clients, $rrdp_remote_proxies;
|
|
|
|
define('WIZARD_RUNNING', 1);
|
|
|
|
// ### -- WELCOME -- ####
|
|
wizard_handle_title(1, 'Welcome');
|
|
wizard_handle_output(rrdp_get_cacti_proxy_logo(), true, true);
|
|
|
|
$msg = 'Welcome to the Wizard of the RRDtool Proxy Server, brought to you by the Cacti Group. ' . PHP_EOL . PHP_EOL
|
|
. 'This tool allows you to setup the RRDtool Proxy Server for the first time as well or to reconfigure an existing configuration. '
|
|
. 'You can abort this script anytime with CTRL+C and restart this wizard with the command: ';
|
|
|
|
wizard_handle_output(wordwrap($msg), true, true);
|
|
|
|
$msg = ANSI_BOLD . '<path_to_php> ./rrdtool-proxy.php --wizard' . ANSI_RESET;
|
|
|
|
wizard_handle_output(wizard_prompt_wordwrap('Example', $msg, 75), true, true);
|
|
|
|
$msg = 'When editing a value, any existing or default value will be shown in square brackets. '
|
|
. 'To accept these, simply press ENTER to reuse them. ' . PHP_EOL . PHP_EOL
|
|
. 'Once the configuration file has been updated, any running instance of RRDtool '
|
|
. 'Proxy Server will need to be restarted to pick these changes up.';
|
|
|
|
wizard_handle_output(wordwrap($msg), true, true);
|
|
|
|
$filter_options = ['options' => ['regexp' => '/[\s]*/']];
|
|
wizard_handle_input(ANSI_BOLD . 'Press ENTER to continue ...' . ANSI_RESET, FILTER_VALIDATE_REGEXP, $filter_options, false, true);
|
|
|
|
// ### -- SYSTEM REQUIREMENTS -- ####
|
|
wizard_handle_title(2,'Requirements');
|
|
wizard_handle_output(rrdp_get_cacti_proxy_logo(), true, true);
|
|
|
|
wizard_handle_output('Checking System Requirements...', true);
|
|
|
|
$microtime_start = microtime(true); // reset system start time
|
|
$support_os = strstr(PHP_OS, 'WIN') ? false : true;
|
|
rrd_system__system_boolean_message('test: operation system supported', $support_os, true);
|
|
|
|
$php_version = (!defined('PHP_VERSION_ID') || (PHP_VERSION_ID < RRDP_PHP_VERSION_REQUIRED)) ? false : true;
|
|
rrd_system__system_boolean_message('test: php version', $php_version, true);
|
|
|
|
// check state of required and optional php modules
|
|
rrd_system__system_boolean_message('test: php module \'sockets\'', extension_loaded('sockets'), true);
|
|
rrd_system__system_boolean_message('test: php module \'posix\'', extension_loaded('posix'), true);
|
|
rrd_system__system_boolean_message('test: php module \'pcntl\'', extension_loaded('pcntl'), true);
|
|
rrd_system__system_boolean_message('test: php module \'gmp\'', extension_loaded('gmp'), true);
|
|
rrd_system__system_boolean_message('test: php module \'openssl\'', extension_loaded('openssl'), true);
|
|
rrd_system__system_boolean_message('test: php module \'zlib\'', extension_loaded('zlib'), true);
|
|
|
|
exec('ulimit -n', $max_open_files);
|
|
$pid_of_php = getmypid();
|
|
exec("ls -l /proc/$pid_of_php/fd/ | wc -l", $open_files);
|
|
|
|
if ($max_open_files[0] == 'unlimited') {
|
|
$max_open_files[0] = 1048576;
|
|
}
|
|
rrd_system__system_boolean_message('test: max. number of open files [' . $max_open_files[0] . ']', $max_open_files[0], true);
|
|
rrd_system__system_boolean_message('test: max. number of connections in backlog [' . SOMAXCONN . ']', SOMAXCONN, true);
|
|
|
|
wizard_handle_output(PHP_EOL . 'Checking System Settings...', true);
|
|
// detect network interfaces
|
|
exec("ip addr show | grep -v '127.0.0.1' | grep -Po '(inet \K[\d.]+)'", $network_interfaces_4);
|
|
exec("ip addr show | grep -v '::1' | grep -v 'fe80::' | grep -Po '(inet6 \K[a-z0-9:]+)'", $network_interfaces_6);
|
|
$network_interfaces = $network_interfaces_4 + $network_interfaces_6;
|
|
|
|
rrd_system__system_boolean_message('test: network interfaces [' . __sizeof($network_interfaces) . ']', $network_interfaces, true);
|
|
|
|
$system__ipv4_supported = defined('AF_INET');
|
|
rrd_system__system_boolean_message('test: ipv4 supported', $system__ipv4_supported);
|
|
$system__ipv6_supported = defined('AF_INET6');
|
|
rrd_system__system_boolean_message('test: ipv6 supported', $system__ipv6_supported);
|
|
|
|
wizard_handle_output(PHP_EOL . 'Checking System Configurations...', true);
|
|
$system__include_folder_writeable = is_readable('./include');
|
|
rrd_system__system_boolean_message('test: Include folder is readable', $system__include_folder_writeable, true);
|
|
|
|
$system__include_folder_writeable = is_writable('./include');
|
|
rrd_system__system_boolean_message('test: Include folder is writeable', $system__include_folder_writeable, true);
|
|
|
|
$system__config = file_exists('./include/config');
|
|
|
|
if ($system__config) {
|
|
include('./include/config');
|
|
$system__config = (isset($rrdp_config) && is_array($rrdp_config));
|
|
}
|
|
rrd_system__system_boolean_message('read: RRDtool Proxy Server configuration file', $system__config);
|
|
|
|
$system__config_tmp = file_exists('./include/config.tmp');
|
|
|
|
if ($system__config_tmp) {
|
|
include('./include/config.tmp');
|
|
$system__config_tmp = (isset($rrdp_config_tmp) && is_array($rrdp_config_tmp));
|
|
}
|
|
rrd_system__system_boolean_message('read: RRDtool Proxy Server temporary configuration file', $system__config_tmp);
|
|
|
|
$system__public_key = file_exists('./include/public.key');
|
|
rrd_system__system_boolean_message('read: RSA public key', $system__public_key);
|
|
$system__private_key = file_exists('./include/private.key');
|
|
rrd_system__system_boolean_message('read: RSA private key', $system__private_key);
|
|
$system__proxies = file_exists('./include/proxies');
|
|
rrd_system__system_boolean_message('read: Server configuration', $system__proxies);
|
|
$system__clients = file_exists('./include/clients');
|
|
rrd_system__system_boolean_message('read: Client configuration', $system__clients);
|
|
|
|
$filter_options = ['options' => ['regexp' => '/[\s]*/']];
|
|
wizard_handle_input(PHP_EOL . ANSI_BOLD . 'Press ENTER to continue ...' . ANSI_RESET, FILTER_VALIDATE_REGEXP, $filter_options, false, true);
|
|
|
|
// ### -- DATA ENCRYPTION -- ####
|
|
wizard_handle_title(3,'Security');
|
|
wizard_handle_output(rrdp_get_cacti_proxy_logo(), true, true);
|
|
|
|
$microtime_start = microtime(true); // reset system start time
|
|
|
|
if ($system__public_key & $system__private_key) {
|
|
$msg = 'All RRDtool Proxy Server communications between client and proxy, or proxy and proxy, '
|
|
. 'are encrypted for security reasons. The only unencrypted connections that can be made '
|
|
. 'are to the local CLI port as these are not transmitted over the network. ' . PHP_EOL . PHP_EOL
|
|
. 'For more technical information, the data that is transmitted over the wider network is '
|
|
. 'encrypted by single-use AES keys of 192Bit with 2048Bit RSA used for the key exchange. ';
|
|
|
|
wizard_handle_output(wordwrap($msg), true, true);
|
|
|
|
$msg = 'This version of RRDtool Proxy Server does currently not have an embedded RSA key rotation '
|
|
. 'process so the same RSA key is used for the lifetime of the process.';
|
|
|
|
wizard_handle_output(wizard_prompt_wordwrap('Note', $msg, 75), true, true);
|
|
|
|
$msg = ANSI_BOLD . ANSI_GREEN_FG . '**RSA key-pair detected:'
|
|
. PHP_EOL . ' Public Key (last updated: ' . date('F d Y H:i:s', filemtime('./include/public.key')) . ')'
|
|
. PHP_EOL . ' Private Key (last updated: ' . date('F d Y H:i:s', filemtime('./include/private.key')) . ')' . ANSI_RESET;
|
|
|
|
wizard_handle_output(wordwrap($msg), true, true);
|
|
|
|
$msg = ANSI_BOLD . 'Would you like to reuse the existing RSA key-pair? [y/n]' . ANSI_RESET;
|
|
$filter_options = ['options'=>['regexp'=>'/[yYnN]/']];
|
|
$input = strtoupper(wizard_handle_input(wordwrap($msg), FILTER_VALIDATE_REGEXP, $filter_options, false, true, false, false, true));
|
|
$microtime_start = microtime(true); // reset system start time
|
|
|
|
if ($input == 'Y') {
|
|
rrd_system__system_boolean_message('load: RSA public key', true);
|
|
rrd_system__system_boolean_message('load: RSA private key', true);
|
|
$refresh_rsa_keys = false;
|
|
} else {
|
|
rrd_system__system_boolean_message('delete: Old RSA public key', unlink('./include/public.key'), true);
|
|
rrd_system__system_boolean_message('delete: Old RSA private key', unlink('./include/private.key'), true);
|
|
$refresh_rsa_keys = true;
|
|
}
|
|
} else {
|
|
$refresh_rsa_keys = true;
|
|
wizard_handle_output(wordwrap($msg), true, true);
|
|
}
|
|
|
|
if ($refresh_rsa_keys === true) {
|
|
$private = RSA::createKey(2048);
|
|
$public = $private->getPublicKey();
|
|
|
|
rrd_system__system_boolean_message('create: Generate RSA key-pair (2048Bit)', [$private, $public], true);
|
|
rrd_system__system_boolean_message(' save: New RSA key pair', rrdp_write_key_pair('./include/public.key', (string) $public, './include/private.key', (string) $private), true);
|
|
}
|
|
|
|
$filter_options = ['options' => ['regexp' => '/[\s]*/']];
|
|
wizard_handle_input(PHP_EOL . ANSI_BOLD . 'Press ENTER to continue ...' . ANSI_RESET, FILTER_VALIDATE_REGEXP, $filter_options, false, true);
|
|
|
|
// ### -- SYSTEM Parameters -- ####
|
|
wizard_handle_title(4,'System Parameters');
|
|
wizard_handle_output(rrdp_get_cacti_proxy_logo(), true, true);
|
|
|
|
$msg = 'This section allows to modify different system and connection parameters of RRDtool Proxy Server. ' . PHP_EOL . PHP_EOL
|
|
. 'If you are unsure whether you should modify a value, the default or current value may be shown in square brackets, eg [' . ANSI_BOLD . ANSI_YELLOW_FG . 'rrdp' . ANSI_RESET . ']' . PHP_EOL . PHP_EOL
|
|
. 'The shown default can be accepted by pressing ';
|
|
|
|
wizard_handle_output(wordwrap($msg) . ANSI_BOLD . ANSI_YELLOW_FG . 'ENTER' . ANSI_RESET, true, true);
|
|
|
|
if ($system__config_tmp) {
|
|
$msg = ANSI_BOLD . ANSI_GREEN_FG . '**Temporary Wizard Configuration detected:'
|
|
. PHP_EOL . ' Temporary configuration file (last updated: ' . date('F d Y H:i:s', filemtime('./include/config.tmp')) . ')' . ANSI_RESET;
|
|
|
|
wizard_handle_output(wordwrap($msg), true, true);
|
|
|
|
$msg = ANSI_BOLD . 'Would you like to reload all configuration parameters of your last wizard session? [y/n]' . ANSI_RESET;
|
|
|
|
$filter_options = ['options'=>['regexp'=>'/[yYnN]/']];
|
|
$input = strtoupper(wizard_handle_input(wordwrap($msg), FILTER_VALIDATE_REGEXP, $filter_options, false, true));
|
|
$microtime_start = microtime(true); // reset system start time
|
|
|
|
if ($input == 'Y') {
|
|
$old_config = $rrdp_config_tmp;
|
|
rrd_system__system_boolean_message('restore: Temporary session data', $old_config, true);
|
|
} else {
|
|
$status = unlink('./include/config.tmp');
|
|
rrd_system__system_boolean_message(' delete: Temporary session data', $status);
|
|
}
|
|
unset($rrdp_config_tmp);
|
|
wizard_handle_output('', true, true);
|
|
}
|
|
|
|
if ($system__config) {
|
|
$msg = ANSI_BOLD . ANSI_GREEN_FG . '**RRDtool Proxy Server - Configuration detected:'
|
|
. PHP_EOL . ' Configuration file (last updated: ' . date('F d Y H:i:s', filemtime('./include/config')) . ')' . ANSI_RESET;
|
|
|
|
wizard_handle_output(wordwrap($msg), true, true);
|
|
|
|
$msg = ANSI_BOLD . 'Would you like to reload all parameters of this configuration file? ' . ($system__config_tmp ? '(Note: This will overwrite one or more attributes of session data being restored one step before.) ' : '') . '[y/n]' . ANSI_RESET;
|
|
|
|
$filter_options = ['options'=>['regexp'=>'/[yYnN]/']];
|
|
$input = strtoupper(wizard_handle_input(wordwrap($msg), FILTER_VALIDATE_REGEXP, $filter_options, false, true));
|
|
$microtime_start = microtime(true); // reset system start time
|
|
|
|
if ($input == 'Y') {
|
|
$old_config = $rrdp_config;
|
|
rrd_system__system_boolean_message('restore: System Configuration', $old_config, true);
|
|
} else {
|
|
$status = unlink('./include/config');
|
|
rrd_system__system_boolean_message(' delete: System Configuration', $status);
|
|
}
|
|
unset($rrdp_config);
|
|
}
|
|
|
|
// neither a final nor a temporary system configuration file have been found
|
|
if (isset($old_config)) {
|
|
if (array_key_exists('version', $old_config)) {
|
|
unset($old_config['version']);
|
|
}
|
|
|
|
foreach ($old_config as $index => $value) {
|
|
if (isset($active_config[$index])) {
|
|
$active_config[$index] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
$rrdp_default_config_inputs = [
|
|
'name' => [
|
|
'msg' => 'Enter the system prompt or name that RRDtool Proxy Server should display when a user connects via the CLI port. This has a maximum of 8 characters',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|^[\s|\w]{0,8}$/',
|
|
'help' => '% Only whitespace and wording characters including underscore with a maximum of 8 chars'
|
|
]
|
|
];
|
|
|
|
if ($system__ipv4_supported & $system__ipv6_supported & __sizeof($network_interfaces_4) & __sizeof($network_interfaces_6)) {
|
|
$rrdp_default_config_inputs += [
|
|
'ip_version' => [
|
|
'msg' => 'Your system supports IPv4 as well as IPv6. Choose the protocol RRDtool Proxy Server should use for network connections',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|^(4|6)$/',
|
|
'help' => '% Please enter 4 for IPv4 or 6 for IPv6'
|
|
]
|
|
];
|
|
}
|
|
|
|
if ($system__ipv4_supported & __sizeof($network_interfaces_4)) {
|
|
$rrdp_default_config_inputs += [
|
|
'address_4' => [
|
|
'msg' => 'Following IPv4 addresses have been detected on your system:' . PHP_EOL . PHP_EOL . ' '
|
|
. implode(',', $network_interfaces_4) . PHP_EOL . PHP_EOL . ' '
|
|
. 'Using the default value \'0.0.0.0\' allows to listen to all IPv4 interfaces for incoming requests, but it is recommended to choose one of the detected IPs listed above',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|^(0.0.0.0|' . implode('|', $network_interfaces_4) . ')$/',
|
|
'help' => '% Only valid IPv4 addresses or 0.0.0.0'
|
|
]
|
|
];
|
|
}
|
|
|
|
if ($system__ipv6_supported & __sizeof($network_interfaces_6)) {
|
|
$rrdp_default_config_inputs += [
|
|
'address_6' => [
|
|
'msg' => 'Following IPv6 addresses have been detected on your system:' . PHP_EOL . PHP_EOL . ' '
|
|
. implode(',', $network_interfaces_6) . PHP_EOL . PHP_EOL . ' '
|
|
. 'Using the default value \'::\' allows to listen to all IPv6 interfaces for incoming requests, but it is recommended to choose one of the detected IPs listed above ',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|^(::|' . implode('|', $network_interfaces_6) . ')$/',
|
|
'help' => '% Only valid IPv6 addresses or ::'
|
|
],
|
|
];
|
|
}
|
|
|
|
$rrdp_default_config_inputs += [
|
|
'port_client' => [
|
|
'msg' => 'Active clients (like Cacti servers for example) have to connect to the proxy using a dedicated TCP port',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|^(102[4-9]|10[3-9]\d|1[1-9]\d{2}|[2-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/',
|
|
'help' => '% Only valid port numbers between 1024 and 65535'
|
|
],
|
|
'port_server' => [
|
|
'msg' => 'Proxy to proxy connections have to run over a separate path using a different port',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|^(102[4-9]|10[3-9]\d|1[1-9]\d{2}|[2-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/',
|
|
'help' => '% Only valid port numbers between 1024 and 65535'
|
|
],
|
|
'port_admin' => [
|
|
'msg' => 'Administrators have to connect to the proxy interface locally using a dedicated TCP port',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|^(102[4-9]|10[3-9]\d|1[1-9]\d{2}|[2-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/',
|
|
'help' => '% Only valid port numbers between 1024 and 65535'
|
|
],
|
|
'max_admin_cnn' => [
|
|
'msg' => 'Maximum number of concurrent local (admin) sessions being allowed',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|^([1-9]|10)$/',
|
|
'help' => '% Range 1 - 10'
|
|
],
|
|
'remote_cnn_timeout' => [
|
|
'msg' => 'Timeout value for client to proxy connections in seconds',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|^([1-9]|1[0-9]|2[0-9]|30)$/',
|
|
'help' => '% Range 1 - 30'
|
|
],
|
|
'logging_size_buffered' => [
|
|
'msg' => 'Maximum number of log entries this system will keep in a memory buffer',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|^([1-9]\d{3,4}|100000)$/',
|
|
'help' => '% Range 1 000 - 100 000'
|
|
],
|
|
'logging_size_snmp' => [
|
|
'msg' => 'Maximum number of notifications this system will keep in memory',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|^([1-9]\d{3,4}|100000)$/',
|
|
'help' => '% Range 1 000 - 100 000'
|
|
],
|
|
'path_rra' => [
|
|
'msg' => 'Absolute(!) path to the RRA folder containing all RRDfiles',
|
|
'filter' => FILTER_CALLBACK,
|
|
'filter_options' => 'wizard_verify_path'
|
|
],
|
|
'path_rrdtool' => [
|
|
'msg' => 'Absolute(!) path to your RRDtool binary',
|
|
'filter' => FILTER_CALLBACK,
|
|
'filter_options' => 'wizard_verify_rrdtool'
|
|
],
|
|
'enable_password' => [
|
|
'msg' => 'Enable password for CLI privileged mode',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|(?=.*[A-Z])(?=.*[0-9])(?=.*[a-z]).{8}$/',
|
|
'help' => 'Password must contain 1 upper char, 1 lower char, 1 number, 8 chars minimum',
|
|
'hide_value' => true,
|
|
'required' => true
|
|
],
|
|
];
|
|
|
|
$attribute_count = 0;
|
|
wizard_handle_settings($rrdp_default_config_inputs, $active_config, $attribute_count);
|
|
|
|
// ### -- Archive Settings -- ####
|
|
wizard_handle_title(5,'Archive Parameters');
|
|
|
|
$msg = 'RRDtool Proxy Server supports to archive RRD files automatically before '
|
|
. 'their removal. Instead of deleting the related file it will just move it '
|
|
. 'to the archive directory keeping the original subfolder structure. ';
|
|
|
|
wizard_handle_output(rrdp_get_cacti_proxy_logo(), true, true);
|
|
wizard_handle_output(wordwrap($msg), true, true);
|
|
|
|
$msg = 'The proxy only stores a single instance of an RRD file. Multiple instances '
|
|
. 'will not be supported.';
|
|
|
|
wizard_handle_output(wizard_prompt_wordwrap('Note', $msg, 75), true, true);
|
|
|
|
$msg = 'If you are running a proxy server cluster then this feature MUST be enabled '
|
|
. 'or disabled on every peer, because the archive itself will be included in '
|
|
. 'the replication process.';
|
|
|
|
wizard_handle_output(wizard_prompt_wordwrap('Warning', $msg, 75), true, true);
|
|
|
|
$msg = ANSI_BOLD . 'Would you like to archive decommissioned RRD files automatically? [y/n]' . ANSI_RESET;
|
|
|
|
$filter_options = ['options'=>['regexp'=>'/[yYnN]/']];
|
|
$input = strtoupper(wizard_handle_input(wordwrap($msg), FILTER_VALIDATE_REGEXP, $filter_options, false, true));
|
|
|
|
if ($input == 'Y') {
|
|
$rrdp_archive_config_inputs = [
|
|
'path_rra_archive' => [
|
|
'msg' => 'Absolute(!) path to the RRA ARCHIVE folder containing all archived RRDfiles',
|
|
'filter' => FILTER_CALLBACK,
|
|
'filter_options' => 'wizard_verify_path'
|
|
],
|
|
];
|
|
|
|
wizard_handle_settings($rrdp_archive_config_inputs, $active_config, $attribute_count, 'Archive');
|
|
} else {
|
|
$active_config['path_rra_archive'] = '';
|
|
file_put_contents('./include/config.tmp', '<?php $rrdp_config_tmp = ' . var_export($active_config, true) . ';');
|
|
}
|
|
|
|
$filter_options = ['options' => ['regexp' => '/[\s]*/']];
|
|
wizard_handle_input(ANSI_BOLD . 'Press ENTER to save new system configuration ...' . ANSI_RESET, FILTER_VALIDATE_REGEXP, $filter_options, false, true);
|
|
$microtime_start = microtime(true); // reset system start time
|
|
rrd_system__system_boolean_message(' save: New system configuration', file_put_contents('./include/config', '<?php $rrdp_config = ' . var_export($active_config, true) . ';'), true);
|
|
rrd_system__system_boolean_message(' delete: Temporary session data', unlink('./include/config.tmp'), true);
|
|
|
|
$filter_options = ['options' => ['regexp' => '/[\s]*/']];
|
|
wizard_handle_input(PHP_EOL . ANSI_BOLD . 'Press ENTER to continue ...' . ANSI_RESET, FILTER_VALIDATE_REGEXP, $filter_options, false, true);
|
|
|
|
// ### -- Cache Settings -- ####
|
|
wizard_handle_title(6,'Cached Parameters');
|
|
wizard_handle_output(rrdp_get_cacti_proxy_logo(), true, true);
|
|
|
|
$msg = 'If you are NOT making use of any other I/O cache like Cacti\'s \'Boost\' process, '
|
|
. 'you can setup the proxy to manage its own cache using a daemon called '
|
|
. '\'RRDcached\' that comes with RRDtool itself. ' . PHP_EOL . PHP_EOL
|
|
. 'Using RRDcached can dramatically reduce the I/O load of your file '
|
|
. 'system and avoid, if configured correctly, RRD files from being '
|
|
. 'updated with every update command that is received.';
|
|
|
|
wizard_handle_output(wordwrap($msg), true, true);
|
|
|
|
$msg = 'RRDcached does NOT SUPPORT updates in combination with both RRDtool flags \'--skip-past-updates\' '
|
|
. 'and \'templates\'. If you are using the RRDtool Proxy Server as data backend for Cacti, '
|
|
. 'it is strongly recommended to leave this add-on being disabled.';
|
|
|
|
wizard_handle_output(wizard_prompt_wordwrap('Warning', $msg, 75), true, true);
|
|
|
|
$msg = ANSI_BOLD . 'Would you like to enable the use of RRDCached? [y/n]' . ANSI_RESET;
|
|
|
|
$filter_options = ['options'=>['regexp'=>'/[yYnN]/']];
|
|
$input = strtoupper(wizard_handle_input(wordwrap($msg), FILTER_VALIDATE_REGEXP, $filter_options, false, true));
|
|
|
|
if ($input == 'Y') {
|
|
$rrdp_rrdcached_config_inputs = [
|
|
'path_rrdcached' => [
|
|
'msg' => 'Absolute(!) path to your RRDCached binary',
|
|
'filter' => FILTER_CALLBACK,
|
|
'filter_options' => 'wizard_verify_rrdcached'
|
|
],
|
|
'rrdcache_update_cycle' => [
|
|
'msg' => 'Threshold in seconds measurements of an active polling object will be kept in memory. If breached related cached data will be written to disk.',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|^([1-9]\d{1,3}|10000)$/',
|
|
'help' => '% Range 10 - 10 000'
|
|
],
|
|
'rrdcache_update_delay' => [
|
|
'msg' => 'A random delay between 0 and x seconds RRDcache will delay writing of each RRD to avoid too many writes being queued simultaneously. By default, there is no delay',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|^(0|[1-9]\d{1}|10)$/',
|
|
'help' => '% Range 0 - 10'
|
|
],
|
|
'rrdcache_life_cycle' => [
|
|
'msg' => 'Define the number of seconds RRDcached should scan the complete cache to ensure that measurements no longer being updated will be written to disk. This process is CPU intensive and should not be executed too often. Due to that reason a high value of e.g. 7200 is acceptable in most cases',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|^([1-9]\d{3}|10000)$/',
|
|
'help' => '% Range 1 000 - 10 000'
|
|
],
|
|
'rrdcache_write_threads' => [
|
|
'msg' => 'Specify the number of write threads used for writing RRD files. Increasing this number will allow RRDCached to have more simultaneous I/O requests into the kernel. Default value is 4',
|
|
'filter' => FILTER_VALIDATE_REGEXP,
|
|
'pattern' => '/^$|^([1-9]\d{1}|10)$/',
|
|
'help' => '% Range 1 - 10'
|
|
],
|
|
];
|
|
|
|
wizard_handle_settings($rrdp_rrdcached_config_inputs, $active_config, $attribute_count, 'RRDcached');
|
|
} else {
|
|
$active_config['path_rrdcached'] = '';
|
|
file_put_contents('./include/config.tmp', '<?php $rrdp_config_tmp = ' . var_export($active_config, true) . ';');
|
|
}
|
|
|
|
$filter_options = ['options' => ['regexp' => '/[\s]*/']];
|
|
wizard_handle_input(ANSI_BOLD . 'Press ENTER to save new system configuration ...' . ANSI_RESET, FILTER_VALIDATE_REGEXP, $filter_options, false, true);
|
|
$microtime_start = microtime(true); // reset system start time
|
|
rrd_system__system_boolean_message(' save: New system configuration', file_put_contents('./include/config', '<?php $rrdp_config = ' . var_export($active_config, true) . ';'), true);
|
|
rrd_system__system_boolean_message(' delete: Temporary session data', unlink('./include/config.tmp'), true);
|
|
|
|
$filter_options = ['options' => ['regexp' => '/[\s]*/']];
|
|
wizard_handle_input(PHP_EOL . ANSI_BOLD . 'Press ENTER to continue ...' . ANSI_RESET, FILTER_VALIDATE_REGEXP, $filter_options, false, true);
|
|
|
|
// ### -- Client Connections -- ####
|
|
wizard_handle_title(7,'Trusted Clients');
|
|
wizard_handle_output(rrdp_get_cacti_proxy_logo(), true, true);
|
|
|
|
$msg = 'This version does not support the automatic registration of trusted clients, these have '
|
|
. 'to be added manually using either this wizard or the admin CLI console. To allow a '
|
|
. 'client permission to access RRDs through the RRDtool Proxy Server, its IP address and '
|
|
. 'RSA fingerprint must be registered.';
|
|
|
|
wizard_handle_output(wordwrap($msg), true, true);
|
|
|
|
$msg = 'Cacti shows its RSA fingerprint in the web console and can be found by navigating '
|
|
. 'to Console -> Utilities -> System Utilities, then looking for RSA Fingerprint on '
|
|
. 'the summary tab';
|
|
|
|
wizard_handle_output(wizard_prompt_wordwrap('Example', $msg, 75), true, true);
|
|
|
|
$msg = 'Other RRDproxies can be registered in Step 8 and should not be included here as it '
|
|
. 'will not be used.';
|
|
|
|
wizard_handle_output(wizard_prompt_wordwrap('Note', $msg, 75), true, true);
|
|
|
|
$msg = 'Within the admin CLI console, it is possible to add/remove other clients '
|
|
. 'dynamically. For example, \'set client add <IP> <Port> <Fingerprint>\'';
|
|
|
|
wizard_handle_output(wizard_prompt_wordwrap('Tip', $msg, 75), true, true);
|
|
|
|
if ($system__clients) {
|
|
$msg = ANSI_BOLD . ANSI_GREEN_FG . '**RRDtool Proxy Server - Trusted Clients detected:'
|
|
. PHP_EOL . ' Configuration file (last updated: ' . date('F d Y H:i:s', filemtime('./include/clients')) . ')' . ANSI_RESET;
|
|
|
|
wizard_handle_output(wordwrap($msg), true, true);
|
|
|
|
$msg = ANSI_BOLD . 'Would you like to reuse all entries of this configuration file? [y/n]' . ANSI_RESET;
|
|
|
|
$filter_options = ['options'=>['regexp'=>'/[yYnN]/']];
|
|
$input = strtoupper(wizard_handle_input(wordwrap($msg), FILTER_VALIDATE_REGEXP, $filter_options, false, true));
|
|
$microtime_start = microtime(true); // reset system start time
|
|
|
|
if ($input == 'Y') {
|
|
include_once('./include/clients');
|
|
rrd_system__system_boolean_message('load: Trusted Client Connections', isset($rrdp_remote_clients), true);
|
|
|
|
if (__sizeof($rrdp_remote_clients) > 0) {
|
|
wizard_handle_output('', true, true);
|
|
$output = sprintf(' %-27s %-12s ' . PHP_EOL, 'IP Address', 'Fingerprint');
|
|
|
|
foreach ($rrdp_remote_clients as $ip => $fingerprint) {
|
|
$output .= sprintf(" %-27s %s \r\n", $ip, $fingerprint);
|
|
}
|
|
wizard_handle_output($output, true);
|
|
}
|
|
} else {
|
|
$status = unlink('./include/clients');
|
|
rrd_system__system_boolean_message(' delete: Trusted Client Connections', $status);
|
|
}
|
|
}
|
|
|
|
$filter_options = ['options'=>['regexp'=>'/[yYnN]/']];
|
|
|
|
while (1) {
|
|
$msg = 'Would you like to add ' . (isset($rrdp_remote_clients) ? 'another ' : 'a new ') . 'trusted client connection ? [y/n]';
|
|
$input = strtoupper(wizard_handle_input(wordwrap($msg), FILTER_VALIDATE_REGEXP, $filter_options, false, true));
|
|
|
|
if (!isset($rrdp_remote_clients)) {
|
|
$rrdp_remote_clients = [];
|
|
}
|
|
|
|
if ($input == 'N') {
|
|
$microtime_start = microtime(true); // reset system start time
|
|
rrd_system__system_boolean_message(' save: New client configuration', file_put_contents('./include/clients', '<?php $rrdp_remote_clients = ' . var_export($rrdp_remote_clients, true) . ';'), true);
|
|
|
|
if (!__sizeof($rrdp_remote_clients) > 0) {
|
|
$msg = ANSI_BOLD . ANSI_YELLOW_FG . '**Warning: You haven\'t defined any trusted client connection yet. External systems will not be able to connect to the proxy.' . ANSI_RESET;
|
|
|
|
wizard_handle_output(wordwrap($msg), true, true);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if ($input == 'Y') {
|
|
$client_ip = wizard_handle_input('IP:', FILTER_VALIDATE_IP, false, '% Invalid IPv4 or IPv6 address format');
|
|
$client_fingerprint = wizard_handle_input('Fingerprint:', FILTER_VALIDATE_REGEXP, ['options'=>['regexp'=>'/^([a-z0-9]{2}:){15}([a-z0-9]{2})$/']], '% Invalid Fingerprint [expected: xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx]');
|
|
$rrdp_remote_clients[$client_ip] = $client_fingerprint;
|
|
}
|
|
}
|
|
|
|
$filter_options = ['options' => ['regexp' => '/[\s]*/']];
|
|
wizard_handle_input(ANSI_BOLD . 'Press ENTER to continue ...' . ANSI_RESET, FILTER_VALIDATE_REGEXP, $filter_options, false, true);
|
|
|
|
// ### -- Proxy-2-Proxy Connections -- ####
|
|
wizard_handle_title(8,'Trusted Proxies');
|
|
wizard_handle_output(rrdp_get_cacti_proxy_logo(), true, true);
|
|
|
|
$msg = 'This version does not support the automatic registration of new proxy cluster members - these have to be defined manually. '
|
|
. 'To decide whether a cluster member has the correct permissions its IP address as well as the fingerprint of its current public RSA key has to be registered to the RRDtool Proxy Server.';
|
|
|
|
wizard_handle_output(wordwrap($msg), true, true);
|
|
|
|
$msg = 'Within the admin CLI console, it is possible to add/remove other proxies '
|
|
. 'dynamically. For example, \'set proxy add <IP> <Port> <Fingerprint>\'';
|
|
|
|
wizard_handle_output(wizard_prompt_wordwrap('Tip', $msg, 75), true, true);
|
|
|
|
if ($system__proxies) {
|
|
$msg = ANSI_BOLD . ANSI_GREEN_FG . '**RRDtool Proxy Server - Trusted Proxies detected:'
|
|
. PHP_EOL . ' Configuration file (last updated: ' . date('F d Y H:i:s', filemtime('./include/proxies')) . ')' . ANSI_RESET;
|
|
|
|
wizard_handle_output(wordwrap($msg), true, true);
|
|
|
|
$msg = ANSI_BOLD . 'Would you like to reuse all entries of this configuration file? [y/n]' . ANSI_RESET;
|
|
$filter_options = ['options'=>['regexp'=>'/[yYnN]/']];
|
|
$input = strtoupper(wizard_handle_input(wordwrap($msg), FILTER_VALIDATE_REGEXP, $filter_options, false, true));
|
|
$microtime_start = microtime(true); // reset system start time
|
|
|
|
if ($input == 'Y') {
|
|
include_once('./include/proxies');
|
|
rrd_system__system_boolean_message('load: Trusted Proxy Connections', isset($rrdp_remote_proxies), true);
|
|
|
|
if (__sizeof($rrdp_remote_proxies) > 0) {
|
|
wizard_handle_output('', true, true);
|
|
$output = sprintf(' %-27s %-12s %-12s' . PHP_EOL, 'IP Address', 'Port', 'Fingerprint');
|
|
|
|
foreach ($rrdp_remote_proxies as $ip => $params) {
|
|
$output .= sprintf(" %-27s %-12s %-12s \r\n", $ip, $params['port'], $params['fingerprint']);
|
|
}
|
|
wizard_handle_output($output, true);
|
|
}
|
|
} else {
|
|
$status = unlink('./include/proxies');
|
|
rrd_system__system_boolean_message(' delete: Trusted Proxy Connections', $status);
|
|
}
|
|
wizard_handle_output('', true, true);
|
|
}
|
|
|
|
$filter_options = ['options'=>['regexp'=>'/[yYnN]/']];
|
|
|
|
while (1) {
|
|
$msg = 'Would you like to add ' . (isset($rrdp_remote_proxies) ? 'another ' : 'a new ') . 'trusted proxy connection ? [y/n]';
|
|
$input = strtoupper(wizard_handle_input(wordwrap($msg), FILTER_VALIDATE_REGEXP, $filter_options, false, true));
|
|
|
|
if (!isset($rrdp_remote_proxies)) {
|
|
$rrdp_remote_proxies = [];
|
|
}
|
|
|
|
if ($input == 'N') {
|
|
$microtime_start = microtime(true); // reset system start time
|
|
rrd_system__system_boolean_message(' save: New proxy configuration', file_put_contents('./include/proxies', '<?php $rrdp_remote_proxies = ' . var_export($rrdp_remote_proxies, true) . ';'), true);
|
|
wizard_handle_output('', true);
|
|
|
|
break;
|
|
}
|
|
|
|
if ($input == 'Y') {
|
|
$client_ip = wizard_handle_input('IP:', FILTER_VALIDATE_IP, false, '% Invalid IPv4 or IPv6 address format');
|
|
$client_port = wizard_handle_input('PORT:', FILTER_VALIDATE_REGEXP, ['options'=>['regexp'=>'/^$|^(102[4-9]|10[3-9]\d|1[1-9]\d{2}|[2-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/']], '% Invalid PORT. Range: [1024-65535]');
|
|
$client_fingerprint = wizard_handle_input('Fingerprint:', FILTER_VALIDATE_REGEXP, ['options'=>['regexp'=>'/^([a-z0-9]{2}:){15}([a-z0-9]{2})$/']], '% Invalid Fingerprint [expected: xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx]');
|
|
$rrdp_remote_proxies[$client_ip] = ['port' => $client_port, 'fingerprint' => $client_fingerprint];
|
|
}
|
|
}
|
|
|
|
$filter_options = ['options' => ['regexp' => '/[\s]*/']];
|
|
wizard_handle_input(PHP_EOL . ANSI_BOLD . 'Press ENTER to continue ...' . ANSI_RESET, FILTER_VALIDATE_REGEXP, $filter_options, false, true);
|
|
|
|
wizard_handle_title(9, 'Finished');
|
|
wizard_handle_output(rrdp_get_cacti_proxy_logo(), true, true);
|
|
|
|
$msg = 'RRDtool Proxy Server v' . RRDP_VERSION . ' configuration has now been updated.' . PHP_EOL . PHP_EOL
|
|
. 'You may now ' . (is_rrdtool_proxy_running() ? 're' : '') . 'start RRDtool Proxy Server '
|
|
. 'to pick up the new configuration. Once started, you may telnet to the admin CLI port via '
|
|
. 'telnet.';
|
|
|
|
wizard_handle_output(wordwrap($msg), true, true);
|
|
|
|
$msg = 'telnet 127.0.0.1 ' . $active_config['port_admin'] . PHP_EOL;
|
|
|
|
wizard_handle_output(wizard_prompt_wordwrap('Example', $msg, 75), true, true);
|
|
}
|
|
|
|
/**
|
|
* Validates that $path (or the configured RRDtool path) is an absolute,
|
|
* executable RRDtool binary of at least version 1.5, printing an error and
|
|
* returning false otherwise.
|
|
*
|
|
* @param string $path
|
|
*
|
|
* @return string|false
|
|
*/
|
|
function wizard_verify_rrdtool($path) {
|
|
global $active_config;
|
|
|
|
$valid_path = false;
|
|
$msg = 'Invalid RRDtool path.';
|
|
|
|
if (!$path) {
|
|
$path = $active_config['path_rrdtool'];
|
|
}
|
|
|
|
// Get RRDtool version
|
|
if (strpos($path, '.') === 0) {
|
|
$msg = 'Path is not absolute.';
|
|
} elseif (!file_exists($path)) {
|
|
$msg = 'File not found';
|
|
} elseif (!is_executable($path)) {
|
|
$msg = 'File is not executable.';
|
|
} else {
|
|
$out_array = [];
|
|
exec(escapeshellcmd($path) . ' 2>/dev/null', $out_array);
|
|
|
|
if (__sizeof($out_array) > 0) {
|
|
if (strpos($out_array[0], 'RRDtool') !== 0) {
|
|
$msg = 'Invalid output.';
|
|
} else {
|
|
if (preg_match('/^RRDtool ([1-9]\.[0-9])/', $out_array[0], $m)) {
|
|
if (version_compare($m[1], '1.5', '>=')) {
|
|
$valid_path = true;
|
|
} else {
|
|
$msg = 'RRDtool version 1.5 or above required.';
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$msg = 'Invalid output.';
|
|
}
|
|
}
|
|
|
|
if ($valid_path) {
|
|
return $path;
|
|
} else {
|
|
wizard_handle_output(ANSI_RESET . ANSI_RED_FG . "%$msg" . ANSI_RESET . PHP_EOL);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates that $path (or the configured rrdcached path) is an absolute,
|
|
* executable file, printing an error and returning false otherwise.
|
|
*
|
|
* @param string $path
|
|
*
|
|
* @return string|false
|
|
*/
|
|
function wizard_verify_rrdcached($path) {
|
|
global $active_config;
|
|
|
|
$valid_path = false;
|
|
$msg = 'Invalid RRDCached path.';
|
|
|
|
if (!$path) {
|
|
$path = $active_config['path_rrdcached'];
|
|
}
|
|
|
|
// Get RRDtool version
|
|
if (strpos($path, '.') === 0) {
|
|
$msg = 'Path is not absolute.';
|
|
} elseif (!file_exists($path)) {
|
|
$msg = 'File not found';
|
|
} elseif (!is_executable($path)) {
|
|
$msg = 'File is not executable.';
|
|
} else {
|
|
$valid_path = true;
|
|
}
|
|
|
|
if ($valid_path) {
|
|
return $path;
|
|
} else {
|
|
wizard_handle_output(ANSI_RESET . ANSI_RED_FG . "%$msg" . ANSI_RESET . PHP_EOL);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates that $path (or the configured RRA path) is an absolute, readable,
|
|
* writable directory, printing an error and returning false otherwise.
|
|
*
|
|
* @param string $path
|
|
*
|
|
* @return string|false
|
|
*/
|
|
function wizard_verify_path($path) {
|
|
global $active_config;
|
|
|
|
$valid_path = false;
|
|
|
|
if (!$path) {
|
|
$path = $active_config['path_rra'];
|
|
}
|
|
|
|
if (strpos($path, '.') === 0) {
|
|
$msg = 'Path is not absolute.';
|
|
} elseif (!is_dir($path)) {
|
|
$msg = 'Unknown directory.';
|
|
} elseif (!is_readable($path)) {
|
|
$msg = 'Directory is not readable.';
|
|
} elseif (!is_writable($path)) {
|
|
$msg = 'Directory is not writeable.';
|
|
} else {
|
|
$valid_path = true;
|
|
}
|
|
|
|
if ($valid_path) {
|
|
return $path;
|
|
} else {
|
|
wizard_handle_output(ANSI_RESET . ANSI_RED_FG . "%$msg" . ANSI_RESET . PHP_EOL);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes a wizard prompt/message to STDOUT, optionally clearing the screen first
|
|
* and/or surrounding the message with newlines.
|
|
*
|
|
* @param string $msg
|
|
* @param bool $new_line
|
|
* @param bool $new_line_after
|
|
* @param bool $clear_commandline_screen
|
|
*
|
|
* @return void
|
|
*/
|
|
function wizard_handle_output($msg, $new_line = false, $new_line_after = false, $clear_commandline_screen = false) {
|
|
fwrite(STDOUT, ($clear_commandline_screen ? ANSI_ERASE_SCREEN . ANSI_ERASE_BUFFER . ANSI_POS_TOP_LEFT : '') . ($new_line ? PHP_EOL : '') . $msg . ($new_line_after ? PHP_EOL : ''));
|
|
}
|
|
|
|
/**
|
|
* Prompts for and reads a line from STDIN, re-prompting (with an optional help
|
|
* message) until the input passes the given filter_var() filter or, if
|
|
* $require_value is set, is non-empty.
|
|
*
|
|
* @param string $msg
|
|
* @param int $filter
|
|
* @param array<mixed>|false $filter_options
|
|
* @param string|false $filter_help_msg
|
|
* @param bool $new_line
|
|
* @param bool $new_line_after
|
|
* @param bool $clear_commandline_screen
|
|
* @param bool $require_value
|
|
*
|
|
* @return mixed
|
|
*/
|
|
function wizard_handle_input($msg, $filter = FILTER_DEFAULT, $filter_options = false, $filter_help_msg = false, $new_line = false, $new_line_after = false, $clear_commandline_screen = false, $require_value = false) {
|
|
$filtered_value = false;
|
|
|
|
while ($filtered_value === false) {
|
|
wizard_handle_output($msg . ' ', $new_line, $new_line_after, $clear_commandline_screen);
|
|
|
|
$input = trim(fgets(STDIN));
|
|
$filtered_value = filter_var($input, $filter, $filter_options);
|
|
|
|
if ($require_value && empty($filtered_value)) {
|
|
$filtered_value = false;
|
|
}
|
|
|
|
if ($filtered_value !== false) {
|
|
return $filtered_value;
|
|
} else {
|
|
if ($filter_help_msg) {
|
|
wizard_handle_output(ANSI_RESET . ANSI_RED_FG . "$filter_help_msg" . ANSI_RESET . PHP_EOL);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Builds the next lettered attribute prompt (a, b, ... z, aa, ab, ...), advancing
|
|
* $attribute_count for the following call.
|
|
*
|
|
* @param int $attribute_count
|
|
*
|
|
* @return array{text: string, ansi: string, padding: string}
|
|
*/
|
|
function wizard_get_prompt(&$attribute_count) {
|
|
$attr_part1 = $attribute_count % 26;
|
|
$attr_part2 = ($attribute_count - $attr_part1) / 26;
|
|
|
|
$attr_prompt['text'] = ($attr_part2 == 0 ? ' ' : chr($attr_part2 + ord('a'))) . chr($attr_part1 + ord('a')) . ') ';
|
|
$attr_prompt['ansi'] = ANSI_BOLD . ANSI_GREEN_FG . $attr_prompt['text'] . ANSI_RESET;
|
|
$attr_prompt['padding'] = str_repeat(' ', strlen($attr_prompt['text']));
|
|
|
|
$attribute_count = $attribute_count + 1;
|
|
|
|
return $attr_prompt;
|
|
}
|
|
|
|
/**
|
|
* Prints the wizard's page banner/title bar (e.g. "Page 2/9") for the given step.
|
|
*
|
|
* @param int $page
|
|
* @param string $title
|
|
*
|
|
* @return void
|
|
*/
|
|
function wizard_handle_title($page, $title = '') {
|
|
$title_prefix = ' RRDtool Proxy Server Wizard';
|
|
|
|
if (!empty($title)) {
|
|
$title_prefix .= ' - ' . $title;
|
|
}
|
|
|
|
$title_suffix = "$page/9 ";
|
|
$title_spaces = strlen($title_prefix) + strlen($title_suffix);
|
|
|
|
if ($title_spaces > 75) {
|
|
$title_prefix = substr($title_prefix, 1, 75 - strlen($title_suffix));
|
|
$title_spaces = 75;
|
|
}
|
|
|
|
wizard_handle_output(ANSI_BOLD . ANSI_YELLOW_FG . ANSI_BLUE_BG . $title_prefix .
|
|
str_repeat(' ', 80 - $title_spaces) . $title_suffix . ANSI_RESET, false, true, true);
|
|
}
|
|
/**
|
|
* Prompts for and applies a group of configuration settings, updating
|
|
* $active_config in place, persisting a temporary config file after each
|
|
* accepted value, and advancing the shared $attribute_count prompt counter.
|
|
*
|
|
* @param array<string, array<string, mixed>> $inputs
|
|
* @param array<string, mixed> $active_config
|
|
* @param int $attribute_count
|
|
* @param string $title
|
|
*
|
|
* @return void
|
|
*/
|
|
function wizard_handle_settings($inputs, &$active_config, &$attribute_count, $title = '') {
|
|
if (empty($title)) {
|
|
$title = '';
|
|
} else {
|
|
$title .= ' ';
|
|
}
|
|
|
|
wizard_handle_output(PHP_EOL . PHP_EOL . ANSI_BOLD . "RRDtool Proxy Server - {$title}Settings:" . ANSI_RESET . PHP_EOL);
|
|
|
|
foreach ($active_config as $attribute => $value) {
|
|
if (isset($inputs[$attribute])) {
|
|
if (isset($inputs[$attribute]['filter_options'])) {
|
|
$filter_options['options'] = $inputs[$attribute]['filter_options'];
|
|
} else {
|
|
$filter_options = [];
|
|
|
|
if ($inputs[$attribute]['filter'] == FILTER_VALIDATE_REGEXP) {
|
|
$filter_options = ['options'=>['regexp'=>$inputs[$attribute]['pattern']]];
|
|
}
|
|
}
|
|
|
|
$attr_prompt = wizard_get_prompt($attribute_count);
|
|
$attr_suffix = !empty($inputs[$attribute]['hide_value']) ? ':' : PHP_EOL . PHP_EOL . $attr_prompt['padding'] . sprintf('[' . ANSI_BOLD . ANSI_YELLOW_FG . '%s' . ANSI_RESET . ']: ', $value);
|
|
$input = wizard_handle_input($attr_prompt['ansi'] . wordwrap($inputs[$attribute]['msg'], 75, PHP_EOL . $attr_prompt['padding']) . $attr_suffix, $inputs[$attribute]['filter'], $filter_options, (isset($inputs[$attribute]['help']) ? $inputs[$attribute]['help'] : false), true, false, false, !empty($inputs[$attribute]['required']));
|
|
|
|
if ($attribute == 'enable_password' && !empty($input)) {
|
|
$input = password_hash($input, PASSWORD_DEFAULT);
|
|
}
|
|
|
|
if ($input) {
|
|
$active_config[$attribute] = $input;
|
|
}
|
|
|
|
// Is this the IP version attribute? (eg, IPv4 or IPv6)
|
|
if ($attribute == 'ip_version') {
|
|
// Unset the address for the protocol we are not using
|
|
unset($inputs['address_' . (($active_config[$attribute] == 4) ? 6 : 4)]);
|
|
}
|
|
|
|
file_put_contents('./include/config.tmp', '<?php $rrdp_config_tmp = ' . var_export($active_config, true) . ';');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Word-wraps $msg to $length columns, indenting wrapped lines to align under an
|
|
* optional bold/colored "$prompt: " prefix.
|
|
*
|
|
* @param string $prompt
|
|
* @param string $msg
|
|
* @param int $length
|
|
*
|
|
* @return string
|
|
*/
|
|
function wizard_prompt_wordwrap($prompt, $msg, $length) {
|
|
$prompt = (empty($prompt) ? '' : $prompt . ': ');
|
|
$prompt_len = strlen($prompt);
|
|
|
|
return ANSI_BOLD . ANSI_YELLOW_FG . $prompt . ANSI_RESET .
|
|
wordwrap($msg, $length - $prompt_len, PHP_EOL . str_repeat(' ', $prompt_len));
|
|
}
|