Modular WebSocket engine, added ssh and telnet to quickshell extension

This commit is contained in:
fbsanchez 2019-10-21 17:36:26 +02:00
parent bb2faca479
commit 6c52462c8c
11 changed files with 717 additions and 334 deletions

View File

@ -1,65 +1,203 @@
<?php
// ______ __ _______ _______ _______
// | __ \.---.-.-----.--| |.-----.----.---.-. | ___| | | __|
// | __/| _ | | _ || _ | _| _ | | ___| |__ |
// |___| |___._|__|__|_____||_____|__| |___._| |___| |__|_|__|_______|
//
// ============================================================================
// Copyright (c) 2007-2018 Artica Soluciones Tecnologicas, http://www.artica.es
// This code is NOT free software. This code is NOT licenced under GPL2 licence
// You cannnot redistribute it without written permission of copyright holder.
// ============================================================================
require_once 'include/functions.php';
require_once 'include/functions_groupview.php';
require_once 'include/auth/mysql.php';
/**
* Quick Shell extension.
*
* @category Extension
* @package Pandora FMS
* @subpackage QuickShell
* @version 1.0.0
* @license See below
*
* ______ ___ _______ _______ ________
* | __ \.-----.--.--.--| |.-----.----.-----. | ___| | | __|
* | __/| _ | | _ || _ | _| _ | | ___| |__ |
* |___| |___._|__|__|_____||_____|__| |___._| |___| |__|_|__|_______|
*
* ============================================================================
* Copyright (c) 2005-2019 Artica Soluciones Tecnologicas
* Please see http://pandorafms.org for full contribution list
* 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 for version 2.
* 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.
* ============================================================================
*/
check_login();
// Begin.
global $config;
if (! check_acl($config['id_user'], 0, 'PM')) {
db_pandora_audit(
'ACL Violation',
'Trying to access Profile Management'
);
include 'general/noaccess.php';
return;
}
require_once $config['homedir'].'/include/functions_agents.php';
require_once $config['homedir'].'/godmode/wizards/Wizard.main.php';
function mainWetty()
/**
* Show Quick Shell interface.
*
* @return void
*/
function quickShell()
{
global $config;
if (!isset($config['wetty_ip'])) {
config_update_value('wetty_ip', $_SERVER['SERVER_ADDR']);
check_login();
if (! check_acl($config['id_user'], 0, 'PM')) {
db_pandora_audit(
'ACL Violation',
'Trying to access Profile Management'
);
include 'general/noaccess.php';
return;
}
if ($config['wetty_ip'] == '127.0.0.1') {
config_update_value('wetty_ip', $_SERVER['SERVER_ADDR']);
$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);
ui_require_css_file('wizard');
ui_require_css_file('discovery');
// Username. Retrieve from form.
if (empty($username) === true) {
// No username provided, ask for it.
$wiz = new Wizard();
$wiz->printForm(
[
'form' => [
'id' => 'pene',
'action' => '#',
'class' => 'wizard',
'method' => 'post',
],
'inputs' => [
[
'label' => __('Username'),
'arguments' => [
'type' => 'text',
'name' => 'username',
],
],
[
'label' => __('Port'),
'arguments' => [
'type' => 'text',
'id' => 'port',
'name' => 'port',
'value' => 22,
],
],
[
'label' => __('Method'),
'arguments' => [
'type' => 'select',
'name' => 'method',
'fields' => [
'ssh' => __('SSH'),
'telnet' => __('Telnet'),
],
],
],
[
'arguments' => [
'type' => 'submit',
'label' => __('Connect'),
'attributes' => 'class="sub next"',
],
],
],
],
false,
true
);
return;
}
if (!isset($config['wetty_port'])) {
config_update_value('wetty_port', '3000');
// WebSocket host, where to connect.
if (isset($config['ws_host']) === false) {
config_update_value('ws_host', $_SERVER['SERVER_ADDR']);
}
$buttons['maps'] = [
'active' => false,
'text' => '<a href="index.php?login=1&extension_in_menu=gextensions&sec=gextensions&sec2=extensions/wetty_conf">'.html_print_image('images/setup.png', true, ['title' => __('Wetty settings')]).'</a>',
];
if (isset($config['ws_port']) === false) {
config_update_value('ws_port', 8080);
}
ui_print_page_header(__('Wetty'), 'images/extensions.png', false, '', true, $buttons);
if (isset($config['gotty_host']) === false) {
config_update_value('gotty_host', '127.0.0.1');
}
$host = '127.0.0.1';
$port = 8080;
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);
}
$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'];
$command_arguments = "var args = '?arg=-l ".$username;
$command_arguments .= '&arg= '.$address;
$command_arguments .= '&arg= '.$method_port."';";
} else {
ui_print_error_message(__('Please use SSH or Telnet.'));
return;
}
$r = file_get_contents('http://'.$host.':'.$port.'/js/hterm.js');
$r .= file_get_contents('http://'.$host.':'.$port.'/auth_token.js');
if (empty($r) === true) {
echo 'No hay nadie al volante, peligro constante';
return;
}
if (empty($config['gotty_user'])
&& empty($config['gotty_pass'])
) {
$r .= "var gotty_auth_token = '';";
} else {
$r .= "var gotty_auth_token = '";
$r .= $config['gotty_user'].':'.$gotty_pass."';";
}
$gotty = file_get_contents('http://'.$host.':'.$port.'/js/gotty.js');
// Set websocket target.
$url = "var url = (httpsEnabled ? 'wss://' : 'ws://') + window.location.host + window.location.pathname + 'ws';";
$new = "var url = (httpsEnabled ? 'wss://' : 'ws://') + window.location.host + ':8081' + window.location.pathname;";
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 = (httpsEnabled ? 'wss://' : 'ws://') + ";
$new .= 'window.location.host + ';
$new .= "'".$config['ws_proxy_url'].'/'.$method."';";
}
// 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);
?>
<style>#terminal {
height: 650px;
@ -79,31 +217,24 @@ function mainWetty()
<?php echo $gotty; ?>
</script>
<?php
/*
$table->width = '100%';
$table->class = 'databox data';
$table->data = [];
$table->head = [];
$table->align = [];
// $table->align[3] = 'left';
$table->style = [];
$table->size = [];
// $table->size[3] = '10%';
$table->style[0] = 'font-weight: bold';
$table->head[0] = __('Wetty');
// $data[0] = '<iframe scrolling="auto" frameborder="0" width="100%" height="600px" src="http://192.168.70.64:3000/"></iframe>';
$data[0] = '<iframe scrolling="auto" frameborder="0" width="100%" height="600px" src="http://'.$config['wetty_ip'].':'.$config['wetty_port'].'/"></iframe>';
// $data[0] .= '<div id="terminal" style="background-color:black;width:100%;height:600px;overflow: hidden;"><div>';
array_push($table->data, $data);
html_print_table($table);
*/
}
extensions_add_godmode_menu_option(__('Wetty'), 'AW', 'gextensions', null, 'v1');
extensions_add_godmode_function('mainWetty');
extensions_add_opemode_tab_agent(
// TabId.
'quick_shell',
// TabName.
__('QuickShell'),
// TabIcon.
'images/ehorus/terminal.png',
// TabFunction.
'quickShell',
// Version.
'N/A',
// Acl.
'PM'
);
// extensions_add_godmode_menu_option(__('Quick Shell'), 'AW', 'gextensions', null, 'v1');
// extensions_add_godmode_function('quickShell');

