Add Zmodem/BINKP/EMSI

This commit is contained in:
Deon George 2021-04-01 21:59:15 +11:00
parent 619cabb751
commit b94e39c7af
33 changed files with 8216 additions and 42 deletions

View File

@ -5,6 +5,7 @@ namespace App\Classes;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use App\Exceptions\InvalidFidoPacketException; use App\Exceptions\InvalidFidoPacketException;
use App\Traits\GetNode;
/** /**
* Class FTNMessage * Class FTNMessage
@ -14,6 +15,8 @@ use App\Exceptions\InvalidFidoPacketException;
*/ */
class FTNMessage extends FTN class FTNMessage extends FTN
{ {
use GetNode;
private $src = NULL; // SRC N/F from packet private $src = NULL; // SRC N/F from packet
private $dst = NULL; // DST N/F from packet private $dst = NULL; // DST N/F from packet
@ -106,6 +109,7 @@ class FTNMessage extends FTN
case 'fp': return ftn_address_split($this->_fqfa,'p'); case 'fp': return ftn_address_split($this->_fqfa,'p');
case 'fqfa': return $this->_fqfa; case 'fqfa': return $this->_fqfa;
case 'fqda': return $this->_fqda;
// Echomails dont have a fully qualified from address // Echomails dont have a fully qualified from address
case 'tz': return ftn_address_split($this->_fqda,'z'); case 'tz': return ftn_address_split($this->_fqda,'z');
@ -136,7 +140,7 @@ class FTNMessage extends FTN
{ {
case 'fqfa': case 'fqfa':
case 'fqda': case 'fqda':
$this->{'_'.$k} = $v; $this->{'_'.$k} = $this->get_node(ftn_address_split($v),TRUE);
if ($this->_fqfa AND $this->_fqda) if ($this->_fqfa AND $this->_fqda)
$this->intl = sprintf('%s %s',$this->_fqda,$this->_fqfa); $this->intl = sprintf('%s %s',$this->_fqda,$this->_fqfa);
@ -178,6 +182,9 @@ class FTNMessage extends FTN
$return .= $this->from."\00"; $return .= $this->from."\00";
$return .= $this->subject."\00"; $return .= $this->subject."\00";
if ($this->type == 'echomail')
$return .= "AREA:".$this->echoarea."\r";
// Add some kludges // Add some kludges
$return .= "\01MSGID ".$this->_fqfa." 1"."\r"; $return .= "\01MSGID ".$this->_fqfa." 1"."\r";

View File

@ -2,11 +2,15 @@
namespace App\Classes; namespace App\Classes;
use App\Exceptions\InvalidFidoPacketException;
use Carbon\Carbon; use Carbon\Carbon;
use App\Exceptions\InvalidFidoPacketException;
use App\Traits\GetNode;
class FTNPacket extends FTN class FTNPacket extends FTN
{ {
use GetNode;
public $pktsrc = NULL; public $pktsrc = NULL;
public $pktdst = NULL; public $pktdst = NULL;
private $pktver = NULL; private $pktver = NULL;
@ -92,12 +96,18 @@ class FTNPacket extends FTN
} }
} }
// @note - messages in this object have the same next destination
public function __toString(): string public function __toString(): string
{ {
// @todo - is this appropriate to set here // @todo - is this appropriate to set here
$this->date = now(); $this->date = now();
$this->pktsrc = '10:1/5.0'; $this->pktsrc = (string)$this->get_node(ftn_address_split('10:1/5.0'),TRUE);
$this->pktdst = '10:1/0.0';
// @todo
if ($this->messages->first()->type == 'echomail')
$this->pktdst = (string)$this->messages->first()->fqfa->uplink;
else
$this->pktdst = (string)$this->messages->first()->fqda->uplink;
$this->software['prodcode-lo'] = 0x00; $this->software['prodcode-lo'] = 0x00;
$this->software['prodcode-hi'] = 0xde; $this->software['prodcode-hi'] = 0xde;
@ -165,6 +175,7 @@ class FTNPacket extends FTN
public function addMessage(FTNMessage $o) public function addMessage(FTNMessage $o)
{ {
// @todo Check that this message is for the same uplink.
$this->messages->push($o); $this->messages->push($o);
} }

135
app/Classes/File/Item.php Normal file
View File

@ -0,0 +1,135 @@
<?php
namespace App\Classes\File;
use Exception;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use League\Flysystem\UnreadableFileException;
/**
* A file we are sending or receiving
*
* @property string $name
* @property string $recvas
* @property int $size
*/
class Item
{
protected const IS_PKT = (1<<1);
protected const IS_ARC = (1<<2);
protected const IS_FILE = (1<<3);
protected const IS_FLO = (1<<4);
protected const IS_REQ = (1<<5);
protected const I_RECV = (1<<6);
protected const I_SEND = (1<<7);
protected string $file_name = '';
protected int $file_size = 0;
protected int $file_mtime = 0;
protected int $file_type = 0;
protected int $action = 0;
public bool $sent = FALSE;
public bool $received = FALSE;
/**
* @throws FileNotFoundException
* @throws UnreadableFileException
* @throws Exception
*/
public function __construct($file,int $action)
{
$this->action |= $action;
switch ($action) {
case self::I_SEND:
if (! is_string($file))
throw new Exception('Invalid object creation - file should be a string');
if (! file_exists($file))
throw new FileNotFoundException('Item doesnt exist: '.$file);
if (! is_readable($file))
throw new UnreadableFileException('Item cannot be read: '.$file);
$this->file_name = $file;
$x = stat($file);
$this->file_size = $x['size'];
$this->file_mtime = $x['mtime'];
break;
case self::I_RECV;
$keys = ['name','mtime','size'];
if (! is_array($file) || array_diff(array_keys($file),$keys))
throw new Exception('Invalid object creation - file is not a valid array :'.serialize(array_diff(array_keys($file),$keys)));
$this->file_name = $file['name'];
$this->file_size = $file['size'];
$this->file_mtime = $file['mtime'];
break;
default:
throw new Exception('Unknown action: '.$action);
}
$this->file_type |= $this->whatType();
}
/**
* @throws Exception
*/
public function __get($key)
{
switch($key) {
case 'mtime':
case 'name':
case 'size':
if ($this->action & self::I_RECV)
return $this->{'file_'.$key};
throw new Exception('Invalid request for key: '.$key);
case 'recvas':
return '/tmp/'.$this->file_name; // @todo this should be inbound temp
case 'sendas':
return basename($this->file_name);
default:
throw new Exception('Unknown key: '.$key);
}
}
protected function isType(int $type): bool
{
return $this->file_type & $type;
}
private function whatType(): int
{
static $ext = ['su','mo','tu','we','th','fr','sa','req'];
$x = strrchr($this->file_name,'.');
if (! $x || (strlen(substr($x,1)) != 3))
return self::IS_FILE;
if (strcasecmp(substr($x,2),'lo') == 0)
return self::IS_FLO;
if (strcasecmp(substr($x,1),'pkt') == 0)
return self::IS_PKT;
if (strcasecmp(substr($x,1),'req') == 0)
return self::IS_REQ;
for ($i=0;$i<count($ext);$i++)
if (! strncasecmp($x,'.'.$ext[$i],strlen($ext[$i])) && (preg_match('/^[0-9a-z]/',strtolower(substr($x,3,1)))))
return self::IS_ARC;
return self::IS_FILE;
}
}

View File

@ -0,0 +1,173 @@
<?php
namespace App\Classes\File;
use Exception;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
/**
* Object representing the files we are receiving
*
* @property-read resource $fd
* @property-read int total_recv
* @property-read int total_recv_bytes
*/
final class Receive extends Item
{
private Collection $list;
private ?Item $receiving;
private mixed $f; // File descriptor
private int $start; // Time we started receiving
private int $file_pos; // Current write pointer
public function __construct()
{
// Initialise our variables
$this->list = collect();
$this->receiving = NULL;
$this->file_pos = 0;
$this->f = NULL;
}
public function __get($key)
{
switch ($key) {
case 'fd':
return is_resource($this->f);
case 'filepos':
return $this->file_pos;
case 'mtime':
case 'name':
case 'size':
return $this->receiving ? $this->receiving->{'file_'.$key} : NULL;
case 'to_get':
return $this->list
->filter(function($item) { return ($item->action & self::I_RECV) && $item->received === FALSE; })
->count();
case 'total_recv':
return $this->list
->filter(function($item) { return ($item->action & self::I_RECV) && $item->received === TRUE; })
->count();
case 'total_recv_bytes':
return $this->list
->filter(function($item) { return ($item->action & self::I_RECV) && $item->received === TRUE; })
->sum(function($item) { return $item->file_size; });
default:
throw new Exception('Unknown key: '.$key);
}
}
/**
* Close the file descriptor for our incoming file
*
* @throws Exception
*/
public function close(): void
{
if (! $this->f)
throw new Exception('No file to close');
if (! $this->file_pos != $this->receiving->file_size)
Log::warning(sprintf('%s: - Closing [%s], but missing [%d] bytes',__METHOD__,$this->receiving->file_name,$this->receiving->file_size-$this->file_pos));
$this->receiving->received = TRUE;
$end = time()-$this->start;
Log::debug(sprintf('%s: - Closing [%s], received in [%d]',__METHOD__,$this->receiving->file_name,$end));
fclose($this->f);
$this->file_pos = 0;
$this->receiving = NULL;
$this->f = NULL;
}
/**
* Open the file descriptor to receive a file
*
* @param bool $check
* @return bool
* @throws Exception
*/
public function open(bool $check=FALSE): bool
{
Log::debug(sprintf('%s: + Start [%d]',__METHOD__,$check));
// Check we can open this file
// @todo
// @todo implement return 2 - SKIP file
// @todo implement return 4 - SUSPEND(?) file
if ($check) {
return 0;
}
if (! $this->receiving)
throw new Exception('No files currently receiving');
$this->file_pos = 0;
$this->start = time();
Log::debug(sprintf('%s: - Opening [%s]',__METHOD__,$this->receiving->recvas));
$this->f = fopen($this->receiving->recvas,'wb');
if (! $this->f) {
Log::error(sprintf('%s: ! Unable to open file [%s] for writing',__METHOD__,$this->receiving->file_name));
return 3; // @todo change to const
}
Log::info(sprintf('%s: = End - File [%s] opened for writing',__METHOD__,$this->receiving->file_name));
return 0; // @todo change to const
}
/**
* Add a new file to receive
*
* @param array $file
* @throws Exception
*/
public function new(array $file): void
{
Log::debug(sprintf('%s: + Start',__METHOD__),['file'=>$file]);
if ($this->receiving)
throw new Exception('Can only have 1 file receiving at a time');
$o = new Item($file,self::I_RECV);
$this->list->push($o);
$this->receiving = $o;
}
/**
* Write data to the file we are receiving
*
* @param string $buf
* @return int
* @throws Exception
*/
public function write(string $buf): int
{
if (! $this->f)
throw new Exception('No file open for read');
if ($this->file_pos+strlen($buf) > $this->receiving->file_size)
throw new Exception(sprintf('Too many bytes received [%d] (%d)?',$this->file_pos+strlen($buf),$this->receiving->file_size));
$rc = fwrite($this->f,$buf);
if ($rc === FALSE)
throw new FileException('Error while writing to file');
$this->file_pos += $rc;
Log::debug(sprintf('%s: - Write [%d] bytes, file pos now [%d] of [%d]',__METHOD__,$rc,$this->file_pos,$this->receiving->file_size));
return $rc;
}
}

235
app/Classes/File/Send.php Normal file
View File

@ -0,0 +1,235 @@
<?php
namespace App\Classes\File;
use Exception;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use League\Flysystem\UnreadableFileException;
/**
* Object representing the files we are sending
*
* @property-read resource fd
* @property-read int file_mtime
* @property-read int file_size
* @property-read string file_name
* @property-read int mail_size
* @property-read int total_count
* @property-read int total_sent
* @property-read int total_sent_bytes
*/
final class Send extends Item
{
private Collection $list;
private ?Item $sending;
private mixed $f; // File descriptor
private int $start; // Time we started sending
private int $file_pos; // Current read pointer
public function __construct()
{
// Initialise our variables
$this->list = collect();
$this->sending = NULL;
$this->file_pos = 0;
$this->f = NULL;
}
public function __get($key)
{
switch ($key) {
case 'fd':
return is_resource($this->f);
case 'file_count':
return $this->list
->filter(function($item) { return $item->isType(self::IS_FILE); })
->count();
case 'file_size':
return $this->list
->filter(function($item) { return $item->isType(self::IS_FILE); })
->sum(function($item) { return $item->file_size; });
case 'filepos':
return $this->file_pos;
case 'mail_count':
return $this->list
->filter(function($item) { return $item->isType(self::IS_ARC|self::IS_PKT); })
->count();
case 'mail_size':
return $this->list
->filter(function($item) { return $item->isType(self::IS_ARC|self::IS_PKT); })
->sum(function($item) { return $item->file_size; });
case 'sendas':
return $this->sending ? $this->sending->{$key} : NULL;
case 'name':
case 'mtime':
case 'size':
return $this->sending ? $this->sending->{'file_'.$key} : NULL;
case 'total_sent':
return $this->list
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; })
->count();
case 'total_sent_bytes':
return $this->list
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; })
->sum(function($item) { return $item->file_size; });
case 'total_count':
return $this->list
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; })
->count();
case 'total_size':
return $this->list
->sum(function($item) { return $item->file_size; });
default:
throw new Exception('Unknown key: '.$key);
}
}
/**
* Add a file to the list of files to send
*
* @param string $file
* @throws Exception
*/
public function add(string $file): void
{
Log::debug(sprintf('%s: + Start [%s]',__METHOD__,$file));
try {
$this->list->push(new Item($file,self::I_SEND));
} catch (FileNotFoundException) {
Log::error(sprintf('%s: ! Item [%s] doesnt exist',__METHOD__,$file));
return;
} catch (UnreadableFileException) {
Log::error(sprintf('%s: ! Item [%s] cannot be read',__METHOD__,$file));
return;
// Uncaught, rethrow the error
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
}
/**
* Close the file descriptor of the file we are sending
*
* @param bool $successful
* @throws Exception
*/
public function close(bool $successful): void
{
if (! $this->f)
throw new Exception('No file to close');
if ($successful) {
$this->sending->sent = TRUE;
$end = time()-$this->start;
Log::debug(sprintf('%s: - Closing [%s], sent in [%d]',__METHOD__,$this->sending->file_name,$end));
}
fclose($this->f);
$this->sending = NULL;
$this->file_pos = 0;
$this->f = NULL;
}
/**
* Check if we are at the end of the file
*
* @return bool
*/
public function feof(): bool
{
return feof($this->f);
}
/**
* Open a file for sending
*
* @return bool
* @throws Exception
*/
public function open(): bool
{
Log::debug(sprintf('%s: + Start',__METHOD__));
$this->sending = $this->list
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; })
->first();
if (! $this->sending)
throw new Exception('No files to open');
$this->file_pos = 0;
$this->start = time();
$this->f = fopen($this->sending->file_name,'rb');
if (! $this->f) {
Log::error(sprintf('%s: ! Unable to open file [%s] for reading',__METHOD__,$this->sending->file_name));
return FALSE;
}
Log::info(sprintf('%s: = End - File [%s] opened with size [%d]',__METHOD__,$this->sending->file_name,$this->sending->file_size));
return TRUE;
}
/**
* Read bytes of the sending file
*
* @param int $length
* @return string|null
* @throws UnreadableFileException
* @throws Exception
*/
public function read(int $length): ?string
{
if (! $this->f)
throw new Exception('No file open for read');
$data = fread($this->f,$length);
$this->file_pos += strlen($data);
Log::debug(sprintf('%s: - Read [%d] bytes, file pos now [%d]',__METHOD__,strlen($data),$this->file_pos));
if ($data === FALSE)
throw new UnreadableFileException('Error reading file: '.$this->sending->file_name);
return $data;
}
/**
* Seek to a specific position of our file
*
* @param int $pos
* @return bool
* @throws Exception
*/
public function seek(int $pos): bool
{
if (! $this->f)
throw new Exception('No file open for seek');
$rc = (fseek($this->f,$pos,SEEK_SET) === 0);
if ($rc)
$this->file_pos = $pos;
Log::debug(sprintf('%s: - Seeked to [%d]',__METHOD__,$this->file_pos));
return $rc;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Classes;
/**
* Class FileReceive
* @package App\Classes
*
* This class represents a file being received.
*/
class FileReceive {
public int $soff = 0;
public int $toff = 0;
public int $foff = 0;
public int $ttot = 0;
public int $stot = 0;
public int $nf = 0;
public int $allf = 0;
public int $start = 0;
}

21
app/Classes/FileSend.php Normal file
View File

@ -0,0 +1,21 @@
<?php
namespace App\Classes;
/**
* Class FileSend
* @package App\Classes
*
* This class represents a file being sent.
*/
class FileSend {
public int $soff = 0;
public int $stot = 0;
public int $toff = 0;
public int $foff = 0;
public int $ttot = 0;
public int $nf = 0;
public int $allf = 0;
public int $cps = 1;
public int $start = 0;
}

260
app/Classes/Node.php Normal file
View File

@ -0,0 +1,260 @@
<?php
namespace App\Classes;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use App\Models\Node as NodeModel;
/**
* Object representing the node we are communicating with
*
* @property int aka_authed
* @property int aka_num
* @property string ftn
* @property string password
* @property Carbon node_time
* @property int session_time
* @property int ver_major
* @property int ver_minor
*/
class Node
{
// Remote Version
private int $version_major = 0;
private int $version_minor = 0;
private Carbon $start_time; // The time our connection started
// @todo Change this to Carbon
private string $node_time; // Current node's time
private Collection $ftns; // The FTNs of the remote system
private Collection $ftns_authed; // The FTNs we have validated
private int $options; // This nodes capabilities/options
public function __construct()
{
$this->options = 0;
$this->start_time = Carbon::now();
$this->ftns = collect();
$this->ftns_authed = collect();
}
/**
* @throws Exception
*/
public function __get($key)
{
switch ($key) {
// Number of AKAs the remote has
case 'aka_num':
return $this->ftns->count();
// Number of AKAs we have validated
case 'aka_authed':
return $this->ftns_authed->count();
case 'ftn':
return ($x=$this->ftns->first()) ? $x->ftn : 'Unknown';
// The nodes password
case 'password':
return ($this->ftns_authed->count() && $x=$this->ftns_authed->first()->sespass) ? $x : '-';
// Return how long our session has been connected
case 'session_time':
return Carbon::now()->diffInSeconds($this->start_time);
case 'system':
case 'sysop':
case 'location':
case 'phone':
case 'flags':
case 'message':
case 'files':
case 'netmail':
// The current session speed
case 'speed':
// The time our session started.
case 'start_time':
case 'software':
return $this->{$key};
// Client version
case 'ver_major':
return $this->version_major;
case 'ver_minor':
return $this->version_minor;
default:
throw new Exception('Unknown key: '.$key);
}
}
/**
* @throws Exception
*/
public function __set($key,$value)
{
switch ($key) {
case 'ftn':
if (! is_object($value) OR ! $value instanceof NodeModel)
throw new Exception('Not a node object: '.(is_object($value) ? get_class($value) : serialize($value)));
// Ignore any duplicate FTNs that we get
if ($this->ftns->search(function($item) use ($value) { return $item->id === $value->id; }) !== FALSE) {
Log::debug(sprintf('%s: - Ignoring Duplicate FTN [%s]',__METHOD__,$value->ftn));
break;
}
$this->ftns->push($value);
break;
case 'system':
case 'sysop':
case 'location':
case 'phone':
case 'flags':
case 'message':
case 'files':
case 'netmail':
case 'software':
case 'speed':
case 'start_time':
case 'node_time':
case 'ver_major':
case 'ver_minor':
$this->{$key} = $value;
break;
default:
throw new Exception('Unknown variable: '.$key);
}
}
/**
* Authenticate the AKAs that the node provided
*
* @param string $password
* @param string $challenge
* @return int
* @throws Exception
*/
public function auth(string $password,string $challenge=''): int
{
Log::debug(sprintf('%s: + Start [%s]',__METHOD__,$password));
// Make sure we havent been here already
if ($this->ftns_authed->count())
throw new Exception('Already authed');
foreach ($this->ftns as $o) {
if (! $o->sespass)
continue;
// If we have challenge, then we are doing MD5
$exp_pwd = $challenge ? $this->md5_challenge($o->sespass,$challenge) : $o->sespass;
if ($exp_pwd === $password)
$this->ftns_authed->push($o);
}
Log::debug(sprintf('%s: = End [%d]',__METHOD__,$this->ftns_authed->count()));
return $this->ftns_authed->count();
}
/**
* When we originate a connection and need to send our MD5 Challenge response
*
* @param string $challenge
* @return string
* @throws Exception
*/
public function get_md5chal(string $challenge): string
{
return $this->md5_challenge($this->password,$challenge);
}
/**
* Return the remotes BINKP version as a int
*
* @return int
*/
public function get_versionint(): int
{
return $this->ver_major*100+$this->ver_minor;
}
/**
* Calculate the response to an MD5 challenge, using the nodes password
*
* @param $pwd
* @param $challenge
* @return string
*/
private function md5_challenge($pwd,$challenge): string
{
$x = $pwd.str_repeat(chr(0x00),64-strlen($pwd));
return md5($this->str_xor($x,0x5c).md5($this->str_xor($x,0x36).$challenge,true));
}
/**
* When we originate a call to a node, we need to store the node we are connecting with in the ftns_authed, so
* authentication proceeds when we send our M_pwd
*
* @param NodeModel $o
*/
public function originate(NodeModel $o): void
{
$this->ftns->push($o);
$this->ftns_authed->push($o);
}
/**
* Check that our received FTNs match who we called
*
* @return bool
*/
public function originate_check(): bool
{
if ($this->ftns_authed->count() !== 1 || ! $this->ftns->count())
return FALSE;
$ftn = $this->ftns_authed->first()->ftn;
return $this->ftns->search(function($item) use ($ftn) {
return $item->ftn == $ftn;
}) !== FALSE;
}
public function optionClear(int $key): void
{
$this->options &= ~$key;
}
public function optionGet(int $key): int
{
return ($this->options & $key);
}
public function optionSet(int $key): void
{
$this->options |= $key;
}
private function str_xor(string $string,int $val): string
{
$result = '';
for ($i=0;$i<strlen($string);$i++)
$result .= chr(ord($string[$i]) ^ $val);
return $result;
}
}

337
app/Classes/Protocol.php Normal file
View File

@ -0,0 +1,337 @@
<?php
namespace App\Classes;
use Exception;
use Illuminate\Support\Facades\Log;
use App\Classes\File\{Receive,Send};
use App\Classes\Sock\SocketClient;
use App\Classes\Sock\SocketException;
use App\Models\Node as NodeModel;
use App\Models\Setup;
abstract class Protocol
{
// Our product code
// @todo Move These to a config file
protected const product_code = 'AB8D';
protected const setup = 1;
// Enable extra debugging
protected bool $DEBUG = FALSE;
// 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 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;
/**
* @throws Exception
*/
public function __get($key)
{
switch ($key) {
case 'ls_SkipGuard': /* double-skip protection on/off */
case 'rxOptions': /* Options from ZRINIT header */
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':
$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]',__METHOD__,$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;
}
/**
* Initialise our Session
*
* @param int $type
* @param SocketClient $client
* @param NodeModel|null $o
* @return int
* @throws Exception
*/
public function session(int $type,SocketClient $client,NodeModel $o=NULL): int
{
Log::debug(sprintf('%s: + Start [%d]',__METHOD__,$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) {
// Our configuration and initialise values
$this->setup = Setup::findOrFail(self::setup);
// The node we are communicating with
$this->node = new Node;
$this->originate = $o->exists;
// If we are connecting to a node
if ($o->exists) {
$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',__METHOD__));
$rc = $this->protocol_init();
if ($rc < 0) {
Log::error(sprintf('%s: ! Unable to start EMSI [%d]',__METHOD__,$rc));
return self::S_REDIAL | self::S_ADDTRY;
}
case self::SESSION_EMSI:
Log::debug(sprintf('%s: - Starting EMSI',__METHOD__));
$rc = $this->protocol_session();
break;
case self::SESSION_BINKP:
Log::debug(sprintf('%s: - Starting BINKP',__METHOD__));
$rc = $this->protocol_session();
break;
case self::SESSION_ZMODEM:
Log::debug(sprintf('%s: - Starting ZMODEM',__METHOD__));
$this->client->speed = SocketClient::TCP_SPEED;
$this->originate = FALSE;
// @todo While Debugging
$this->send->add('/tmp/aa');
return $this->protocol_session();
default:
Log::error(sprintf('%s: ! Unsupported session type [%d]',__METHOD__,$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',
__METHOD__,
$this->node->ftn,
$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',
));
// @todo Log to history log in the DB.
//if ($this->node->start_time && $this->setup->cfg('CFG_HISTORY')) {}
// @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;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
<?php
namespace App\Classes\Protocol;
final class BinkpMessage
{
public const BLK_HDR_SIZE = 2; /* header size */
private int $id;
private string $body;
public function __construct(int $id,string $body)
{
$this->id = $id;
$this->body = $body;
}
public function __get($key)
{
switch ($key) {
case 'len':
return strlen($this->body)+1+self::BLK_HDR_SIZE;
case 'msg':
$buf = self::mkheader((strlen($this->body)+1) | 0x8000);
$buf .= chr($this->id);
$buf .= $this->body;
return $buf;
default:
throw new \Exception('Unknown key :'.$key);
}
}
public static function mkheader(int $l): string
{
$buf = chr(($l>>8)&0xff);
$buf .= chr($l&0xff);
return $buf;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,23 +2,199 @@
namespace App\Classes\Sock; namespace App\Classes\Sock;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
/**
* Class SocketClient
*
* @package App\Classes\Sock
* @property int speed
* @property int cps
*/
final class SocketClient { final class SocketClient {
public \Socket $connection; // @todo make private // For deep debugging
private bool $DEBUG = FALSE;
private \Socket $connection;
private string $address = ''; private string $address = '';
private int $port = 0; private int $port = 0;
public function __construct (\Socket $connection) { // Our session state
private array $session = [];
private const OK = 0;
private const EOF = -1;
private const TIMEOUT = -2;
private const RCDO = -3;
private const GCOUNT = -4;
private const ERROR = -5;
private const TTY_SUCCESS = self::OK;
private const TTY_TIMEOUT = self::TIMEOUT;
private const TTY_HANGUP = self::RCDO;
private const TTY_ERROR = self::ERROR;
public const TCP_SPEED = 115200;
// Buffer for sending
private const TX_BUF_SIZE = (0x8100);
private int $tx_ptr = 0;
private int $tx_free = self::TX_BUF_SIZE;
private int $tty_status = 0;
private string $tx_buf = '';
// Buffer for receiving
private const RX_BUF_SIZE = (0x8100);
private int $rx_ptr = 0;
private int $rx_left = 0;
private string $rx_buf = '';
public function __construct (\Socket $connection,int $speed=self::TCP_SPEED) {
socket_getsockname($connection,$this->address,$this->port); socket_getsockname($connection,$this->address,$this->port);
Log::info(sprintf('Connection from [%s] on port [%d]',$this->address,$this->port),['m'=>__METHOD__]); Log::info(sprintf('%s: + Connection from [%s] on port [%d]',__METHOD__,$this->address,$this->port));
$this->connection = $connection; $this->connection = $connection;
} }
public function __get($key) {
switch ($key) {
case 'cps':
case 'speed':
return Arr::get($this->session,$key);
default:
throw new \Exception(sprintf('%s: Unknown key [%s]:',__METHOD__,$key));
}
}
public function __set($key,$value) {
switch ($key) {
case 'cps':
case 'speed':
return $this->session[$key] = $value;
default:
throw new \Exception(sprintf('%s: Unknown key [%s]:',__METHOD__,$key));
}
}
/**
* We'll add to our transmit buffer and if doesnt have space, we'll empty it first
*
* @param string $data
* @return void
* @throws \Exception
*/
public function buffer_add(string $data): void
{
if ($this->DEBUG)
Log::debug(sprintf('%s: + Start [%s] (%d)',__METHOD__,$data,strlen($data)));
//$rc = self::OK;
//$tx_ptr = self::TX_BUF_SIZE-$this->tx_free;
$ptr = 0;
$num_bytes = strlen($data);
$this->tty_status = self::TTY_SUCCESS;
while ($num_bytes) {
if ($this->DEBUG)
Log::debug(sprintf('%s: - Num Bytes [%d]: TX Free [%d]',__METHOD__,$num_bytes,$this->tx_free));
if ($num_bytes > $this->tx_free) {
do {
$this->buffer_flush(5);
if ($this->tty_status == self::TTY_SUCCESS) {
$n = min($this->tx_free,$num_bytes);
$this->tx_buf = substr($data,$ptr,$n);
$this->tx_free -= $n;
$num_bytes -= $n;
$ptr += $n;
}
} while ($this->tty_status != self::TTY_SUCCESS);
} else {
if ($this->DEBUG)
Log::debug(sprintf('%s: - Remaining data to send [%d]',__METHOD__,$num_bytes));
$this->tx_buf .= substr($data,$ptr,$num_bytes);
$this->tx_free -= $num_bytes;
$num_bytes = 0;
}
}
if ($this->DEBUG)
Log::debug(sprintf('%s: = End [%s]',__METHOD__,strlen($this->tx_buf)));
}
/**
* Clear our TX buffer
*/
public function buffer_clear(): void
{
$this->tx_buf = '';
$this->tx_free = self::TX_BUF_SIZE;
}
/**
* Empty our TX buffer
*
* @param int $timeout
* @return int
* @throws \Exception
*/
public function buffer_flush(int $timeout): int
{
if ($this->DEBUG)
Log::debug(sprintf('%s: + Start [%d]',__METHOD__,$timeout));
$rc = self::OK;
$tx_ptr = 0;
$restsize = self::TX_BUF_SIZE-$this->tx_free;
$tm = $this->timer_set($timeout);
while (self::TX_BUF_SIZE != $this->tx_free) {
$tv = $this->timer_rest($tm);
if ($rc = $this->canSend($tv)>0) {
if ($this->DEBUG)
Log::debug(sprintf('%s: - Sending [%d]',__METHOD__,$restsize));
$rc = $this->send(substr($this->tx_buf,$tx_ptr,$restsize),0);
Log::debug(sprintf('%s: - Sent [%d] (%s)',__METHOD__,$rc,Str::limit($this->tx_buf,15)));
if ($rc == $restsize) {
$this->tx_buf = '';
$tx_ptr = 0;
$this->tx_free += $rc;
$this->buffer_clear();
} else if ($rc > 0) {
$tx_ptr += $rc;
$restsize -= $rc;
}
} else {
return $rc;
}
// @todo Enable a delay for slow clients
//sleep(1);
if ($this->timer_expired($tm))
return self::ERROR;
}
if ($this->DEBUG)
Log::debug(sprintf('%s: = End [%d]',__METHOD__,$rc));
return $rc;
}
/** /**
* @param int $timeout * @param int $timeout
* @return int * @return int
* @throws \Exception
*/ */
public function canSend(int $timeout): int public function canSend(int $timeout): int
{ {
@ -38,11 +214,15 @@ final class SocketClient {
/** /**
* Create a client socket * Create a client socket
* @param string $address
* @param int $port
* @param int $speed
* @return static * @return static
* @throws SocketException
*/ */
public static function create(string $address,int $port): self public static function create(string $address,int $port,int $speed=self::TCP_SPEED): self
{ {
Log::debug(sprintf('Creating connection to [%s:%d]',$address,$port)); Log::debug(sprintf('%s: + Creating connection to [%s:%d]',__METHOD__,$address,$port));
$address = gethostbyname($address); $address = gethostbyname($address);
@ -55,13 +235,14 @@ final class SocketClient {
if ($result === FALSE) if ($result === FALSE)
throw new SocketException(SocketException::CANT_CONNECT,socket_strerror(socket_last_error($socket))); throw new SocketException(SocketException::CANT_CONNECT,socket_strerror(socket_last_error($socket)));
return new self($socket); return new self($socket,$speed);
} }
/** /**
* Return the client's address * Return the client's address
* *
* @return string * @return string
* @todo change to __get()
*/ */
public function getAddress(): string public function getAddress(): string
{ {
@ -72,6 +253,7 @@ final class SocketClient {
* Return the port in use * Return the port in use
* *
* @return int * @return int
* @todo change to __get()
*/ */
public function getPort(): int public function getPort(): int
{ {
@ -82,17 +264,96 @@ final class SocketClient {
* @param int $timeout * @param int $timeout
* @return int * @return int
* @note use socketSelect() * @note use socketSelect()
* @todo Node used by bink yet? * @throws \Exception
* @todo to test
*/ */
public function hasData(int $timeout): int public function hasData(int $timeout): int
{ {
$read = [$this->connection]; $read = [$this->connection];
$write = $except = NULL;
//$rc = socket_select($read,$write,$except,$timeout); return $this->rx_left ?: $this->socketSelect($read,NULL,NULL,$timeout);
//return $rc; }
return $this->socketSelect($read,NULL,NULL,$timeout);
/**
* Read data from the socket.
* If we only want 1 character, we'll return the ASCII value of the data received
*
* @param int $timeout
* @param int $len
* @return int|string
* @throws SocketException
*/
public function read(int $timeout,int $len=1024)
{
if ($this->DEBUG)
Log::debug(sprintf('%s: + Start [%d] (%d)',__METHOD__,$len,$timeout));
if ($timeout AND ($this->hasData($timeout) === 0))
return '';
$buf = '';
$rc = socket_recv($this->connection,$buf, $len,MSG_DONTWAIT);
if ($this->DEBUG)
Log::debug(sprintf('%s: - Read [%d]',__METHOD__,$rc));
if ($rc === FALSE)
throw new SocketException($x=socket_last_error($this->connection),socket_strerror($x));
return is_null($buf) ? '' : $buf;
}
/**
* Read a character from the remote.
* We'll buffer everything received
*
* @param int $timeout
* @return int
* @throws SocketException
*/
public function read_ch(int $timeout): int
{
if ($this->DEBUG)
Log::debug(sprintf('%s: + Start [%d]',__METHOD__,$timeout),['rx_left'=>$this->rx_left,'rx_ptr'=>$this->rx_ptr]);
// If our buffer is empty, we'll try and read from the remote
if ($this->rx_left == 0) {
if ($this->hasData($timeout) > 0) {
try {
if (! strlen($this->rx_buf = $this->read(0,self::RX_BUF_SIZE))) {
Log::debug(sprintf('%s: - Nothing read',__METHOD__));
return self::TTY_TIMEOUT;
}
} catch (\Exception $e) {
return ($e->getCode() == 11) ? self::TTY_TIMEOUT : self::ERROR;
}
if ($this->DEBUG)
Log::info(sprintf('%s: - Read [%d] bytes',__METHOD__,strlen($this->rx_buf)));
$this->rx_ptr = 0;
$this->rx_left = strlen($this->rx_buf);
} else {
return self::TTY_TIMEOUT;
}
}
$rc = ord(substr($this->rx_buf,$this->rx_ptr,1));
$this->rx_left--;
$this->rx_ptr++;
if ($this->DEBUG)
Log::debug(sprintf('%s: = Return [%x] (%c)',__METHOD__,$rc,$rc));
return $rc;
}
public function rx_purge(): void
{
$this->rx_ptr = $this->rx_left = 0;
$this->rx_buf = '';
} }
/** /**
@ -102,8 +363,10 @@ final class SocketClient {
* @param int $timeout * @param int $timeout
* @param null $length * @param null $length
* @return false|int * @return false|int
* @throws \Exception
*/ */
public function send($message,int $timeout,$length=NULL) { public function send($message,int $timeout,$length=NULL)
{
if ($timeout AND (! $rc = $this->canSend($timeout))) if ($timeout AND (! $rc = $this->canSend($timeout)))
return $rc; return $rc;
@ -113,33 +376,77 @@ final class SocketClient {
return socket_write($this->connection,$message,$length); return socket_write($this->connection,$message,$length);
} }
private function socketSelect(?array $read,?array $write,?array $except,int $timeout): int /**
* Set our speed
*
* @param int $value
* @todo change to __set()
*/
public function setSpeed(int $value): void
{ {
return socket_select($read,$write,$except,$timeout); $this->speed = $value;
} }
/** /**
* Read data from the socket. * Wait for data on a socket
* If we only want 1 character, we'll return the ASCII value of the data received
* *
* @param array|null $read
* @param array|null $write
* @param array|null $except
* @param int $timeout * @param int $timeout
* @param int $len * @return int
* @return false|int|string|null * @throws \Exception
*/ */
public function read(int $timeout,int $len=1024) private function socketSelect(?array $read,?array $write,?array $except,int $timeout): int
{ {
Log::debug(sprintf('+ Start [%d]',$len),['m'=>__METHOD__]); $rc = socket_select($read,$write,$except,$timeout);
if ($timeout AND (! $rc = $this->hasData($timeout))) if ($rc === FALSE)
return $rc; throw new \Exception('Socket Error: '.socket_strerror(socket_last_error()));
if (($buf=socket_read($this->connection,$len,PHP_BINARY_READ)) === FALSE) { return $rc;
return NULL; }
}
Log::debug(sprintf(' - Read [%d]',strlen($buf)),['m'=>__METHOD__]); /**
* Return our speed in bps
*
* @return int
* @todo change to __get()
*/
public function speed(): int
{
return $this->speed;
}
// For single character reads, we'll return the ASCII value of the buf public function timer_expired(int $timer): int
return ($len == 1 and (ord($buf) != 0)) ? ord($buf) : $buf; {
return (time()>=$timer);
}
public function timer_rest(int $timer): int
{
return (($timer)-time());
}
public function timer_set(int $expire): int
{
return (time()+$expire);
}
/**
* See if we there is data waiting to collect, or if we can send
*
* @param bool $read
* @param bool $write
* @param int $timeout
* @return int
* @throws \Exception
*/
public function ttySelect(bool $read,bool $write, int $timeout): int
{
$read = $read ? [$this->connection] : NULL;
$write = $write ? [$this->connection] : NULL;
return $this->socketSelect($read,$write,NULL,$timeout);
} }
} }

View File

@ -8,6 +8,8 @@ final class SocketException extends \Exception {
public const CANT_LISTEN = 3; public const CANT_LISTEN = 3;
public const CANT_ACCEPT = 4; public const CANT_ACCEPT = 4;
public const CANT_CONNECT = 5; public const CANT_CONNECT = 5;
public const SOCKET_ERROR = 6;
public const SOCKET_EAGAIN = 11;
private array $messages = [ private array $messages = [
self::CANT_CREATE_SOCKET => 'Can\'t create socket: "%s"', self::CANT_CREATE_SOCKET => 'Can\'t create socket: "%s"',
@ -15,6 +17,8 @@ final class SocketException extends \Exception {
self::CANT_LISTEN => 'Can\'t listen: "%s"', self::CANT_LISTEN => 'Can\'t listen: "%s"',
self::CANT_ACCEPT => 'Can\'t accept connections: "%s"', self::CANT_ACCEPT => 'Can\'t accept connections: "%s"',
self::CANT_CONNECT => 'Can\'t connect: "%s"', self::CANT_CONNECT => 'Can\'t connect: "%s"',
self::SOCKET_ERROR => 'Socket Error: "%s"',
self::SOCKET_EAGAIN => 'Socket Resource Temporarily Unavailable - Try again',
]; ];
public function __construct(int $code,string $params=NULL) { public function __construct(int $code,string $params=NULL) {

918
app/Classes/TTY.php Normal file
View File

@ -0,0 +1,918 @@
<?php
namespace App\Classes;
use Illuminate\Support\Facades\Log;
use App\Classes\Sock\SocketClient;
/**
* Class TTY
* @package App\Classes
*
* This base class handles all the comms with the remote
*/
class TTY
{
protected SocketClient $client; /* Our incoming client socket */
private const RX_BUF_SIZE = (0x8100);
private const TX_BUF_SIZE = (0x8100);
private const MSG_BUFFER = 2048;
protected const OK = 0;
private const EOF = -1;
protected const TIMEOUT = -2;
protected const RCDO = -3;
protected const GCOUNT = -4;
protected const ERROR = -5;
private const TTY_SUCCESS = self::OK;
private const TTY_TIMEOUT = self::TIMEOUT;
private const TTY_HANGUP = self::RCDO;
private const TTY_ERROR = self::ERROR;
protected const QC_RECVD = 'c';
protected const QC_SENDD = 'd';
protected const QC_EMSID = 'g';
private const QR_POLL = 'A';
private const QR_REQ = 'B';
private const QR_SEND = 'D';
private const QR_STS = 'E';
private const QR_CONF = 'F';
private const QR_QUIT = 'G';
private const QR_INFO = 'H';
private const QR_SCAN = 'I';
private const QR_KILL = 'J';
private const QR_QUEUE = 'K';
private const QR_SKIP = 'L';
private const QR_REFUSE = 'M';
private const QR_HANGUP = 'N';
private const QR_RESTMR = 'O';
private const QR_CHAT = 'P';
private const QR_SET = 'S';
private const QR_STYPE = 'T';
private const HUP_NONE = 0;
private const HUP_LINE = 1;
private const HUP_OPERATOR = 2;
private const HUP_SESLIMIT = 3;
private const HUP_CPS = 4;
// @tod these FOP has been duplicated from Protocol - added back here for optimising EMSI
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 int $tty_rx_left = 0; /* Number of bytes in receive buffer */
private string $tty_tx_buf = ''; /* Data in the buffer to send */
private int $tty_tx_ptr = 0; /* Pointer to next byte to send out in transmit buffer */
private int $tty_tx_free = self::TX_BUF_SIZE;/* Number of byte left in transmit buffer */
private int $tty_status = self::TTY_SUCCESS;
private int $tty_gothup = 0;
//protected int $rxstatus = 0;
public function __construct()
{
$this->sendf = new FileSend; // @todo these should be delcared private if they are staying here
$this->recvf = new FileReceive;
$this->rnode = new rnode;
}
protected function check_cps(): void
{
Log::debug('- Start',['m'=>__METHOD__]);
$cpsdelay=10; // $cpsdelay=cfgi(CFG_MINCPSDELAY);
$ncps = 38400/1000; $this->speed/1000;
$r=1; //$r=cfgi(CFG_REALMINCPS);
if(!($this->sendf->cps=time()-$this->sendf->start)) {
$this->sendf->cps=1;
} else {
$this->sendf->cps=($this->sendf->foff-$this->sendf->soff)/$this->sendf->cps;
}
if(!($this->recvf->cps=time()-$this->recvf->start)) {
$this->recvf->cps=1;
} else {
$this->recvf->cps=($this->recvf->foff-$this->recvf->soff)/$this->recvf->cps;
}
if($this->sendf->start&&(true ? 0 : cfgi(CFG_MINCPSOUT))>0&&(time()-$this->sendf->start)>$cpsdelay&&$this->sendf->cps<($r?$cci:$cci*$ncps)) {
//write_log("mincpsout=%d reached, aborting session",r?cci:cci*ncps);
$tty_gothup = self::HUP_CPS;
}
if($this->recvf->start&&(true ? 0 : cfgi(CFG_MINCPSIN))>0&&(time()-$this->recvf->start)>$cpsdelay&&$this->recvf->cps<($r?$cci:$cci*$ncps)) {
//write_log("mincpsin=%d reached, aborting session",r?cci:cci*ncps);
$tty_gothup = self::HUP_CPS;
}
$this->getevt();
Log::debug('- End',['m'=>__METHOD__]);
}
// @todo no longer used?
protected function getevt(): void
{
Log::debug('+ Start', ['m' => __METHOD__]);
$qsndbuflen = 0;
while($this->qrecvpkt($qrcv_buf)) {
Log::debug(' - qrecvpkt Returned', ['m' => __METHOD__,'qrcv_buf'=>$qrcv_buf]);
switch($qrcv_buf[2]) { // @todo this doesnt seem right?
case self::QR_SKIP:
//$this->rxstatus=self::RX_SKIP;
break;
case self::QR_REFUSE:
//$this->rxstatus=self::RX_SUSPEND;
break;
case self::QR_HANGUP:
$tty_gothup = self::HUP_OPERATOR;
break;
case self::QR_CHAT:
/*
if($qrcv_buf[3]) {
xstrcpy((qsnd_buf+qsndbuflen),(qrcv_buf+3),self::CHAT_BUF-$qsndbuflen);
$qsndbuflen+=strlen((qrcv_buf+3));
if($qsndbuflen>self::CHAT_BUF-128) {
$qsndbuflen=self::CHAT_BUF-128;
}
} else {
$i=$chatprot;
$chatprot=-1;
chatsend(qsnd_buf);
if($chatlg) {
chatlog_done();
}
$chatlg=0;
$chatprot=i;
xstrcat($qsnd_buf,"\n * Chat closed\n",CHAT_BUF);
chatsend($qsnd_buf);
if($chattimer>1) {
qlcerase();
}
$qsndbuflen=0;
$chattimer=1;
}
*/
break;
}
$qsnd_buf[$qsndbuflen]=0;
}
if ($qsndbuflen>0)
if(! $this->chatsend($qsnd_buf)) {
$qsndbuflen=0;
}
Log::debug('- End', ['m' => __METHOD__]);
}
protected function qrecvpkt(&$str): int
{
Log::debug('+ Start',['m'=>__METHOD__,'str'=>$str]);
/*
if (! $this->xsend_cb) {
return 0;
}
*/
$rc = $this->xrecv($this->client->connection,$str,self::MSG_BUFFER-1,0);
Log::debug(sprintf(' - qrecvpkt Got [%x] (%d)',$rc,$rc),['m'=>__METHOD__]);
if ( $rc < 0 && $this->errno != 11 /*MSG_EAGAIN*/ ) {
if ($this->errno == self::ECONNREFUSED) {
$xsend_cb = NULL;
}
//DEBUG(('I',1,"can't recv (fd=%d): %s",ssock,strerror(errno)));
}
Log::debug(sprintf(' - qrecvpkt Got [%x] (%d)',$rc,$rc),['m'=>__METHOD__,'str'=>$str,'len'=>strlen($str)]);
if ($rc < 3 || ! substr($str,0,2)) {
Log::debug('+ End',['m'=>__METHOD__,'rc'=>0]);
return 0;
}
//str[rc] = '\0';
if (! $rc)
$str = '';
Log::debug('+ End',['m'=>__METHOD__,'rc'=>$rc]);
return $rc;
}
protected function rxclose(&$f, int $what): int
{
Log::debug('+ Start',['m'=>__METHOD__,'what'=>$what,'f'=>$f]);
$cps=time()-$this->recvf->start;
$ss = '';
if(!$f || !$f) {
Log::debug('= End',['m'=>__METHOD__,'rc'=>self::FOP_ERROR]);
return self::FOP_ERROR;
}
$this->recvf->toff+=$this->recvf->foff;
$this->recvf->stot+=$this->recvf->soff;
$p2=0;
if(! $cps) {
$cps=1;
}
$cps=($this->recvf->foff-$this->recvf->soff)/$cps;
/*
IFPerl(if((ss=perl_end_recv(what))) {
if(!$ss) {
$what=self::FOP_SKIP;
} else {
$p2 = $ss; //xstrcpy(p2,ss,MAX_PATH);
}
});
*/
switch($what) {
case self::FOP_SUSPEND:
$ss="suspended";
break;
case self::FOP_SKIP:
$ss="skipped";
break;
case self::FOP_ERROR:
$ss="error";
break;
case self::FOP_OK:
$ss="ok";
break;
default:
$ss="";
}
Log::debug(' -',['m'=>__METHOD__,'soff'=>$this->recvf->soff,'ss'=>$ss]);
if($this->recvf->soff) {
//write_log("rcvd: %s, %lu bytes (from %lu), %ld cps [%s]",
// recvf.fname, (long) recvf.foff, (long) recvf.soff, cps, ss);
} else {
//write_log("rcvd: %s, %lu bytes, %ld cps [%s]",
// recvf.fname, (long) recvf.foff, cps, ss);
}
fclose($f);
$f='';
/*
snprintf(p, MAX_PATH, "%s/tmp/%s", cfgs(CFG_INBOUND), recvf.fname);
if($p2) {
if($p2!='/'&&*$p2=='.') {
$ss=xstrdup($p2);
snprintf($p2,MAX_PATH,"%s/%s",cfgs(CFG_INBOUND),$ss);
xfree($ss);
}
} else {
snprintf(p2, MAX_PATH, "%s/%s", cfgs(CFG_INBOUND), recvf.fname);
}
*/
//$ut->actime=$ut->modtime=$this->recvf->mtime;
$this->recvf->foff=0;
switch($what) {
case self::FOP_SKIP:
unlink($p);
break;
case self::FOP_SUSPEND:
case self::FOP_ERROR:
/*
if($this->whattype($p)==self::IS_PKT&&cfgi(self::CFG_KILLBADPKT)) {
unlink($p);
} else {
//utime(p,&ut);
touch ($this->recvf->name,octdec($this->recvf->mtime));
}
*/
break;
case self::FOP_OK:
$rc=isset($receive_callback)?receive_callback($p):0;
if($rc) {
//lunlink(p);
} else {
$ss=$p2+strlen($p2)-1;
$overwrite=0;
/*
for(i=cfgsl(CFG_ALWAYSOVERWRITE); i; i=i->next)
if(!xfnmatch(i->str,recvf.fname,FNM_PATHNAME)) {
$overwrite=1;
}
while(!$overwrite&&!stat(p2, &sb)&&p2[0]) {
if(sifname(ss)) {
ss--;
while('.' == *ss && ss >= p2) {
ss--;
}
if(ss < p2) {
write_log("can't find suitable name for %s: leaving in temporary directory",p);
p2[0] = '\x00';
}
}
}
if(p2[0]) {
if(overwrite) {
lunlink(p2);
}
if(rename(p, p2)) {
write_log("can't rename %s to %s: %s",p,p2,strerror(errno));
} else {
utime(p2,&ut);
chmod(p2,cfgi(CFG_DEFPERM));
}
}
*/
Log::debug(sprintf('Recevied [%s] with mtime [%s]',$this->f->name,$this->recvf->mtime),['m'=>__METHOD__]);
touch('/tmp/tmp/'.$this->f->name,$this->recvf->mtime);
}
break;
}
if($what==self::FOP_SKIP||$what==self::FOP_SUSPEND) {
$skipiftic=$what;
}
$this->recvf->start=0;
$this->recvf->ftot=0;
//$this->rxstatus=0;
Log::debug('= End',['m'=>__METHOD__,'rc'=>$what]);
return $what;
}
protected function rxopen(string $name,int $rtime,int $rsize,string &$f): int
{
Log::debug('+ Start',['m'=>__METHOD__,'name'=>$name,'rtime'=>$rtime,'rsize'=>$rsize,'f'=>$f]);
$ccs = '/tmp'; // @todo Base path needs to be a config item
$this->speed = 38400;
$prevcps = ($this->recvf->start&&(time()-$this->recvf->start>2))?$this->recvf->cps:$this->speed/10;
if(! $name) {
return self::FOP_ERROR;
}
$bn = basename($name);//xstrcpy($bn, qbasename($name), self::MAX_PATH);
Log::debug(sprintf(' - bn[%s]',$bn),['m'=>__METHOD__]);
//mapname((char*)bn, cfgs(CFG_MAPIN), MAX_PATH);
//$this->recvf->start=(int)decoct(time());
$this->recvf->start=time();
//xfree(recvf.fname);
$this->recvf->fname=$bn; //xstrdup($bn);
//dd(['rtime'=>$rtime,'start'=>$this->recvf->start]);
$this->recvf->mtime=$rtime; //-gmtoff($this->recvf->start);
$this->recvf->ftot=$rsize;
if($this->recvf->toff+$rsize > $this->recvf->ttot) {
$this->recvf->ttot+=$rsize;
}
$this->recvf->nf++;
if($this->recvf->nf > $this->recvf->allf) {
$this->recvf->allf++;
}
//IFPerl(if((rc=perl_on_recv())!=FOP_OK)return rc);
/*
if($this->whattype($name)==self::IS_PKT&&($rsize==60||!$rsize)&&cfgi(self::CFG_KILLBADPKT)) {
return self::FOP_SKIP;
}
*/
$rc=$skipiftic = 0; // @todo
$skipiftic=0;
if($rc&&istic($bn)&&cfgi(self::CFG_AUTOTICSKIP)) {
//write_log($rc==self::FOP_SKIP?$weskipstr:$wesusstr,$this->recvf->fname,"auto");
return $rc;
}
// @todo
/*
for($i=cfgsl(self::CFG_AUTOSKIP); $i; $i=$i->next)
if(!$this->xfnmatch($i->str,$bn, self::FNM_PATHNAME)) {
//write_log(weskipstr,$this->recvf.fname,"");
$skipiftic=self::FOP_SKIP;
return self::FOP_SKIP;
}
for($i=cfgsl(self::CFG_AUTOSUSPEND); $i; $i=$i->next)
if(!$this->xfnmatch($i->str, $bn, self::FNM_PATHNAME)) {
//write_log(wesusstr,$this->recvf->fname,"");
$skipiftic=self::FOP_SUSPEND;
return self::FOP_SUSPEND;
}
*/
$p = '/tmp/tmp/'; //@todo snprintf(p, MAX_PATH, "%s/tmp/", cfgs(CFG_INBOUND));
//if ($sb = stat($p)) // if(stat($p, &sb))
if(! is_dir($p) AND ! mkdir($p)) { // && $errno!=EEXIST
Log::debug(sprintf(' - dir doesnt exist and cannot make it? [%s]',$p),['m'=>__METHOD__,'rc'=>self::FOP_SUSPEND]);
//write_log("can't make directory %s: %s", p, strerror(errno));
//write_log(wesusstr,$this->recvf.fname,"");
$skipiftic=self::FOP_SUSPEND;
return self::FOP_SUSPEND;
}
$p = sprintf('%s/%s',$ccs,$bn);// snprintf($p, self::MAX_PATH, "%s/%s", $ccs, $bn);
if(file_exists($p) AND ($sb=stat($p)) && $sb['size']==$rsize) {//if(!stat(p, &sb) && sb.st_size==rsize) {
Log::debug(sprintf(' - file exists and size is same? [%s]',$p),['m'=>__METHOD__,'sb'=>$sb,'rsize'=>$rsize,'rc'=>self::FOP_SKIP]);
//write_log(weskipstr,$this->recvf.fname,"");
$skipiftic=self::FOP_SKIP;
return self::FOP_SKIP;
}
//dd(['maxpath'=>self::MAX_PATH,'bn'=>$bn]);
//snprintf($p, self::MAX_PATH, "%s/tmp/%s", $ccs, $bn);
$p = sprintf('%s/tmp/%s',$ccs,$bn);
// If the file exists
if (file_exists($p) AND $sb=stat($p)) {//if(!stat(p, &sb)) {
Log::debug(sprintf(' - file exists... [%s]',$p),['m'=>__METHOD__,'sb'=>$sb,'rsize'=>$rsize,
'mtime'=>$this->recvf->mtime,
//'mtime-decopt'=>(int)decoct($this->recvf->mtime),
//'mtime-octdec'=>(int)octdec($this->recvf->mtime),
'sbmtime'=>$sb['mtime'],
'sbmtime-decopt'=>(int)decoct($sb['mtime']),
//'sbmtime-octdec'=>(int)octdec($sb['mtime']),
]);
// @todo binkp doesnt use octal.
if($sb['size']<$rsize && $sb['mtime']==(int)$this->recvf->mtime) {
Log::debug(sprintf(' - attempt open for append [%s]',$p),['m'=>__METHOD__]);
$f=fopen($p, "ab");
if(!$f) {
Log::debug(sprintf(' - attempt open for append FAILED [%s]',$p),['m'=>__METHOD__,'rc'=>self::FOP_SUSPEND]);
//write_log("can't open file %s for writing: %s", p,strerror(errno));
//write_log(wesusstr,$this->recvf.fname,"");
$skipiftic=self::FOP_SUSPEND;
return self::FOP_SUSPEND;
}
Log::debug(sprintf(' - FTELL REPORTS [%s]',serialize(ftell($f))),['m'=>__METHOD__]);
// ftell() gives undefined results for append-only streams (opened with "a" flag).
$this->recvf->foff = $this->recvf->soff = $sb['size']; //ftell($f);
Log::debug(sprintf(' - open for append [%s] at [%d]',$p,$this->recvf->soff),['m'=>__METHOD__,'rc'=>self::FOP_CONT]);
//if(cfgi(self::CFG_ESTIMATEDTIME)) {
//write_log("start recv: %s, %lu bytes (from %lu), estimated time %s",
// $this->recvf.fname, (long) rsize, (long) $this->recvf.soff, estimatedtime(rsize-$this->recvf.soff,prevcps,effbaud));
//}
return self::FOP_CONT;
}
}
$f=fopen($p, "wb");
if(!$f) {
//write_log("can't open file %s for writing: %s", p,strerror(errno));
//write_log(wesusstr,$this->recvf.fname,"");
$skipiftic=self::FOP_SUSPEND;
return self::FOP_SUSPEND;
}
//dd(['sb'=>$sb,'recvf'=>$this->recvf]);
Log::debug(sprintf(' - new file created [%s]',$p),['m'=>__METHOD__,'rc'=>self::FOP_OK]);
$this->recvf->foff = $this->recvf->soff = 0;
//if(cfgi(self::CFG_ESTIMATEDTIME)) {
//write_log("start recv: %s, %lu bytes, estimated time %s",
// $this->recvf.fname, (long) rsize, estimatedtime(rsize,prevcps,effbaud));
//}
return self::FOP_OK;
}
public function setClient(SocketClient $client): void
{
$this->client = $client;
}
public function timer_expired(int $timer): int
{
return (time()>=$timer);
}
public function timer_rest(int $timer): int
{
return (($timer)-time());
}
public function timer_set(int $expire): int
{
return (time()+$expire);
}
protected function txclose(&$f, int $what):int
{
$cps=time()-$this->sendf->start;
if(!$f) {
return self::FOP_ERROR;
}
$this->sendf->toff+=$this->sendf->foff;
$this->sendf->stot+=$this->sendf->soff;
if(!$cps) {
$cps=1;
}
$cps=($this->sendf->foff-$this->sendf->soff)/$cps;
//IFPerl(perl_end_send(what));
switch($what) {
case self::FOP_SUSPEND:
$ss="suspended";
break;
case self::FOP_SKIP:
$ss="skipped";
break;
case self::FOP_ERROR:
$ss="error";
break;
case self::FOP_OK:
$ss="ok";
break;
default:
$ss="";
}
if($this->sendf->soff) {}
//write_log("sent: %s, %lu bytes (from %lu), %ld cps [%s]", sendf.fname, (long) sendf.foff, (long) sendf.soff, cps, ss);
else {}
//write_log("sent: %s, %lu bytes, %ld cps [%s]",sendf.fname, (long) sendf.foff, cps, ss);
$this->sendf->foff=0;
$this->sendf->ftot=0;
$this->sendf->start=0;
fclose($f);
$f=NULL;
return $what;
}
protected function xrecv($sock,&$buf,int $len,int $wait):int
{
Log::debug('+ Start',['m'=> __METHOD__]);
$l = 0;
if (! $sock) {
$this->errno = self::EBADF;
return -1;
}
if (! $wait) {
Log::debug(' - Not wait',['m'=> __METHOD__]);
$tv_tv_sec = 0;
$tv_tv_usec = 0;
$rfd = 0; //FD_ZERO(&rfd);
//FD_SET(sock, &rfd);
$read = [$sock];
$write = [];
$except = [];
$rc = socket_select($read,$write,$except,0,0);
//$foo = '';
//$rc = socket_recv($this->client->connection,$foo,1,MSG_PEEK | MSG_DONTWAIT);
Log::debug(' - socket_select',['m'=> __METHOD__,'rc'=>$rc,'read'=>$read,'write'=>$write,'except'=>$except]);
//$rc = $this->client->hasData(0);
if ($rc < 1) {
if (! $rc) {
$this->errno = 11; //MSG_EAGAIN;
}
return -1;
}
}
Log::debug(sprintf(' - doing a read now for [%d].',$len));
$rc = socket_recv($read[0],$l,$len,MSG_PEEK | MSG_DONTWAIT);
Log::debug(' - socket_recv PEEK', ['m' => __METHOD__,'l'=>$l,'rc'=>$rc]);
if ($rc <= 0) {
return $rc;
}
if ($rc == 2) {
return 2;
//l = I2H16(l);
// $l = unpack('s',$l);
$l = ((ord($l[0])&0x7f)<<8)+ord($l[1]);
// dd(['l'=>$l,'0'=>ord($l[0])&0xf,'00'=>((ord($l[0])&0x7f)<<8)+ord($l[1]),'1'=>ord($l[1]),'hex'=>sprintf('%x',unpack('v',$l)),'len'=>$len]);
if (! $l) {
return 0;
}
if ($l > $len) {
$l = $len;
}
Log::debug(' - L is ',['m' => __METHOD__,'l'=>min($l+$rc,$len)]);
$rc = socket_recv($sock,$buf,min($l+$rc,$len),MSG_WAITALL);
Log::debug(' - socket_recv GOT', ['m' => __METHOD__,'buf'=>$buf,'len'=>strlen($buf),'rc'=>$rc]);
if ($rc <= 0) {
return $rc;
}
$rc = min($rc - 2, strlen($buf));
if ($rc < 1) {
return 0;
}
if ($rc >= $len) {
$rc = $len - 2;
}
$buf = substr($buf,2,$rc); //memcpy(buf, buf + 2, rc);
return $rc;
}
return 0;
}
private function tty_bufc(int $ch): int
{
return $this->tty_bufblock( chr($ch), 1 );
}
// SocketClient::buffer_add()
public function tty_bufblock(string $data, int $nbytes): int
{
Log::debug(sprintf('%s: + Start [%s] (%d)',__METHOD__,$data,$nbytes));
$rc = self::OK;
$txptr = self::TX_BUF_SIZE - $this->tty_tx_free;
$nptr = 0;
$this->tty_status = self::TTY_SUCCESS;
while ( $nbytes ) {
Log::debug(sprintf(' - Num Bytes [%d]: TX Free [%d]',$nbytes,$this->tty_tx_free));
if ( $nbytes > $this->tty_tx_free ) {
do {
$this->tty_bufflush( 5 );
if ( $this->tty_status == self::TTY_SUCCESS ) {
$n = min($this->tty_tx_free,$nbytes);
$this->tty_tx_buf = substr($data,$nptr,$n);
$this->tty_tx_free -= $n;
$nbytes -= $n;
$nptr += $n;
}
} while ( $this->tty_status != self::TTY_SUCCESS );
} else {
Log::debug(sprintf(' -'),['data'=>$data,'nptr'=>$nptr,'txptr'=>$txptr,'tx_buff'=>substr($data,$nptr+$txptr,$nbytes)]);
$this->tty_tx_buf .= $data;// memcpy( (void *) (tty_tx_buf + txptr), nptr, nbytes );
$this->tty_tx_free -= $nbytes;
$nbytes = 0;
}
}
Log::debug('= End',['m'=>__METHOD__,'rc'=>$rc]);
return $rc;
}
private function tty_bufclear(): void
{
$this->tty_tx_ptr = 0;
$this->tty_tx_free = self::TX_BUF_SIZE;
$this->tty_tx_buf = '';
}
protected function tty_bufflush(int $tsec): int
{
Log::debug('+ Start',['m'=>__METHOD__,'tsec'=>$tsec,'txfree'=>$this->tty_tx_free,'txptr'=>$this->tty_tx_ptr,'txbuff'=>$this->tty_tx_buf]);
$rc = self::OK;
$restsize = self::TX_BUF_SIZE - $this->tty_tx_free - $this->tty_tx_ptr;
$tm = $this->timer_set( $tsec );
while (self::TX_BUF_SIZE != $this->tty_tx_free ) {
$wd = true;
$tv = $this->timer_rest( $tm );
if (( $rc = $this->client->canSend($tv) > 0 && $wd )) {
Log::debug(sprintf(' - Sending [%d]: Buffer [%s] Size [%d]',substr($this->tty_tx_buf,$this->tty_tx_ptr,$restsize),$this->tty_tx_buf,$restsize));
$rc = $this->client->send(substr($this->tty_tx_buf,$this->tty_tx_ptr,$restsize),0,$restsize);
Log::debug(sprintf(' - Sent [%d]: Buffer [%s] Size [%d]',$rc,$this->tty_tx_buf,$restsize));
if ($rc == $restsize ) {
$this->tty_bufclear();
} else if ( $rc > 0 ) {
$this->tty_tx_ptr += $rc;
$restsize -= $rc;
} else if ( $rc < 0 && $this->tty_status != self::TTY_TIMEOUT ) {
return self::ERROR;
}
} else {
return $rc;
}
if ($this->timer_expired( $tm )) {
return self::ERROR;
}
}
Log::debug('= End',['m'=>__METHOD__,'rc'=>$rc]);
return $rc;
}
public function tty_getc(int $timeout): int
{
Log::debug(sprintf('%s: + Start [%d]',__METHOD__,$timeout),['rx_left'=>$this->tty_rx_left]);
if ($this->tty_rx_left == 0 ) {
if ($this->client->hasData($timeout) > 0) {
if (! ($this->tty_rx_buf = $this->client->read(0,self::RX_BUF_SIZE))) {
Log::debug(sprintf('%s: - Nothing read',__METHOD__));
return ($this->EWBOEA()) ? self::TTY_TIMEOUT : self::ERROR;
}
Log::info(sprintf('%s: - Read [%d]',__METHOD__,strlen($this->tty_rx_buf)));
$this->tty_rx_ptr = 0;
$this->tty_rx_left = strlen($this->tty_rx_buf);
} else {
return ( $this->tty_gothup ? self::TTY_HANGUP : self::TTY_TIMEOUT );
}
}
$rc = ord(substr($this->tty_rx_buf,$this->tty_rx_ptr,1)); //tty_rx_buf[tty_rx_ptr++];
$this->tty_rx_left--;
$this->tty_rx_ptr++;
Log::debug(sprintf('%s: = Return [%x] (%c)',__METHOD__,$rc,$rc));
return $rc;
}
private function tty_getc_timed(int $timeout): int
{
$t = time();
$rc = $this->tty_getc($timeout);
$timeout -= (time() - $t);
return $rc;
}
protected function tty_purge(): void
{
//DEBUG(('M',3,"tty_purge"));
$this->tty_rx_ptr = $this->tty_rx_left = 0;
/*
if ( isatty( tty_fd )) {
tio_flush_queue( tty_fd, TIO_Q_IN );
}
*/
}
private function tty_purgeout(): void
{
//DEBUG(('M',3,"tty_purgeout"));
$this->tty_bufclear();
/*
if ( isatty( tty_fd )) {
tio_flush_queue( tty_fd, TIO_Q_OUT );
}
*/
}
private function tty_putc(string $ch):int
{
$this->tty_bufblock($ch,1);
return $this->tty_bufflush(5);
}
protected function tty_select($rd,$wd,int $tval): int
{
//DEBUG(('T',2,"tty_select"));
$rfd = $this->client->connection;
$wfd = $this->client->connection;
//dump($rfd,$wfd);
//FD_ZERO( &rfd );
//FD_ZERO( &wfd );
//if ($rd && $rd) {
//FD_SET($tty_fd,$rfd);
$rd = FALSE;
//}
//if ($wd && $wd ) {
//FD_SET($tty_fd,$wfd);
$wd = FALSE;
//}
$tty_error = 0;
$read = [$this->client->connection];
$write = [$this->client->connection];
$except = [];
dump('calling socket_select',['timeout'=>$tval,'read'=>$read,'write'=>$write]);
$rc = socket_select($read, $write, $except,($tval ?: NULL));
dump('done socket_select',$tval);
$tty_error = socket_last_error();
$tty_status = self::TTY_SUCCESS;
if ($rc < 0 ) {
if (EWBOEA()) {
$tty_status = self::TTY_TIMEOUT;
} else if ($errno == self::EINTR) {
$tty_status = ($tty_online && $tty_gothup ) ? self::TTY_HANGUP : self::TTY_TIMEOUT;
} else if ($errno == self::EPIPE) {
$tty_gothup = self::HUP_LINE;
$tty_status = self::TTY_HANGUP;
} else {
$tty_status = self::TTY_ERROR;
}
} else if ($rc == 0) {
$tty_status = self::TTY_TIMEOUT;
/*
} else {
if ($rd /*&& FD_ISSET( tty_fd, &rfd )*) {
$rd = TRUE;
}
if ($wd /*&& FD_ISSET( tty_fd, &wfd )*) {
$wd = TRUE;
}
*/
}
//DEBUG(('T',2,"tty_select: fd=%d rc=%i (rd=%s, wd=%s)", tty_fd, rc, FDS( rd ), FDS( wd )));
return $rc;
}
protected function BUFCHAR(int $c)
{
$this->tty_bufc($c);
}
protected function BUFFLUSH(int $tsec): int
{
return $this->tty_bufflush($tsec);
}
// @todo this should go into SocketCLient?
protected function EWBOEA(): bool
{
$errno = socket_last_error($this->client->connection);
Log::debug('+ Start',['m'=> __METHOD__,'errno'=>$errno]);
return $errno === 11 /*MSG_EAGAIN*/;
}
protected function GETCHAR(int $t): int
{
return $this->tty_getc($t);
}
protected function GETCHART($t): int
{
return $this->tty_getc_timed($t);
}
public function NOTTO(string $ch): int
{
return (($ch)==self::ERROR || ($ch)==self::RCDO || ($ch)==self::EOF);
}
protected function PUTSTR(string $s):void
{
$this->tty_bufblock($s,strlen($s));
$this->BUFFLUSH( 5);
}
protected function PURGEALL(): void
{
$this->tty_purge();
$this->tty_purgeout();
}
protected function PUTCHAR(string $c)
{
$this->tty_putc( $c );
}
protected function PUTSTRCR(string $str)
{
$this->tty_bufblock($str."\r",strlen($str)+1);
return $this->tty_bufflush(5);
}
}
class rnode
{
public $starttime = 0;
public $options = 0;
public $netmail = 0;
public $files = 0;
public $ewboea = 0;
public $phone = '';
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Classes\Sock\SocketException;
use App\Classes\Sock\SocketServer;
use App\Classes\Protocol\Binkd as BinkdClass;
class BinkpReceive extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'binkp:receive';
/**
* The console command description.
*
* @var string
*/
protected $description = 'BINKP receive';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
Log::info('Listening for BINKP connections...');
$server = new SocketServer(24554,'0.0.0.0');
$server->setConnectionHandler([new BinkdClass,'onConnect']);
try {
$server->listen();
} catch (SocketException $e) {
if ($e->getMessage() == 'Can\'t accept connections: "Success"')
Log::debug('Server Terminated');
else
Log::emergency('Uncaught Message: '.$e->getMessage());
}
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Classes\Protocol\Binkd as BinkdClass;
use App\Classes\Sock\SocketClient;
use App\Models\Node;
class BinkpSend extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'binkp:send {ftn : FTN to Send to}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Binkp send';
/**
* Execute the console command.
*
* @return mixed
* @throws \App\Classes\Sock\SocketException
*/
public function handle()
{
Log::info('Call BINKP send');
$no = Node::findFTN($this->argument('ftn'));
$client = SocketClient::create($no->address,$no->port);
$o = new BinkdClass;
$o->session(BinkdClass::SESSION_BINKP,$client,$no);
Log::info(sprintf('Connection ended: %s',$client->getAddress()),['m'=>__METHOD__]);
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Classes\Sock\SocketException;
use App\Classes\Sock\SocketServer;
use App\Classes\Protocol\EMSI as EMSIClass;
class EMSIReceive extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'emsi:receive';
/**
* The console command description.
*
* @var string
*/
protected $description = 'EMSI receive';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
Log::info('Listening for EMSI connections...');
$server = new SocketServer(60179,'0.0.0.0');
$server->setConnectionHandler([new EMSIClass,'onConnect']);
try {
$server->listen();
} catch (SocketException $e) {
if ($e->getMessage() == 'Can\'t accept connections: "Success"')
Log::debug('Server Terminated');
else
Log::emergency('Uncaught Message: '.$e->getMessage());
}
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Console\Commands;
use App\Models\Node;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Classes\Sock\SocketClient;
use App\Classes\Protocol\EMSI as EMSIClass;
class EMSISend extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'emsi:send {ftn : FTN to Send to}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'EMSI send';
/**
* Execute the console command.
*
* @return mixed
* @throws \App\Classes\Sock\SocketException
*/
public function handle()
{
Log::info('Call EMSI send');
$no = Node::findFTN($this->argument('ftn'));
$client = SocketClient::create($no->address,$no->port,38400);
$o = new EMSIClass;
$o->session(EMSIClass::SESSION_AUTO,$client,$no);
Log::info(sprintf('Connection ended: %s',$client->getAddress()),['m'=>__METHOD__]);
}
}

View File

@ -60,7 +60,11 @@ class ImportPacket extends Command
'msgid'=>$o->msgid, 'msgid'=>$o->msgid,
]); ]);
$oo->area = $o->echoarea; if (md5(utf8_decode($eo->message)) == md5($o->message))
{
$this->warn(sprintf('Duplicate message: %s@%s with id: %s',$o->from,$o->fqfa,$o->msgid));
break 2;
}
break; break;

View File

@ -0,0 +1,50 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Classes\Sock\SocketException;
use App\Classes\Sock\SocketServer;
use App\Classes\Protocol\Zmodem as ZmodemClass;
class ZmodemReceive extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'zmodem:receive';
/**
* The console command description.
*
* @var string
*/
protected $description = 'ZMODEM receive';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
Log::info('Listening for ZMODEM connections...');
$server = new SocketServer(60177,'0.0.0.0');
$server->setConnectionHandler([new ZmodemClass,'onConnect']);
try {
$server->listen();
} catch (SocketException $e) {
if ($e->getMessage() == 'Can\'t accept connections: "Success"')
Log::debug('Server Terminated');
else
Log::emergency('Uncaught Message: '.$e->getMessage());
}
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Classes\Protocol;
use App\Classes\Sock\SocketClient;
use App\Classes\Protocol\Zmodem as ZmodemClass;
class ZmodemSend extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'zmodem:send {ip}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'ZMODEM send';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
Log::info('Call ZMODEM send');
[$address,$service_port] = explode(':',$this->argument('ip'),2);
$client = SocketClient::create($address,$service_port);
$o = new ZmodemClass;
$o->session(Protocol::SESSION_ZMODEM,$client);
Log::info(sprintf('Connection ended: %s',$client->getAddress()),['m'=>__METHOD__]);
}
}

View File

@ -19,6 +19,8 @@ class DomainController extends Controller
public function add_edit(Request $request,Domain $o) public function add_edit(Request $request,Domain $o)
{ {
if ($request->post()) { if ($request->post()) {
// @todo Add validation that we can only have 1 "default" domain for a zone.
// The default domain is used when a node connects and doesnt use a full 5D address, eg: 10:999/1 vs 10:999/1@private
foreach (['name','dnsdomain','active','notes'] as $key) foreach (['name','dnsdomain','active','notes'] as $key)
$o->{$key} = $request->post($key); $o->{$key} = $request->post($key);

84
app/Interfaces/CRC.php Normal file
View File

@ -0,0 +1,84 @@
<?php
namespace App\Interfaces;
interface CRC
{
const CRC16USD_INIT = 0x0000;
const LSZ_INIT_CRC16 = self::CRC16USD_INIT;
const CRC32_INIT = (0xffffffff);
const LSZ_INIT_CRC32 = self::CRC32_INIT;
/* CRC polynomial 0x1021 -- CCITT upside-down CRC16. EMSI,ZModem,Janus. */
public const crc16usd_tab = [
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
];
/* CRC polynomial 0xedb88320 -- CCITT CRC32. */
public const crc32_tab = [
0x00000000,0x77073096,0xee0e612c,0x990951ba,0x076dc419,0x706af48f,0xe963a535,0x9e6495a3,
0x0edb8832,0x79dcb8a4,0xe0d5e91e,0x97d2d988,0x09b64c2b,0x7eb17cbd,0xe7b82d07,0x90bf1d91,
0x1db71064,0x6ab020f2,0xf3b97148,0x84be41de,0x1adad47d,0x6ddde4eb,0xf4d4b551,0x83d385c7,
0x136c9856,0x646ba8c0,0xfd62f97a,0x8a65c9ec,0x14015c4f,0x63066cd9,0xfa0f3d63,0x8d080df5,
0x3b6e20c8,0x4c69105e,0xd56041e4,0xa2677172,0x3c03e4d1,0x4b04d447,0xd20d85fd,0xa50ab56b,
0x35b5a8fa,0x42b2986c,0xdbbbc9d6,0xacbcf940,0x32d86ce3,0x45df5c75,0xdcd60dcf,0xabd13d59,
0x26d930ac,0x51de003a,0xc8d75180,0xbfd06116,0x21b4f4b5,0x56b3c423,0xcfba9599,0xb8bda50f,
0x2802b89e,0x5f058808,0xc60cd9b2,0xb10be924,0x2f6f7c87,0x58684c11,0xc1611dab,0xb6662d3d,
0x76dc4190,0x01db7106,0x98d220bc,0xefd5102a,0x71b18589,0x06b6b51f,0x9fbfe4a5,0xe8b8d433,
0x7807c9a2,0x0f00f934,0x9609a88e,0xe10e9818,0x7f6a0dbb,0x086d3d2d,0x91646c97,0xe6635c01,
0x6b6b51f4,0x1c6c6162,0x856530d8,0xf262004e,0x6c0695ed,0x1b01a57b,0x8208f4c1,0xf50fc457,
0x65b0d9c6,0x12b7e950,0x8bbeb8ea,0xfcb9887c,0x62dd1ddf,0x15da2d49,0x8cd37cf3,0xfbd44c65,
0x4db26158,0x3ab551ce,0xa3bc0074,0xd4bb30e2,0x4adfa541,0x3dd895d7,0xa4d1c46d,0xd3d6f4fb,
0x4369e96a,0x346ed9fc,0xad678846,0xda60b8d0,0x44042d73,0x33031de5,0xaa0a4c5f,0xdd0d7cc9,
0x5005713c,0x270241aa,0xbe0b1010,0xc90c2086,0x5768b525,0x206f85b3,0xb966d409,0xce61e49f,
0x5edef90e,0x29d9c998,0xb0d09822,0xc7d7a8b4,0x59b33d17,0x2eb40d81,0xb7bd5c3b,0xc0ba6cad,
0xedb88320,0x9abfb3b6,0x03b6e20c,0x74b1d29a,0xead54739,0x9dd277af,0x04db2615,0x73dc1683,
0xe3630b12,0x94643b84,0x0d6d6a3e,0x7a6a5aa8,0xe40ecf0b,0x9309ff9d,0x0a00ae27,0x7d079eb1,
0xf00f9344,0x8708a3d2,0x1e01f268,0x6906c2fe,0xf762575d,0x806567cb,0x196c3671,0x6e6b06e7,
0xfed41b76,0x89d32be0,0x10da7a5a,0x67dd4acc,0xf9b9df6f,0x8ebeeff9,0x17b7be43,0x60b08ed5,
0xd6d6a3e8,0xa1d1937e,0x38d8c2c4,0x4fdff252,0xd1bb67f1,0xa6bc5767,0x3fb506dd,0x48b2364b,
0xd80d2bda,0xaf0a1b4c,0x36034af6,0x41047a60,0xdf60efc3,0xa867df55,0x316e8eef,0x4669be79,
0xcb61b38c,0xbc66831a,0x256fd2a0,0x5268e236,0xcc0c7795,0xbb0b4703,0x220216b9,0x5505262f,
0xc5ba3bbe,0xb2bd0b28,0x2bb45a92,0x5cb36a04,0xc2d7ffa7,0xb5d0cf31,0x2cd99e8b,0x5bdeae1d,
0x9b64c2b0,0xec63f226,0x756aa39c,0x026d930a,0x9c0906a9,0xeb0e363f,0x72076785,0x05005713,
0x95bf4a82,0xe2b87a14,0x7bb12bae,0x0cb61b38,0x92d28e9b,0xe5d5be0d,0x7cdcefb7,0x0bdbdf21,
0x86d3d2d4,0xf1d4e242,0x68ddb3f8,0x1fda836e,0x81be16cd,0xf6b9265b,0x6fb077e1,0x18b74777,
0x88085ae6,0xff0f6a70,0x66063bca,0x11010b5c,0x8f659eff,0xf862ae69,0x616bffd3,0x166ccf45,
0xa00ae278,0xd70dd2ee,0x4e048354,0x3903b3c2,0xa7672661,0xd06016f7,0x4969474d,0x3e6e77db,
0xaed16a4a,0xd9d65adc,0x40df0b66,0x37d83bf0,0xa9bcae53,0xdebb9ec5,0x47b2cf7f,0x30b5ffe9,
0xbdbdf21c,0xcabac28a,0x53b39330,0x24b4a3a6,0xbad03605,0xcdd70693,0x54de5729,0x23d967bf,
0xb3667a2e,0xc4614ab8,0x5d681b02,0x2a6f2b94,0xb40bbe37,0xc30c8ea1,0x5a05df1b,0x2d02ef8d
];
}

30
app/Interfaces/Zmodem.php Normal file
View File

@ -0,0 +1,30 @@
<?php
namespace App\Interfaces;
interface Zmodem
{
/* ZMODEM PROTOCOLS
* # 1 - ZModem
* # Z - ZedZap (ZModem with 8k blocks)
* # D - DirZap (ZModem with 8k blocks and minimal escaping)
*/
public const P_NONE = 0x0000; /* 0 0000 0000 */
public const P_NCP = 0x0001; /* 0 0000 0001 */
public const P_BINKP = 0x0001; /* 0 0000 0001 */
public const P_ZMODEM = 0x0002; /* 0 0000 0010 */
public const P_ZEDZAP = 0x0004; /* 0 0000 0100 */
public const P_DIRZAP = 0x0008; /* 0 0000 1000 */
public const P_HYDRA = 0x0010; /* 0 0001 0000 */
public const P_JANUS = 0x0020; /* 0 0010 0000 */
public const P_HYDRA4 = 0x0040; /* 0 0100 0000 */
public const P_HYDRA8 = 0x0080; /* 0 1000 0000 */
public const P_HYDRA16 = 0x0100; /* 1 0000 0000 */
public const P_MASK = 0x01FF; /* 1 1111 1111 */
/* canzap constants */
public const CZ_ZEDZIP = 0;
public const CZ_ZEDZAP = 1;
public const CZ_DIRZAP = 2;
}

View File

@ -2,23 +2,30 @@
namespace App\Models; namespace App\Models;
use Exception;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use App\Traits\ScopeActive;
class Node extends Model class Node extends Model
{ {
use ScopeActive;
protected $casts = [ protected $casts = [
'is_zc'=>'boolean', 'is_zc'=>'boolean',
'is_rc'=>'boolean', 'is_rc'=>'boolean',
'is_hub'=>'boolean', 'is_hub'=>'boolean',
'is_host'=>'boolean', 'is_host'=>'boolean',
]; ];
protected $fillable = ['zone_id','host_id','node_id','point_id']; protected $fillable = ['zone_id','host_id','node_id','point_id'];
/* SCOPES */ /* SCOPES */
public function scopeHost() public function scopeHost()
{ {
// @todo
} }
/* RELATIONS */ /* RELATIONS */
@ -26,7 +33,7 @@ class Node extends Model
/** /**
* Node nodelist flags * Node nodelist flags
* *
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany * @return BelongsToMany
*/ */
public function flags() public function flags()
{ {
@ -48,7 +55,7 @@ class Node extends Model
public function getFTNAttribute() public function getFTNAttribute()
{ {
return $this->zone_id return $this->zone_id
? sprintf('%s:%s/%s.%s',$this->zone->zone_id,$this->host_id,$this->node_id,$this->point_id) ? sprintf('%d:%d/%d.%d@%s',$this->zone->zone_id,$this->host_id,$this->node_id,$this->point_id,$this->zone->domain->name)
: '-'; : '-';
} }
@ -63,7 +70,51 @@ class Node extends Model
/* METHODS */ /* METHODS */
public function hasFlag($relation,$model) /**
* Find a record in the DB for a node string, eg: 10:1/1.0
*
* @param string $ftn
* @return Node|null
* @throws Exception
*/
public static function findFTN(string $ftn): ?self
{
$matches = [];
// @todo domain can have more chars.
if (! preg_match('#^([0-9]+):([0-9]+)/([0-9]+)(.([0-9]+))?(@([a-z]{0,8}))?$#',strtolower($ftn),$matches))
throw new Exception('Invalid FTN: '.$ftn);
// Check our numbers are correct.
foreach ([1,2,3] as $i) {
if (! $matches[$i] || ($matches[$i] > 0xffff))
throw new Exception('Invalid FTN: '.$ftn);
}
if (isset($matches[5]) AND $matches[5] > 0xffff)
throw new Exception('Invalid FTN: '.$ftn);
return (new self)->active()
->select('nodes.*')
->where('zones.zone_id',$matches[1])
->where(function($query) use ($matches) {
$query->where('hub_id',$matches[2])
->orWhere('host_id',$matches[2]);
})
->join('zones',['zones.id'=>'nodes.zone_id'])
->join('domains',['domains.id'=>'zones.domain_id'])
->where('zones.active',TRUE)
->where('node_id',$matches[3])
->where('point_id',(isset($matches[5]) AND $matches[5]) ? $matches[5] : 0)
->when(isset($matches[7]),function($query) use ($matches) {
$query->where('domains.name',$matches[7]);
})
->when((! isset($matches[7]) OR ! $matches[7]),function($query) {
$query->where('domains.default',TRUE);
})
->single();
}
public function hasFlag($relation,$model): bool
{ {
return (bool) $this->{$relation}() return (bool) $this->{$relation}()
->wherePivot($model->getForeignKey(),$model->{$model->getKeyName()}) ->wherePivot($model->getForeignKey(),$model->{$model->getKeyName()})

125
app/Models/Setup.php Normal file
View File

@ -0,0 +1,125 @@
<?php
namespace App\Models;
use Exception;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\File;
/**
* Class Setup
*
* @package App\Models
* @property Collection nodes
* @property array binkp_options
*/
class Setup extends Model
{
// Our non model attributes and values
private array $internal = [];
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
// @todo This should go in a config file in the config dir
$this->opt_cht = 0; /* CHAT mode - not implemented*/
$this->opt_cr = 0; /* Crypt mode - not implemented*/
$this->opt_mb = 1; /* Multi-Batch mode */
$this->opt_md = 0; /* CRAM-MD5 mode */
$this->opt_nd = 0; /* http://ftsc.org/docs/fsp-1027.001: No-dupes mode */
$this->opt_nda = 1; /* http://ftsc.org/docs/fsp-1027.001: Asymmetric ND mode */
$this->opt_mpwd = 0; /* Multi-Password mode - not implemented */
$this->opt_nr = 1; /* http://ftsc.org/docs/fsp-1027.001: Non-Reliable mode */
$this->binkp_options = ['m','d','r','b'];
/* EMSI SETTINGS */
$this->do_prevent = 1; /* EMSI - send an immediate EMSI_INQ on connect */
$this->ignore_nrq = 0;
$this->options = 0; /* EMSI - our capabilities */
/* EMSI - the order of protocols we are able to accept */
$this->inbound = '/tmp';
}
/* RELATIONS */
public function nodes()
{
return $this->belongsToMany(Node::class);
}
/* ATTRIBUTES */
public function getLocationAttribute()
{
return $this->nodes->first()->location;
}
public function getSysopAttribute()
{
return $this->nodes->first()->sysop;
}
public function getSystemNameAttribute()
{
return $this->nodes->first()->system;
}
/* METHODS */
/**
* @throws Exception
*/
public function __get($key)
{
switch ($key) {
case 'binkp_options':
case 'ignore_nrq':
case 'inbound':
case 'opt_nr':
case 'opt_nd':
case 'opt_nda':
case 'opt_md':
case 'opt_cr':
case 'opt_mb':
case 'opt_cht':
case 'do_prevent':
case 'options':
return $this->internal[$key] ?? FALSE;
case 'version':
return File::exists('VERSION') ? chop(File::get('VERSION')) : 'dev';
default:
return parent::__get($key);
}
}
/**
* @throws Exception
*/
public function __set($key,$value)
{
switch ($key) {
case 'binkp_options':
case 'ignore_nrq':
case 'inbound':
case 'opt_nr':
case 'opt_nd':
case 'opt_nda':
case 'opt_md':
case 'opt_cr':
case 'opt_mb':
case 'opt_cht':
case 'do_prevent':
case 'options':
$this->internal[$key] = $value;
break;
default:
parent::__get($key);
}
}
}

View File

@ -2,6 +2,7 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider

63
app/Traits/CRC.php Normal file
View File

@ -0,0 +1,63 @@
<?php
namespace App\Traits;
trait CRC
{
private function CRC16USD(string $string): int
{
$crc = self::CRC16USD_INIT;
for ($c=0;$c<strlen($string);$c++)
$crc = $this->CRC16USD_UPDATE(ord($string[$c]),$crc);
return $crc;
}
private function CRC16USD_UPDATE($b,$crc): int
{
return (self::crc16usd_tab[(($crc >> 8) ^ $b) & 0xff] ^ (($crc & 0x00ff) << 8)) & 0xffff;
}
/**
* Calculate CRC32
*
* @param string $string
* @param bool $finish
* @return int
*/
private function CRC32(string $string,bool $finish=TRUE): int
{
$crc = 0xffffffff;
for ($i=0;$i<strlen($string);$i++)
$crc = (self::crc32_tab[($crc^ord($string[$i])) & 0xff] ^ (($crc>>8) & 0x00ffffff)) & 0xffffffff;
return $finish ? $this->CRC32_FINISH($crc) : $crc;
}
private function CRC32_FINISH($crc)
{
return ~$crc & 0xffffffff;
}
private function CRC32_UPDATE($b,$crc)
{
return ((self::crc32_tab[($crc^$b) & 0xff] ^ (($crc>>8) & 0x00ffffff)) & 0xffffffff);
}
private function LSZ_INIT_CRC()
{
return ($this->ls_Protocol & self::LSZ_OPTCRC32) ? self::LSZ_INIT_CRC32 : self::LSZ_INIT_CRC16;
}
private function LSZ_FINISH_CRC($crc)
{
return ($this->ls_Protocol & self::LSZ_OPTCRC32) ? $this->CRC32_FINISH($crc) : $crc;
}
private function LSZ_UPDATE_CRC($b,$crc)
{
return ($this->ls_Protocol & self::LSZ_OPTCRC32) ? $this->CRC32_UPDATE($b,$crc) : $this->CRC16USD_UPDATE($b,$crc);
}
}

View File

@ -12,6 +12,6 @@ trait ScopeActive
*/ */
public function scopeActive() public function scopeActive()
{ {
return $this->where('active',TRUE); return $this->where($this->getTable().'.active',TRUE);
} }
} }

View File

@ -19,6 +19,7 @@ class CreateDomains extends Migration
$table->string('name',8)->unique(); $table->string('name',8)->unique();
$table->string('dnsdomain')->nullable(); $table->string('dnsdomain')->nullable();
$table->string('notes')->nullable(); $table->string('notes')->nullable();
$table->boolean('default')->default(FALSE);
$table->boolean('active'); $table->boolean('active');
}); });
} }

View File

@ -31,6 +31,7 @@ class InitialSetupSeeder extends Seeder
DB::table('domains')->insert([ DB::table('domains')->insert([
'name'=>'private', 'name'=>'private',
'default'=>TRUE,
'active'=>TRUE, 'active'=>TRUE,
]); ]);
@ -46,19 +47,36 @@ class InitialSetupSeeder extends Seeder
DB::table('nodes')->insert([ DB::table('nodes')->insert([
'zone_id'=>'1', 'zone_id'=>'1',
'host_id'=>'999', 'host_id'=>'999',
'node_id'=>'999', 'node_id'=>'2',
'is_host'=>TRUE, 'is_host'=>TRUE,
'active'=>TRUE, 'active'=>TRUE,
'system'=>'FTN Clearing House Dev', 'system'=>'FTN Clearing House Dev',
'sysop'=>'Deon George', 'sysop'=>'Deon George',
'location'=>'Parkdale, AUS', 'location'=>'Parkdale, AUS',
'email'=>'deon@leenooks.net', 'email'=>'deon@leenooks.net',
'address'=>'fidohub.leenooks.net', 'address'=>'10.1.3.165',
'port'=>24554, 'port'=>24554,
'protocol_id'=>1, 'protocol_id'=>1,
'software_id'=>1, 'software_id'=>1,
]); ]);
DB::table('nodes')->insert([
'zone_id'=>'1',
'host_id'=>'999',
'node_id'=>'1',
'is_host'=>TRUE,
'active'=>TRUE,
'system'=>'Alterant MailHUB DEV',
'sysop'=>'Deon George',
'location'=>'Parkdale, AUS',
'email'=>'deon@leenooks.net',
'address'=>'d-1-4.ipv4.leenooks.vpn',
'port'=>14554,
'sespass'=>'PASSWORD',
'protocol_id'=>1,
'software_id'=>1,
]);
DB::table('setups')->insert([ DB::table('setups')->insert([
'opt_md'=>'1', 'opt_md'=>'1',
]); ]);
@ -67,5 +85,11 @@ class InitialSetupSeeder extends Seeder
'node_id'=>'1', 'node_id'=>'1',
'setup_id'=>'1', 'setup_id'=>'1',
]); ]);
DB::table('users')->insert([
'name'=>'Deon George',
'email'=>'deon@leenooks.net',
'password'=>'$2y$10$bJQDLfxnKrh6o5Sa02MZOukXcLTNQiByXSTJ7fTr.kHMpV2wxbG6.',
]);
} }
} }