Transfering netmail via EMSI

This commit is contained in:
Deon George 2021-07-17 15:48:07 +10:00
parent 6ce4e64cb6
commit 1fa566b26c
15 changed files with 226 additions and 83 deletions

View File

@ -195,7 +195,7 @@ class Message extends FTNBase
if (($x=$o->validate($domain))->fails()) {
Log::debug('Message fails validation',['result'=>$x->errors()]);
throw new \Exception('Message validation fails:'.join(' ',$x->errors()->all()));
//throw new \Exception('Message validation fails:'.join(' ',$x->errors()->all()));
}
return $o;
@ -249,7 +249,7 @@ class Message extends FTNBase
case 'date':
return Carbon::createFromFormat('d M y H:i:s O',
sprintf('%s %s',chop(Arr::get($this->header,$key)),($x=$this->kludge->get('tzutc')) < 0 ? $x : '+'.$x));
sprintf('%s %s',chop(Arr::get($this->header,$key)),(is_null($x=$this->kludge->get('tzutc')) || ($x < 0)) ? $x : '+'.$x));
case 'flags':
case 'cost': return Arr::get($this->header,$key);
@ -404,7 +404,7 @@ class Message extends FTNBase
foreach ($this->_kludge as $k=>$v) {
if ($x=$this->kludge->get($k))
$return .= sprintf("\01%s %s\r",$v,$x);
$return .= sprintf("\01%s%s\r",$v,$x);
}
$return .= $this->message."\r";

View File

@ -230,12 +230,17 @@ class Packet extends FTNBase
*/
public function __toString(): string
{
$return = $this->createHeader();
// Cache the packet creation
static $return = NULL;
foreach ($this->messages as $o)
$return .= "\02\00".(string)$o;
if (is_null($return)) {
$return = $this->createHeader();
$return .= "\00\00";
foreach ($this->messages as $o)
$return .= "\02\00".(string)$o;
$return .= "\00\00";
}
return $return;
}
@ -296,7 +301,7 @@ class Packet extends FTNBase
/**
* When creating a new packet, set the header.
*
* @param array $header
* @param Address $o
*/
private function newHeader(Address $o): void
{
@ -329,8 +334,8 @@ class Packet extends FTNBase
* Parse a message in a mail packet
*
* @param string $message
* @param Domain $domain
* @throws \Exception
* @param Domain|null $domain
* @throws InvalidPacketException
*/
public function parseMessage(string $message,Domain $domain=NULL): void
{

View File

@ -21,7 +21,7 @@ abstract class Process
*/
protected static function format_msg(string $text): string
{
$msg = utf8_decode(join("\n",static::msg_header()))."\n";
$msg = utf8_decode(join("\r",static::msg_header()))."\r";
$c = 0;
$offset = 0;
@ -35,7 +35,7 @@ abstract class Process
}
// Look for a return
$return = strpos($text,"\n",$offset);
$return = strpos($text,"\r",$offset);
if ($return !== FALSE)
$return -= $offset;
@ -55,13 +55,13 @@ abstract class Process
$subtext = substr($text,$offset,$space);
}
$msg .= $ll.$subtext."\n";
$msg .= $ll.$subtext."\r";
$offset += strlen($subtext)+1;
}
// In case our text is shorter than the loo
for ($c; $c<count(static::$logo);$c++)
$msg .= utf8_decode(Arr::get(static::$logo,$c))."\n";
$msg .= utf8_decode(Arr::get(static::$logo,$c))."\r";
return $msg;
}

View File