View File

@ -63,9 +63,20 @@ if (is_ajax()) {
$test_address = get_parameter('test_address', '');
$res = enterprise_hook('send_email_attachment', [$test_address, __('This is an email test sent from Pandora FMS. If you can read this, your configuration works.'), __('Testing Pandora FMS email'), null]);
$res = enterprise_hook(
'send_email_attachment',
[
$test_address,
__('This is an email test sent from Pandora FMS. If you can read this, your configuration works.'),
__('Testing Pandora FMS email'),
null,
]
);
echo $res;
// Exit after ajax response.
exit();
}
$table = new StdClass();
@ -389,6 +400,42 @@ html_print_input_hidden('update_config', 1);
html_print_table($table_mail_conf);
echo '</fieldset>';
echo '<fieldset>';
echo '<legend>'.__('WebSocket settings').'</legend>';
$t = new StdClass();
$t->data = [];
$t->width = '100%';
$t->class = 'databox filters';
$t->data = [];
$t->style[0] = 'font-weight: bold';
$t->data[0][0] = __('Bind address');
$t->data[0][1] = html_print_input_text(
'ws_bind_address',
$config['ws_bind_address'],
'',
30,
100,
true
);
$t->data[1][0] = __('Bind port');
$t->data[1][2] = html_print_input_text(
'ws_port',
$config['ws_port'],
'',
30,
100,
true
);
html_print_input_hidden('update_config', 1);
html_print_table($t);
echo '</fieldset>';
echo '<div class="action-buttons" style="width: '.$table->width.'">';

View File

