diff --git a/extras/pandora_gotty/.gitattributes b/extras/pandora_gotty/.gitattributes new file mode 100644 index 0000000000..ca0a55fe2e --- /dev/null +++ b/extras/pandora_gotty/.gitattributes @@ -0,0 +1 @@ +src/pandora_gotty filter=lfs diff=lfs merge=lfs -text diff --git a/extras/pandora_gotty/.gitignore b/extras/pandora_gotty/.gitignore new file mode 100644 index 0000000000..db388678f7 --- /dev/null +++ b/extras/pandora_gotty/.gitignore @@ -0,0 +1,2 @@ +*.rpm +*.deb \ No newline at end of file diff --git a/extras/pandora_gotty/Dockerfile-RPM b/extras/pandora_gotty/Dockerfile-RPM new file mode 100644 index 0000000000..f708a6f4a7 --- /dev/null +++ b/extras/pandora_gotty/Dockerfile-RPM @@ -0,0 +1,8 @@ +FROM rockylinux:8 + +RUN dnf install -y rpm-build rpmdevtools +RUN rpmdev-setuptree +RUN mkdir /root/pandora_gotty +WORKDIR /root/pandora_gotty + +#CMD bash build.sh diff --git a/extras/pandora_gotty/Dockerfile-deb b/extras/pandora_gotty/Dockerfile-deb new file mode 100644 index 0000000000..02a30a43d3 --- /dev/null +++ b/extras/pandora_gotty/Dockerfile-deb @@ -0,0 +1,6 @@ +FROM ubuntu:22.04 + +RUN apt-get update +RUN apt-get install -y dh-make debhelper build-essential +RUN mkdir /root/pandora_gotty +WORKDIR /root/pandora_gotty \ No newline at end of file diff --git a/extras/pandora_gotty/README.md b/extras/pandora_gotty/README.md new file mode 100644 index 0000000000..2e261c2e53 --- /dev/null +++ b/extras/pandora_gotty/README.md @@ -0,0 +1 @@ +To create the .deb and .rpm package need to hace docker installed on main system and execit `build_all_docker.sh` \ No newline at end of file diff --git a/extras/pandora_gotty/build_all_docker.sh b/extras/pandora_gotty/build_all_docker.sh new file mode 100755 index 0000000000..cb280999ba --- /dev/null +++ b/extras/pandora_gotty/build_all_docker.sh @@ -0,0 +1,11 @@ +#Build RPM +docker build -t pandora_gotty_builder_rpm -f Dockerfile-RPM . || exit 1 +docker run --rm -it -v `pwd`:/root/pandora_gotty pandora_gotty_builder_rpm /root/pandora_gotty/build_rpm.sh || exit 1 + +#Buikd DEB +docker build -t pandora_gotty_builder_deb -f Dockerfile-deb . || exit 1 +docker run --rm -it -v `pwd`:/root/pandora_gotty pandora_gotty_builder_deb /root/pandora_gotty/build_deb.sh || exit 1 + +echo " - Done" +pwd +ls -l | grep -E "(\.deb|\.rpm)" \ No newline at end of file diff --git a/extras/pandora_gotty/build_deb.sh b/extras/pandora_gotty/build_deb.sh new file mode 100755 index 0000000000..fbcb2a6790 --- /dev/null +++ b/extras/pandora_gotty/build_deb.sh @@ -0,0 +1,17 @@ +#!/bin/bash +#DEB +cd deb +VERSION=$(grep 'Version:' pandora_gotty/DEBIAN/control | awk '{print $2}') +mkdir -p pandora_gotty/usr/bin +mkdir -p pandora_gotty/etc/pandora_gotty +cp -a ../src/pandora_gotty pandora_gotty/usr/bin +cp -a ../src/pandora_gotty.conf pandora_gotty/etc/pandora_gotty +curl -SsL --output pandora_gotty/usr/bin/pandora_gotty_exec http://192.168.50.31/installers/installers/Linux/x86_64/pandora_gotty_exec +chmod +x pandora_gotty/usr/bin/pandora_gotty_exec +dpkg-deb --build pandora_gotty +mv pandora_gotty.deb ../ +rm -rf pandora_gotty/usr/ +rm -rf pandora_gotty/etc/ +cd .. +mv pandora_gotty.deb pandora_gotty_${VERSION}.deb +chmod 777 pandora_gotty_${VERSION}.deb diff --git a/extras/pandora_gotty/build_rpm.sh b/extras/pandora_gotty/build_rpm.sh new file mode 100755 index 0000000000..e08fa0e65b --- /dev/null +++ b/extras/pandora_gotty/build_rpm.sh @@ -0,0 +1,15 @@ +#!/bin/bash +#RPM +VERSION=$(grep '%define version' pandora_gotty.spec | awk '{print $3}') +mkdir -p pandora_gotty-${VERSION} +cp src/pandora_gotty pandora_gotty-${VERSION}/ +cp src/pandora_gotty.conf pandora_gotty-${VERSION}/ +curl -SsL --output pandora_gotty-${VERSION}/pandora_gotty_exec http://192.168.50.31/installers/installers/Linux/x86_64/pandora_gotty_exec +chmod +x pandora_gotty-${VERSION}/pandora_gotty_exec +tar -cvzf pandora_gotty-${VERSION}.tar.gz pandora_gotty-${VERSION}/* +mv pandora_gotty-${VERSION}.tar.gz ${HOME}/rpmbuild/SOURCES/ +rm -rf ${HOME}/rpmbuild/RPMS/x86_64/pandora_gotty* +rpmbuild -ba pandora_gotty.spec +rm -rf pandora_gotty-${VERSION} +mv ${HOME}/rpmbuild/RPMS/x86_64/pandora_gotty* . +chmod 777 *.rpm \ No newline at end of file diff --git a/extras/pandora_gotty/deb/.gitignore b/extras/pandora_gotty/deb/.gitignore new file mode 100644 index 0000000000..92a9f94179 --- /dev/null +++ b/extras/pandora_gotty/deb/.gitignore @@ -0,0 +1,3 @@ +*.deb +**/usr +**/etc diff --git a/extras/pandora_gotty/deb/pandora_gotty/DEBIAN/changelog b/extras/pandora_gotty/deb/pandora_gotty/DEBIAN/changelog new file mode 100644 index 0000000000..5d7e7f2ab6 --- /dev/null +++ b/extras/pandora_gotty/deb/pandora_gotty/DEBIAN/changelog @@ -0,0 +1,5 @@ +pandora_gotty (1.0-1) stable; urgency=low + + * Initial release. + + -- PandoraFMS Mon, 18 Sep 2023 00:00:00 +0000 \ No newline at end of file diff --git a/extras/pandora_gotty/deb/pandora_gotty/DEBIAN/control b/extras/pandora_gotty/deb/pandora_gotty/DEBIAN/control new file mode 100644 index 0000000000..f715ee1b49 --- /dev/null +++ b/extras/pandora_gotty/deb/pandora_gotty/DEBIAN/control @@ -0,0 +1,9 @@ +Source: pandora_gotty +Section: utils +Version: 1.0.0 +Priority: optional +Maintainer: PandoraFMS +Build-Depends: debhelper (>= 12) +Package: pandora-gotty +Architecture: amd64 +Description: pandora_gotty for Pandora FMS. diff --git a/extras/pandora_gotty/pandora_gotty.spec b/extras/pandora_gotty/pandora_gotty.spec new file mode 100644 index 0000000000..813c9726c9 --- /dev/null +++ b/extras/pandora_gotty/pandora_gotty.spec @@ -0,0 +1,41 @@ +%define name pandora_gotty +%define version 1.0 +%define release 1%{?dist} +Summary: pandora_gptty for Pandora FMS +Name: %{name} +Version: %{version} +Release: %{release} +License: GPL +Vendor: PandoraFMS +Source0: %{name}-%{version}.tar.gz +URL: https://pandorafms.com +Group: System/Monitoring +Packager: PandoraFMS +BuildArch: x86_64 +Provides: %{name}-%{version} + +%description +pandora_gotty for Pandora FMS. + +%prep +%setup -q + +%install +rm -rf $RPM_BUILD_ROOT +mkdir -p $RPM_BUILD_ROOT/%{_bindir} +mkdir -p %{buildroot}/etc/pandora_gotty/ +cp %{name} $RPM_BUILD_ROOT/%{_bindir} +cp pandora_gotty_exec $RPM_BUILD_ROOT/%{_bindir} +cp pandora_gotty.conf %{buildroot}/etc/pandora_gotty/ +%clean +rm -Rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root,-) +%config(noreplace) /etc/pandora_gotty/pandora_gotty.conf +%{_bindir}/%{name} +%{_bindir}/pandora_gotty_exec + +%changelog +* Mon Sep 18 2023 PandoraFMS - 1.0-1 +- Initial RPM release diff --git a/extras/pandora_gotty/src/.gitignore b/extras/pandora_gotty/src/.gitignore new file mode 100644 index 0000000000..a2acfc48bf --- /dev/null +++ b/extras/pandora_gotty/src/.gitignore @@ -0,0 +1,2 @@ +*rpm +bin/* diff --git a/extras/pandora_gotty/src/pandora_gotty b/extras/pandora_gotty/src/pandora_gotty new file mode 100755 index 0000000000..dc71d46eaf --- /dev/null +++ b/extras/pandora_gotty/src/pandora_gotty @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3758eddb64db79c6ea1dac4cb200ee8ec86ef3f51723dad5be4365a1315b952b +size 13642854 diff --git a/extras/pandora_gotty/src/pandora_gotty.conf b/extras/pandora_gotty/src/pandora_gotty.conf new file mode 100644 index 0000000000..09d54e64c0 --- /dev/null +++ b/extras/pandora_gotty/src/pandora_gotty.conf @@ -0,0 +1,29 @@ +//Pandora Gotty config file + +// [bool] Permit clients to write to the TTY +permit_write = true + +// [bool] Permit clients to send command line arguments in URL (e.g. http://example.com:8080/?arg=AAA&arg=BBB) +permit_arguments = true + +// [bool] Enable random URL generation +enable_random_url = true + +// [int] Default length of random strings appended to URL +// To enable random URL generation, set `true` to `enable_random_url` +random_url_length = 32 + +// [bool] Enable TLS/SSL +// enable_tls = false + +// [string] Default TLS certificate file path +// tls_crt_file = "~/.gotty.crt" + +// [string] Default TLS key file path +// tls_key_file = "~/.gotty.key" + +// [bool] Enable client certificate authentication +// enable_tls_client_auth = false + +// [string] Certificate file of CA for client certificates +// tls_ca_crt_file = "~/.gotty.ca.crt" \ No newline at end of file diff --git a/extras/pandora_gotty/src/pandora_gotty_exec.py b/extras/pandora_gotty/src/pandora_gotty_exec.py new file mode 100644 index 0000000000..1f7756745f --- /dev/null +++ b/extras/pandora_gotty/src/pandora_gotty_exec.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__author__ = "PandoraFMS Team" +__copyright__ = "Copyright 2023, PandoraFMS" +#__credits__ = ["Rob Knight", "Peter Maxwell", "Gavin Huttley", "Matthew Wakefield"] +__maintainer__ = "Projects/QA department" +__status__ = "Prod" +__version__ = "1.0" + +import sys, argparse, signal, re, datetime, subprocess + +info= f""" +SSH and TELNET helper for pandora_gotty. +Version: {__version__} +""" + +parser = argparse.ArgumentParser(description= info, formatter_class=argparse.RawTextHelpFormatter) +parser.add_argument('exec_cmd', + help='Aplication to be executed, avalibles: ssh or telnet',type=str, choices=['ssh', 'telnet']) +parser.add_argument('address', + help='IP addres or dns name to connect', type=str, default="") +parser.add_argument('port', + help='Port to connect', type=int, default=23) +parser.add_argument('user', + help='Username, only requiered for ssh connection', type=str, default="", nargs='?') + +args = parser.parse_args() + +# Define a function to handle the SIGINT signal +def sigint_handler(signal, frame): + print ('\nInterrupted by user', file=sys.stderr) + sys.exit(0) +signal.signal(signal.SIGINT, sigint_handler) + +# Define a function to handle the SIGTERM signal +def sigterm_handler(signum, frame): + print("Received SIGTERM signal.", file=sys.stderr) + sys.exit(0) +signal.signal(signal.SIGTERM, sigterm_handler) + +# Functions +def is_valid_add(add:str): + # Regular expression to match an IP address + ip_pattern = r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$' + + # Regular expression to match a DNS name (domain name) + dns_pattern = r'^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' + + if re.match(ip_pattern, add) or re.match(dns_pattern, add): + return True + else: + print(f"Error not valid address: {add}", file=sys.stderr) + return False + +def is_valid_username(username:str): + # Regular expression to match a valid Linux username + username_pattern = r'^[a-zA-Z_][a-zA-Z0-9_]{0,31}$' + if re.match(username_pattern, username) is not None: + return True + else: + print(f"Error not valid username: {username}", file=sys.stderr) + return False + +def exec_ssh (user:str, add:str, port:int): + # Previus checks + if is_valid_username(user) == False: + return False + if is_valid_add(add) == False: + return False + if port == 0 : + return False + + try: + print("> Starting SSH connection...") + ssh_command = f"ssh {user}@{add} -p {port}" + subprocess.run(ssh_command, shell=True) + + except subprocess.CalledProcessError as e: + raise SystemExit(e) + return True + +def exec_telnet (add:str, port:int): + # Previus checks + if is_valid_add(add) == False: + return False + + try: + print("> Starting Telnet connection...") + ssh_command = f"telnet -E {add} {port}" + subprocess.run(ssh_command, shell=True) + + except subprocess.CalledProcessError as e: + raise SystemExit(e) + return True + + +# Main +if __name__ == "__main__": + if args.exec_cmd == "ssh": + exec_ssh(args.user, args.address, args.port) + print ("> ssh session finished") + sys.exit(0) + + if args.exec_cmd == "telnet": + exec_telnet(args.address, args.port) + print ("> telnet session finished") + sys.exit(0) + + sys.exit(0) diff --git a/pandora_console/cron.php b/pandora_console/cron.php new file mode 100644 index 0000000000..0811a9ef40 --- /dev/null +++ b/pandora_console/cron.php @@ -0,0 +1,31 @@ + get_system_time()], + ['token' => 'cron_last_run'] +); + +$tasks = new DiscoveryConsoleTask(); + +$tasks->run(); + +if (is_reporting_console_node() === true) { + $supervisor = new ConsoleSupervisor(); + $supervisor->run(); +} diff --git a/pandora_console/extensions/quick_shell.php b/pandora_console/extensions/quick_shell.php index 7e6db3271e..ac3d1d22c5 100644 --- a/pandora_console/extensions/quick_shell.php +++ b/pandora_console/extensions/quick_shell.php @@ -31,6 +31,7 @@ global $config; require_once $config['homedir'].'/include/functions_agents.php'; require_once $config['homedir'].'/godmode/wizards/Wizard.main.php'; +require_once $config['homedir'].'/include/functions_cron_task.php'; /** @@ -78,91 +79,78 @@ function quickShell() return; } + $form_sent = get_parameter('form-sent', false); + $method = get_parameter('method', null); + + $setup_anchor = html_print_anchor( + [ + 'href' => 'index.php?sec=gsetup&sec2=godmode/setup/setup§ion=quickshell', + 'content' => __('GoTTY setup'), + ], + true + ); + + if ((bool) $config['gotty_ssh_enabled'] === false + && (bool) $config['gotty_telnet_enabled'] === false + ) { + ui_print_warning_message(__('Please, enable GoTTY in %s', $setup_anchor)); + return; + } + $agent_id = get_parameter('id_agente', 0); $username = get_parameter('username', null); - $method = get_parameter('method', null); $method_port = get_parameter('port', null); // Retrieve main IP Address. - $address = agents_get_address($agent_id); + $agent_address = agents_get_address($agent_id); ui_require_css_file('wizard'); ui_require_css_file('discovery'); - // Settings. - // WebSocket host, where client should connect. - if (isset($config['ws_port']) === false) { - config_update_value('ws_port', 8080); + // Build URL args. + if ($method === 'ssh') { + // SSH. + $args .= '&arg='.$agent_address.'&arg='.$method_port.'&arg='.$username; + } else if ($method == 'telnet') { + // Telnet. + $args .= '&arg='.$agent_address.'&arg='.$method_port; } - if (empty($config['ws_proxy_url']) === true) { - $ws_url = 'http://'.$_SERVER['SERVER_ADDR'].':'.$config['ws_port']; - } else { - preg_match('/\/\/(.*)/', $config['ws_proxy_url'], $matches); - if (isset($_SERVER['HTTPS']) === true) { - $ws_url = 'https://'.$matches[1]; - } else { - $ws_url = 'http://'.$matches[1]; - } - } - - // Gotty settings. Internal communication (WS). - if (isset($config['gotty_host']) === false) { - config_update_value('gotty_host', '127.0.0.1'); - } - - if (isset($config['gotty_telnet_port']) === false) { - config_update_value('gotty_telnet_port', 8082); - } - - if (isset($config['gotty_ssh_port']) === false) { - config_update_value('gotty_ssh_port', 8081); - } - - // Context to allow self-signed certs. - $context = stream_context_create( - [ - 'http' => [ 'method' => 'GET'], - 'ssl' => [ - 'verify_peer' => false, - 'verify_peer_name' => false, - ], - ] - ); + $connectionURL = buildConnectionURL($method); + $gotty_addr = $connectionURL.$args; // Username. Retrieve from form. - if (empty($username) === true) { + if ($form_sent === false) { // No username provided, ask for it. $wiz = new Wizard(); - $test = curl($ws_url, []); - if ($test === false) { - ui_print_error_message(__('WebService engine has not been started, please check documentation.')); - $wiz->printForm( - [ - 'form' => [ - 'method' => 'POST', - 'action' => '#', - 'id' => 'retry_form', - ], - ] - ); + $method_fields = []; - html_print_action_buttons( - html_print_submit_button( - __('Retry'), - 'submit', - false, - [ - 'icon' => 'next', - 'form' => 'retry_form', - ], - true - ) - ); - return; + if ($config['gotty_telnet_enabled']) { + $method_fields['telnet'] = __('Telnet'); + $port_value = 23; } + if ($config['gotty_ssh_enabled']) { + $method_fields['ssh'] = __('SSH'); + $port_value = 22; + } + + $method_script = " + var wizard = document.querySelector('.wizard'); + p=22; + wizard.querySelector('ul > li').classList.remove('invisible_important'); + wizard.querySelector('ul > li').classList.add('visible'); + if(this.value == 'telnet') { + p=23; + wizard.querySelector('ul > li').classList.remove('visible'); + wizard.querySelector('ul > li').classList.add('invisible_important'); + $('#text-username').prop('required', false); + } else { + $('#text-username').prop('required', true); + } + $('#text-port').val(p);"; + $wiz->printForm( [ 'form' => [ @@ -175,8 +163,9 @@ function quickShell() [ 'label' => __('Username'), 'arguments' => [ - 'type' => 'text', - 'name' => 'username', + 'type' => 'text', + 'name' => 'username', + 'required' => true, ], ], [ @@ -185,7 +174,7 @@ function quickShell() 'type' => 'text', 'id' => 'port', 'name' => 'port', - 'value' => 22, + 'value' => $port_value, ], ], [ @@ -193,13 +182,17 @@ function quickShell() 'arguments' => [ 'type' => 'select', 'name' => 'method', - 'fields' => [ - 'ssh' => __('SSH'), - 'telnet' => __('Telnet'), - ], - 'script' => "p=22; if(this.value == 'telnet') { p=23; } $('#text-port').val(p);", + 'fields' => $method_fields, + 'script' => $method_script, ], ], + [ + 'arguments' => [ + 'type' => 'hidden', + 'name' => 'form-sent', + 'value' => true, + ], + ] ], ], false, @@ -221,119 +214,71 @@ function quickShell() return; } - // Initialize Gotty Client. - $host = $config['gotty_host']; - if ($method == 'ssh') { - // SSH. - $port = $config['gotty_ssh_port']; - $command_arguments = "var args = '?arg=".$username.'@'.$address; - $command_arguments .= '&arg=-p '.$method_port."';"; - } else if ($method == 'telnet') { - // Telnet. - $port = $config['gotty_telnet_port']; - $username = preg_replace('/[^a-zA-Z0-9\-\.]/', '', $username); - $command_arguments = "var args = '?arg=-l ".$username; - $command_arguments .= '&arg='.$address; - $command_arguments .= '&arg='.$method_port."&arg=-E';"; - } else { - ui_print_error_message(__('Please use SSH or Telnet.')); - return; + // Check gotty connection before trying to load iframe. + $ch = curl_init($gotty_addr); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + // Maximum time for the entire request. + curl_setopt($ch, CURLOPT_TIMEOUT, 3); + // Maximum time to establish a connection. + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, 10); + + $response = curl_exec($ch); + $responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $finalUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); + + curl_close($ch); + + if ($responseCode !== 200) { + ui_print_error_message(__('Connection error. Please check your settings at %s', $setup_anchor)); + exit; } - // If rediretion is enabled, we will try to connect using - // http:// or https:// endpoint. - $test = get_headers($ws_url, false, $context); - if ($test === false) { - if (empty($wiz) === true) { - $wiz = new Wizard(); - } - - ui_print_error_message(__('WebService engine has not been started, please check documentation.')); - echo $wiz->printGoBackButton('#'); - return; - } - - // Check credentials. - $auth_str = ''; - $gotty_url = $host.':'.$port; - if (empty($config['gotty_user']) === false - && empty($config['gotty_pass']) === false - ) { - $auth_str = io_safe_output($config['gotty_user']); - $auth_str .= ':'.io_output_password($config['gotty_pass']); - $gotty_url = $auth_str.'@'.$host.':'.$port; - } - - $r = file_get_contents('http://'.$gotty_url.'/js/hterm.js'); - if (empty($r) === true) { - if (empty($wiz) === true) { - $wiz = new Wizard(); - } - - ui_print_error_message(__('WebService engine is not working properly, please check documentation.')); - echo $wiz->printGoBackButton('#'); - return; - } - - // Override gotty client settings. - if (empty($auth_str) === true) { - $r .= "var gotty_auth_token = '';"; - } else { - $r .= "var gotty_auth_token = '"; - $r .= $auth_str."';"; - } - - // Set websocket target and method. - $gotty = file_get_contents('http://'.$gotty_url.'/js/gotty.js'); - $url = "var url = (httpsEnabled ? 'wss://' : 'ws://') + window.location.host + window.location.pathname + 'ws';"; - if (empty($config['ws_proxy_url']) === true) { - $new = "var url = (httpsEnabled ? 'wss://' : 'ws://')"; - $new .= " + window.location.host + ':"; - $new .= $config['ws_port'].'/'.$method."';"; - } else { - $new = "var url = '"; - $new .= $config['ws_proxy_url'].'/'.$method."';"; - } - - // Update firefox issue. - $original = ' this.iframe_.src = \'#\';'; - $trick = 'this.iframe_.src = \'javascript:\';'; - - $r = str_replace($original, $trick, $r); - - // Update url. - $gotty = str_replace($url, $new, $gotty); - - // Update websocket arguments. - $args = 'var args = window.location.search;'; - $new = $command_arguments; - - // Update arguments. - $gotty = str_replace($args, $new, $gotty); - ?> -
- - + + + 0) { - $msg = __('%d Updated', $changes); - if ($critical > 0) { - $msg = __( - '%d Updated, please restart WebSocket engine service', - $changes - ); - } - - ui_print_success_message($msg); - } - - // Form. Using old style. echo ''; + + echo ''; + echo ''; } -// This extension is usefull only if the agent has associated IP. +if (is_ajax() === true) { + $method = (string) get_parameter('method', ''); + + if (empty($method) === false) { + $address = buildConnectionURL($method); + + $ch = curl_init($address); + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + // Maximum time for the entire request. + curl_setopt($ch, CURLOPT_TIMEOUT, 2); + + // Maximum time to establish a connection. + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1); + + curl_exec($ch); + $response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($response_code === 200) { + $result = ['status' => 'success']; + } else { + $result = ['status' => 'error']; + } + + echo json_encode($result); + return; + } + + $result = ['status' => 'error']; + return; +} + +// This extension is useful only if the agent has associated IP. $agent_id = get_parameter('id_agente'); if (empty($agent_id) === false && get_parameter('sec2', '') == 'operation/agentes/ver_agente' @@ -573,4 +585,136 @@ if (empty($agent_id) === false } } +echo ''; + extensions_add_godmode_function('quickShellSettings'); diff --git a/pandora_console/extras/delete_files/delete_files.txt b/pandora_console/extras/delete_files/delete_files.txt index 513b626262..a3c2eea0ca 100644 --- a/pandora_console/extras/delete_files/delete_files.txt +++ b/pandora_console/extras/delete_files/delete_files.txt @@ -1708,6 +1708,7 @@ enterprise/godmode/wizards/Cloud.class.php enterprise/images/wizard/applications.png enterprise/images/wizard/cloud.png enterprise/images/wizard/consoletasks.png +pandora_websocket_engine operation/incidents/configure_integriaims_incident.php operation/incidents/dashboard_detail_integriaims_incident.php operation/incidents/incident_statistics.php diff --git a/pandora_console/godmode/menu.php b/pandora_console/godmode/menu.php index a09c2a1897..d0d21c14a6 100644 --- a/pandora_console/godmode/menu.php +++ b/pandora_console/godmode/menu.php @@ -486,8 +486,8 @@ if ($access_console_node === true) { $sub2['godmode/setup/setup§ion=notifications']['text'] = __('Notifications'); $sub2['godmode/setup/setup§ion=notifications']['refr'] = 0; - $sub2['godmode/setup/setup§ion=websocket_engine']['text'] = __('Websocket Engine'); - $sub2['godmode/setup/setup§ion=websocket_engine']['refr'] = 0; + $sub2['godmode/setup/setup§ion=quickshell']['text'] = __('QuickShell'); + $sub2['godmode/setup/setup§ion=quickshell']['refr'] = 0; $sub2['godmode/setup/setup§ion=external_tools']['text'] = __('External Tools'); $sub2['godmode/setup/setup§ion=external_tools']['refr'] = 0; diff --git a/pandora_console/godmode/setup/setup.php b/pandora_console/godmode/setup/setup.php index d7f496c2d6..a868017ad2 100644 --- a/pandora_console/godmode/setup/setup.php +++ b/pandora_console/godmode/setup/setup.php @@ -221,13 +221,13 @@ $buttons['notifications'] = [ ).'', ]; -$buttons['websocket_engine'] = [ +$buttons['quickshell'] = [ 'active' => false, - 'text' => ''.html_print_image( + 'text' => ''.html_print_image( 'images/websocket_small.png', true, [ - 'title' => __('Websocket engine'), + 'title' => __('QuickShell'), 'class' => 'invert_filter', ] ).'', @@ -325,9 +325,9 @@ switch ($section) { $subpage = __('Notifications'); break; - case 'websocket_engine': - $buttons['websocket_engine']['active'] = true; - $subpage = __('Pandora Websocket Engine'); + case 'quickshell': + $buttons['quickshell']['active'] = true; + $subpage = __('QuickShell'); $help_header = 'quickshell_settings'; break; @@ -454,8 +454,8 @@ switch ($section) { include_once $config['homedir'].'/godmode/setup/setup_notifications.php'; break; - case 'websocket_engine': - include_once $config['homedir'].'/godmode/setup/setup_websocket_engine.php'; + case 'quickshell': + include_once $config['homedir'].'/godmode/setup/setup_quickshell.php'; break; case 'external_tools': diff --git a/pandora_console/godmode/setup/setup_quickshell.php b/pandora_console/godmode/setup/setup_quickshell.php new file mode 100644 index 0000000000..84b3fad1d0 --- /dev/null +++ b/pandora_console/godmode/setup/setup_quickshell.php @@ -0,0 +1,53 @@ +'; + +if (function_exists('quickShellSettings') === true) { + quickShellSettings(); +} + +$action_btns = html_print_submit_button( + __('Update'), + 'update_button', + false, + [ 'icon' => 'update' ], + true +); + +html_print_action_buttons( + $action_btns +); + +echo ''; diff --git a/pandora_console/godmode/setup/setup_websocket_engine.php b/pandora_console/godmode/setup/setup_websocket_engine.php deleted file mode 100644 index 5ee134e558..0000000000 --- a/pandora_console/godmode/setup/setup_websocket_engine.php +++ /dev/null @@ -1,195 +0,0 @@ -'; - -echo ''; - -if (function_exists('quickShellSettings') === true) { - quickShellSettings(); -} - -html_print_action_buttons( - html_print_submit_button( - __('Update'), - 'update_button', - false, - [ 'icon' => 'update' ], - true - ) -); - -echo ''; - -echo ''; diff --git a/pandora_console/godmode/wizards/DiscoveryTaskList.class.php b/pandora_console/godmode/wizards/DiscoveryTaskList.class.php index d0bbcb7cb5..995116ac19 100644 --- a/pandora_console/godmode/wizards/DiscoveryTaskList.class.php +++ b/pandora_console/godmode/wizards/DiscoveryTaskList.class.php @@ -160,10 +160,9 @@ class DiscoveryTaskList extends HTML return $this->enableTask(); } - if (enterprise_installed()) { - // This check only applies to enterprise users. - enterprise_hook('tasklist_checkrunning'); + enterprise_hook('tasklist_checkrunning'); + if (enterprise_installed()) { $ret = $this->showListConsoleTask(); } else { $ret = false; diff --git a/pandora_console/include/class/ConsoleSupervisor.php b/pandora_console/include/class/ConsoleSupervisor.php index a434a30e7f..b4320b1a4d 100644 --- a/pandora_console/include/class/ConsoleSupervisor.php +++ b/pandora_console/include/class/ConsoleSupervisor.php @@ -200,9 +200,7 @@ class ConsoleSupervisor * NOTIF.CRON.CONFIGURED */ - if (enterprise_installed()) { - $this->checkCronRunning(); - } + $this->checkCronRunning(); /* * Check if instance is registered. @@ -501,9 +499,7 @@ class ConsoleSupervisor * NOTIF.CRON.CONFIGURED */ - if (enterprise_installed()) { - $this->checkCronRunning(); - } + $this->checkCronRunning(); /* * Check if instance is registered. @@ -2651,14 +2647,20 @@ class ConsoleSupervisor if (strtoupper(substr(PHP_OS, 0, 3)) != 'WIN') { $message_conf_cron .= __('Discovery relies on an appropriate cron setup.'); $message_conf_cron .= '. '.__('Please, add the following line to your crontab file:'); - $message_conf_cron .= '* * * * * <user> wget -q -O - --no-check-certificate --load-cookies /tmp/cron-session-cookies --save-cookies /tmp/cron-session-cookies --keep-session-cookies '; - $message_conf_cron .= str_replace( - ENTERPRISE_DIR.'/meta/', - '', - ui_get_full_url(false) - ); - $message_conf_cron .= ENTERPRISE_DIR.'/'.EXTENSIONS_DIR; - $message_conf_cron .= '/cron/cron.php >>'; + if (enterprise_installed()) { + $message_conf_cron .= '
* * * * * <user> wget -q -O - --no-check-certificate --load-cookies /tmp/cron-session-cookies --save-cookies /tmp/cron-session-cookies --keep-session-cookies '; + $message_conf_cron .= str_replace( + ENTERPRISE_DIR.'/meta/', + '', + ui_get_full_url(false) + ); + $message_conf_cron .= ENTERPRISE_DIR.'/'.EXTENSIONS_DIR; + $message_conf_cron .= '/cron/cron.php >>'; + } else { + $message_conf_cron .= '
* * * * * <user> wget -q -O - --no-check-certificate --load-cookies /tmp/cron-session-cookies --save-cookies /tmp/cron-session-cookies --keep-session-cookies '; + $message_conf_cron .= ui_get_full_url(false).'cron.php >>'; + } + $message_conf_cron .= $config['homedir'].'/log/cron.log'; } diff --git a/pandora_console/include/class/DiscoveryConsoleTask.php b/pandora_console/include/class/DiscoveryConsoleTask.php new file mode 100644 index 0000000000..b0bd4846e9 --- /dev/null +++ b/pandora_console/include/class/DiscoveryConsoleTask.php @@ -0,0 +1,345 @@ + 0) { + $console_exists = db_get_row('tconsole', 'id_console', $config['id_console']); + if ($console_exists === false) { + db_process_sql_insert( + 'tconsole', + [ + 'id_console' => $config['id_console'], + 'description' => $config['console_description'], + 'version' => $pandora_version, + 'console_type' => ($config['reporting_console_node'] === true) ? 1 : 0, + 'timezone' => $config['timezone'], + 'public_url' => $config['public_url'], + ] + ); + } else { + db_process_sql_update( + 'tconsole', + [ + 'description' => $config['console_description'], + 'timezone' => $config['timezone'], + 'public_url' => $config['public_url'], + 'console_type' => (int) $config['reporting_console_node'], + 'version' => $pandora_version, + ], + [ + 'id_console' => $config['id_console'], + ] + ); + } + } + + // Maintenance task: schedule daily task to manage GoTTY processes if not defined yet. + // Must do at every Cron execution. + $gotty_ssh_enabled = (bool) $config['gotty_ssh_enabled']; + $gotty_telnet_enabled = (bool) $config['gotty_telnet_enabled']; + + if ($gotty_ssh_enabled === true || $gotty_telnet_enabled === true) { + // Create necessary data in task tables when some method of GoTTY is enabled in setup. + if ((bool) $config['enterprise_installed'] === false) { + $call_func_user_task_id = db_get_value_sql('SELECT id FROM `tuser_task` WHERE `function_name` = "cron_task_call_user_function"'); + if ($call_func_user_task_id === false) { + db_process_sql("INSERT INTO `tuser_task` (`function_name`, `parameters`, `name`) VALUES ('cron_task_call_user_function','a:1:{i:0;a:2:{s:11:\"description\";s:13:\"Function name\";s:4:\"type\";s:4:\"text\";}}','Call PHP function')"); + } + } + + $user_function_task_id = db_get_value_sql('SELECT id FROM `tuser_task_scheduled` WHERE `args` LIKE "%cron_task_start_gotty%"'); + + if ($user_function_task_id === false) { + // Schedule task to manage GoTTY processes daily if it is not scheduled yet. + $this->schedule( + 'cron_task_call_user_function', + [ + 0 => 'cron_task_start_gotty', + 'function_name' => 'cron_task_start_gotty', + 'internal' => 1, + ], + 'daily', + 0, + 0, + strtotime('tomorrow') + ); + } + } + + // Maintenance task: check whether start GoTTY SSH and Telnet processes are running and start otherwise. + // Must do at every Cron execution. + cron_task_start_gotty(false); + + // Do not output anything until is completed. There're session + // operations inside cron_task_run function. + ob_start(); + + if (cron_task_lock() === false) { + // Cannot continue. Locked. + echo ob_get_clean(); + exit; + } + + $time = get_system_time(); + $scheduled_tasks = db_get_all_rows_in_table('tuser_task_scheduled'); + if (!$scheduled_tasks) { + $scheduled_tasks = []; + } + + /* + Watch out! First_execution corresponds to next_execution the name + of the bbdd is maintained to ensure integrity. + */ + + foreach ($scheduled_tasks as $task) { + $params = unserialize($task['args']); + if ($this->shouldTaskRun($task) === false) { + continue; + } + + if ($task['scheduled'] == 'no') { + if (($params['first_execution']) < $time) { + echo date('Y/m/d H:i:s').' Execute once time cron task: '; + echo $task['id']; + echo "\n\n"; + cron_task_run($task['id']); + // The task was not scheduled and was executed once. + db_process_sql_delete( + 'tuser_task_scheduled', + ['id' => $task['id']] + ); + } + } else { + if (($params['first_execution']) < $time) { + echo date('Y/m/d H:i:s').' EXECUTED CRON TASK: '.$task['id']; + echo "\n"; + echo "\n"; + cron_task_run($task['id']); + } + } + } + + // Dump to output. + echo ob_get_clean(); + + // Release the lock. + cron_task_release_lock(); + + } + + + /** + * Schedules a discovery console task to be executed by cron. + * + * @param string $function_name Name of the function: + * cron_task_generate_report + * cron_task_generate_report_by_template + * cron_task_save_report_to_disk + * cron_task_do_backup + * cron_task_execute_custom_script + * cron_task_save_xml_report_to_disk + * cron_task_feedback_send_mail + * cron_task_generate_csv_log. + * @param array $arguments Task execution arguments (if needed). + * @param string $schedule Task schedule options: + * 'no', + * 'hourly', + * 'daily', + * 'weekly', + * 'monthly', + * 'yearly', + * 'custom'. + * @param integer $group_id Group id (0 => all). + * @param string|null $id_user User id, if null, current user. + * @param integer|null $time_start When to start, if null, now. + * + * @return boolean Sucessfully scheduled or not. + */ + public function schedule( + string $function_name, + array $arguments=[], + string $schedule='no', + int $group_id=0, + ?string $id_user=null, + ?int $time_start=null + ) { + global $config; + + if ($id_user === null) { + $id_user = $config['id_user']; + } + + $idUserTask = db_get_value( + 'id', + 'tuser_task', + 'function_name', + $function_name + ); + + if ($idUserTask === false) { + // Failed to identify function. + return false; + } + + if (in_array($schedule, self::SCHEDULES) === false) { + // Failed to schedule. Not a valid schedule option. + return false; + } + + if ($time_start === null) { + $time_start = strtotime('now'); + } + + // Params for send mail with cron. + $parameters = array_merge( + $arguments, + [ 'first_execution' => $time_start ] + ); + + // Values insert task cron. + $task = [ + 'id_usuario' => $id_user, + 'id_user_task' => $idUserTask, + 'args' => serialize($parameters), + 'scheduled' => $schedule, + 'id_grupo' => $group_id, + ]; + + $result = db_process_sql_insert( + 'tuser_task_scheduled', + $task + ); + + return ($result !== false); + } + + +} diff --git a/pandora_console/include/functions_config.php b/pandora_console/include/functions_config.php index 28f5f695cd..f2579fe53a 100644 --- a/pandora_console/include/functions_config.php +++ b/pandora_console/include/functions_config.php @@ -1497,7 +1497,6 @@ function config_update_config() $interval_values = implode(',', $interval_values_array); } - hd($interval_values, true); if (config_update_value('interval_values', $interval_values, true) === false) { $error_update[] = __('Delete interval'); } @@ -2034,20 +2033,6 @@ function config_update_config() } break; - case 'websocket_engine': - if (config_update_value('ws_bind_address', get_parameter('ws_bind_address'), true) === false) { - $error_update[] = __('WebSocket bind address'); - } - - if (config_update_value('ws_port', get_parameter('ws_port'), true) === false) { - $error_update[] = __('WebSocket port'); - } - - if (config_update_value('ws_proxy_url', get_parameter('ws_proxy_url'), true) === false) { - $error_update[] = __('WebSocket proxy url'); - } - break; - default: // Ignore. break; @@ -2504,6 +2489,18 @@ function config_process_config() config_update_value('2Fa_auth', ''); } + if (!isset($config['gotty_ssh_enabled'])) { + config_update_value('gotty_ssh_enabled', 1); + } + + if (!isset($config['gotty_telnet_enabled'])) { + config_update_value('gotty_telnet_enabled', 0); + } + + if (!isset($config['gotty_port'])) { + config_update_value('gotty_port', 8080); + } + if (isset($config['performance_variables_control']) === false) { config_update_value( 'performance_variables_control', diff --git a/pandora_console/include/functions_cron.php b/pandora_console/include/functions_cron.php index 2eafd8dccb..711ed7f6ba 100644 --- a/pandora_console/include/functions_cron.php +++ b/pandora_console/include/functions_cron.php @@ -463,7 +463,6 @@ function cron_list_table() ); $defined_tasks = db_get_all_rows_sql($sql); - if (!check_acl($config['id_user'], 0, 'PM')) { $read_tasks = []; foreach ($defined_tasks as $task) { diff --git a/pandora_console/include/functions_cron_task.php b/pandora_console/include/functions_cron_task.php new file mode 100644 index 0000000000..ffc5958d85 --- /dev/null +++ b/pandora_console/include/functions_cron_task.php @@ -0,0 +1,542 @@ + time()) + ) { + // Locked! + return false; + } + } + + // Try to get a lock from DB. + $dblock = db_get_lock($config['dbname'].'.'.$lockfile); + if ($dblock !== 1) { + // Locked! + return false; + } + + // Store PID in lock file. + $PID = getmypid(); + echo 'CRON running ['.$PID."]\n"; + file_put_contents($lock, $PID); + return true; +} + + +/** + * Check if CRON.task is available to start. + * + * @return boolean True, available. False not available. + */ +function cron_task_lock() +{ + return cron_lock('cron.lock'); +} + +/** + * Release CRON.task lock + * + * @return void + */ +function cron_task_release_lock() +{ + global $config; + + // Release DB lock. + $dblock = db_release_lock($config['dbname'].'.cron.lock'); + unlink($config['attachment_store'].'/cron.lock'); +} + +/** + * Calculates target schedule time + * + * @param string $scheduled_time Desired scheduled time. + * @param integer $custom_data Custom scheduled time. + * @param integer|null $timestamp Custom timestamp. + * + * @return integer amount of time. + */ +function cron_get_scheduled_time( + string $scheduled_time, + int $custom_data=0, + $timestamp=null +) { + if ($scheduled_time == 'no') { + return 0; + } + + if ($scheduled_time == 'hourly') { + return SECONDS_1HOUR; + } + + if ($scheduled_time == 'daily') { + return SECONDS_1DAY; + } + + if ($scheduled_time == 'weekly') { + return SECONDS_1WEEK; + } + + if ($scheduled_time == 'monthly') { + $month = (($timestamp === null) ? date('m') : date('m', $timestamp)); + $year = (($timestamp === null) ? date('Y') : date('Y', $timestamp)); + + $days_month = (cal_days_in_month( + CAL_GREGORIAN, + $month, + $year + ) * SECONDS_1DAY); + + return $days_month; + } + + if ($scheduled_time == 'yearly') { + return SECONDS_1YEAR; + } + + if ($scheduled_time == 'custom') { + return $custom_data; + } + + return 0; +} + +/** + * Run scheduled task. + * + * @param integer $id_user_task Task to be run. + * @param boolean $force_run Force run. + * + * @return void + */ +function cron_task_run( + int $id_user_task, + bool $force_run=false +) { + global $config; + + if (isset($config['id_console']) === true && $config['id_console'] > 0) { + $sql = sprintf( + 'SELECT * + FROM tuser_task_scheduled + WHERE id=%d AND id_console IN (0, %d)', + $id_user_task, + $config['id_console'] + ); + + $task_scheduled = db_get_row_sql($sql); + + if ($task_scheduled !== false) { + db_process_sql_update( + 'tconsole', + ['last_execution' => time()], + ['id_console' => $config['id_console']] + ); + } + } else { + $filter = [ + 'id' => $id_user_task, + 'id_console' => 0, + ]; + + $task_scheduled = db_get_row_filter('tuser_task_scheduled', $filter, false); + } + + $args = unserialize($task_scheduled['args']); + + if ((bool) $config['enterprise_installed'] === false + && isset($args['function_name']) === true + && $args['function_name'] !== 'cron_task_start_gotty' + ) { + // Only cron_task_start_gotty is allowed to run in non enterprise environments. + return; + } + + if ((bool) $config['enterprise_installed'] === true) { + $task = db_get_row('tuser_task', 'id', $task_scheduled['id_user_task']); + } else { + $task = [ + 'name' => 'Call PHP function', + 'function_name' => 'cron_task_call_user_function', + ]; + } + + // Register shutdown function in case of fatal error, like. + register_shutdown_function('cron_task_handle_error', $task_scheduled, $task, $force_run); + + if (is_metaconsole() && !defined('METACONSOLE')) { + define('METACONSOLE', 1); + } + + if (! function_exists($task['function_name'])) { + return; + } + + // If the task is disable, not run. + if ((bool) $task_scheduled['enabled'] === false) { + return; + } + + if (session_status() === PHP_SESSION_DISABLED) { + return; + } + + $old_user = ''; + if (isset($config['id_user']) === false) { + $config['id_user'] = $task_scheduled['id_usuario']; + } + + $old_user = $config['id_user']; + + $old_session_id = session_id(); + $new_session_id = 'cron-'.uniqid(); + + // Simulate user login. + session_id($new_session_id); + session_start(); + $_SESSION['id_usuario'] = $config['id_user']; + session_write_close(); + + set_time_limit(0); + + if ($task['function_name'] == 'cron_task_generate_report_by_template' + || $task['function_name'] == 'cron_task_generate_report' + ) { + // If empty agent position, add it. + if (!isset($args[1])) { + array_splice($args, 1, 0, ''); + } + + $args[] = $task_scheduled['scheduled']; + } + + call_user_func_array( + $task['function_name'], + array_merge(array_values(($args ?? [])), [$id_user_task]) + ); + + if (session_status() === PHP_SESSION_ACTIVE) { + @session_destroy(); + } + + session_id($old_session_id); + session_start(); + + $config['id_user'] = $old_user; + $sql = ''; + $sql2 = ''; + + if (!$force_run) { + $period = cron_get_scheduled_time( + $task_scheduled['scheduled'], + $task_scheduled['custom_data'] + ); + $old_args = unserialize($task_scheduled['args']); + if ($period > 3600) { + $array_explode = explode( + ':', + date('H:i', $old_args['first_execution']) + ); + $hora_en_segundos = (($array_explode[0] * 3600 ) + ($array_explode[1] * 60)); + + $array_explode_period = explode( + ':', + date('H:i', ($old_args['first_execution'] + $period)) + ); + $hora_en_segundos2 = (($array_explode_period[0] * 3600 ) + ($array_explode_period[1] * 60)); + + if ($hora_en_segundos !== $hora_en_segundos2) { + $period = ($period + ($hora_en_segundos - $hora_en_segundos2)); + } + } + + try { + /* + Calculate the number of periods between last execution and + current time. + */ + + $num_of_periods = 0; + if ($period !== 0) { + $num_of_periods = ceil( + (time() - $old_args['first_execution']) / $period + ); + } + + if ($task_scheduled['scheduled'] == 'monthly') { + $updated_time = $old_args['first_execution']; + + // Update updated_time adding the period for each month individually since it is a variable value depending on the number of days a month has. + while ($num_of_periods > 0) { + // Get days of current month. + $monthly_period = cron_get_scheduled_time( + 'monthly', + $task_scheduled['custom_data'], + $updated_time + ); + $updated_time += $monthly_period; + $num_of_periods--; + } + + $old_args['first_execution'] = $updated_time; + } else if ($task_scheduled['scheduled'] == 'weekly') { + $weekly_schedule = json_decode(io_safe_output($old_args['weekly_schedule']), true); + if (empty($weekly_schedule) !== true) { + $datetime = new DateTime('tomorrow'); + $nameday = strtolower($datetime->format('l')); + $continue = true; + while ($continue === true) { + if (isset($weekly_schedule[$nameday]) === true) { + $weekly_date = $datetime->format('Y-m-d'); + $weekly_time = $weekly_schedule[$nameday][0]['start']; + $old_args['first_execution'] = strtotime($weekly_date.' '.$weekly_time); + + $continue = false; + } else { + $datetime->modify('+1 day'); + $nameday = strtolower($datetime->format('l')); + } + } + } + } else { + // Add it to next execution. + $old_args['first_execution'] += ($period * $num_of_periods); + } + } catch (Exception $e) { + // If some error (ex $period=0) next execution=current time+period. + $old_args['first_execution'] = (time() + $period); + } + + $new_args = serialize($old_args); + } + + if ($config['timesource'] == 'sql') { + $sql = sprintf( + 'UPDATE tuser_task_scheduled + SET last_run=UNIX_TIMESTAMP() + WHERE id=%d', + $id_user_task + ); + } else { + $sql = sprintf( + 'UPDATE tuser_task_scheduled + SET last_run= %d + WHERE id=%d', + time(), + $id_user_task + ); + } + + if (!$force_run) { + $sql2 = "UPDATE tuser_task_scheduled + SET args = '".$new_args."' + WHERE id=".$id_user_task; + } + + db_process_sql($sql); + db_process_sql($sql2); +} + + +/** + * Execuytes custom function defined in PHP. + * + * @param string $function_name Name to execute. + * + * @return void + */ +function cron_task_call_user_function(string $function_name) +{ + global $config; + + include_once $config['homedir'].'/vendor/autoload.php'; + + call_user_func($function_name); +} + + +/** + * Check whether GoTTY SSH and Telnet processes are running and start otherwise. + * + * @param boolean $restart_mode Restart the processes if running. + * + * @return void + */ +function cron_task_start_gotty(bool $restart_mode=true) +{ + global $config; + + include_once $config['homedir'].'/include/functions_config.php'; + + $gotty_ssh_enabled = (bool) $config['gotty_ssh_enabled']; + $gotty_telnet_enabled = (bool) $config['gotty_telnet_enabled']; + + // Check prev process running and kill it (only if port changed in setup params). + if (empty($config['restart_gotty_next_cron_port']) === false) { + config_update_value('restart_gotty_next_cron_port', ''); + + $prevProcessRunning = shell_exec("pgrep -f 'pandora_gotty.*-p ".$config['restart_gotty_next_cron_port']."'"); + + if (empty($prevProcessRunning) === false) { + shell_exec("pkill -f 'pandora_gotty.*-p ".$config['restart_gotty_next_cron_port']."'"); + } + } + + // Check if gotty is running on the configured port. + $processRunning = shell_exec("pgrep -f 'pandora_gotty.*-p ".$config['gotty_port']."'"); + + $start_proc = true; + + // If both methods are disabled, do not start process. + if ($gotty_ssh_enabled === false && $gotty_telnet_enabled === false) { + $start_proc = false; + } + + if (empty($processRunning) === false) { + // Process is running. + if ($restart_mode === true || $start_proc === false) { + // Stop the process for restarting or in case GoTTY method is disabled in this iteration. + shell_exec("pkill -f 'pandora_gotty.*-p ".$config['gotty_port']."'"); + } else { + // Prevent starting if already running and must not be restarted or terminated. + return; + } + } + + if ($start_proc === true) { + $logFilePath = $config['homedir'].'/log/gotty_cron_tmp.log'; + shell_exec('touch '.$logFilePath); + + // Start gotty process and capture the output. + $command = '/usr/bin/nohup /usr/bin/pandora_gotty --config /etc/pandora_gotty/pandora_gotty.conf -p '.$config['gotty_port'].' /usr/bin/pandora_gotty_exec > '.$logFilePath.' 2>&1 &'; + shell_exec($command); + } else { + return; + } + + $hash_read = false; + + // Maximum wait time to read asynchronously the output of the executed commands (seconds). + $maxWaitTime = 10; + + // Wait for content to appear in the log file. + $startTime = time(); + + // Workaround to wait until process inputs data in the log. + while (time() - $startTime < $maxWaitTime) { + if ($start_proc === true) { + // Read command output. + $log_content = file_get_contents($logFilePath); + } + + if ($start_proc === true + && !empty($log_content) + && $hash_read === false + ) { + // Extract the URL from the output. + if (preg_match('/.*?HTTP server is listening at:\s+(\S+)/', $log_content, $matches)) { + $url = $matches[1]; + + // Extract the hash. + $parts = explode('/', $url); + $hash = array_slice($parts, -2, 1)[0]; + + config_update_value('gotty_connection_hash', $hash); + $hash_read = true; + } + + unlink($logFilePath); + } + + if ($start_proc === false || $hash_read === true) { + // As soon as the read has completed, the timing loop will terminate. + break; + } + + // Sleep for a short interval before checking again. + usleep(100000); + } +} diff --git a/pandora_console/pandora_websocket_engine b/pandora_console/pandora_websocket_engine deleted file mode 100755 index 2d4536aae0..0000000000 --- a/pandora_console/pandora_websocket_engine +++ /dev/null @@ -1,175 +0,0 @@ -#!/bin/bash -# Copyright (c) 2005-2023 Pandora FMS -# -# /etc/init.d/websocket -# -# System startup script for Pandora FMS Console websocket engine -# -# Comments to support chkconfig on RedHat Linux -# chkconfig: 2345 90 10 -# description: Pandora FMS Console webscoket engine startup script -# -# Comments to support LSB init script conventions -### BEGIN INIT INFO -# Provides: websocket -# Required-Start: $syslog cron -# Should-Start: $network cron mysql -# Required-Stop: $syslog -# Should-Stop: $network -# Default-Start: 2 3 5 -# Default-Stop: 0 1 6 -# Short-Description: Pandora FMS Console websocket engine startup script -# Description: Pandora FMS Console websocket engine startup script -### END INIT INFO - -if [ -x /lib/lsb/init-functions ]; then -. /lib/lsb/init-functions -fi - -# If you want to run several pandora Console Websocket engines in this machine, just copy -# this script to another name, editing PANDORA_HOME to the new .conf - -export WS_ENGINE="/var/www/html/pandora_console/ws.php" -export PHP=/usr/bin/php -export WS_LOG="/var/log/pandora/web_socket.log" -export GOTTY="/tmp/" - -# Environment variables -if [[ -z ${PANDORA_RB_PRODUCT_NAME} ]]; then - PANDORA_RB_PRODUCT_NAME="Pandora FMS" -fi -if [[ -z ${PANDORA_RB_COPYRIGHT_NOTICE} ]]; then - PANDORA_RB_COPYRIGHT_NOTICE="Pandora FMS" -fi - -export PANDORA_RB_PRODUCT_NAME=$PANDORA_RB_PRODUCT_NAME -export PANDORA_RB_COPYRIGHT_NOTICE=$PANDORA_RB_COPYRIGHT_NOTICE - -# Uses a wait limit before sending a KILL signal, before trying to stop -# Pandora FMS Console Websocket engine nicely. Some big systems need some time before close -# all pending tasks / threads. - -export MAXWAIT=60 - -# Check for SUSE status scripts -if [ -f /etc/rc.status ] -then - . /etc/rc.status - rc_reset -else - # Define part of rc functions for non-suse systems - function rc_status () { - RETVAL=$? - case $1 in - -v) RETVAL=0;; - esac - } - function rc_exit () { exit $RETVAL; } - function rc_failed () { RETVAL=${1:-1}; } - RETVAL=0 -fi - -# This function replace pidof, not working in the same way in different linux distros - -function pidof_pandora () { - # This sets COLUMNS to XXX chars, because if command is run - # in a "strech" term, ps aux don't report more than COLUMNS - # characters and this will not work. - COLUMNS=300 - PANDORA_PID=`ps aux | grep "$PHP $WS_ENGINE" | grep -v grep | tail -1 | awk '{ print $2 }'` - echo $PANDORA_PID -} - -# Main script - -if [ ! -x $GOTTY ] -then - echo "Gotty not found in $GOTTY" - rc_failed 5 # program is not installed - rc_exit -fi - -if [ ! -f $PHP ] -then - echo "$PHP not found, please install version >= 7.0" - rc_failed 5 # program is not installed - rc_exit -fi - -case "$1" in - start) - PANDORA_PID=`pidof_pandora` - if [ ! -z "$PANDORA_PID" ] - then - echo "$PANDORA_RB_PRODUCT_NAME Console Websocket engine is currently running on this machine with PID ($PANDORA_PID)." - rc_exit # running start on a service already running - fi - - export PERL_LWP_SSL_VERIFY_HOSTNAME=0 - $PHP $WS_ENGINE >> $WS_LOG 2>&1 & - sleep 1 - - PANDORA_PID=`pidof_pandora` - - if [ ! -z "$PANDORA_PID" ] - then - echo "$PANDORA_RB_PRODUCT_NAME Console Websocket engine is now running with PID $PANDORA_PID" - rc_status -v - else - echo "Cannot start $PANDORA_RB_PRODUCT_NAME Console Websocket engine. Aborted." - echo "Check $PANDORA_RB_PRODUCT_NAME log files at $WS_LOG" - rc_failed 7 # program is not running - fi - ;; - - stop) - PANDORA_PID=`pidof_pandora` - if [ -z "$PANDORA_PID" ] - then - echo "$PANDORA_RB_PRODUCT_NAME Console Websocket engine is not running, cannot stop it." - rc_exit # running stop on a service already stopped or not running - else - echo "Stopping $PANDORA_RB_PRODUCT_NAME Console Websocket engine" - kill $PANDORA_PID > /dev/null 2>&1 - COUNTER=0 - - while [ $COUNTER -lt $MAXWAIT ] - do - _PID=`pidof_pandora` - if [ "$_PID" != "$PANDORA_PID" ] - then - COUNTER=$MAXWAIT - fi - COUNTER=`expr $COUNTER + 1` - sleep 1 - done - - # Send a KILL -9 signal to process, if it's alive after 60secs, we need - # to be sure is really dead, and not pretending... - if [ "$_PID" = "$PANDORA_PID" ] - then - kill -9 $PANDORA_PID > /dev/null 2>&1 - fi - rc_status -v - fi - ;; - status) - PANDORA_PID=`pidof_pandora` - if [ -z "$PANDORA_PID" ] - then - echo "$PANDORA_RB_PRODUCT_NAME Console Websocket engine is not running." - rc_failed 7 # program is not running - else - echo "$PANDORA_RB_PRODUCT_NAME Console Websocket engine is running with PID $PANDORA_PID." - rc_status - fi - ;; - force-reload|restart) - $0 stop - $0 start - ;; - *) - echo "Usage: $0 { start | stop | restart | status }" - exit 1 -esac -rc_exit