@ -26,17 +26,18 @@ final class Ping extends Process
if (strtolower($msg->user_to) !== 'ping')
return FALSE;
$reply = sprintf("Your ping was received here on %s and it took %s to get here.\n",
$reply = sprintf("Your ping was received here on %s and it took %s to get here.\r",
Carbon::now()->toDateTimeString(),
$msg->date->diffForHumans(['parts'=>3,'syntax'=>CarbonInterface::DIFF_ABSOLUTE])
);
$reply .= "\n";
$reply .= "Your message travelled along this path to get here:\n";
$reply .= "\r";
$reply .= "\r";
$reply .= "Your message travelled along this path on the way here:\r";
foreach ($msg->via as $path)
$reply .= sprintf(" * %s\n",$path);
$reply .= sprintf(" * %s\r",$path);
$o = new Netmail();
$o = new Netmail;
$o->to = $msg->user_from;
$o->from = Setup::PRODUCT_NAME;
$o->subject = 'Ping Reply';

38
app/Classes/File/Mail.php Normal file
View File

@ -0,0 +1,38 @@
<?php
namespace App\Classes\File;
use Carbon\Carbon;
use App\Classes\FTN\Packet;
class Mail extends Item
{
private Packet $file;
/**
* @throws \Exception
*/
public function __construct(Packet $mail,int $action)
{
$this->action |= $action;
switch ($action) {
case self::I_SEND:
$this->file = $mail;
$this->file_name = sprintf('%08X.PKT',Carbon::now()->timestamp);
$this->file_size = strlen($mail);
$this->file_mtime = Carbon::now()->timestamp; // @todo This timestamp should be consistent incase of retries
break;
default:
throw new \Exception('Unknown action: '.$action);
}
}
public function read(int $start,int $length): string
{
return substr((string)$this->file,$start,$length);
}
}

View File

@ -8,6 +8,8 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use League\Flysystem\UnreadableFileException;
use App\Models\Address;
/**
* Object representing the files we are sending
*
@ -24,6 +26,7 @@ final class Send extends Item
{
private Collection $list;
private ?Item $sending;
private Collection $packets;
private mixed $f; // File descriptor
private int $start; // Time we started sending
@ -33,6 +36,7 @@ final class Send extends Item
{
// Initialise our variables
$this->list = collect();
$this->packets = collect();
$this->sending = NULL;
$this->file_pos = 0;
$this->f = NULL;
@ -60,12 +64,14 @@ final class Send extends Item
case 'mail_count':
return $this->list
->filter(function($item) { return $item->isType(self::IS_ARC|self::IS_PKT); })
->count();
->count()
+ $this->packets->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; });
->sum(function($item) { return $item->file_size; })
+ $this->packets->sum(function($item) { return $item->file_size; });
case 'sendas':
return $this->sending ? $this->sending->{$key} : NULL;
@ -78,21 +84,31 @@ final class Send extends Item
case 'total_sent':
return $this->list
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; })
->count();
->count()
+ $this->packets
->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; });
->sum(function($item) { return $item->file_size; })
+ $this->packets
->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();
->count()
+ $this->packets
->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; });
->sum(function($item) { return $item->file_size; })
+ $this->packets->sum(function($item) { return $item->file_size; });
default:
throw new Exception('Unknown key: '.$key);
@ -143,7 +159,9 @@ final class Send extends Item
Log::debug(sprintf('%s: - Closing [%s], sent in [%d]',__METHOD__,$this->sending->file_name,$end));
}
fclose($this->f);
if (! $this->sending instanceof Mail)
fclose($this->f);
$this->sending = NULL;
$this->file_pos = 0;
$this->f = NULL;
@ -156,7 +174,7 @@ final class Send extends Item
*/
public function feof(): bool
{
return feof($this->f);
return ($this->sending instanceof Mail) ? ($this->file_pos == $this->size) : feof($this->f);
}
/**
@ -169,6 +187,18 @@ final class Send extends Item
{
Log::debug(sprintf('%s: + Start',__METHOD__));
// If we have mail, we'll send that first
if ($this->sending = $this->packets
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; })
->first())
{
$this->file_pos = 0;
$this->start = time();
$this->f = TRUE;
return TRUE;
}
$this->sending = $this->list
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; })
->first();
@ -189,6 +219,19 @@ final class Send extends Item
return TRUE;
}
/**
* Add our mail to the send queue
*
* @param Address $ao
* @throws Exception
*/
public function mail(Address $ao): void
{
// Netmail
if ($x=$ao->getNetmail())
$this->packets->push(new Mail($x,self::I_SEND));
}
/**
* Read bytes of the sending file
*
@ -202,7 +245,13 @@ final class Send extends Item
if (! $this->f)
throw new Exception('No file open for read');
$data = fread($this->f,$length);
// We are sending mail
if ($this->sending instanceof Mail) {
$data = $this->sending->read($this->file_pos,$length);
} else {
$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));
@ -224,7 +273,13 @@ final class Send extends Item
if (! $this->f)
throw new Exception('No file open for seek');
$rc = (fseek($this->f,$pos,SEEK_SET) === 0);
if ($this->sending instanceof Mail) {
$rc = ($pos < $this->size) ? $pos : $this->size;
} else {
$rc = (fseek($this->f,$pos,SEEK_SET) === 0);
}
if ($rc)
$this->file_pos = $pos;

View File

@ -33,12 +33,14 @@ class Node
private Collection $ftns; // The FTNs of the remote system
private Collection $ftns_authed; // The FTNs we have validated
private bool $authed; // Have we authenticated the remote.
private int $options; // This nodes capabilities/options
public function __construct()
{
$this->options = 0;
$this->authed = FALSE;
$this->start_time = Carbon::now();
$this->ftns = collect();
$this->ftns_authed = collect();
@ -54,16 +56,20 @@ class Node
case 'aka_num':
return $this->ftns->count();
// Number of AKAs we have validated
// The authenticated remote addresses
case 'aka_remote':
return $this->ftns_authed;
// Have we authenticated the remote
case 'aka_authed':
return $this->ftns_authed->count();
return $this->authed;
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()->session('sespass'))) ? $x : '-';
return ($this->ftns->count() && ($x=$this->ftns->first()->session('sespass'))) ? $x : '-';
// Return how long our session has been connected
case 'session_time':
@ -163,6 +169,7 @@ class Node
$o->system->last_session = Carbon::now();
$o->system->save();
$this->ftns_authed->push($o);
$this->authed = TRUE;
}
}
@ -224,6 +231,10 @@ class Node
*/
public function originate_check(): bool
{
// If we have already authed, we wont do it again
if ($this->authed)
return TRUE;
if ($this->ftns_authed->count() !== 1 || ! $this->ftns->count())
return FALSE;
@ -233,6 +244,7 @@ class Node
if ($item->ftn == $ftn) {
$item->system->last_session = Carbon::now();
$item->system->save();
$this->authed = TRUE;
return TRUE;
}
}) !== FALSE;