@ -2354,26 +2354,52 @@ class ConsoleSupervisor
public function checkConsoleServerVersions()
{
global $config;
// List all servers except satellite server
// List all servers except satellite server.
$server_version_list = db_get_all_rows_sql(
'SELECT name, version FROM tserver WHERE server_type != '.SERVER_TYPE_ENTERPRISE_SATELLITE
sprintf(
'SELECT `name`, `version`
FROM tserver
WHERE server_type != %d
GROUP BY `version`',
SERVER_TYPE_ENTERPRISE_SATELLITE
)
);
foreach ($server_version_list as $server) {
if (strpos($server['version'], $config['current_package_enterprise']) === false) {
$title_ver_misaligned = $server['name'].' version misaligned with Console';
$message_ver_misaligned = 'Server '.$server['name'].' and this console have different versions. This might cause several malfunctions. Please, update this server.';
$missed = 0;
$this->notify(
[
'type' => 'NOTIF.SERVER.MISALIGNED',
'title' => __($title_ver_misaligned),
'message' => __($message_ver_misaligned),
'url' => ui_get_full_url('index.php?sec=messages&sec2=godmode/update_manager/update_manager&tab=online'),
]
);
if (is_array($server_version_list) === true) {
foreach ($server_version_list as $server) {
if (strpos(
$server['version'],
$config['current_package_enterprise']
) === false
) {
$missed++;
$title_ver_misaligned = __(
'%s version misaligned with Console',
$server['name']
);
$message_ver_misaligned = __(
'Server %s and this console have different versions. This might cause several malfunctions. Please, update this server.',
$server['name']
);
$this->notify(
[
'type' => 'NOTIF.SERVER.MISALIGNED',
'title' => __($title_ver_misaligned),
'message' => __($message_ver_misaligned),
'url' => ui_get_full_url('index.php?sec=messages&sec2=godmode/update_manager/update_manager&tab=online'),
]
);
}
}
}
// Cleanup notifications if exception is recovered.
if ($missed == 0) {
$this->cleanNotifications('NOTIF.SERVER.MISALIGNED');
}
}

View File

@ -353,6 +353,14 @@ function config_update_config()
if (!config_update_value('email_password', get_parameter('email_password'))) {
$error_update[] = __('Email password');
}
if (!config_update_value('ws_bind_address', get_parameter('ws_bind_address'))) {
$error_update[] = __('WebSocket bind address');
}
if (!config_update_value('ws_port', get_parameter('ws_port'))) {
$error_update[] = __('WebSocket port');
}
break;
case 'enterprise':

View File

@ -1,6 +1,6 @@
<?php
/**
* PHP WebSocketServer Proxy.
* PHP WebSocketServer Manager.
*
* Adapted to PandoraFMS by Fco de Borja Sanchez <fborja.sanchez@artica.es>
* Compatible with PHP >= 7.0
@ -52,7 +52,7 @@ require_once __DIR__.'/../functions.php';
/**
* Redirects ws communication between two endpoints.
*/
class WSProxy extends WebSocketServer
class WSManager extends WebSocketServer
{
/**
@ -82,14 +82,14 @@ class WSProxy extends WebSocketServer
*
* @var integer
*/
protected $maxBufferSize = 1048576;
public $maxBufferSize = 1048576;
/**
* Interactive mode.
*
* @var boolean
*/
protected $interative = true;
public $interative = true;
/**
* Use a timeout of 100 milliseconds to search for messages..
@ -98,37 +98,104 @@ class WSProxy extends WebSocketServer
*/
public $timeout = 250;
/**
* Handlers for connected step:
* 'protocol' => 'function';
*
* @var array
*/
public $handlerConnected = [];
/**
* Handlers for process step:
* 'protocol' => 'function';
*
* @var array
*/
public $handlerProcess = [];
/**
* Handlers for processRaw step:
* 'protocol' => 'function';
*
* @var array
*/
public $handlerProcessRaw = [];
/**
* Handlers for tick step:
* 'protocol' => 'function';
*
* @var array
*/
public $handlerTick = [];
/**
* Builder.
*
* @param string $listen_addr Target address (external).
* @param integer $listen_port Target port (external).
* @param string $to_addr Target address (internal).
* @param integer $to_port Target port (internal).
* @param integer $to_url Target url (internal).
* @param array $connected Handlers for <connected> step.
* @param array $process Handlers for <process> step.
* @param array $processRaw Handlers for <processRaw> step.
* @param array $tick Handlers for <tick> step.
* @param integer $bufferLength Max buffer size.
* @param boolean $debug Enable traces.
*/
public function __construct(
$listen_addr,
$listen_port,
$to_addr,
$to_port,
$to_url='/ws',
$connected=[],
$process=[],
$processRaw=[],
$tick=[],
$bufferLength=1048576,
$debug=false
) {
$this->intHost = $to_addr;
$this->intPort = $to_port;
$this->intUrl = $to_url;
$this->maxBufferSize = $bufferLength;
$this->debug = $debug;
// Configure handlers.
$this->handlerConnected = $connected;
$this->handlerProcess = $process;
$this->handlerProcessRaw = $processRaw;
$this->handlerTick = $tick;
$this->userClass = '\\PandoraFMS\\Websockets\\WebSocketUser';
parent::__construct($listen_addr, $listen_port, $bufferLength);
}
/**
* Call a target handler function.
*
* @param User $user User.
* @param array $handler Internal handler.
* @param array $arguments Arguments for handler function.
*
* @return mixed handler return or null.
*/
public function callHandler($user, $handler, $arguments)
{
if (isset($user->headers['sec-websocket-protocol'])) {
$proto = $user->headers['sec-websocket-protocol'];
if (isset($handler[$proto])
&& function_exists($handler[$proto])
) {
// Launch configured handler.
$this->stderr('Calling '.$handler[$proto]);
return call_user_func_array(
$handler[$proto],
$arguments
);
}
}
return null;
}
/**
* Read from user's socket.
*
@ -136,7 +203,7 @@ class WSProxy extends WebSocketServer
*
* @return string Buffer.
*/
protected function readSocket($user)
public function readSocket($user)
{
$buffer;
@ -171,7 +238,7 @@ class WSProxy extends WebSocketServer
*
* @return void
*/
protected function writeSocket($user, $message)
public function writeSocket($user, $message)
{
if (is_resource($user->socket)) {
if (!socket_write($user->socket, $message)) {
@ -186,53 +253,6 @@ class WSProxy extends WebSocketServer
}
/**
* Connects to internal socket.
*
* @param array $headers Communication headers.
*
* @return socket Active socket or null.
*/
protected function connectInt($headers)
{
$intSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$connect = socket_connect(
$intSocket,
$this->intHost,
$this->intPort
);
if (!$connect) {
return null;
}
$c_str = 'GET '.$this->intUrl." HTTP/1.1\r\n";
$c_str .= 'Host: '.$this->intHost."\r\n";
$c_str .= "Upgrade: websocket\r\n";
$c_str .= "Connection: Upgrade\r\n";
$c_str .= 'Origin: http://'.$this->intHost."\r\n";
$c_str .= 'Sec-WebSocket-Key: '.$headers['Sec-WebSocket-Key']."\r\n";
$c_str .= 'Sec-WebSocket-Version: '.$headers['Sec-WebSocket-Version']."\r\n";
if (isset($headers['Sec-WebSocket-Protocol'])) {
$c_str .= 'Sec-WebSocket-Protocol: '.$headers['Sec-WebSocket-Protocol']."\r\n";
}
$c_str .= "\r\n";
// Send.
// Register user - internal.
$intUser = new $this->userClass('INTERNAL-'.uniqid('u'), $intSocket);
$intUser->headers = [
'get' => $this->intUrl.' HTTP/1.1',
'host' => $this->intHost,
'origin' => $this->intHost,
];
$this->writeSocket($intUser, $c_str);
return $intUser;
}
/**
* User already connected.
*
@ -240,7 +260,7 @@ class WSProxy extends WebSocketServer
*
* @return void
*/
protected function connected($user)
public function connected($user)
{
global $config;
@ -275,31 +295,15 @@ class WSProxy extends WebSocketServer
// Disconnect previous sessions.
$this->cleanupSocketByCookie($user);
/*
* $user->intSocket is connected to internal.
* $user->socket is connected to external.
*/
// Create a new socket connection (internal).
$intUser = $this->connectInt($this->rawHeaders);
if ($intUser === null) {
$this->disconnect($user->socket);
return;
}
// Map user.
$user->intUser = $intUser;
// And socket.
$user->intSocket = $intUser->socket;
$user->redirect = $intUser;
$intUser->redirect = $user;
// Keep an eye on changes.
$this->remoteSockets[$intUser->id] = $intUser->socket;
$this->remoteUsers[$intUser->id] = $intUser;
// Ignore. Cleanup socket.
$response = $this->readSocket($user->intUser);
// Launch registered handler.
$this->callHandler(
$user,
$this->handlerConnected,
[
$this,
$user,
]
);
}
@ -310,12 +314,34 @@ class WSProxy extends WebSocketServer
*
* @return string
*/
protected function processProtocol($protocol): string
public function processProtocol($protocol): string
{
return 'Sec-Websocket-Protocol: '.$protocol."\r\n";
}
/**
* Process programattic function
*
* @return void
*/
public function tick()
{
foreach ($this->users as $user) {
// Launch registered handler.
$this->callHandler(
$user,
$this->handlerTick,
[
$this,
$user,
]
);
}
}
/**
* Process undecoded user message.
*
@ -324,18 +350,18 @@ class WSProxy extends WebSocketServer
*
* @return boolean
*/
protected function processRaw($user, $buffer)
public function processRaw($user, $buffer)
{
if (!isset($user->redirect)) {
$this->disconnect($user->socket);
return false;
}
$this->stderr($user->id.' >> '.$user->redirect->id);
$this->stderr($this->dump($buffer));
$this->writeSocket($user->redirect, $buffer);
return true;
// Launch registered handler.
return $this->callHandler(
$user,
$this->handlerProcessRaw,
[
$this,
$user,
$buffer,
]
);
}
@ -348,12 +374,24 @@ class WSProxy extends WebSocketServer
*
* @return void
*/
protected function process($user, $message, $str_message)
public function process($user, $message, $str_message)
{
if ($str_message === true) {
$remmitent = $user->address.'('.$user->account->idUser.')';
$this->stderr($remmitent.': '.$message);
}
// Launch registered handler.
$this->callHandler(
$user,
$this->handlerProcess,
[
$this,
$user,
$message,
$str_message,
]
);
}
@ -364,7 +402,7 @@ class WSProxy extends WebSocketServer
*
* @return void
*/
protected function closed($user)
public function closed($user)
{
if ($user->account) {
$_SERVER['REMOTE_ADDR'] = $user->address;

View File

@ -1,89 +0,0 @@
<?php
/**
* PHP WebSocketUser Proxy.
*
* Adapted to PandoraFMS by Fco de Borja Sanchez <fborja.sanchez@artica.es>
* Compatible with PHP >= 7.0
*
* @category External library
* @package Pandora FMS
* @subpackage WebSocketServer
* @version 1.0.0
* @license See below
* @filesource https://github.com/ghedipunk/PHP-Websockets
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of PHP WebSockets nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
// Begin.
namespace PandoraFMS\WebSockets;
use \PandoraFMS\WebSockets\WebSocketUser;
/**
* WebSocket proxy user.
*/
class WSProxyUser extends WebSocketUser
{
/**
* Redirection socket.
*
* @var socket
*/
public $intSocket;
/**
* Pair resend packages.
*
* @var WSProxyUser
*/
public $redirect;
/**
* User identifier.
*
* @var string
*/
public $myId;
/**
* Builder.
*
* @param string $id Identifier.
* @param socket $socket Socket (origin).
*/
public function __construct($id, $socket)
{
parent::__construct($id, $socket);
$this->myId = $id;
}
}

View File

@ -56,7 +56,7 @@ abstract class WebSocketServer
*
* @var string
*/
protected $userClass = 'WebSocketUser';
public $userClass = 'WebSocketUser';
/**
* Redefine this if you want a custom user class. The custom user class
@ -64,91 +64,91 @@ abstract class WebSocketServer
*
* @var integer
*/
protected $maxBufferSize;
public $maxBufferSize;
/**
* Max. concurrent connections.
*
* @var integer
*/
protected $maxConnections = 20;
public $maxConnections = 20;
/**
* Undocumented variable
*
* @var [type]
*/
protected $master;
public $master;
/**
* Incoming sockets.
*
* @var array
*/
protected $sockets = [];
public $sockets = [];
/**
* Outgoing sockets.
*
* @var array
*/
protected $remoteSockets = [];
public $remoteSockets = [];
/**
* Client list.
*
* @var array
*/
protected $users = [];
public $users = [];
/**
* Servers list.
*
* @var array
*/
protected $remoteUsers = [];
public $remoteUsers = [];
/**
* Undocumented variable
*
* @var array
*/
protected $heldMessages = [];
public $heldMessages = [];
/**
* Show output.
*
* @var boolean
*/
protected $interactive = true;
public $interactive = true;
/**
* Debug.
*
* @var boolean
*/
protected $debug = false;
public $debug = false;
/**
* Undocumented variable
*
* @var array
*/
protected $headerOriginRequired = false;
public $headerOriginRequired = false;
/**
* Undocumented variable
*
* @var array
*/
protected $headerSecWebSocketProtocolRequired = false;
public $headerSecWebSocketProtocolRequired = false;
/**
* Undocumented variable
*
* @var array
*/
protected $headerSecWebSocketExtensionsRequired = false;
public $headerSecWebSocketExtensionsRequired = false;
/**
* Stored raw headers for redirection.
@ -217,7 +217,7 @@ abstract class WebSocketServer
*
* @return void
*/
abstract protected function process($user, $message, $str_message);
abstract public function process($user, $message, $str_message);
/**
@ -228,7 +228,7 @@ abstract class WebSocketServer
*
* @return void
*/
protected function processRaw($user, $buffer)
public function processRaw($user, $buffer)
{
}
@ -241,7 +241,7 @@ abstract class WebSocketServer
*
* @return void
*/
abstract protected function connected($user);
abstract public function connected($user);
/**
@ -251,7 +251,7 @@ abstract class WebSocketServer
*
* @return void
*/
abstract protected function closed($user);
abstract public function closed($user);
/**
@ -263,7 +263,7 @@ abstract class WebSocketServer
*
* @return void
*/
protected function connecting($user)
public function connecting($user)
{
// Optional implementation.
}
@ -277,9 +277,8 @@ abstract class WebSocketServer
*
* @return void
*/
protected function send($user, $message)
public function send($user, $message)
{
var_dump($user->handshake);
if ($user->handshake) {
$message = $this->frame($message, $user);
$result = socket_write($user->socket, $message, strlen($message));
@ -301,7 +300,7 @@ abstract class WebSocketServer
*
* @return void
*/
protected function tick()
public function tick()
{
// Optional implementation.
}
@ -312,7 +311,7 @@ abstract class WebSocketServer
*
* @return void
*/
protected function pTick()
public function pTick()
{
// Core maintenance processes, such as retrying failed messages.
foreach ($this->heldMessages as $key => $hm) {
@ -532,7 +531,7 @@ abstract class WebSocketServer
*
* @return void
*/
protected function connect($socket)
public function connect($socket)
{
$user = new $this->userClass(
uniqid('u'),
@ -553,7 +552,7 @@ abstract class WebSocketServer
*
* @return void
*/
protected function disconnect(
public function disconnect(
$socket,
bool $triggerClosed=true,
$sockErrNo=null
@ -606,7 +605,7 @@ abstract class WebSocketServer
*
* @return void
*/
protected function doHandshake($user, $buffer)
public function doHandshake($user, $buffer)
{
$magicGUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
$headers = [];
@ -742,7 +741,7 @@ abstract class WebSocketServer
*
* @return boolean Ok or not.
*/
protected function checkHost($hostName): bool
public function checkHost($hostName): bool
{
// Override and return false if host is not one that you would expect.
// Ex: You only want to accept hosts from the my-domain.com domain,
@ -758,7 +757,7 @@ abstract class WebSocketServer
*
* @return boolean Allowed or not.
*/
protected function checkOrigin($origin): bool
public function checkOrigin($origin): bool
{
// Override and return false if origin is not one that you would expect.
return true;
@ -772,7 +771,7 @@ abstract class WebSocketServer
*
* @return boolean Expected or not.
*/
protected function checkWebsocProtocol($protocol): bool
public function checkWebsocProtocol($protocol): bool
{
// Override and return false if a protocol is not found that you
// would expect.
@ -787,7 +786,7 @@ abstract class WebSocketServer
*
* @return boolean Allowed or not.
*/
protected function checkWebsocExtensions($extensions): bool
public function checkWebsocExtensions($extensions): bool
{
// Override and return false if an extension is not found that you
// would expect.
@ -810,7 +809,7 @@ abstract class WebSocketServer
*
* @return string
*/
protected function processProtocol($protocol): string
public function processProtocol($protocol): string
{
return '';
}
@ -825,7 +824,7 @@ abstract class WebSocketServer
*
* @return string
*/
protected function processExtensions($extensions): string
public function processExtensions($extensions): string
{
return '';
}
@ -838,7 +837,7 @@ abstract class WebSocketServer
*
* @return object
*/
protected function getUserBySocket($socket)
public function getUserBySocket($socket)
{
foreach ($this->users as $user) {
if ($user->socket == $socket) {
@ -863,7 +862,7 @@ abstract class WebSocketServer
*
* @return void
*/
protected function cleanupSocketByCookie($user)
public function cleanupSocketByCookie($user)
{
$cookie = $user->headers['cookie'];
foreach ($this->users as $u) {
@ -884,7 +883,7 @@ abstract class WebSocketServer
*
* @return object
*/
protected function getIntUserBySocket($socket)
public function getIntUserBySocket($socket)
{
foreach ($this->users as $user) {
if ($user->intSocket == $socket) {
@ -941,7 +940,7 @@ abstract class WebSocketServer
*
* @return string Framed message.
*/
protected function frame(
public function frame(
$message,
$user,
$messageType='text',
@ -1046,7 +1045,7 @@ abstract class WebSocketServer
*
* @return void
*/
protected function splitPacket(
public function splitPacket(
int $length,
$packet,
$user
@ -1106,7 +1105,7 @@ abstract class WebSocketServer
*
* @return integer Calculated offset.
*/
protected function calcOffset(array $headers): int
public function calcOffset(array $headers): int
{
$offset = 2;
if ($headers['hasmask']) {
@ -1131,7 +1130,7 @@ abstract class WebSocketServer
*
* @return boolean Process ok or not.
*/
protected function deframe(
public function deframe(
$message,
&$user
) {
@ -1226,7 +1225,7 @@ abstract class WebSocketServer
*
* @return array Headers.
*/
protected function extractHeaders($message): array
public function extractHeaders($message): array
{
$header = [
'fin' => ($message[0] & chr(128)),
@ -1304,7 +1303,7 @@ abstract class WebSocketServer
*
* @return string
*/
protected function extractPayload(
public function extractPayload(
$message,
array $headers
) {
@ -1331,7 +1330,7 @@ abstract class WebSocketServer
*
* @return string Xor.
*/
protected function applyMask(
public function applyMask(
array $headers,
$payload
) {
@ -1373,7 +1372,7 @@ abstract class WebSocketServer
*
* @return boolean OK or not.
*/
protected function checkRSVBits(
public function checkRSVBits(
array $headers,
$user
): bool {
@ -1400,7 +1399,7 @@ abstract class WebSocketServer
*
* @return string HEX string.
*/
protected function strtohex(
public function strtohex(
$str=''
): string {
$strout = '';
@ -1441,7 +1440,7 @@ abstract class WebSocketServer
*
* @return void
*/
protected function printHeaders($headers)
public function printHeaders($headers)
{
echo "Array\n(\n";
foreach ($headers as $key => $value) {
@ -1468,7 +1467,7 @@ abstract class WebSocketServer
// Init.
$hexi = '';
$ascii = '';
$dump = '';
$dump = "Hex Message:\n";
$offset = 0;
$len = strlen($data);

View File

@ -0,0 +1,189 @@
<?php
/**
* Extra functionality for PandoraFMS WebSockets.
*
* Register here your methods to handle different WebSocket steps.
* * connected
* * process
* * disconnect
* * tick
*
* DO NOT FORGET TO REGISTER THEM TO ws.php!!
*
* @category Websocket
* @package Pandora FMS
* @subpackage Console
* @version 1.0.0
* @license See below
*
* ______ ___ _______ _______ ________
* | __ \.-----.--.--.--| |.-----.----.-----. | ___| | | __|
* | __/| _ | | _ || _ | _| _ | | ___| |__ |
* |___| |___._|__|__|_____||_____|__| |___._| |___| |__|_|__|_______|
*
* ============================================================================
* Copyright (c) 2005-2019 Artica Soluciones Tecnologicas
* Please see http://pandorafms.org for full contribution list
* 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 for version 2.
* 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.
* ============================================================================
*/
/*
* ============================================================================
* * GOTTY PROTOCOL: PROXY
* ============================================================================
*/
/**
* Connects to internal socket.
*
* @param WSManager $ws_object Main WebSocket manager object.
* @param array $headers Communication headers.
* @param string $to_addr Target address (internal).
* @param integer $to_port Target port (internal).
* @param integer $to_url Target url (internal).
*
* @return socket Active socket or null.
*/
function connectInt(
$ws_object,
$headers,
$to_addr,
$to_port,
$to_url
) {
$intSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$connect = socket_connect(
$intSocket,
$to_addr,
$to_port
);
if (!$connect) {
return null;
}
$c_str = 'GET '.$to_url." HTTP/1.1\r\n";
$c_str .= 'Host: '.$to_addr."\r\n";
$c_str .= "Upgrade: websocket\r\n";
$c_str .= "Connection: Upgrade\r\n";
$c_str .= 'Origin: http://'.$to_addr."\r\n";
$c_str .= 'Sec-WebSocket-Key: '.$headers['Sec-WebSocket-Key']."\r\n";
$c_str .= 'Sec-WebSocket-Version: '.$headers['Sec-WebSocket-Version']."\r\n";
if (isset($headers['Sec-WebSocket-Protocol'])) {
$c_str .= 'Sec-WebSocket-Protocol: '.$headers['Sec-WebSocket-Protocol']."\r\n";
}
$c_str .= "\r\n";
// Send.
// Register user - internal.
$intUser = new $ws_object->userClass('INTERNAL-'.uniqid('u'), $intSocket);
$intUser->headers = [
'get' => $to_url.' HTTP/1.1',
'host' => $to_addr,
'origin' => $to_addr,
'sec-websocket-protocol' => 'gotty',
];
$ws_object->writeSocket($intUser, $c_str);
return $intUser;
}
/**
* Process a connected step on proxied protocols.
*
* @param WSManager $ws_object Main WebSocket manager object.
* @param User $user WebSocketUser.
*
* @return void
*/
function proxyConnected(
$ws_object,
$user
) {
global $config;
/*
* $user->redirect is connected to internal (reflexive).
* $user->socket is connected to external.
*/
// Gotty. Based on the command selected, redirect to a target port.
if ($user->requestedResource == '/ssh') {
$port = $config['gotty_ssh_port'];
} else if ($user->requestedResource == '/telnet') {
$port = $config['gotty_telnet_port'];
} else {
$ws_object->disconnect($user->socket);
return;
}
// Switch between ports...
// Create a new socket connection (internal).
$intUser = connectInt(
$ws_object,
$ws_object->rawHeaders,
$config['gotty_host'],
$port,
'/ws'
);
if ($intUser === null) {
$ws_object->disconnect($user->socket);
return;
}
// Map user.
$user->intUser = $intUser;
// And socket.
$user->intSocket = $intUser->socket;
$user->redirect = $intUser;
$intUser->redirect = $user;
// Keep an eye on changes.
$ws_object->remoteSockets[$intUser->id] = $intUser->socket;
$ws_object->remoteUsers[$intUser->id] = $intUser;
// Ignore. Cleanup socket.
$response = $ws_object->readSocket($user->intUser);
}
/**
* Redirects input from user to redirection stabished.
*
* @param WSManager $ws_object Main WebSocket manager object.
* @param WebSocketUser $user WebSocket user.
* @param string $buffer Buffer.
*
* @return boolean Ok or not.
*/
function proxyProcessRaw($ws_object, $user, $buffer)
{
if (isset($user->redirect) !== true) {
$ws_object->disconnect($user->socket);
return false;
}
$ws_object->stderr($user->id.' >> '.$user->redirect->id);
$ws_object->stderr($ws_object->dump($buffer));
$ws_object->writeSocket($user->redirect, $buffer);
return true;
}
/*
* ============================================================================
* * ENDS: GOTTY PROTOCOL: PROXY
* ============================================================================
*/

View File

@ -308,8 +308,7 @@ return array(
'Mpdf\\Utils\\PdfDate' => $vendorDir . '/mpdf/mpdf/src/Utils/PdfDate.php',
'Mpdf\\Utils\\UtfString' => $vendorDir . '/mpdf/mpdf/src/Utils/UtfString.php',
'PandoraFMS\\User' => $baseDir . '/include/lib/User.php',
'PandoraFMS\\WebSockets\\WSProxy' => $baseDir . '/include/lib/WSProxy.php',
'PandoraFMS\\WebSockets\\WSProxyUser' => $baseDir . '/include/lib/WSProxyUser.php',
'PandoraFMS\\WebSockets\\WSManager' => $baseDir . '/include/lib/WSManager.php',
'PandoraFMS\\Websockets\\WebSocketServer' => $baseDir . '/include/lib/WebSocketServer.php',
'PandoraFMS\\Websockets\\WebSocketUser' => $baseDir . '/include/lib/WebSocketUser.php',
'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php',

View File

@ -390,8 +390,7 @@ class ComposerStaticInitfdecadadce22e6dde51e9535fe4ad7aa
'Mpdf\\Utils\\PdfDate' => __DIR__ . '/..' . '/mpdf/mpdf/src/Utils/PdfDate.php',
'Mpdf\\Utils\\UtfString' => __DIR__ . '/..' . '/mpdf/mpdf/src/Utils/UtfString.php',
'PandoraFMS\\User' => __DIR__ . '/../..' . '/include/lib/User.php',
'PandoraFMS\\WebSockets\\WSProxy' => __DIR__ . '/../..' . '/include/lib/WSProxy.php',
'PandoraFMS\\WebSockets\\WSProxyUser' => __DIR__ . '/../..' . '/include/lib/WSProxyUser.php',
'PandoraFMS\\WebSockets\\WSManager' => __DIR__ . '/../..' . '/include/lib/WSManager.php',
'PandoraFMS\\Websockets\\WebSocketServer' => __DIR__ . '/../..' . '/include/lib/WebSocketServer.php',
'PandoraFMS\\Websockets\\WebSocketUser' => __DIR__ . '/../..' . '/include/lib/WebSocketUser.php',
'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php',

View File

@ -28,7 +28,7 @@
// Begin.
require_once __DIR__.'/vendor/autoload.php';
use \PandoraFMS\WebSockets\WSProxy;
use \PandoraFMS\WebSockets\WSManager;
// Set to true to get full output.
$debug = false;
@ -48,10 +48,11 @@ $_SERVER['DOCUMENT_ROOT'] = __DIR__.'/../';
// Don't start a session before this import.
// The session is configured and started inside the config process.
require_once 'include/config.php';
require_once 'include/functions.php';
require_once 'include/functions_db.php';
require_once 'include/auth/mysql.php';
require_once __DIR__.'/include/config.php';
require_once __DIR__.'/include/functions.php';
require_once __DIR__.'/include/functions_db.php';
require_once __DIR__.'/include/auth/mysql.php';
require_once __DIR__.'/include/websocket_registrations.php';
// Enterprise support.
if (file_exists(ENTERPRISE_DIR.'/load_enterprise.php') === true) {
@ -67,7 +68,23 @@ if (isset($_SERVER['REMOTE_ADDR']) === true) {
if (isset($config['ws_port']) === false) {
config_update_value('ws_port', 8081);
config_update_value('ws_port', 8080);
}
if (isset($config['ws_bind_address']) === false) {
config_update_value('ws_bind_address', '0.0.0.0');
}
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);
}
if (isset($config['gotty']) === false) {
@ -80,25 +97,44 @@ error_reporting(E_ALL);
$os = strtolower(PHP_OS);
if (substr($os, 0, 3) !== 'win') {
// Launch gotty.
$cmd = 'nohup "'.$config['gotty'].'" -a 127.0.0.1 -w /bin/bash';
$cmd .= ' >> '.__DIR__.'/pandora_console.log 2>&1 &';
// Kill previous gotty running.
shell_exec('killall "'.$config['gotty'].'" >/dev/null 2>&1');
// Common.
$base_cmd = 'nohup "'.$config['gotty'].'"';
$base_cmd .= ' --permit-arguments -a 127.0.0.1 -w ';
// Launch gotty - SSH.
$cmd = $base_cmd.' --port '.$config['gotty_ssh_port'];
$cmd .= ' ssh >> '.__DIR__.'/pandora_console.log 2>&1 &';
shell_exec($cmd);
// Launch gotty - telnet.
$cmd = $base_cmd.' --port '.$config['gotty_telnet_port'];
$cmd .= ' telnet >> '.__DIR__.'/pandora_console.log 2>&1 &';
shell_exec($cmd);
}
// Start Web SocketProxy.
$wsproxy = new WSProxy(
'0.0.0.0',
$ws = new WSManager(
// Bind address.
$config['ws_bind_address'],
// Bind port.
$config['ws_port'],
'127.0.0.1',
'8080',
'/ws',
// Connected handlers.
['gotty' => 'proxyConnected'],
// Process handlers.
[],
// ProcessRaw handlers.
['gotty' => 'proxyProcessRaw'],
// Tick handlers.
[],
$bufferSize,
$debug
);
try {
$wsproxy->run();
$ws->run();
} catch (Exception $e) {
$wsproxy->stdout($e->getMessage());
$ws->stdout($e->getMessage());
}