pandorafms/pandora_console/include/lib/Websockets/WSManager.php

461 lines
12 KiB
PHP
Raw Normal View History

2019-10-14 19:33:00 +02:00
<?php
2019-10-15 22:16:54 +02:00
/**
* PHP WebSocketServer Manager.
2019-10-15 22:16:54 +02:00
*
* 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.
*/
2019-10-14 19:33:00 +02:00
2019-10-15 22:16:54 +02:00
// Begin.
namespace PandoraFMS\Websockets;
2019-10-14 19:33:00 +02:00
2019-10-17 15:31:17 +02:00
use \PandoraFMS\Websockets\WebSocketServer;
2019-10-18 18:55:29 +02:00
use \PandoraFMS\User;
2019-10-14 19:33:00 +02:00
require_once __DIR__.'/../../functions.php';
2019-10-18 18:55:29 +02:00
2019-10-14 19:33:00 +02:00
/**
* Redirects ws communication between two endpoints.
*/
class WSManager extends WebSocketServer
2019-10-14 19:33:00 +02:00
{
/**
* Target host.
*
* @var string
*/
private $intHost = '127.0.0.1';
/**
* Target port
*
* @var integer
*/
private $intPort = 8080;
/**
* Internal URL.
*
* @var string
*/
private $intUrl = '/ws';
/**
* 1MB... overkill for an echo server, but potentially plausible for other
* applications.
*
* @var integer
*/
public $maxBufferSize = 1048576;
2019-10-14 19:33:00 +02:00
/**
* Interactive mode.
*
* @var boolean
*/
public $interative = true;
2019-10-14 19:33:00 +02:00
2019-10-15 22:16:54 +02:00
/**
2019-10-18 18:55:29 +02:00
* Use a timeout of 100 milliseconds to search for messages..
2019-10-15 22:16:54 +02:00
*
* @var integer
*/
2019-10-18 18:55:29 +02:00
public $timeout = 250;
2019-10-15 22:16:54 +02:00
/**
* 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 = [];
/**
* Allow only one connection per user session.
*
* @var boolean
*/
public $socketPerSession = false;
2019-10-14 19:33:00 +02:00
/**
* Builder.
*
* @param string $listen_addr Target address (external).
* @param integer $listen_port Target port (external).
* @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.
2019-10-14 19:33:00 +02:00
* @param integer $bufferLength Max buffer size.
* @param boolean $debug Enable traces.
*/
public function __construct(
$listen_addr,
2019-12-13 11:58:12 +01:00
int $listen_port,
$connected=[],
$process=[],
$processRaw=[],
$tick=[],
2019-10-14 19:33:00 +02:00
$bufferLength=1048576,
$debug=false
) {
$this->maxBufferSize = $bufferLength;
2019-10-18 18:55:29 +02:00
$this->debug = $debug;
// Configure handlers.
$this->handlerConnected = $connected;
$this->handlerProcess = $process;
$this->handlerProcessRaw = $processRaw;
$this->handlerTick = $tick;
2019-10-17 15:31:17 +02:00
$this->userClass = '\\PandoraFMS\\Websockets\\WebSocketUser';
2019-10-14 19:33:00 +02:00
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],
2022-01-13 12:43:55 +01:00
array_values(($arguments ?? []))
);
}
}
return null;
}
2019-10-14 19:33:00 +02:00
/**
2019-10-15 22:16:54 +02:00
* Read from user's socket.
2019-10-14 19:33:00 +02:00
*
2022-01-26 18:40:45 +01:00
* @param object $user Target user connection.
* @param integer $flags Socket receive flags:
* Flag Description
* MSG_OOB Process out-of-band data.
* MSG_PEEK Receive data from the beginning of the receive
* queue without removing it from the queue.
* MSG_WAITALL Block until at least len are received. However,
* if a signal is caught or the remote host
* disconnects, the function may return less data.
* MSG_DONTWAIT With this flag set, the function returns even
* if it would normally have blocked.
2019-10-14 19:33:00 +02:00
*
* @return string Buffer.
*/
2022-01-26 18:40:45 +01:00
public function readSocket($user, $flags=0)
2019-10-14 19:33:00 +02:00
{
$buffer = '';
2019-10-14 19:33:00 +02:00
$numBytes = socket_recv(
$user->socket,
$buffer,
$this->maxBufferSize,
2022-01-26 18:40:45 +01:00
$flags
2019-10-14 19:33:00 +02:00
);
if ($numBytes === false) {
// Failed. Disconnect.
2019-10-15 22:16:54 +02:00
$this->handleSocketError($user->socket);
return false;
} else if ($numBytes == 0) {
2019-10-14 19:33:00 +02:00
$this->disconnect($user->socket);
2019-10-15 22:16:54 +02:00
$this->stderr(
2022-01-26 18:40:45 +01:00
'Client disconnected. TCP connection lost: '.$user->id
2019-10-15 22:16:54 +02:00
);
2019-10-14 19:33:00 +02:00
return false;
}
2019-10-15 17:20:23 +02:00
$user->lastRawPacket = $buffer;
return $buffer;
2019-10-14 19:33:00 +02:00
}
/**
* Write to socket.
*
2019-10-15 22:16:54 +02:00
* @param object $user Target user connection.
2019-10-14 19:33:00 +02:00
* @param string $message Target message to be sent.
*
* @return void
*/
public function writeSocket($user, $message)
2019-10-14 19:33:00 +02:00
{
2019-10-15 22:16:54 +02:00
if (is_resource($user->socket)) {
2022-01-26 18:40:45 +01:00
if (socket_write($user->socket, $message) === false) {
2019-10-15 17:20:23 +02:00
$this->disconnect($user->socket);
}
2019-10-14 19:33:00 +02:00
} else {
2019-10-15 22:16:54 +02:00
// Failed. Disconnect all.
2022-01-21 17:39:11 +01:00
if (isset($user) === true) {
$this->disconnect($user->socket);
}
if (isset($user->redirect) === true) {
$this->disconnect($user->redirect->socket);
}
2019-10-14 19:33:00 +02:00
}
}
/**
* User already connected.
*
* @param object $user User.
*
* @return void
*/
public function connected($user)
2019-10-14 19:33:00 +02:00
{
2019-10-18 18:55:29 +02:00
global $config;
$match = [];
2019-12-11 13:04:45 +01:00
$php_session_id = '';
\preg_match(
'/PHPSESSID=(.*)/',
$user->headers['cookie'],
$match
2019-10-18 18:55:29 +02:00
);
2019-12-11 13:04:45 +01:00
if (is_array($match) === true) {
$php_session_id = $match[1];
}
$php_session_id = \preg_replace('/;.*$/', '', $php_session_id);
2019-10-24 12:18:49 +02:00
// If being redirected from proxy.
if (isset($user->headers['x-forwarded-for']) === true) {
$user->address = $user->headers['x-forwarded-for'];
}
2019-10-18 18:55:29 +02:00
$user->account = new User(['phpsessionid' => $php_session_id]);
$_SERVER['REMOTE_ADDR'] = $user->address;
// Ensure user is allowed to connect.
if (\check_login(false) === false) {
$this->disconnect($user->socket);
\db_pandora_audit(
'WebSockets engine',
'Trying to access websockets engine without a valid session',
'N/A'
);
return;
}
// User exists, and session is valid.
\db_pandora_audit(
'WebSockets engine',
'WebSocket connection started',
$user->account->idUser
);
$this->stderr('ONLINE '.$user->address.'('.$user->account->idUser.')');
if ($this->socketPerSession === true) {
// Disconnect previous sessions.
$this->cleanupSocketByCookie($user);
}
2019-10-14 19:33:00 +02:00
// Launch registered handler.
$this->callHandler(
$user,
$this->handlerConnected,
[
$this,
$user,
]
);
2019-10-14 19:33:00 +02:00
}
/**
* Protocol.
*
* @param string $protocol Protocol.
*
* @return string
*/
public function processProtocol($protocol): string
2019-10-14 19:33:00 +02:00
{
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,
]
);
}
}
2019-10-14 19:33:00 +02:00
/**
2019-10-15 17:20:23 +02:00
* Process undecoded user message.
2019-10-14 19:33:00 +02:00
*
2019-10-15 17:20:23 +02:00
* @param object $user User.
* @param string $buffer Message.
2019-10-14 19:33:00 +02:00
*
2019-10-15 17:20:23 +02:00
* @return boolean
2019-10-14 19:33:00 +02:00
*/
public function processRaw($user, $buffer)
2019-10-14 19:33:00 +02:00
{
// Launch registered handler.
return $this->callHandler(
$user,
$this->handlerProcessRaw,
[
$this,
$user,
$buffer,
]
);
2019-10-15 17:20:23 +02:00
}
/**
2019-10-15 22:16:54 +02:00
* Process user message. Implement.
2019-10-15 17:20:23 +02:00
*
2019-10-18 18:55:29 +02:00
* @param object $user User.
* @param string $message Message.
* @param boolean $str_message String message or not.
2019-10-15 17:20:23 +02:00
*
* @return void
*/
public function process($user, $message, $str_message)
2019-10-15 17:20:23 +02:00
{
2019-10-18 18:55:29 +02:00
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,
]
);
2019-10-14 19:33:00 +02:00
}
/**
* Also close internal socket.
*
* @param object $user User.
*
* @return void
*/
public function closed($user)
2019-10-14 19:33:00 +02:00
{
2019-10-18 18:55:29 +02:00
if ($user->account) {
$_SERVER['REMOTE_ADDR'] = $user->address;
\db_pandora_audit(
'WebSockets engine',
'WebSocket connection finished',
$user->account->idUser
);
$this->stderr('OFFLINE '.$user->address.'('.$user->account->idUser.')');
}
// Ensure both sockets are disconnected.
$this->disconnect($user->socket);
if ($user->redirect) {
$this->disconnect($user->redirect->socket);
}
2019-10-14 19:33:00 +02:00
}
}