View File

@ -12,11 +12,6 @@ use App\Models\{Address,Setup};
abstract class Protocol
{
// Our product code
// @todo Move These to a config file
protected const product_code = 'AB8D';
public const setup = 1;
// Enable extra debugging
protected bool $DEBUG = FALSE;
@ -254,9 +249,6 @@ abstract class Protocol
$this->client->speed = SocketClient::TCP_SPEED;
$this->originate = FALSE;
// @todo While Debugging
$this->send->add('/tmp/aa');
return $this->protocol_session();
default:

View File

@ -9,7 +9,7 @@ use Illuminate\Support\Facades\Log;
use App\Classes\Protocol as BaseProtocol;
use App\Classes\Sock\SocketClient;
use App\Classes\Sock\SocketException;
use App\Models\Address;
use App\Models\{Address,Setup};
use App\Interfaces\CRC as CRCInterface;
use App\Interfaces\Zmodem as ZmodemInterface;
use App\Traits\CRC as CRCTrait;
@ -82,7 +82,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
if (! parent::onConnect($client)) {
$this->session(self::SESSION_AUTO,$client,(new Address));
$this->client->close();
Log::info(sprintf('%s: = End - Connection closed [%s]',__METHOD__,$client->getAddress()));
Log::info(sprintf('%s: = End - Connection closed [%s]',__METHOD__,$client->address_remote));
}
return NULL;
@ -191,7 +191,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
// Mailer Details
$makedata .= sprintf('{%s}{%s}{%s}{%s}',
self::product_code,
Setup::product_id(),
config('app.name'),
$this->setup->version,
'#000000' // Serial Numbers
@ -346,7 +346,12 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
} else {
$this->node->optionSet(self::O_PWD);
Log::info(sprintf('%s: - Remote Authed [%d] AKAs',__METHOD__,$c));
$this->send->add('/tmp/aa');
// Add our mail to the queue if we have authenticated
if ($this->node->aka_authed)
foreach ($this->node->aka_remote as $ao) {
$this->send->mail($ao);
}
}
/* Link codes */
@ -961,9 +966,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
if ($rc < 0)
return (self::S_REDIAL|self::S_ADDTRY);
Log::info(sprintf('%s: - Starting outbound EMSI session to [%s]',__METHOD__,$this->client->getAddress()));
// @todo Lock Node AKAs
Log::info(sprintf('%s: - Starting outbound EMSI session to [%s]',__METHOD__,$this->client->address_remote));
// Inbound session
} else {
@ -975,9 +978,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
return (self::S_REDIAL|self::S_ADDTRY);
}
Log::info(sprintf('%s: - Starting inbound EMSI session from [%s]',__METHOD__,$this->client->getAddress()));
// @todo Lock Node AKAs
Log::info(sprintf('%s: - Starting inbound EMSI session from [%s]',__METHOD__,$this->client->address_remote));
if ($this->node->aka_authed) {
$xproto = $this->is_freq_available();
@ -1003,6 +1004,8 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
return (self::S_REDIAL|self::S_ADDTRY);
}
// @todo Lock Node AKAs
Log::info(sprintf('%s: - We have %lu%s mail, %lu%s files',__METHOD__,$this->send->mail_size,'b',$this->send->file_size,'b'));
$proto = $this->originate ? $this->node->optionGet(self::P_MASK) : $this->optionGet(self::P_MASK);
@ -1172,6 +1175,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
if (! $z->zmodem_sendinit($this->client,$zap) && $this->send->total_count)
$z->zmodem_sendfile($this->send);
Log::debug(sprintf('%s: - Finished sending',__METHOD__));
return ($z->zmodem_senddone()<0);
}
}

