390 lines
11 KiB
PHP
390 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Classes;
|
|
|
|
use Exception;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
use App\Classes\File\{Receive,Send};
|
|
use App\Classes\Sock\SocketClient;
|
|
use App\Classes\Sock\SocketException;
|
|
use App\Models\{Address,Setup,System,SystemLog};
|
|
|
|
abstract class Protocol
|
|
{
|
|
// Enable extra debugging
|
|
protected bool $DEBUG = FALSE;
|
|
|
|
private const LOGKEY = 'P--';
|
|
|
|
// Return constants
|
|
protected const OK = 0;
|
|
protected const EOF = -1;
|
|
protected const TIMEOUT = -2;
|
|
protected const RCDO = -3;
|
|
protected const GCOUNT = -4;
|
|
protected const ERROR = -5;
|
|
|
|
// Our sessions Types
|
|
public const SESSION_AUTO = 0;
|
|
public const SESSION_EMSI = 1;
|
|
public const SESSION_BINKP = 2;
|
|
public const SESSION_ZMODEM = 3;
|
|
|
|
protected const MAX_PATH = 1024;
|
|
|
|
/* 9 most right bits are zeros */
|
|
private const O_BASE = 9; /* First 9 bits are protocol */
|
|
protected const O_NRQ = (1<<self::O_BASE); /* 0000 0000 0000 0000 0010 0000 0000 BOTH - No file requests accepted by this system */
|
|
protected const O_HRQ = (1<<(self::O_BASE+1)); /* 0000 0000 0000 0000 0100 0000 0000 BOTH - Hold file requests (not processed at this time). */
|
|
protected const O_FNC = (1<<(self::O_BASE+2)); /* 0000 0000 0000 0000 1000 0000 0000 - Filename conversion, transmitted files must be 8.3 */
|
|
protected const O_XMA = (1<<(self::O_BASE+3)); /* 0000 0000 0000 0001 0000 0000 0000 - Supports other forms of compressed mail */
|
|
protected const O_HAT = (1<<(self::O_BASE+4)); /* 0000 0000 0000 0010 0000 0000 0000 BOTH - Hold ALL files (Answering System) */
|
|
protected const O_HXT = (1<<(self::O_BASE+5)); /* 0000 0000 0000 0100 0000 0000 0000 BOTH - Hold Mail traffic */
|
|
protected const O_NPU = (1<<(self::O_BASE+6)); /* 0000 0000 0000 1000 0000 0000 0000 - No files pickup desired (Calling System) */
|
|
protected const O_PUP = (1<<(self::O_BASE+7)); /* 0000 0000 0001 0000 0000 0000 0000 - Pickup files for primary address only */
|
|
protected const O_PUA = (1<<(self::O_BASE+8)); /* 0000 0000 0010 0000 0000 0000 0000 EMSI - Pickup files for all presented addresses */
|
|
protected const O_PWD = (1<<(self::O_BASE+9)); /* 0000 0000 0100 0000 0000 0000 0000 BINK - Node needs to be password validated */
|
|
protected const O_BAD = (1<<(self::O_BASE+10)); /* 0000 0000 1000 0000 0000 0000 0000 BOTH - Node invalid password presented */
|
|
protected const O_RH1 = (1<<(self::O_BASE+11)); /* 0000 0001 0000 0000 0000 0000 0000 EMSI - Use RH1 for Hydra (files-after-freqs) */
|
|
protected const O_LST = (1<<(self::O_BASE+12)); /* 0000 0010 0000 0000 0000 0000 0000 BOTH - Node is nodelisted */
|
|
protected const O_INB = (1<<(self::O_BASE+13)); /* 0000 0100 0000 0000 0000 0000 0000 BOTH - Inbound session */
|
|
protected const O_TCP = (1<<(self::O_BASE+14)); /* 0000 1000 0000 0000 0000 0000 0000 BOTH - TCP session */
|
|
protected const O_EII = (1<<(self::O_BASE+15)); /* 0001 0000 0000 0000 0000 0000 0000 EMSI - Remote understands EMSI-II */
|
|
|
|
// Session Status
|
|
protected const S_OK = 0;
|
|
protected const S_NODIAL = 1;
|
|
protected const S_REDIAL = 2;
|
|
protected const S_BUSY = 3;
|
|
protected const S_FAILURE = 4;
|
|
protected const S_MASK = 7;
|
|
protected const S_HOLDR = 8;
|
|
protected const S_HOLDX = 16;
|
|
protected const S_HOLDA = 32;
|
|
protected const S_ADDTRY = 64;
|
|
protected const S_ANYHOLD = (self::S_HOLDR|self::S_HOLDX|self::S_HOLDA);
|
|
|
|
// File transfer status
|
|
protected const FOP_OK = 0;
|
|
protected const FOP_CONT = 1;
|
|
protected const FOP_SKIP = 2;
|
|
protected const FOP_ERROR = 3;
|
|
protected const FOP_SUSPEND = 4;
|
|
|
|
protected const MO_CHAT = 4;
|
|
|
|
protected SocketClient $client; /* Our socket details */
|
|
protected ?Setup $setup; /* Our setup */
|
|
protected Node $node; /* The node we are communicating with */
|
|
protected Send $send; /* The list of files we are sending */
|
|
protected Receive $recv; /* The list of files we are receiving */
|
|
|
|
private int $options; /* Our options for a session */
|
|
private int $session; /* Tracks where we are up to with this session */
|
|
protected bool $originate; /* Are we originating a connection */
|
|
private array $comms; /* Our comms details */
|
|
|
|
abstract protected function protocol_init(): int;
|
|
|
|
abstract protected function protocol_session(): int;
|
|
|
|
public function __construct(Setup $o=NULL)
|
|
{
|
|
if ($o && ! $o->system->addresses->count())
|
|
throw new Exception('We dont have any FTN addresses assigned');
|
|
|
|
$this->setup = $o;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
public function __get($key)
|
|
{
|
|
switch ($key) {
|
|
case 'ls_SkipGuard': /* double-skip protection on/off */
|
|
case 'rxOptions': /* Options from ZRINIT header */
|
|
case 'socket_error':
|
|
return $this->comms[$key] ?? 0;
|
|
|
|
case 'ls_rxAttnStr':
|
|
return $this->comms[$key] ?? '';
|
|
|
|
default:
|
|
throw new Exception('Unknown key: '.$key);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
public function __set($key,$value)
|
|
{
|
|
switch ($key) {
|
|
case 'ls_rxAttnStr':
|
|
case 'ls_SkipGuard':
|
|
case 'rxOptions':
|
|
case 'socket_error':
|
|
$this->comms[$key] = $value;
|
|
break;
|
|
|
|
default:
|
|
throw new Exception('Unknown key: '.$key);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* We got an error, close anything we are have open
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
protected function error_close(): void
|
|
{
|
|
if ($this->send->fd)
|
|
$this->send->close(FALSE);
|
|
|
|
if ($this->recv->fd)
|
|
$this->recv->close();
|
|
}
|
|
|
|
/**
|
|
* Incoming Protocol session
|
|
*
|
|
* @param SocketClient $client
|
|
* @return int|null
|
|
* @throws SocketException
|
|
*/
|
|
public function onConnect(SocketClient $client): ?int
|
|
{
|
|
$pid = pcntl_fork();
|
|
|
|
if ($pid == -1)
|
|
throw new SocketException(SocketException::CANT_ACCEPT,'Could not fork process');
|
|
|
|
Log::debug(sprintf('%s:= End [%d]',self::LOGKEY,$pid));
|
|
|
|
// Parent return ready for next connection
|
|
return $pid;
|
|
}
|
|
|
|
protected function optionClear(int $key): void
|
|
{
|
|
$this->options &= ~$key;
|
|
}
|
|
|
|
protected function optionGet(int $key): int
|
|
{
|
|
return ($this->options & $key);
|
|
}
|
|
|
|
protected function optionSet(int $key): void
|
|
{
|
|
$this->options |= $key;
|
|
}
|
|
|
|
/**
|
|
* Our addresses to send to the remote
|
|
* @return Collection
|
|
*/
|
|
protected function our_addresses(): Collection
|
|
{
|
|
if ($this->setup->optionGet(Setup::O_HIDEAKA)) {
|
|
$addresses = collect();
|
|
|
|
foreach (($this->originate ? $this->node->aka_remote_authed : $this->node->aka_remote) as $ao)
|
|
$addresses = $addresses->merge($this->setup->system->match($ao->zone,Address::NODE_ZC|Address::NODE_RC|Address::NODE_NC|Address::NODE_HC|Address::NODE_ACTIVE|Address::NODE_PVT|Address::NODE_POINT));
|
|
|
|
$addresses = $addresses->unique();
|
|
|
|
Log::debug(sprintf('%s: - Presenting limited AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(',')));
|
|
|
|
} else {
|
|
$addresses = $this->setup->system->addresses;
|
|
|
|
Log::debug(sprintf('%s: - Presenting ALL our AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(',')));
|
|
}
|
|
|
|
return $addresses;
|
|
}
|
|
|
|
/**
|
|
* Initialise our Session
|
|
*
|
|
* @param int $type
|
|
* @param SocketClient $client
|
|
* @param Address|null $o
|
|
* @return int
|
|
* @throws Exception
|
|
*/
|
|
public function session(int $type,SocketClient $client,Address $o=NULL): int
|
|
{
|
|
if ($o->exists)
|
|
Log::withContext(['ftn'=>$o->ftn]);
|
|
|
|
Log::debug(sprintf('%s:+ Start [%d]',self::LOGKEY,$type));
|
|
|
|
// This sessions options
|
|
$this->options = 0;
|
|
$this->session = 0;
|
|
|
|
// Our files that we are sending/receive
|
|
$this->send = new Send;
|
|
$this->recv = new Receive;
|
|
|
|
if ($o) {
|
|
// The node we are communicating with
|
|
$this->node = new Node;
|
|
|
|
$this->originate = $o->exists;
|
|
|
|
// If we are connecting to a node
|
|
if ($o->exists) {
|
|
Log::debug(sprintf('%s: + Originating a connection to [%s]',self::LOGKEY,$o->ftn));
|
|
$this->node->originate($o);
|
|
|
|
} else {
|
|
$this->optionSet(self::O_INB);
|
|
}
|
|
}
|
|
|
|
// We are an IP node
|
|
$this->optionSet(self::O_TCP);
|
|
$this->setClient($client);
|
|
|
|
switch ($type) {
|
|
/** @noinspection PhpMissingBreakStatementInspection */
|
|
case self::SESSION_AUTO:
|
|
Log::debug(sprintf('%s: - Trying EMSI',self::LOGKEY));
|
|
|
|
$rc = $this->protocol_init();
|
|
if ($rc < 0) {
|
|
Log::error(sprintf('%s:! Unable to start EMSI [%d]',self::LOGKEY,$rc));
|
|
|
|
return self::S_REDIAL | self::S_ADDTRY;
|
|
}
|
|
|
|
case self::SESSION_EMSI:
|
|
Log::debug(sprintf('%s: - Starting EMSI',self::LOGKEY));
|
|
$rc = $this->protocol_session();
|
|
|
|
break;
|
|
|
|
case self::SESSION_BINKP:
|
|
Log::debug(sprintf('%s: - Starting BINKP',self::LOGKEY));
|
|
$rc = $this->protocol_session();
|
|
|
|
break;
|
|
|
|
case self::SESSION_ZMODEM:
|
|
Log::debug(sprintf('%s: - Starting ZMODEM',self::LOGKEY));
|
|
$this->client->speed = SocketClient::TCP_SPEED;
|
|
$this->originate = FALSE;
|
|
|
|
return $this->protocol_session();
|
|
|
|
default:
|
|
Log::error(sprintf('%s: ! Unsupported session type [%d]',self::LOGKEY,$type));
|
|
|
|
return self::S_REDIAL | self::S_ADDTRY;
|
|
}
|
|
|
|
// @todo Unlock outbounds
|
|
|
|
// @todo These flags determine when we connect to the remote.
|
|
// If the remote indicated that they dont support file requests (NRQ) or temporarily hold them (HRQ)
|
|
if (($this->node->optionGet(self::O_NRQ) && (! $this->setup->ignore_nrq)) || $this->node->optionGet(self::O_HRQ))
|
|
$rc |= self::S_HOLDR;
|
|
|
|
if ($this->optionGet(self::O_HXT))
|
|
$rc |= self::S_HOLDX;
|
|
|
|
if ($this->optionGet(self::O_HAT))
|
|
$rc |= self::S_HOLDA;
|
|
|
|
Log::info(sprintf('%s: Total: %s - %d:%02d:%02d online, (%d) %lu%s sent, (%d) %lu%s received - %s',
|
|
self::LOGKEY,
|
|
$this->node->address ? $this->node->address->ftn : 'Unknown',
|
|
$this->node->session_time/3600,
|
|
$this->node->session_time%3600/60,
|
|
$this->node->session_time%60,
|
|
$this->send->total_sent,$this->send->total_sent_bytes,'b',
|
|
$this->recv->total_recv,$this->recv->total_recv_bytes,'b',
|
|
(($rc & self::S_MASK) == self::S_OK) ? 'Successful' : 'Failed',
|
|
));
|
|
|
|
// Add unknown FTNs to the DB
|
|
if ($this->node->aka_remote_authed->count()) {
|
|
$so = $this->node->aka_remote_authed->first()->system;
|
|
} else {
|
|
$so = System::where('name','Discovered System')->single();
|
|
}
|
|
|
|
if ($so && $so->exists) {
|
|
foreach ($this->node->aka_other as $aka) {
|
|
Address::findFTN($aka,TRUE,$so);
|
|
}
|
|
|
|
// Log session in DB
|
|
$slo = new SystemLog;
|
|
$slo->items_sent = $this->send->total_sent;
|
|
$slo->items_sent_size = $this->send->total_sent_bytes;
|
|
$slo->items_recv = $this->recv->total_recv;
|
|
$slo->items_recv_size = $this->recv->total_recv_bytes;
|
|
$slo->sessiontype = $type;
|
|
$slo->sessiontime = $this->node->session_time;
|
|
$slo->result = ($rc & self::S_MASK);
|
|
|
|
$so->logs()->save($slo);
|
|
}
|
|
|
|
// @todo Optional after session execution event
|
|
// if ($this->node->start_time && $this->setup->cfg('CFG_AFTERSESSION')) {}
|
|
|
|
// @todo Optional after session includes mail event
|
|
// if ($this->node->start_time && $this->setup->cfg('CFG_AFTERMAIL')) {}
|
|
|
|
return ($rc & ~self::S_ADDTRY);
|
|
}
|
|
|
|
/**
|
|
* Clear a session bit
|
|
*
|
|
* @param int $key
|
|
*/
|
|
protected function sessionClear(int $key): void
|
|
{
|
|
$this->session &= ~$key;
|
|
}
|
|
|
|
/**
|
|
* Get a session bit
|
|
* @param int $key
|
|
* @return bool
|
|
*/
|
|
protected function sessionGet(int $key): bool
|
|
{
|
|
return ($this->session & $key);
|
|
}
|
|
|
|
/**
|
|
* Set a session bit (with SE_*)
|
|
*
|
|
* @param int $key
|
|
*/
|
|
protected function sessionSet(int $key): void
|
|
{
|
|
$this->session |= $key;
|
|
}
|
|
|
|
/**
|
|
* Set our client that we are communicating with
|
|
*
|
|
* @param SocketClient $client
|
|
*/
|
|
private function setClient(SocketClient $client): void
|
|
{
|
|
$this->client = $client;
|
|
}
|
|
} |