View File

@ -500,29 +500,31 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
{
Log::debug(sprintf('%s: + Start',__METHOD__));
try {
$send->open();
$rc = $this->ls_zsendfile($send,$this->ls_SerialNum++,$send->total_count,$send->total_size);
while ($send->total_count && $send->open()) {
try {
$rc = $this->ls_zsendfile($send,$this->ls_SerialNum++,$send->total_count,$send->total_size);
switch ($rc) {
case self::LSZ_OK:
$send->close(TRUE);
switch ($rc) {
case self::LSZ_OK:
$send->close(TRUE);
break;
case self::ZSKIP:
case self::ZFERR:
$send->close(FALSE);
break;
case self::ZSKIP:
case self::ZFERR:
$send->close(FALSE);
break;
default:
$send->close(FALSE);
$this->ls_zabort();
break;
default:
$send->close(FALSE);
$this->ls_zabort();
break;
}
return $rc;
} catch (\Exception $e) {
Log::error(sprintf('%s: ! Error [%s]',__METHOD__,$e->getMessage()));
}
return $rc;
} catch (\Exception $e) {
Log::error(sprintf('%s: ! Error [%s]',__METHOD__,$e->getMessage()));
}
return self::OK;

View File

@ -10,16 +10,18 @@ use Illuminate\Support\Str;
* Class SocketClient
*
* @package App\Classes\Sock
* @property int speed
* @property int cps
* @property int speed
*/
final class SocketClient {
// For deep debugging
private bool $DEBUG = FALSE;
private \Socket $connection;
private string $address = '';
private int $port = 0;
private string $address_local = '';
private int $port_local = 0;
private string $address_remote = '';
private int $port_remote = 0;
// Our session state
private array $session = [];
@ -52,14 +54,19 @@ final class SocketClient {
private string $rx_buf = '';
public function __construct (\Socket $connection,int $speed=self::TCP_SPEED) {
socket_getsockname($connection,$this->address,$this->port);
Log::info(sprintf('%s: + Connection from [%s] on port [%d]',__METHOD__,$this->address,$this->port));
socket_getsockname($connection,$this->address_local,$this->port_local);
socket_getpeername($connection,$this->address_remote,$this->port_remote);
Log::info(sprintf('%s: + Connection from [%s] on port [%d]',__METHOD__,$this->address_remote,$this->port_remote));
$this->connection = $connection;
}
public function __get($key) {
switch ($key) {
case 'address_remote':
case 'port_remote':
return $this->{$key};
case 'cps':
case 'speed':
return Arr::get($this->session,$key);
@ -244,6 +251,7 @@ final class SocketClient {
*
* @return string
* @todo change to __get()
* @deprecated
*/
public function getAddress(): string
{
@ -255,6 +263,7 @@ final class SocketClient {
*
* @return int
* @todo change to __get()
* @deprecated
*/
public function getPort(): int
{

View File

@ -51,6 +51,6 @@ class EMSISend extends Command
$o = new EMSI(Setup::findOrFail(config('app.id')));
$o->session(EMSI::SESSION_AUTO,$client,$no);
Log::info(sprintf('Connection ended: %s',$client->getAddress()),['m'=>__METHOD__]);
Log::info(sprintf('Connection ended: %s',$client->address_remote),['m'=>__METHOD__]);
}
}

View File

@ -53,7 +53,8 @@ class Address extends Model
return $this->hasMany(self::class,'hub_id','id');
case 'node':
return NULL;
// Nodes dont have children, but must return a relationship instance
return $this->hasOne(self::class,NULL,'void');
default:
throw new Exception('Unknown role: '.$this->role);
@ -151,18 +152,24 @@ class Address extends Model
/**
* Get netmail for this node (including it's children)
*
* @return Packet|null
*/
public function getNetmail(): Packet
public function getNetmail(): ?Packet
{
$o = new Packet($this);
if (($x=Netmail::whereIn('tftn_id',$this->children->pluck('id')->push($this->id)))->count()) {
$o = new Packet($this);
foreach (Netmail::whereIn('tftn_id',$this->children->pluck('id')->push($this->id))->get() as $oo) {
$o->addNetmail($oo->packet());
foreach ($x->get() as $oo) {
$o->addNetmail($oo->packet());
// @todo We need to mark the netmail as sent
// @todo We need to mark the netmail as sent
}
return $o;
}
return $o;
return NULL;
}
/**

View File

@ -21,7 +21,8 @@ class Netmail extends Model
{
return $this
->setConnection('pgsql')
->belongsTo(Address::class);
->belongsTo(Address::class)
->withTrashed();
}
public function tftn()
@ -52,6 +53,8 @@ class Netmail extends Model
{
$o = new Message;
// @todo Dont bundle mail to nodes that have been disabled, or addresses that have been deleted
$o->header = [
'onode' => $this->fftn->node_id,
'dnode' => $this->tftn->node_id,
@ -88,6 +91,9 @@ class Netmail extends Model
// @todo Point handling FMPT/TOPT
$o->intl = sprintf('%s %s',$this->tftn->ftn3d,$this->fftn->ftn3d);
// TZUTC
$o->kludge->put('tzutc',str_replace('+','',$this->created_at->getOffsetString('')));
return $o;
}
}

View File

@ -42,9 +42,21 @@ class Setup extends Model
public const PRODUCT_VERSION_MAJ = 0;
public const PRODUCT_VERSION_MIN = 0;
public const hexdigitslower = '0123456789abcdef';
// Our non model attributes and values
private array $internal = [];
public static function product_id(int $c=self::PRODUCT_ID): string
{
$x = substr(static::hexdigitslower,($c&0xf000)>>12,1);
$x .= substr(static::hexdigitslower,($c&0x0f00)>>8,1);
$x .= substr(static::hexdigitslower,($c&0x00f0)>>4,1);
$x .= substr(static::hexdigitslower,($c&0x000f),1);
return $x;
}
/* RELATIONS */
public function system()