Complete rework of packet parsing and packet generation

This commit is contained in:
Deon George 2024-05-17 22:10:54 +10:00
parent 1650d07d5c
commit 29710c37c2
30 changed files with 1394 additions and 1403 deletions

View File

@ -11,7 +11,7 @@ abstract class FTN
public function __get(string $key)
{
switch ($key) {
case 'fftn':
case 'fftn_t':
return sprintf('%d:%d/%d.%d',
$this->fz,
$this->fn,
@ -19,7 +19,7 @@ abstract class FTN
$this->fp,
).($this->zone ? sprintf('@%s',$this->zone->domain->name) : '');
case 'tftn':
case 'tftn_t':
return sprintf('%d:%d/%d.%d',
$this->tz,
$this->tn,
@ -27,10 +27,10 @@ abstract class FTN
$this->tp,
).($this->zone ? sprintf('@%s',$this->zone->domain->name) : '');
case 'fftn_o':
return Address::findFTN($this->fftn);
case 'tftn_o':
return Address::findFTN($this->tftn);
case 'fftn':
return Address::findFTN($this->fftn_t);
case 'tftn':
return Address::findFTN($this->tftn_t);
default:
throw new \Exception('Unknown key: '.$key);

File diff suppressed because it is too large Load Diff

View File

@ -10,23 +10,30 @@ use Illuminate\Support\Facades\Notification;
use Symfony\Component\HttpFoundation\File\File;
use App\Classes\FTN as FTNBase;
use App\Models\{Address,Domain,Software,System,Zone};
use App\Exceptions\InvalidPacketException;
use App\Models\{Address,Domain,Echomail,Netmail,Software,Zone};
use App\Notifications\Netmails\EchomailBadAddress;
/**
* Represents a Fidonet Packet, that contains an array of messages.
*
* Thus this object is iterable as an array of Message::class.
* Thus this object is iterable as an array of Echomail::class or Netmail::class.
*/
class Packet extends FTNBase implements \Iterator, \Countable
abstract class Packet extends FTNBase implements \Iterator, \Countable
{
private const LOGKEY = 'PKT';
protected const PACKED_MSG_LEAD = "\02\00";
protected const PACKED_END = "\00\00";
// @todo Rename this regex to something more descriptive, ie: FILENAME_REGEX
public const regex = '([[:xdigit:]]{4})(?:-(\d{4,10}))?-(.+)';
/**
* Packet types we support, in specific order for auto-detection to work
*
* @var string[]
*/
public const PACKET_TYPES = [
'2.2' => FTNBase\Packet\FSC45::class,
'2+' => FTNBase\Packet\FSC48::class,
@ -35,115 +42,15 @@ class Packet extends FTNBase implements \Iterator, \Countable
];
protected array $header; // Packet Header
protected ?string $name; // Packet name
protected ?string $name = NULL; // Packet name
public File $file; // Packet filename
public Collection $messages; // Messages in the Packet
protected Address $fftn_p; // Address the packet is from (when packing messages)
protected Address $tftn_p; // Address the packet is to (when packing messages)
protected Collection $messages; // Messages in the Packet
public Collection $errors; // Messages that fail validation
protected int $index; // Our array index
/**
* @param string|null $header
* @throws \Exception
*/
public function __construct(string $header=NULL)
{
$this->messages = collect();
$this->errors = collect();
$this->domain = NULL;
$this->name = NULL;
if ($header)
$this->header = unpack(self::unpackheader(static::HEADER),$header);
}
/**
* @throws \Exception
*/
public function __get($key)
{
switch ($key) {
// From Addresses
case 'fz': return Arr::get($this->header,'ozone');
case 'fn': return Arr::get($this->header,'onet');
case 'ff': return Arr::get($this->header,'onode');
case 'fp': return Arr::get($this->header,'opoint');
case 'fd': return rtrim(Arr::get($this->header,'odomain',"\x00"));
// To Addresses
case 'tz': return Arr::get($this->header,'dzone');
case 'tn': return Arr::get($this->header,'dnet');
case 'tf': return Arr::get($this->header,'dnode');
case 'tp': return Arr::get($this->header,'dpoint');
case 'td': return rtrim(Arr::get($this->header,'ddomain',"\x00"));
case 'date':
return Carbon::create(
Arr::get($this->header,'y'),
Arr::get($this->header,'m')+1,
Arr::get($this->header,'d'),
Arr::get($this->header,'H'),
Arr::get($this->header,'M'),
Arr::get($this->header,'S')
);
case 'password':
return rtrim(Arr::get($this->header,$key),"\x00");
case 'fftn':
case 'fftn_o':
case 'tftn':
case 'tftn_o':
return parent::__get($key);
case 'software':
$code = Arr::get($this->header,'prodcode-hi')<<8|Arr::get($this->header,'prodcode-lo');
Software::unguard();
$o = Software::singleOrNew(['code'=>$code,'type'=>Software::SOFTWARE_TOSSER]);
Software::reguard();
return $o;
case 'software_ver':
return sprintf('%d.%d',Arr::get($this->header,'prodrev-maj'),Arr::get($this->header,'prodrev-min'));
case 'capability':
// This needs to be defined in child classes, since not all children have it
return NULL;
// Packet Type
case 'type':
return static::TYPE;
// Packet name:
case 'name':
return $this->{$key} ?: sprintf('%08x',timew());
default:
throw new \Exception('Unknown key: '.$key);
}
}
/**
* Return the packet
*
* @return string
* @throws \Exception
*/
public function __toString(): string
{
$return = $this->header();
foreach ($this->messages as $o) {
if ($o->packed)
$return .= self::PACKED_MSG_LEAD.$o;
}
$return .= "\00\00";
return $return;
}
/* STATIC */
/**
@ -164,10 +71,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
* @param string $header
* @return bool
*/
public static function is_type(string $header): bool
{
return FALSE;
}
abstract public static function is_type(string $header): bool;
/**
* Process a packet file
@ -224,7 +128,9 @@ class Packet extends FTNBase implements \Iterator, \Countable
// No message attached
} else
throw new InvalidPacketException('Not a valid packet, not EOP or SOM'.bin2hex($x));
throw new InvalidPacketException('Not a valid packet, not EOP or SOM:'.bin2hex($x));
Log::info(sprintf('%s:- Packet [%s] is a [%s] packet, dated [%s]',self::LOGKEY,$o->name,get_class($o),$o->date));
// Work out the packet zone
if ($o->fz && ($o->fd || $domain)) {
@ -233,16 +139,10 @@ class Packet extends FTNBase implements \Iterator, \Countable
->where('zone_id',$o->fz)
->where('name',$o->fd ?: $domain->name)
->single();
// We need not knowing the domain, we use the default zone
} else {
$o->zone = Zone::where('zone_id',$o->fz)
->where('default',TRUE)
->single();
}
// If zone is not set, then we need to use a default zone - the messages may not be from this zone.
if (! $o->zone) {
if (empty($o->zone)) {
Log::alert(sprintf('%s:! We couldnt work out the packet zone, so we have fallen back to the default for [%d]',self::LOGKEY,$o->fz));
$o->zone = Zone::where('zone_id',$o->fz)
@ -252,7 +152,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
$message = ''; // Current message we are building
$msgbuf = '';
$leader = Message::HEADER_LEN+strlen(self::PACKED_MSG_LEAD);
$leader = Message::header_len()+strlen(self::PACKED_MSG_LEAD);
// We loop through reading from the buffer, to find our end of message tag
while ((! feof($f) && ($readbuf=fread($f,$leader)))) {
@ -269,7 +169,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
$msgbuf = substr($msgbuf,$end+3);
continue;
// If we have more to read
// If we have more to read
} elseif ($read_ptr < $size) {
continue;
}
@ -285,18 +185,103 @@ class Packet extends FTNBase implements \Iterator, \Countable
}
/**
* Location of the version
*
* @return int
* @param string|null $header
* @throws \Exception
*/
public static function version_offset(): int
public function __construct(string $header=NULL)
{
return Arr::get(collect(static::HEADER)->get('type'),0);
$this->messages = collect();
$this->errors = collect();
if ($header)
$this->header = unpack(self::unpackheader(static::HEADER),$header);
}
public static function version_offset_len(): int
/**
* @throws \Exception
*/
public function __get($key)
{
return Arr::get(collect(static::HEADER)->get('type'),2);
Log::debug(sprintf('%s:/ Requesting key for Packet::class [%s]',self::LOGKEY,$key));
switch ($key) {
// From Addresses
case 'fz': return Arr::get($this->header,'ozone');
case 'fn': return Arr::get($this->header,'onet');
case 'ff': return Arr::get($this->header,'onode');
case 'fp': return Arr::get($this->header,'opoint');
case 'fd': return rtrim(Arr::get($this->header,'odomain',"\x00"));
// To Addresses
case 'tz': return Arr::get($this->header,'dzone');
case 'tn': return Arr::get($this->header,'dnet');
case 'tf': return Arr::get($this->header,'dnode');
case 'tp': return Arr::get($this->header,'dpoint');
case 'td': return rtrim(Arr::get($this->header,'ddomain',"\x00"));
case 'date':
return Carbon::create(
Arr::get($this->header,'y'),
Arr::get($this->header,'m')+1,
Arr::get($this->header,'d'),
Arr::get($this->header,'H'),
Arr::get($this->header,'M'),
Arr::get($this->header,'S')
);
case 'password':
return rtrim(Arr::get($this->header,$key),"\x00");
case 'fftn_t':
case 'fftn':
case 'tftn_t':
case 'tftn':
return parent::__get($key);
case 'software':
$code = Arr::get($this->header,'prodcode-hi')<<8|Arr::get($this->header,'prodcode-lo');
Software::unguard();
$o = Software::singleOrNew(['code'=>$code,'type'=>Software::SOFTWARE_TOSSER]);
Software::reguard();
return $o;
case 'software_ver':
return sprintf('%d.%d',Arr::get($this->header,'prodrev-maj'),Arr::get($this->header,'prodrev-min'));
case 'capability':
// This needs to be defined in child classes, since not all children have it
return NULL;
// Packet Type
case 'type':
return static::TYPE;
// Packet name:
case 'name':
return $this->{$key} ?: sprintf('%08x',timew());
default:
throw new \Exception('Unknown key: '.$key);
}
}
/**
* Return the packet
*
* @return string
* @throws \Exception
*/
public function __toString(): string
{
$return = $this->header();
foreach ($this->messages as $o)
$return .= self::PACKED_MSG_LEAD.$o->packet($this->tftn_p);
$return .= "\00\00";
return $return;
}
/* INTERFACE */
@ -309,7 +294,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
return $this->messages->count();
}
public function current(): Message
public function current(): Echomail|Netmail
{
return $this->messages->get($this->index);
}
@ -342,6 +327,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
* @param Address $oo
* @param Address $o
* @param string|null $passwd Override the password used in the packet
* @deprecated Use Packet::generate(), which should generate a packet of the right type
*/
public function addressHeader(Address $oo,Address $o,string $passwd=NULL): void
{
@ -375,12 +361,38 @@ class Packet extends FTNBase implements \Iterator, \Countable
* Add a message to this packet
*
* @param Message $o
* @deprecated No longer used when Address::class is updated
*/
public function addMail(Message $o): void
{
$this->messages->push($o);
}
public function for(Address $ao): self
{
$this->tftn_p = $ao;
$this->fftn_p = our_address($ao);
return $this;
}
/**
* Generate a packet
*
* @param Collection $msgs
* @return string
* @throws InvalidPacketException
*/
public function generate(Collection $msgs): string
{
$this->messages = $msgs;
if (empty($this->tftn_p) || empty($this->fftn_p))
throw new InvalidPacketException('Cannot generate a packet without a destination address');
return (string)$this;
}
/**
* Parse a message in a mail packet
*
@ -393,7 +405,8 @@ class Packet extends FTNBase implements \Iterator, \Countable
$msg = Message::parseMessage($message,$this->zone);
// If the message from domain is different to the packet address domain, we'll skip this message
// @todo If the message from domain (eg: $msg->fftn->zone->domain) is different to the packet address domain ($pkt->fftn->zone->domain), we'll skip this message
Log::debug(sprintf('%s:^ Message [%s] - Packet from domain [%d], Message domain [%d]',self::LOGKEY,$msg->msgid,$this->fftn->zone->domain_id,$msg->fftn->zone->domain_id));
// If the message is invalid, we'll ignore it
if ($msg->errors) {
@ -401,14 +414,17 @@ class Packet extends FTNBase implements \Iterator, \Countable
// If the messages is not for the right zone, we'll ignore it
if ($msg->errors->messages()->has('invalid-zone')) {
Log::alert(sprintf('%s:! Message is from an invalid zone [%s], packet is from [%s] - ignoring it',self::LOGKEY,$msg->fftn,$msg->zone->domain->name));
Log::alert(sprintf('%s:! Message [%s] is from an invalid zone [%s], packet is from [%s] - ignoring it',self::LOGKEY,$msg->msgid,$msg->fftn->zone->zone_id,$this->fftn->zone->zone_id));
if (! $msg->rescanned->count())
Notification::route('netmail',$this->fftn_o)->notify(new EchomailBadAddress($msg));
Notification::route('netmail',$this->fftn)->notify(new EchomailBadAddress($msg));
return;
}
// @todo If the $msg->fftn doesnt exist, we'll need to create it
// @todo If the $msg->tftn doesnt exist (netmail), we'll need to create it (ergo intransit)
/*
// If the to address doenst exist, we'll create a new entry
if ($msg->errors->messages()->has('to') && $msg->tzone) {
try {
@ -470,10 +486,13 @@ class Packet extends FTNBase implements \Iterator, \Countable
Log::alert(sprintf('%s:- From FTN is not defined, creating new entry for [%s] (%d)',self::LOGKEY,$msg->fboss,$ao->id));
}
*/
if ($msg->errors->messages()->has('user_from') || $msg->errors->messages()->has('user_to')) {
// If the from/to user is missing
if ($msg->errors->messages()->has('from') || $msg->errors->messages()->has('to')) {
Log::error(sprintf('%s:! Skipping message [%s] due to errors (%s)...',self::LOGKEY,$msg->msgid,join(',',$msg->errors->messages()->keys())));
$this->errors->push($msg);
return;
}
}
@ -481,8 +500,10 @@ class Packet extends FTNBase implements \Iterator, \Countable
$this->messages->push($msg);
}
/** @deprecated Is this used? */
public function pluck(string $key): Collection
{
throw new \Exception(sprintf('%s:! This function is deprecated - [%s]',self::LOGKEY,$key));
return $this->messages->pluck($key);
}
}

View File

@ -59,37 +59,40 @@ final class FSC48 extends Packet
/**
* Create our message packet header
* // @todo add the ability to overwrite the password
*/
protected function header(): string
{
$oldest = $this->messages->sortBy(function($item) { return $item->datetime; })->last();
try {
return pack(collect(self::HEADER)->pluck(1)->join(''),
$this->ff, // Orig Node
$this->tf, // Dest Node
Arr::get($this->header,'y'), // Year
Arr::get($this->header,'m'), // Month
Arr::get($this->header,'d'), // Day
Arr::get($this->header,'H'), // Hour
Arr::get($this->header,'M'), // Minute
Arr::get($this->header,'S'), // Second
0, // Baud
2, // Packet Version (should be 2)
$this->fp ? 0xffff : $this->fn, // Orig Net (0xFFFF when OrigPoint != 0)
$this->tn, // Dest Net
(Setup::PRODUCT_ID & 0xff), // Product Code Lo
Setup::PRODUCT_VERSION_MAJ, // Product Version Major
$this->password, // Packet Password
$this->fz, // Orig Zone
$this->tz, // Dest Zone
$this->fp ? $this->fn : 0x00, // Aux Net
Arr::get($this->header,'capvalid',1<<0), // fsc-0039.004 (copy of 0x2c)
((Setup::PRODUCT_ID >> 8) & 0xff), // Product Code Hi
Setup::PRODUCT_VERSION_MIN, // Product Version Minor
Arr::get($this->header,'capword',1<<0), // Capability Word
$this->fz, // Orig Zone
$this->tz, // Dest Zone
$this->fp, // Orig Point
$this->tp, // Dest Point
$this->fftn_p->node_id, // Orig Node
$this->tftn_p->node_id, // Dest Node
$oldest->datetime->format('Y'), // Year
$oldest->datetime->format('m')-1, // Month
$oldest->datetime->format('d'), // Day
$oldest->datetime->format('H'), // Hour
$oldest->datetime->format('i'), // Minute
$oldest->datetime->format('s'), // Second
0, // Baud
2, // Packet Version (should be 2)
$this->fftn_p->point_id ? 0xffff : $this->fftn_p->node_id, // Orig Net (0xFFFF when OrigPoint != 0)
$this->tftn_p->host_id, // Dest Net
(Setup::PRODUCT_ID & 0xff), // Product Code Lo
Setup::PRODUCT_VERSION_MAJ, // Product Version Major
$this->tftn_p->session('pktpass'), // Packet Password
$this->fftn_p->zone->zone_id, // Orig Zone
$this->tftn_p->zone->zone_id, // Dest Zone
$this->fftn_p->point_id ? $this->fftn_p->node_id : 0x00, // Aux Net
1<<0, // fsc-0039.004 (copy of 0x2c)
((Setup::PRODUCT_ID >> 8) & 0xff), // Product Code Hi
Setup::PRODUCT_VERSION_MIN, // Product Version Minor
1<<0, // Capability Word
$this->fftn_p->zone->zone_id, // Orig Zone
$this->tftn_p->zone->zone_id, // Dest Zone
$this->fftn_p->point_id, // Orig Point
$this->tftn_p->point_id, // Dest Point
strtoupper(hexstr(Setup::PRODUCT_ID)), // ProdData
);

View File

@ -2,7 +2,7 @@
namespace App\Classes\FTN;
use App\Models\Echoarea;
use App\Models\{Echoarea,Echomail,Netmail};
/**
* Abstract class to hold the common functions for automatic responding to echomail/netmail messages
@ -19,8 +19,8 @@ abstract class Process
/**
* Return TRUE if the process class handled the message.
*
* @param Message $msg
* @param Echomail|Netmail $mo
* @return bool
*/
abstract public static function handle(Message $msg): bool;
abstract public static function handle(Echomail|Netmail $mo): bool;
}

View File

@ -5,7 +5,8 @@ namespace App\Classes\FTN\Process\Echomail;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\{Message,Process};
use App\Classes\FTN\Process;
use App\Models\{Echomail,Netmail};
use App\Notifications\Echomails\Test as TestNotification;
/**
@ -19,16 +20,16 @@ final class Test extends Process
private const testing = ['test','testing'];
public static function handle(Message $msg): bool
public static function handle(Echomail|Netmail $mo): bool
{
if (! self::canProcess($msg->echoarea)
|| (strtolower($msg->user_to) !== 'all')
|| (! in_array(strtolower($msg->subject),self::testing)))
if (! self::canProcess($mo->echoarea)
|| (strtolower($mo->to) !== 'all')
|| (! in_array(strtolower($mo->subject),self::testing)))
return FALSE;
Log::info(sprintf('%s:- Processing TEST message from (%s) [%s] in [%s]',self::LOGKEY,$msg->user_from,$msg->fftn,$msg->echoarea));
Notification::route('echomail',$msg->echoarea)->notify(new TestNotification($msg));
Notification::route('echomail',$mo->echoarea)->notify(new TestNotification($msg));
return TRUE;
}

View File

@ -11,6 +11,8 @@ use App\Classes\Sock\SocketClient;
use App\Classes\Sock\SocketException;
use App\Models\{Address,Mailer,Setup,System,SystemLog};
// @todo after receiving a mail packet/file, dont acknowledge it until we can validate that we can read it properly.
abstract class Protocol
{
// Enable extra debugging

View File

@ -50,8 +50,8 @@ class PacketAddress extends Command
exit(1);
}
$o = $o->where('id',$this->argument('dbid'))->get();
echo hex_dump($ao->system->packet($ao)->generate($o->where('id',$this->argument('dbid'))->get()));
dd(hex_dump($ao->getPacket($o)));
return Command::SUCCESS;
}
}

View File

@ -7,7 +7,7 @@ use Illuminate\Support\Facades\Storage;
use App\Classes\File;
use App\Classes\FTN\Packet;
use App\Models\Address;
use App\Models\{Address,Echomail};
class PacketInfo extends Command
{
@ -31,7 +31,7 @@ class PacketInfo extends Command
* Execute the console command.
*
* @return mixed
* @throws \App\Classes\FTN\InvalidPacketException
* @throws \App\Exceptions\InvalidPacketException
*/
public function handle()
{
@ -55,27 +55,38 @@ class PacketInfo extends Command
$this->alert(sprintf('File Name: %s',$x));
$this->info(sprintf('Packet Type : %s (%s)',$pkt->type,get_class($pkt)));
$this->info(sprintf('From : %s to %s',$pkt->fftn,$pkt->tftn));
$this->info(sprintf('Dated : %s (%s)',$pkt->date,$pkt->date->timestamp));
$this->info(sprintf('Password : %s (%s)',$pkt->password,$pkt->password ? 'SET' : 'NOT set'));
$this->info(sprintf('Messages : %d',$pkt->messages->count()));
$this->info(sprintf('From : %s to %s',$pkt->fftn->ftn,$pkt->tftn->ftn));
$this->info(sprintf('Dated : %s (%s) [%s]',$pkt->date,$pkt->date->timestamp,$pkt->date->tz->toOffsetName()));
$this->info(sprintf('Password : %s (%s)',$pkt->password ?: '-',$pkt->password ? 'SET' : 'NOT set'));
$this->info(sprintf('Messages : %d',$pkt->count()));
$this->info(sprintf('Tosser : %d (%s) version %s',$pkt->software->code,$pkt->software->name,$pkt->software_ver));
$this->info(sprintf('Capabilities: %x',$pkt->capability));
$this->info(sprintf('Has Errors : %s',$pkt->errors->count() ? 'YES' : 'No'));
$this->info(sprintf('Messages : %d',$pkt->count()));
foreach ($pkt as $msg) {
try {
$this->warn(sprintf('- Date : %s',$msg->date));
$this->warn(sprintf(' - Flags : %s',$msg->flags()->filter()->keys()->join(', ')));
$this->warn(sprintf(' - From : %s (%s)',$msg->user_from,$msg->fftn));
$this->warn(sprintf(' - To : %s (%s)',$msg->user_to,$msg->tftn));
$this->warn(sprintf(' - Subject: %s',$msg->subject));
$this->warn(sprintf(' - Area : %s',$msg->echoarea));
echo "\n";
if ($msg->errors)
try {
$this->warn(sprintf('- Date : %s (%s)',$msg->datetime,$msg->datetime->tz->toOffsetName()));
$this->warn(sprintf(' - Errors : %s',$msg->errors?->errors()->count() ? 'YES' : 'No'));
$this->warn(sprintf(' - Flags : %s',$msg->flags()->keys()->join(', ')));
$this->warn(sprintf(' - Cost : %d',$msg->cost));
$this->warn(sprintf(' - From : %s (%s)',$msg->from,$msg->fftn->ftn));
if ($msg instanceof Echomail)
$this->warn(sprintf(' - To : %s',$msg->to));
else
$this->warn(sprintf(' - To : %s (%s)',$msg->to,$msg->tftn->ftn));
$this->warn(sprintf(' - Subject: %s',$msg->subject));
if ($msg instanceof Echomail)
$this->warn(sprintf(' - Area : %s',$msg->echoarea->name));
if ($msg->errors) {
echo "\n";
$this->error("Errors:");
foreach ($msg->errors->errors()->all() as $error)
$this->line(' - '.$error);
$this->error(' - '.$error);
}
} catch (\Exception $e) {
$this->error('! ERROR: '.$e->getMessage());

View File

@ -2,13 +2,12 @@
namespace App\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use App\Classes\File;
use App\Classes\FTN\Packet;
use App\Jobs\MessageProcess as Job;
use App\Jobs\PacketProcess as Job;
use App\Models\Address;
class PacketProcess extends Command
@ -35,7 +34,7 @@ class PacketProcess extends Command
* Execute the console command.
*
* @return void
* @throws \App\Classes\FTN\InvalidPacketException
* @throws \App\Exceptions\InvalidPacketException
*/
public function handle()
{
@ -56,17 +55,8 @@ class PacketProcess extends Command
exit(1);
}
foreach ($f as $packet) {
foreach ($pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$a?->zone->domain) as $msg) {
// @todo Quick check that the packet should be processed by us.
$this->info(sprintf('Processing message from [%s] with msgid [%s] in (%s)',$msg->fboss,$msg->msgid,$f->pktName()));
$x = Job::dispatchSync($rel_name,$a->zone->domain,$this->option('dontqueue'));
// Dispatch job.
if ($this->option('dontqueue'))
Job::dispatchSync($msg,$f->pktName(),$a,$pkt->fftn_o,Carbon::now(),$this->option('nobot'));
else
Job::dispatch($msg,$f->pktName(),$a,$pkt->fftn_o,Carbon::now(),$this->option('nobot'));
}
}
dd(['job completed'=>$x]);
}
}

View File

@ -27,7 +27,7 @@ class PacketSystem extends Command
* Execute the console command.
*
* @return mixed
* @throws \App\Classes\FTN\InvalidPacketException
* @throws \App\Exceptions\InvalidPacketException
*/
public function handle()
{

View File

@ -1,6 +1,6 @@
<?php
namespace App\Classes\FTN;
namespace App\Exceptions;
use Exception;

View File

@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use Exception;
class NoReadSecurityException extends Exception
{
}

View File

@ -13,7 +13,7 @@ use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use App\Classes\FTN\Message;
use App\Models\{Address,Echoarea,Echomail,Netmail,User};
use App\Models\{Echoarea,Echomail,Netmail,User};
use App\Notifications\Netmails\{EchoareaNotExist,EchoareaNotSubscribed,EchoareaNoWrite,NetmailForward,Reject};
use App\Traits\ParseAddresses;
@ -23,20 +23,19 @@ class MessageProcess implements ShouldQueue
use Dispatchable,InteractsWithQueue,Queueable,SerializesModels,ParseAddresses;
private Address $sender;
private Message $msg;
private Address $pktsrc;
private Carbon $recvtime;
private Echomail|Netmail|string $mo;
private bool $skipbot;
private string $packet;
public function __construct(Message $msg,string $packet,Address $sender,Address $pktsrc,Carbon $recvtime,bool $skipbot=FALSE)
/**
* Process a message from a packet
*
* @param Echomail|Netmail $mo The message object
* @param bool $skipbot Dont trigger bot actions
*/
public function __construct(Echomail|Netmail $mo,bool $skipbot=FALSE)
{
$this->msg = $msg;
$this->packet = $packet;
$this->sender = $sender;
$this->pktsrc = $pktsrc;
$this->recvtime = $recvtime;
// @todo We need to serialize this model here, because laravel has an error unserializing it (Model Not Found)
$this->mo = serialize($mo);
$this->skipbot = $skipbot;
}
@ -44,7 +43,7 @@ class MessageProcess implements ShouldQueue
{
switch ($key) {
case 'subject':
return sprintf('%s-%s-%s',$this->packet,$this->sender->ftn,$this->msg->msgid);
return sprintf('%s-%s-%s',$this->pktname,$this->mo->set->get('set_sender')->ftn,$this->mo->msgid);
default:
return NULL;
@ -52,21 +51,25 @@ class MessageProcess implements ShouldQueue
}
/**
* When calling MessageProcess - we assume that the packet is from a valid source, and
* the destination (netmail/echomail) is also valid
* At this point, we know that the packet is from a system we know about, and the packet is to us:
* + From a system that is configured with us, and the password has been validated
* + From a system that is not configured with us, and it may have netmails for us
*/
public function handle()
{
$this->mo = unserialize($this->mo);
// Load our details
$ftns = our_address();
// If we are a netmail
if ($this->msg->isNetmail()) {
if ($this->mo instanceof Netmail) {
// @todo generate exception when netmail to system that doesnt exist (node/point) and its this host's responsibility
Log::info(sprintf('%s:- Processing Netmail [%s] to (%s) [%s] from (%s) [%s].',
self::LOGKEY,
$this->msg->msgid,
$this->msg->user_to,$this->msg->tftn,
$this->msg->user_from,$this->msg->fftn,
$this->mo->msgid,
$this->mo->to,$this->mo->tftn->ftn,
$this->mo->from,$this->mo->fftn->ftn,
));
// @todo Enable checks to reject old messages
@ -74,89 +77,51 @@ class MessageProcess implements ShouldQueue
// Check for duplicate messages
// FTS-0009.001
if ($this->msg->msgid) {
$o = Netmail::where('msgid',$this->msg->msgid)
->where('fftn_id',($x=$this->msg->fboss_o) ? $x->id : NULL)
if ($this->mo->msgid) {
Log::debug(sprintf('%s:- Checking for duplicate from host [%s].',self::LOGKEY,$this->mo->fftn->ftn));
$o = Netmail::where('msgid',$this->mo->msgid)
->where('fftn_id',$this->mo->fftn->id)
->where('datetime','>',Carbon::now()->subYears(3))
->single();
Log::debug(sprintf('%s:- Checking for duplicate from host id [%d].',self::LOGKEY,($x=$this->msg->fboss_o) ? $x->id : NULL));
if ($o) {
Log::alert(sprintf('%s:! Duplicate netmail [%s] in [%s] from (%s) [%s] to (%s) - ignorning.',
Log::alert(sprintf('%s:! Duplicate netmail #%d [%s] from (%s) [%s] to (%s) - ignoring.',
self::LOGKEY,
$this->msg->msgid,
$this->msg->echoarea,
$this->msg->user_from,$this->msg->fftn,
$this->msg->user_to,
$o->id,
$this->mo->msgid,
$this->mo->from,$this->mo->fftn->ftn,
$this->mo->to,
));
if (! $o->msg_crc)
$o->msg_crc = md5($this->msg->message);
$o->save();
return;
}
}
// @todo Enable checks to see if this is a file request or file send
$o = new Netmail;
$o->to = $this->msg->user_to;
$o->from = $this->msg->user_from;
$o->fftn_id = ($x=$this->msg->fftn_o) ? $x->id : NULL;
$o->tftn_id = ($x=$this->msg->tftn_o) ? $x->id : NULL;
$o->datetime = $this->msg->date;
$o->tzoffset = $this->msg->date->utcOffset();
$o->flags = $this->msg->flags;
$o->cost = $this->msg->cost;
$o->msgid = $this->msg->msgid;
$o->tagline = $this->msg->tagline;
$o->tearline = $this->msg->tearline;
$o->origin = $this->msg->origin;
$o->subject = $this->msg->subject;
$o->msg = $this->msg->message;
foreach ($this->msg->via as $v)
$o->msg .= sprintf("\01Via %s\r",$v);
$o->msg_src = $this->msg->message_src;
$o->msg_crc = md5($this->msg->message);
$o->set_pkt = $this->packet;
$o->set_sender = $this->sender;
$o->set_path = $this->msg->via;
$o->set_recvtime = $this->recvtime;
// Strip any local/transit flags
$o->flags &= ~(Message::FLAG_LOCAL|Message::FLAG_INTRANSIT);
$this->mo->flags &= ~(Message::FLAG_LOCAL|Message::FLAG_INTRANSIT);
// Determine if the message is to this system, or in transit
if ($ftns->search(function($item) { return $this->msg->tftn === $item->ftn; }) !== FALSE) {
// @todo Check if it is a duplicate message
// @todo Check if the message is from a system we know about
if ($ftns->contains($this->mo->tftn)) {
$processed = FALSE;
// If the message is to a bot, we'll process it
if (! $this->skipbot)
foreach (config('process.robots') as $class) {
if ($processed=$class::handle($this->msg)) {
$o->flags |= Message::FLAG_RECD;
$o->save();
if ($processed=$class::handle($this->mo)) {
$this->mo->flags |= Message::FLAG_RECD;
$this->mo->save();
Log::info(sprintf('%s:= Netmail [%s] from (%s:%s) - was processed by us internally [%d]',
self::LOGKEY,
$this->msg->msgid,
$this->msg->user_from,
$this->msg->fftn,
$o->id,
$this->mo->msgid,
$this->mo->from,
$this->mo->fftn->ftn,
$this->mo->id,
));
break;
}
}
@ -165,38 +130,40 @@ class MessageProcess implements ShouldQueue
// Check if the netmail is to a user, with netmail forwarding enabled
$uo = User::active()
->where(function($query) {
return $query->whereRaw(sprintf("LOWER(name)='%s'",strtolower($this->msg->user_to)))
->orWhereRaw(sprintf("LOWER(alias)='%s'",strtolower($this->msg->user_to)));
return $query->whereRaw(sprintf("LOWER(name)='%s'",strtolower($this->mo->to)))
->orWhereRaw(sprintf("LOWER(alias)='%s'",strtolower($this->mo->to)));
})
->whereNotNull('system_id')
->single();
if ($uo && ($ao=$uo->system->match($this->msg->tftn_o->zone)?->pop())) {
if ($uo && ($ao=$uo->system->match($this->mo->tftn->zone)?->pop())) {
$note = "+--[ FORWARDED MESSAGE ]----------------------------------+\r";
$note .= "+ This message has been forwarded to you, it was originally sent to you\r";
$note .= sprintf("+ at [%s]\r",$this->msg->tftn_o->ftn);
$note .= sprintf("+ at [%s]\r",$this->mo->tftn->ftn);
$note .= "+---------------------------------------------------------+\r\r";
$o->msg = $note.$this->msg->message;
$o->tftn_id = $ao->id;
$o->flags |= Message::FLAG_INTRANSIT;
$o->save();
$this->mo->msg = $note.$this->mo->content;
$this->mo->tftn_id = $ao->id;
$this->mo->flags |= Message::FLAG_INTRANSIT;
$this->mo->save();
$processed = TRUE;
// Dont send an advisement to an areabot
if (! in_array(strtolower($this->msg->user_from),config('fido.areabots')))
Notification::route('netmail',$this->msg->fftn_o)->notify(new NetmailForward($this->msg,$ao));
if (! in_array(strtolower($this->mo->from),config('fido.areabots')))
Notification::route('netmail',$this->mo->fftn)->notify(new NetmailForward($this->mo,$ao));
// We'll ignore messages from *fix users
} elseif (in_array(strtolower($this->msg->user_from),config('fido.areabots'))) {
$o->flags |= Message::FLAG_RECD;
$o->save();
} elseif (in_array(strtolower($this->mo->from),config('fido.areabots'))) {
$this->mo->flags |= Message::FLAG_RECD;
$this->mo->save();
Log::alert(sprintf('%s:! Ignoring Netmail [%s] to the Hub from (%s:%s) - its from a bot [%d]',
self::LOGKEY,
$this->msg->msgid,
$this->msg->user_from,
$this->msg->fftn,
$o->id,
$this->mo->msgid,
$this->mo->from,
$this->mo->fftn->ftn,
$this->mo->id,
));
$processed = TRUE;
@ -205,99 +172,87 @@ class MessageProcess implements ShouldQueue
// If not processed, no users here!
if (! $processed) {
Log::alert(sprintf('%s:! Netmail to the Hub from (%s) [%s] but no users here.',self::LOGKEY,$this->msg->user_from,$this->msg->fftn));
Log::alert(sprintf('%s:! Netmail to the Hub from (%s) [%s] but no users here.',self::LOGKEY,$this->mo->from,$this->mo->fftn->ftn));
Notification::route('netmail',$this->msg->fftn_o)->notify(new Reject($this->msg));
Notification::route('netmail',$this->mo->fftn)->notify(new Reject($this->mo));
}
// If in transit, store for collection
} else {
// @todo Check if the message is to a system we know about
// @todo In transit loop checking
// @todo In transit TRACE response
$o->flags |= Message::FLAG_INTRANSIT;
$o->save();
$this->mo->flags |= Message::FLAG_INTRANSIT;
$this->mo->save();
Log::info(sprintf('%s:= Netmail [%s] in transit to (%s:%s) from (%s:%s) [%d].',
self::LOGKEY,
$this->msg->msgid,
$this->msg->user_to,$this->msg->tftn,
$this->msg->user_from,$this->msg->fftn,
$o->id,
$this->mo->msgid,
$this->mo->to,$this->mo->tftn->ftn,
$this->mo->from,$this->mo->fftn->ftn,
$this->mo->id,
));
}
// Else we are echomail
} else {
Log::debug(sprintf('%s:- Looking for echomail area [%s] for mail from [%s]',self::LOGKEY,$this->msg->echoarea,$this->msg->fboss));
// The packet sender
$sender = $this->mo->set->get('set_sender');
if (! $this->msg->fboss_o) {
Log::error(sprintf('%s:! Cannot process message for echomail area [%s] for mail from [%s] with msgid [%s] - no boss object?',self::LOGKEY,$this->msg->echoarea,$this->msg->fboss,$this->msg->msgid));
// @todo Check that this does evaulate to true if a message has been rescanned
$rescanned = $this->mo->kludges->get('RESCANNED',FALSE);
// Echoarea doesnt exist, cant import the message
if (! $this->mo->echoarea) {
Log::alert(sprintf('%s:! Echoarea [%s] doesnt exist for zone [%d@%s]',self::LOGKEY,$this->mo->set->get('set_echoarea'),$sender->zone->zone_id,$sender->zone->domain->name));
Notification::route('netmail',$sender)->notify(new EchoareaNotExist($this->mo));
return;
}
$ea = Echoarea::where('name',strtoupper($this->msg->echoarea))
->where('domain_id',$this->msg->fboss_o->zone->domain_id)
->single();
Log::debug(sprintf('%s:- Processing echomail [%s] in [%s] from [%s].',self::LOGKEY,$this->mo->msgid,$this->mo->echoarea->name,$sender->ftn));
if (! $ea) {
Log::alert(sprintf('%s:! Echoarea [%s] doesnt exist for zone [%d-%d]',self::LOGKEY,$this->msg->echoarea,$this->msg->fboss_o->zone->domain_id,$this->msg->fboss_o->zone->zone_id));
Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNotExist($this->msg));
return;
}
Log::debug(sprintf('%s:- Processing echomail [%s] in [%s].',self::LOGKEY,$this->msg->msgid,$this->msg->echoarea));
if (! $this->pktsrc->zone->domain->zones->pluck('zone_id')->contains($this->msg->fboss_o->zone->zone_id)) {
// Message from zone is incorrect for echoarea
if (! $this->mo->echoarea->domain->zones->contains($this->mo->fftn->zone)) {
Log::alert(sprintf('%s:! The message [%s] is from a different zone [%d] than the packet sender [%d] - not importing',
self::LOGKEY,
$this->msg->msgid,
$this->msg->fboss_o->zone->zone_id,
$this->pktsrc->zone->zone_id));
$this->mo->msgid,
$this->mo->fftn->zone->zone_id,
$this->mo->fftn->zone->zone_id));
return;
}
// Check for duplicate messages
// FTS-0009.001
if ($this->msg->msgid) {
$o = Echomail::where('msgid',$this->msg->msgid)
->where('fftn_id',($x=$this->msg->fboss_o) ? $x->id : NULL)
->where('datetime','>=',$this->msg->date->subYears(3))
->where('datetime','<=',$this->msg->date)
if ($this->mo->msgid) {
$o = Echomail::where('msgid',$this->mo->msgid)
->where('fftn_id',$this->mo->fftn->id)
->where('datetime','>=',$this->mo->date->subYears(3))
->where('datetime','<=',$this->mo->date)
->single();
Log::debug(sprintf('%s:- Checking for duplicate from host id [%d].',self::LOGKEY,($x=$this->msg->fboss_o) ? $x->id : NULL));
Log::debug(sprintf('%s:- Checking for duplicate from host id [%d].',self::LOGKEY,$this->mo->fftn->id));
if ($o) {
// @todo Actually update seenby
Log::alert(sprintf('%s:! Duplicate echomail [%s] in [%s] from (%s) [%s] to (%s) - updating seenby.',
self::LOGKEY,
$this->msg->msgid,
$this->msg->echoarea,
$this->msg->user_from,$this->msg->fftn,
$this->msg->user_to,
$this->mo->msgid,
$this->mo->echoarea->name,
$this->mo->from,$this->mo->fftn->ftn,
$this->mo->to,
));
if (! $o->msg_crc)
$o->msg_crc = md5($this->msg->message);
$o->save();
//$o->save();
// @todo This duplicate message may have gone via a different path, be nice to record it.
/*
// If we didnt get the path on the original message, we'll override it
if (! $o->path->count()) {
$dummy = collect();
$path = $this->parseAddresses('path',$this->msg->path,$this->pktsrc->zone,$dummy);
/*
// If our sender is not in the path, add it
if (! $path->contains($this->sender->id)) {
Log::alert(sprintf('%s:? Echomail adding sender to PATH [%s] for [%d].',self::LOGKEY,$x->ftn,$o->id));
$path->push($this->sender->id);
}
*/
$path = $this->parseAddresses('path',$this->mo->path,$sender->zone,$dummy);
$ppoid = NULL;
foreach ($path as $aoid) {
@ -310,23 +265,24 @@ class MessageProcess implements ShouldQueue
$ppoid = $po[0]->id;
}
}
*/
// @todo if we have an export for any of the seenby addresses, remove it
$seenby = $this->parseAddresses('seenby',$this->msg->seenby,$this->pktsrc->zone,$o->rogue_seenby);
$x = $o->seenby()->syncWithoutDetaching($seenby);
$seenby = $this->parseAddresses('seenby',$this->mo->seenby,$sender->zone,$o->rogue_seenby);
$this->mo->seenby()->syncWithoutDetaching($seenby);
// In case our rogue_seenby changed
if ($o->getDirty())
$o->save();
$this->mo->save();
return;
}
}
// Find another message with the same msg_crc
if ($this->msg->message) {
$o = Echomail::where('msg_crc',$xx=md5($this->msg->message))
->where('fftn_id',($x=$this->msg->fboss_o) ? $x->id : NULL)
if ($this->mo->msg_crc) {
$o = Echomail::where('msg_crc',$xx=md5($this->mo->msg_crc))
->where('fftn_id',$this->mo->fftn->id)
->where('datetime','>',Carbon::now()->subWeek())
->get();
@ -339,73 +295,38 @@ class MessageProcess implements ShouldQueue
}
// If the node is not subscribed
if ($this->pktsrc->echoareas->search(function($item) use ($ea) { return $item->id === $ea->id; }) === FALSE) {
Log::alert(sprintf('%s:! FTN [%s] is not subscribed to [%s] for [%s].',self::LOGKEY,$this->pktsrc->ftn,$ea->name,$this->msg->msgid));
if ($sender->echoareas->search(function($item) { return $item->id === $this->mo->echoarea->id; }) === FALSE) {
Log::alert(sprintf('%s:! FTN [%s] is not subscribed to [%s] for [%s].',self::LOGKEY,$sender->ftn,$this->mo->echoarea->name,$this->mo->msgid));
if (! $this->msg->rescanned->count())
Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNotSubscribed($this->msg));
if (! $rescanned)
Notification::route('netmail',$sender)->notify(new EchoareaNotSubscribed($this->mo));
}
// Can the system send messages to this area?
if (! $ea->can_write($this->pktsrc->security)) {
Log::alert(sprintf('%s:! FTN [%s] is not allowed to post [%s] to [%s].',self::LOGKEY,$this->pktsrc->ftn,$this->msg->msgid,$ea->name));
if (! $this->msg->rescanned->count())
Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNoWrite($this->msg));
if (! $this->mo->echoarea->can_write($sender->security)) {
Log::alert(sprintf('%s:! FTN [%s] is not allowed to post [%s] to [%s].',self::LOGKEY,$sender->ftn,$this->mo->msgid,$this->mo->echoarea->name));
if (! $rescanned)
Notification::route('netmail',$sender)->notify(new EchoareaNoWrite($this->mo));
return;
}
// We know about this area, store it
$o = new Echomail;
$o->init();
$o->to = $this->msg->user_to;
$o->from = $this->msg->user_from;
$o->subject = $this->msg->subject;
$o->datetime = $this->msg->date;
$o->tzoffset = $this->msg->date->utcOffset();
if ($x=$this->msg->fboss_o) {
$o->fftn_id = $x->id;
} else {
$o->fftn_id = NULL; // @todo This should be the node that originated the message - but since that node is not in the DB it would be null
}
$o->echoarea_id = $ea->id;
$o->msgid = $this->msg->msgid;
$o->replyid = $this->msg->replyid;
$o->tagline = $this->msg->tagline;
$o->tearline = $this->msg->tearline;
$o->origin = $this->msg->origin;
$o->msg = $this->msg->message;
$o->msg_src = $this->msg->message_src;
$o->msg_crc = md5($this->msg->message);
$o->set_path = $this->msg->path;
$o->set_seenby = $this->msg->seenby;
$o->set_recvtime = $this->recvtime;
$o->set_sender = $this->pktsrc->id;
// Record receiving packet and sender
$o->set_pkt = $this->packet;
$o->save();
$this->mo->save();
Log::info(sprintf('%s:= Echomail [%s] in [%s] from (%s) [%s] to (%s) - [%s].',
self::LOGKEY,
$this->msg->msgid,
$this->msg->echoarea,
$this->msg->user_from,$this->msg->fftn,
$this->msg->user_to,
$o->id,
$this->mo->msgid,
$this->mo->echoarea->name,
$this->mo->from,$this->mo->fftn->ftn,
$this->mo->to,
$this->mo->id,
));
// If the message is to a bot, but not rescanned, or purposely skipbot set, we'll process it
if ((! $this->skipbot) && (! $this->msg->rescanned->count()))
if ((! $this->skipbot) && (! $rescanned))
foreach (config('process.echomail') as $class) {
if ($class::handle($this->msg)) {
if ($class::handle($this->mo)) {
break;
}
}

View File

@ -15,9 +15,9 @@ use Illuminate\Support\Facades\Storage;
use League\Flysystem\UnableToMoveFile;
use App\Classes\File;
use App\Classes\File\Item;
use App\Classes\FTN\{InvalidPacketException,Packet};
use App\Models\Address;
use App\Classes\FTN\Packet;
use App\Exceptions\InvalidPacketException;
use App\Models\{Domain,Echomail,Netmail};
use App\Notifications\Netmails\PacketPasswordInvalid;
class PacketProcess implements ShouldQueue
@ -26,22 +26,26 @@ class PacketProcess implements ShouldQueue
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private Item $file;
private Address $ao;
private string $filename;
private Domain $do;
private Carbon $rcvd_time;
private bool $interactive;
private bool $nobot;
public function __construct(Item $file,Address $ao,Carbon $rcvd_time)
public function __construct(string $filename,Domain $do,bool $interactive=FALSE,Carbon $rcvd_time=NULL,bool $nobot=FALSE)
{
$this->file = $file;
$this->ao = $ao;
$this->rcvd_time = $rcvd_time;
$this->filename = $filename;
$this->do = $do;
$this->interactive = $interactive;
$this->rcvd_time = $rcvd_time ?: Carbon::now();
$this->nobot = $nobot;
}
public function __get($key): mixed
{
switch ($key) {
case 'subject':
return $this->file->name;
return $this->filename;
default:
return NULL;
@ -54,30 +58,47 @@ class PacketProcess implements ShouldQueue
*/
public function handle()
{
Log::info(sprintf('%s:- Processing mail %s [%s]',self::LOGKEY,$this->file->whatType() === Item::IS_PKT ? 'PACKET' : 'ARCHIVE',$this->file->nameas));
Log::info(sprintf('%s:- Processing mail [%s]',self::LOGKEY,$this->filename));
$fs = Storage::disk(config('fido.local_disk'));
// @todo Catch files that we cannot process, eg: ARJ bundles.
try {
$f = new File($this->file->full_name);
$f = new File($fs->path($this->filename));
$processed = FALSE;
foreach ($f as $packet) {
$pkt = Packet::process($packet,Arr::get(stream_get_meta_data($packet),'uri'),$f->itemSize(),$this->ao->zone->domain);
$pkt = Packet::process($packet,Arr::get(stream_get_meta_data($packet),'uri'),$f->itemSize(),$this->do);
// Check the messages are from the uplink
if ($this->ao->system->addresses->search(function($item) use ($pkt) { return $item->id === $pkt->fftn_o->id; }) === FALSE) {
Log::error(sprintf('%s:! Packet [%s] is not from this link? [%d]',self::LOGKEY,$pkt->fftn_o->ftn,$this->ao->system_id));
// Check that the packet is from a system that is defined in the DB
if (! $pkt->fftn) {
Log::error(sprintf('%s:! Packet [%s] is not from a system we know about? [%s]',self::LOGKEY,$this->filename,$pkt->fftn_t));
break;
}
// Check the packet password
if (strtoupper($this->ao->session('pktpass')) !== strtoupper($pkt->password)) {
Log::error(sprintf('%s:! Packet from [%s] with password [%s] is invalid.',self::LOGKEY,$this->ao->ftn,$pkt->password));
if (! our_nodes($this->do)->contains($pkt->fftn)) {
Log::error(sprintf('%s:! Packet [%s] is from a system that is not configured with us? [%s] for [%s]',self::LOGKEY,$this->filename,$pkt->fftn_t,$this->do->name));
Notification::route('netmail',$this->ao)->notify(new PacketPasswordInvalid($pkt->password,$this->file->nameas));
// @todo Notification::route('netmail',$pkt->fftn)->notify(new UnexpectedPacketFromYou($this->filename));
// @todo Parse the packet for netmails and process them. We'll only accept netmails to us, and ignore all others
break;
}
// Check the packet is to our address, if not we'll reject it.
if (! our_address($this->do)->contains($pkt->tftn)) {
Log::error(sprintf('%s:! Packet [%s] is not to our address? [%s]',self::LOGKEY,$this->filename,$pkt->tftn));
// @todo Notification::route('netmail',$pkt->fftn)->notify(new UnexpectedPacketToUs($this->filename));
break;
}
// Check the packet password
if (strtoupper($pkt->fftn->session('pktpass')) !== strtoupper($pkt->password)) {
Log::error(sprintf('%s:! Packet from [%s] with password [%s] is invalid.',self::LOGKEY,$pkt->fftn->ftn,$pkt->password));
Notification::route('netmail',$pkt->fftn)->notify(new PacketPasswordInvalid($pkt->password,$this->filename));
break;
}
@ -89,37 +110,34 @@ class PacketProcess implements ShouldQueue
$count = 0;
foreach ($pkt as $msg) {
Log::info(sprintf('%s:- Mail from [%s] to [%s]',self::LOGKEY,$msg->fftn,$msg->tftn));
if ($msg instanceof Netmail)
Log::info(sprintf('%s:- Netmail from [%s] to [%s]',self::LOGKEY,$msg->fftn->ftn,$msg->tftn->ftn));
elseif ($msg instanceof Echomail)
Log::info(sprintf('%s:- Echomail from [%s]',self::LOGKEY,$msg->fftn->ftn));
// @todo Quick check that the packet should be processed by us.
// @todo validate that the packet's zone is in the domain.
if ($msg->errors) {
Log::error(sprintf('%s:! Message [%s] has [%d] errors, unable to process',self::LOGKEY,$msg->msgid,$msg->errors->errors()->count()));
/*
* // @todo generate exception when echomail for an area that doesnt exist
* // @todo generate exception when echomail for an area sender cannot post to
* // @todo generate exception when echomail for an area sender not subscribed to
* // @todo generate exception when echomail comes from a system not defined here
* // @todo generate exception when echomail comes from a system doesnt exist
*
* // @todo generate exception when netmail to system that doesnt exist (node/point)
* // @todo generate exception when netmail from system that doesnt exist (node/point)
* // @todo generate warning when netmail comes from a system not defined here
*
* // @todo generate exception when packet has wrong password
*/
continue;
}
$msg->set_sender = $pkt->fftn->withoutRelations();
// Record receiving packet and sender
$msg->set_pkt = $f->pktName();
$msg->set_recvtime = $this->rcvd_time;
try {
// Dispatch job.
if ($queue)
MessageProcess::dispatch($msg,$f->pktName(),$this->ao->withoutRelations(),$pkt->fftn_o->withoutRelations(),$this->rcvd_time);
if ($queue || (! $this->interactive))
MessageProcess::dispatch($msg->withoutRelations(),$this->nobot);
else
MessageProcess::dispatchSync($msg,$f->pktName(),$this->ao->withoutRelations(),$pkt->fftn_o->withoutRelations(),$this->rcvd_time);
MessageProcess::dispatchSync($msg->withoutRelations(),$this->nobot);
$count++;
} catch (\Exception $e) {
Log::error(sprintf('%s:! Got error dispatching message [%s] (%d:%s-%s).',self::LOGKEY,$msg->msgid,$e->getLine(),$e->getFile(),$e->getMessage()));
}
$count++;
}
if ($count === $pkt->count())
@ -127,42 +145,41 @@ class PacketProcess implements ShouldQueue
}
if (! $processed) {
Log::alert(sprintf('%s:- Not deleting packet [%s], it doesnt seem to be processed?',self::LOGKEY,$this->file->nameas));
Log::alert(sprintf('%s:- Not deleting packet [%s], it doesnt seem to be processed?',self::LOGKEY,$this->filename));
} else {
// If we want to keep the packet, we could do that logic here
if (config('fido.packet_keep')) {
$dir = sprintf('%s/%s/%s/%s',config('fido.dir'),($x=Carbon::now())->format('Y'),$x->format('m'),$x->format('d'));
Log::debug(sprintf('%s:- Moving processed packet [%s] to [%s]',self::LOGKEY,$this->file->rel_name,$dir));
Log::debug(sprintf('%s:- Moving processed packet [%s] to [%s]',self::LOGKEY,$this->filename,$dir));
try {
if ($fs->makeDirectory($dir)) {
$fs->move($this->file->rel_name,$x=sprintf('%s/%s',$dir,$this->file->pref_name));
Log::info(sprintf('%s:- Moved processed packet [%s] to [%s]',self::LOGKEY,$this->file->rel_name,$x));
$fs->move($this->filename,$x=sprintf('%s/%s',$dir,$f->itemName()));
Log::info(sprintf('%s:- Moved processed packet [%s] to [%s]',self::LOGKEY,$this->filename,$x));
} else
Log::error(sprintf('%s:! Unable to create dir [%s]',self::LOGKEY,$dir));
} catch (UnableToMoveFile $e) {
Log::error(sprintf('%s:! Unable to move packet [%s] to [%s] (%s)',self::LOGKEY,$this->file->full_name,$dir,$e->getMessage()));
Log::error(sprintf('%s:! Unable to move packet [%s] to [%s] (%s)',self::LOGKEY,$this->filename,$dir,$e->getMessage()));
} catch (\Exception $e) {
Log::error(sprintf('%s:! Failed moving packet [%s] to [%s] (%s)',self::LOGKEY,$this->file->full_name,$dir,$e->getMessage()));
Log::error(sprintf('%s:! Failed moving packet [%s] to [%s] (%s)',self::LOGKEY,$this->filename,$dir,$e->getMessage()));
}
} else {
Log::debug(sprintf('%s:- Deleting processed packet [%s]',self::LOGKEY,$this->file->full_name));
Log::debug(sprintf('%s:- Deleting processed packet [%s]',self::LOGKEY,$this->filename));
// @todo Change this to use Storage::disk()
unlink($this->file->full_name);
$fs->delete($this->filename);
}
}
} catch (InvalidPacketException $e) {
Log::error(sprintf('%s:- Not deleting packet [%s], as it generated an InvalidPacketException',self::LOGKEY,$this->file->nameas),['e'=>$e->getMessage()]);
Log::error(sprintf('%s:- Not deleting packet [%s], as it generated an InvalidPacketException',self::LOGKEY,$this->filename),['e'=>$e->getMessage()]);
} catch (\Exception $e) {
Log::error(sprintf('%s:- Not deleting packet [%s], as it generated an uncaught exception',self::LOGKEY,$this->file->nameas),['e'=>$e->getMessage()]);
Log::error(sprintf('%s:- Not deleting packet [%s], as it generated an uncaught exception',self::LOGKEY,$this->filename),['e'=>$e->getMessage(),'l'=>$e->getLine(),'f'=>$e->getFile()]);
}
}
}

View File

@ -1053,6 +1053,8 @@ class Address extends Model
if ($passpos > 8)
Log::alert(sprintf('%s:! Password would be greater than 8 chars? [%d]',self::LOGKEY,$passpos));
// @todo Do the strip pass where, if we dont want the password in the netmail
$pkt = $this->getPacket($x,substr($x->last()->subject,0,$passpos));
if ($pkt && $pkt->count() && $update)
@ -1091,8 +1093,9 @@ class Address extends Model
* @param string|null $passwd Override password used in packet
* @return Packet|null
* @throws \Exception
* @deprecated
*/
public function getPacket(Collection $msgs,string $passwd=NULL): ?Packet
private function getPacket(Collection $msgs,string $passwd=NULL): ?Packet
{
$s = Setup::findOrFail(config('app.id'));
$ao = our_address($this);
@ -1104,7 +1107,7 @@ class Address extends Model
}
// Get packet type
$o = $ao->system->packet();
$o = $ao->system->packet($this);
$o->addressHeader($ao,$this,$passwd);
// $oo = Netmail/Echomail Model

View File

@ -10,54 +10,77 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Casts\{CollectionOrNull,CompressedString};
use App\Classes\FTN\Message;
use App\Interfaces\Packet;
use App\Traits\{EncodeUTF8,MsgID,ParseAddresses,QueryCacheableConfig};
use App\Traits\{MessageAttributes,MsgID,ParseAddresses,QueryCacheableConfig};
final class Echomail extends Model implements Packet
{
use SoftDeletes,EncodeUTF8,MsgID,ParseAddresses,QueryCacheableConfig;
use SoftDeletes,MessageAttributes,MsgID,ParseAddresses,QueryCacheableConfig;
private const LOGKEY = 'ME-';
private Collection $set_seenby;
private Collection $set_path;
private Carbon $set_recvtime;
private string $set_pkt;
private string $set_sender;
private bool $no_export = FALSE;
private const kludges = [
'MSGID:'=>'msgid',
'PATH:'=>'set_path',
'REPLY:'=>'replyid',
'SEEN-BY:'=>'set_seenby',
];
// When generating a packet for this echomail, the packet recipient is our tftn
public Address $tftn;
protected $casts = [
'datetime' => 'datetime:Y-m-d H:i:s',
'kludges' => CollectionOrNull::class,
'msg' => CompressedString::class,
'msg_src' => CompressedString::class,
'rogue_seenby' => CollectionOrNull::class,
'rogue_path' => CollectionOrNull::class,
];
private const cast_utf8 = [
'to',
'from',
'subject',
'msg',
'msg_src',
'origin',
'tearline',
'tagline',
'rogue_path' => CollectionOrNull::class, // @deprecated?
];
public function __set($key,$value)
{
switch ($key) {
case 'kludges':
if (! count($value))
return;
if (array_key_exists($value[0],self::kludges)) {
$this->{self::kludges[$value[0]]} = $value[1];
} else {
$this->kludges->put($value[0],$value[1]);
}
break;
case 'no_export':
case 'set_path':
case 'set_pkt':
case 'set_sender':
case 'set_recvtime':
case 'set_seenby':
$this->{$key} = $value;
break;
// Values that we pass to boot() to record how we got this echomail
case 'set_pkt':
case 'set_recvtime':
case 'set_sender':
// @todo We'll normalise these values when saving the netmail
case 'set_tagline':
case 'set_tearline':
case 'set_origin':
// For us to record the echoarea the message is for, if the area isnt defined (eg: packet dump)
case 'set_echoarea':
$this->set->put($key,$value);
break;
// The path and seenby the echomail went through to get here
case 'set_path':
case 'set_seenby':
if (! $this->set->has($key))
$this->set->put($key,collect());
$this->set->get($key)->push($value);
break;
default:
parent::__set($key,$value);
}
@ -67,6 +90,12 @@ final class Echomail extends Model implements Packet
{
parent::boot();
static::creating(function($model) {
if (! is_null($model->errors))
throw new \Exception('Cannot save, validation errors exist');
});
// @todo dont save us in the seenby/path, we'll add it dynamically when we send out.
// @todo if the message is updated with new SEEN-BY's from another route, we'll delete the pending export for systems (if there is one)
static::created(function($model) {
$rogue = collect();
@ -74,8 +103,10 @@ final class Echomail extends Model implements Packet
$path = collect();
// Parse PATH
if ($model->set_path->count())
$path = self::parseAddresses('path',$model->set_path,$model->fftn->zone,$rogue);
if ($model->set->has('set_path'))
$path = self::parseAddresses('path',$model->set->get('set_path'),$model->fftn->zone,$rogue);
Log::debug(sprintf('%s:^ Message [%d] from point address is [%d]',self::LOGKEY,$model->id,$model->fftn->point_id));
// Make sure our sender is first in the path
if (! $path->contains($model->fftn_id)) {
@ -84,9 +115,9 @@ final class Echomail extends Model implements Packet
}
// Make sure our pktsrc is last in the path
if (isset($model->set_sender) && (! $path->contains($model->set_sender))) {
Log::alert(sprintf('%s:? Echomail adding pktsrc to end of PATH [%s].',self::LOGKEY,$model->set_sender));
$path->push($model->set_sender);
if ($model->set->has('set_sender') && (! $path->contains($model->set->get('set_sender')->id))) {
Log::alert(sprintf('%s:? Echomail adding pktsrc to end of PATH [%s].',self::LOGKEY,$model->set->get('set_sender')->ftn));
$path->push($model->set->get('set_sender')->id);
}
// Save the Path
@ -105,8 +136,8 @@ final class Echomail extends Model implements Packet
// @todo move the parseAddress processing into Message::class, and our address to the seenby (and thus no need to add it when we export)
// Parse SEEN-BY
if ($model->set_seenby->count())
$seenby = self::parseAddresses('seenby',$model->set_seenby,$model->fftn->zone,$rogue);
if ($model->set->has('set_seenby'))
$seenby = self::parseAddresses('seenby',$model->set->get('set_seenby'),$model->fftn->zone,$rogue);
// Make sure our sender is in the seenby
if (! $seenby->contains($model->fftn_id)) {
@ -115,9 +146,9 @@ final class Echomail extends Model implements Packet
}
// Make sure our pktsrc is in the seenby
if (isset($model->set_sender) && (! $seenby->contains($model->set_sender))) {
Log::alert(sprintf('%s:? Echomail adding pktsrc to SEENBY [%s].',self::LOGKEY,$model->set_sender));
$seenby->push($model->set_sender);
if ($model->set->has('set_sender') && (! $seenby->contains($model->set->get('set_sender')->id))) {
Log::alert(sprintf('%s:? Echomail adding pktsrc to SEENBY [%s].',self::LOGKEY,$model->set->get('set_sender')->ftn));
$seenby->push($model->set->get('set_sender')->id);
}
if (count($rogue)) {
@ -129,26 +160,26 @@ final class Echomail extends Model implements Packet
$model->seenby()->sync($seenby);
// Our last node in the path is our sender
if (isset($model->set_pkt) && isset($model->set_recvtime)) {
if ($model->set->has('set_pkt') && $model->set->has('set_recvtime')) {
if ($path->count()) {
DB::update('UPDATE echomail_path set recv_pkt=?,recv_at=? where address_id=? and echomail_id=?',[
$model->set_pkt,
$model->set_recvtime,
$model->set->get('set_pkt'),
$model->set->get('set_recvtime'),
$path->last(),
$model->id,
]);
} else {
Log::critical(sprintf('%s:! Wasnt able to set packet details for [%d] to [%s] to [%s], no path information',self::LOGKEY,$model->id,$model->set_pkt,$model->set_recvtime));
Log::critical(sprintf('%s:! Wasnt able to set packet details for [%d] to [%s] to [%s], no path information',self::LOGKEY,$model->id,$model->set->get('set_pkt'),$model->set->get('set_recvtime')));
}
}
// See if we need to export this message.
if ($model->echoarea->sec_read) {
$exportto = ($x=$model
$exportto = $model
->echoarea
->addresses
->filter(function($item) use ($model) { return $model->echoarea->can_read($item->security); }))
->filter(function($item) use ($model) { return $model->echoarea->can_read($item->security); })
->pluck('id')
->diff($seenby);
@ -193,90 +224,19 @@ final class Echomail extends Model implements Packet
->withPivot(['id','parent_id','recv_pkt','recv_at']);
}
/* METHODS */
/* ATTRIBUTES */
public function init(): void
public function getSeenByAttribute(): Collection
{
$this->set_path = collect();
$this->set_seenby = collect();
return ((! $this->exists) && $this->set->has('set_seenby'))
? $this->set->get('set_seenby')
: $this->getRelationValue('seenby');
}
public function jsonSerialize(): array
public function getPathAttribute(): Collection
{
return $this->encode();
}
/**
* Return this model as a packet
*/
public function packet(Address $ao): Message
{
Log::info(sprintf('%s:+ Bundling [%s]',self::LOGKEY,$this->id));
$sysaddress = our_address($this->fftn);
if (! $sysaddress)
throw new \Exception(sprintf('%s:! We dont have an address in this network? (%s)',self::LOGKEY,$this->fftn->zone->domain->name));
// @todo Dont bundle mail to nodes that have been disabled, or addresses that have been deleted
$o = new Message;
$o->header = [
'onode' => $sysaddress->node_id,
'dnode' => $ao->node_id,
'onet' => $sysaddress->host_id,
'dnet' => $ao->host_id,
'flags' => 0,
'cost' => 0,
'date'=>$this->datetime->format('d M y H:i:s'),
];
$o->tzutc = $this->datetime->utcOffset($this->tzoffset)->getOffsetString('');
$o->user_to = $this->to;
$o->user_from = $this->from;
$o->subject = $this->subject;
$o->echoarea = $this->echoarea->name;
$o->flags = $this->flags;
if ($this->kludges)
$o->kludge = collect($this->kludges);
$o->kludge->put('dbid',$this->id);
$o->msgid = $this->msgid;
if ($this->replyid)
$o->replyid = $this->replyid;
$o->message = $this->msg;
if ($this->tagline)
$o->tagline = $this->tagline;
if ($this->tearline)
$o->tearline = $this->tearline;
if ($this->origin)
$o->origin = $this->origin;
$o->seenby = $this->seenby->push($sysaddress)->unique()->pluck('ftn2d');
// Add our address to the path and seenby
$o->path = $this->pathorder()->merge($sysaddress->ftn2d);
$o->packed = TRUE;
return $o;
}
public function pathorder(string $display='ftn2d',int $start=NULL): Collection
{
$result = collect();
if ($x=$this->path->firstWhere('pivot.parent_id',$start)) {
$result->push($x->$display);
$result->push($this->pathorder($display,$x->pivot->id));
}
return $result->flatten()->filter();
return ((! $this->exists) && $this->set->has('set_path'))
? $this->set->get('set_path')
: $this->getRelationValue('path');
}
}

View File

@ -10,35 +10,30 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Casts\CompressedString;
use App\Classes\FTN\Message;
use App\Casts\{CollectionOrNull,CompressedString};
use App\Interfaces\Packet;
use App\Traits\{EncodeUTF8,MsgID};
use App\Pivots\ViaPivot;
use App\Traits\{MessageAttributes,MsgID};
final class Netmail extends Model implements Packet
{
use SoftDeletes,MsgID,MessageAttributes;
private const LOGKEY = 'MN-';
private const PATH_REGEX = '/^([0-9]+:[0-9]+\/[0-9]+(\..*)?)\s+@([0-9.a-zA-Z]+)\s+(.*)$/';
use SoftDeletes,EncodeUTF8,MsgID;
private Collection $set_path;
private Address $set_sender;
private Carbon $set_recvtime;
private string $set_pkt;
private const cast_utf8 = [
'to',
'from',
'subject',
'msg',
'msg_src',
'origin',
'tearline',
'tagline',
/**
* Kludges that we absorb in this model
*/
private const kludges = [
'MSGID:'=>'msgid',
'REPLY:'=>'replyid',
'Via' => 'set_path',
];
protected $casts = [
'datetime' => 'datetime:Y-m-d H:i:s',
'kludges' => CollectionOrNull::class,
'msg' => CompressedString::class,
'msg_src' => CompressedString::class,
'sent_at' => 'datetime:Y-m-d H:i:s',
@ -47,11 +42,36 @@ final class Netmail extends Model implements Packet
public function __set($key,$value)
{
switch ($key) {
case 'set_path':
case 'kludges':
if (! count($value))
return;
if (array_key_exists($value[0],self::kludges)) {
$this->{self::kludges[$value[0]]} = $value[1];
} else {
$this->kludges->put($value[0],$value[1]);
}
break;
// Values that we pass to boot() to record how we got this netmail
case 'set_pkt':
case 'set_recvtime':
case 'set_sender':
$this->{$key} = $value;
// @todo We'll normalise these values when saving the netmail
case 'set_tagline':
case 'set_tearline':
case 'set_origin':
$this->set->put($key,$value);
break;
// The path the netmail went through to get here
case 'set_path':
if (! $this->set->has($key))
$this->set->put($key,collect());
$this->set->get($key)->push($value);
break;
default:
@ -63,43 +83,48 @@ final class Netmail extends Model implements Packet
{
parent::boot();
static::creating(function($model) {
if (! is_null($model->errors))
throw new \Exception('Cannot save, validation errors exist');
});
static::created(function($model) {
$nodes = collect();
// Parse PATH
// @todo dont save us in the path, we'll add it dynamically when we send out.
// <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone] <Program Name> <Version> [Serial Number]
if (isset($model->set_path)) {
if ($model->set_path->count()) {
foreach ($model->set_path as $line) {
$m = [];
if ($model->set->has('set_path')) {
foreach ($model->set->get('set_path') as $line) {
$m = [];
if (preg_match('/^([0-9]+:[0-9]+\/[0-9]+(\..*)?)\s+@([0-9.a-zA-Z]+)\s+(.*)$/',$line,$m)) {
// Address
$ao = Address::findFTN($m[1]);
if (preg_match(self::PATH_REGEX,$line,$m)) {
// Address
// @todo Do we need to add a domain here, since the path line may not include one
$ao = Address::findFTN($m[1]);
// Time
$t = [];
$datetime = '';
// Time
$t = [];
$datetime = '';
if (! preg_match('/^([0-9]+\.[0-9]+)(\.?(.*))?$/',$m[3],$t))
Log::alert(sprintf('%s:! Unable to determine time from [%s]',self::LOGKEY,$m[3]));
else
$datetime = Carbon::createFromFormat('Ymd.His',$t[1],$t[3] ?? '');
if (! preg_match('/^([0-9]+\.[0-9]+)(\.?(.*))?$/',$m[3],$t))
Log::alert(sprintf('%s:! Unable to determine time from [%s]',self::LOGKEY,$m[3]));
else
$datetime = Carbon::createFromFormat('Ymd.His',$t[1],$t[3] ?? '');
if (! $ao) {
Log::alert(sprintf('%s:! Undefined Node [%s] for Netmail.',self::LOGKEY,$m[1]));
//$rogue->push(['node'=>$m[1],'datetime'=>$datetime,'program'=>$m[4]]);
if (! $ao) {
Log::alert(sprintf('%s:! Undefined Node [%s] in netmail path.',self::LOGKEY,$m[1]));
//$rogue->push(['node'=>$m[1],'datetime'=>$datetime,'program'=>$m[4]]);
} else {
$nodes->push(['node'=>$ao,'datetime'=>$datetime,'program'=>$m[4]]);
}
} else {
$nodes->push(['node'=>$ao,'datetime'=>$datetime,'program'=>$m[4]]);
}
}
// If there are no details (Mystic), we'll create a blank
} else {
$nodes->push(['node'=>$model->set_sender,'datetime'=>Carbon::now(),'program'=>'Unknown']);
}
// If there are no details (Mystic), we'll create a blank
} else {
$nodes->push(['node'=>$model->set->get('set_sender'),'datetime'=>Carbon::now(),'program'=>sprintf('%s (%04X)',Setup::PRODUCT_NAME,Setup::PRODUCT_ID)]);
}
// Save the Path
@ -118,15 +143,25 @@ final class Netmail extends Model implements Packet
}
// Our last node in the path is our sender
if ($nodes->count() && isset($model->set_pkt) && isset($model->set_sender) && isset($model->set_recvtime)) {
if ($nodes->count() && $model->set->has('set_pkt') && $model->set->has('set_sender') && $model->set->has('set_recvtime')) {
DB::update('UPDATE netmail_path set recv_pkt=?,recv_at=?,recv_id=? where address_id=? and netmail_id=?',[
$model->set_pkt,
$model->set_recvtime,
$model->set_sender->id,
$model->set->get('set_pkt'),
$model->set->get('set_recvtime'),
$model->set->get('set_sender')->id,
Arr::get($nodes->last(),'node')->id,
$model->id,
]);
}
// Save our origin, tearline & tagline
if ($model->set->has('set_tagline'))
$model->tagline = $model->set->get('set_tagline');
if ($model->set->has('set_tearline'))
$model->tearline = $model->set->get('set_tearline');
if ($model->set->has('set_origin'))
$model->origin = $model->set->get('set_origin');
$model->save();
});
}
@ -142,13 +177,8 @@ final class Netmail extends Model implements Packet
public function path()
{
return $this->belongsToMany(Address::class,'netmail_path')
->withPivot(['id','parent_id','datetime','program','recv_pkt','recv_id']);
}
public function received()
{
return $this->belongsToMany(Address::class,'netmail_path','netmail_id','recv_id')
->withPivot(['id','parent_id','datetime','program','recv_pkt','recv_id']);
->withPivot(['id','parent_id','datetime','program','recv_pkt','recv_id'])
->using(ViaPivot::class);
}
public function tftn()
@ -158,89 +188,41 @@ final class Netmail extends Model implements Packet
->withTrashed();
}
/* ATTRIBUTES */
/**
* Enable rendering the path even if the model hasnt been saved
*
* @return Collection
*/
public function getPathAttribute(): Collection
{
return ((! $this->exists) && $this->set->has('set_path'))
? $this->set->get('set_path')->map(function($item) {
$m = [];
preg_match(self::PATH_REGEX,$item,$m);
return $m[1];
})
: $this->getRelationValue('path');
}
/* METHODS */
/**
* Return this model as a packet
* Render the via line
*
* @param Address $ao
* @return string
* @throws \Exception
*/
public function packet(Address $ao,string $strippass=NULL): Message
public function via(Address $ao): string
{
Log::debug(sprintf('%s:+ Bundling [%s]',self::LOGKEY,$this->id));
if (! $ao->pivot)
throw new \Exception('Cannot render the via line without an address record without a path pivot');
// @todo Dont bundle mail to nodes that have been disabled, or addresses that have been deleted
$o = new Message;
try {
$o->header = [
'onode' => $this->fftn->node_id,
'dnode' => $this->tftn->node_id,
'onet' => $this->fftn->host_id,
'dnet' => $this->tftn->host_id,
'opoint' => $this->fftn->point_id,
'dpoint' => $this->tftn->point_id,
'flags' => 0,
'cost' => 0,
'date'=>$this->datetime->format('d M y H:i:s'),
];
$o->tzutc = $this->datetime->utcOffset($this->tzoffset)->getOffsetString('');
$o->user_to = $this->to;
$o->user_from = $this->from;
$o->subject = (! is_null($strippass)) ? preg_replace('/^'.$strippass.':/','',$this->subject) : $this->subject;
// INTL kludge
$o->intl = sprintf('%s %s',$this->tftn->ftn3d,$this->fftn->ftn3d);
$o->flags = $this->flags;
$o->msgid = $this->msgid
? $this->msgid
: sprintf('%s %08x',$this->fftn->ftn4d,timew($this->datetime));
if ($this->replyid)
$o->replyid = $this->replyid;
$o->kludge->put('dbid',$this->id);
$o->message = $this->msg;
$o->tagline = $this->tagline;
$o->tearline = $this->tearline;
$o->origin = $this->origin;
// VIA kludge
$via = $this->via ?: collect();
// Add our address to the VIA line
$via->push(
sprintf('%s @%s.UTC %s %d.%d/%s %s',
our_address($this->fftn)->ftn3d,
Carbon::now()->utc()->format('Ymd.His'),
str_replace(' ','_',Setup::PRODUCT_NAME),
Setup::PRODUCT_VERSION_MAJ,
Setup::PRODUCT_VERSION_MIN,
(new Setup)->version,
Carbon::now()->format('Y-m-d'),
));
$o->via = $via;
$o->packed = TRUE;
} catch (\Exception $e) {
Log::error(sprintf('%s:! Error converting netmail [%s] to a message (%d:%s)',self::LOGKEY,$this->id,$e->getLine(),$e->getMessage()));
dump($this);
}
return $o;
}
public function pathorder(string $display='ftn2d',int $start=NULL): Collection
{
$result = collect();
if ($x=$this->path->firstWhere('pivot.parent_id',$start)) {
$result->push($x->$display);
$result->push($this->pathorder($display,$x->pivot->id));
}
return $result->flatten()->filter();
return sprintf('%s @%s.UTC %s',
$ao->ftn3d,
$ao->pivot->datetime->format('Ymd.His'),
$ao->pivot->program);
}
}

View File

@ -251,12 +251,15 @@ class System extends Model
/**
* Return the packet that this system uses
*
* @param Address $ao
* @return Packet
*/
public function packet(): Packet
public function packet(Address $ao): Packet
{
return new (collect(Packet::PACKET_TYPES)
->get($this->pkt_type ?: config('fido.packet_default')));
// @todo Check that the address is one of the system's addresses
return (new (collect(Packet::PACKET_TYPES)
->get($this->pkt_type ?: config('fido.packet_default'))))->for($ao);
}
public function poll(): ?Job

View File

@ -41,6 +41,6 @@ class EchomailChannel
$o = $notification->toEchomail($notifiable);
Log::info(sprintf('%s:= Posted echomail [%s] to [%s]',self::LOGKEY,$o->msgid,$echoarea));
Log::info(sprintf('%s:= Posted echomail (%d) [%s] to [%s]',self::LOGKEY,$o->id,$o->msgid,$echoarea));
}
}

View File

@ -41,6 +41,6 @@ class NetmailChannel
$o = $notification->toNetmail($notifiable);
Log::info(sprintf('%s:= Sent netmail [%s] to [%s]',self::LOGKEY,$o->msgid,$ao->ftn));
Log::info(sprintf('%s:= Sent netmail (%d) [%s] to [%s]',self::LOGKEY,$o->id,$o->msgid,$ao->ftn));
}
}

View File

@ -52,13 +52,14 @@ abstract class Netmails extends Notification //implements ShouldQueue
$ao = $notifiable->routeNotificationFor(static::via);
$o = new Netmail;
$o->set_sender = our_address($ao);
$o->to = $ao->system->sysop;
$o->from = Setup::PRODUCT_NAME;
$o->datetime = Carbon::now();
$o->tzoffset = $o->datetime->utcOffset();
$o->fftn_id = our_address($ao)->id;
$o->fftn_id = $o->set->get('set_sender')->id;
$o->tftn_id = $ao->id;
$o->flags = (Message::FLAG_LOCAL|Message::FLAG_PRIVATE);
$o->cost = 0;

View File

@ -7,7 +7,7 @@ use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Message;
use App\Notifications\Netmails;
use App\Models\{Netmail,System};
use App\Models\{Echomail,Netmail};
use App\Traits\{MessagePath,PageTemplate};
class EchoareaNotExist extends Netmails
@ -16,14 +16,14 @@ class EchoareaNotExist extends Netmails
private const LOGKEY = 'NNW';
private Message $mo;
private Echomail $mo;
/**
* Send a sysop a message if they attempt to write to an area that doesnt exist.
*
* @param Message $mo
*/
public function __construct(Message $mo)
public function __construct(Echomail $mo)
{
parent::__construct();
@ -44,7 +44,7 @@ class EchoareaNotExist extends Netmails
Log::info(sprintf('%s:+ Creating ECHOMAIL NOT EXIST netmail to [%s]',self::LOGKEY,$ao->ftn));
$o->subject = 'Echoarea doesnt exist - '.$this->mo->echoarea;
$o->subject = 'Echoarea doesnt exist - '.$this->mo->set->get('set_echoarea');
// Message
$msg = $this->page(FALSE,'nothere');
@ -52,7 +52,7 @@ class EchoareaNotExist extends Netmails
$msg->addText(
sprintf("Your echomail with ID [%s] to [%s] here was received here on [%s] and it looks like you sent it on [%s].\r\r",
$this->mo->msgid,
$this->mo->user_to,
$this->mo->to,
Carbon::now()->utc()->toDateTimeString(),
$this->mo->date->utc()->toDateTimeString(),
)

12
app/Pivots/ViaPivot.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Pivots;
use Illuminate\Database\Eloquent\Relations\Pivot;
class ViaPivot extends Pivot
{
protected $casts = [
'datetime' => 'datetime',
];
}

View File

@ -0,0 +1,184 @@
<?php
/**
* Common Attributes used by message packets (and thus their Models)
*/
namespace App\Traits;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Validator as ValidatorResult;
use App\Classes\FTN\Message;
use App\Models\Address;
trait MessageAttributes
{
use EncodeUTF8;
// Items we need to set when creating()
public Collection $set;
// Validation Errors
public ?ValidatorResult $errors = NULL;
private const cast_utf8 = [
'to',
'from',
'subject',
'msg',
'msg_src',
'origin',
'tearline',
'tagline',
];
public function __construct()
{
parent::__construct();
// Init
$this->set = collect();
}
/* ATTRIBUTES */
public function getContentAttribute(): string
{
if ($this->msg_src)
return $this->msg_src."\r";
// If we have a msg_src attribute, we'll use that
$result = $this->msg."\r\r";
if ($this->tagline)
$result .= sprintf("%s\r",$this->tagline);
if ($this->tearline)
$result .= sprintf("%s\r",$this->tearline);
if ($this->origin)
$result .= sprintf("%s",$this->origin);
return rtrim($result,"\r")."\r";
}
public function getDateAttribute(): Carbon
{
return $this->datetime->utcOffset($this->tzoffset);
}
public function getOriginAttribute(string $val=NULL): ?string
{
// If $val is not set, then it may be an unsaved object
return ((! $this->exists) && $this->set->has('set_origin'))
? sprintf(' * Origin: %s',$this->set->get('set_origin'))
: $val;
}
public function getTaglineAttribute(string $val=NULL): ?string
{
// If $val is not set, then it may be an unsaved object
return ((! $this->exists) && $this->set->has('set_tagline'))
? sprintf('... %s',$this->set->get('set_tagline'))
: $val;
}
public function getTearlineAttribute(string $val=NULL): ?string
{
// If $val is not set, then it may be an unsaved object
return ((! $this->exists) && $this->set->has('set_tearline'))
? sprintf('--- %s',$this->set->get('set_tearline'))
: $val;
}
/* METHODS */
/**
* Return an array of flag descriptions
*
* @return Collection
*
* http://ftsc.org/docs/fsc-0001.000
* AttributeWord bit meaning
* --- --------------------
* 0 + Private
* 1 + s Crash
* 2 Recd
* 3 Sent
* 4 + FileAttached
* 5 InTransit
* 6 Orphan
* 7 KillSent
* 8 Local
* 9 s HoldForPickup
* 10 + unused
* 11 s FileRequest
* 12 + s ReturnReceiptRequest
* 13 + s IsReturnReceipt
* 14 + s AuditRequest
* 15 s FileUpdateReq
*
* s - this bit is supported by SEAdog only
* + - this bit is not zeroed before packeting
*/
public function flags(): Collection
{
return collect([
'private' => $this->isFlagSet(Message::FLAG_PRIVATE),
'crash' => $this->isFlagSet(Message::FLAG_CRASH),
'recd' => $this->isFlagSet(Message::FLAG_RECD),
'sent' => $this->isFlagSet(Message::FLAG_SENT),
'fileattach' => $this->isFlagSet(Message::FLAG_FILEATTACH),
'intransit' => $this->isFlagSet(Message::FLAG_INTRANSIT),
'orphan' => $this->isFlagSet(Message::FLAG_ORPHAN),
'killsent' => $this->isFlagSet(Message::FLAG_KILLSENT),
'local' => $this->isFlagSet(Message::FLAG_LOCAL),
'hold' => $this->isFlagSet(Message::FLAG_HOLD),
'unused-10' => $this->isFlagSet(Message::FLAG_UNUSED_10),
'filereq' => $this->isFlagSet(Message::FLAG_FREQ),
'receipt-req' => $this->isFlagSet(Message::FLAG_RETRECEIPT),
'receipt' => $this->isFlagSet(Message::FLAG_ISRETRECEIPT),
'audit' => $this->isFlagSet(Message::FLAG_AUDITREQ),
'fileupdate' => $this->isFlagSet(Message::FLAG_FILEUPDATEREQ),
])->filter();
}
private function isFlagSet($flag): bool
{
return ($this->flags & $flag);
}
/**
* Return this model as a packet
*/
public function packet(Address $ao): Message
{
Log::debug(sprintf('%s:+ Bundling [%s]',self::LOGKEY,$this->id),['type'=>get_class($this)]);
// For netmails, our tftn is the next hop
$this->tftn = $ao;
// @todo Dont bundle mail to nodes that have been disabled, or addresses that have been deleted
return Message::packMessage($this);
}
/**
* Return our path in order
*
* @param string $display
* @param int|NULL $start
* @return Collection
*/
public function pathorder(string $display='ftn2d',int $start=NULL): Collection
{
$result = collect();
if ($x=$this->path->firstWhere('pivot.parent_id',$start)) {
$result->push($x->$display);
$result->push($this->pathorder($display,$x->pivot->id));
}
return $result->flatten()->filter();
}
}

View File

@ -5,32 +5,32 @@
*/
namespace App\Traits;
use App\Classes\FTN\Message;
use App\Models\{Echomail,Netmail};
trait MessagePath
{
protected function message_path(Message $mo): string
protected function message_path(Echomail|Netmail $mo): string
{
$reply = "This is your original message:\r\r";
$reply .= "+--[ BEGIN MESSAGE ]----------------------------------+\r";
$reply .= sprintf("TO: %s\r",$mo->user_to);
$reply .= sprintf("TO: %s\r",$mo->to);
$reply .= sprintf("SUBJECT: %s\r",$mo->subject);
$reply .= str_replace("\r---","\r#--",$mo->message)."\r";
$reply .= str_replace("\r---","\r#--",$mo->msg)."\r";
$reply .= "+--[ CONTROL LINES ]----------------------------------+\r";
$reply .= sprintf("DATE: %s\r",$mo->date->format('Y-m-d H:i:s'));
$reply .= sprintf("MSGID: %s\r",$mo->msgid);
foreach ($mo->kludge as $k=>$v)
$reply .= sprintf("@%s: %s\r",strtoupper($k),$v);
foreach ($mo->kludges as $k=>$v)
$reply .= sprintf("%s %s\r",$k,$v);
$reply .= "+--[ PATH ]-------------------------------------------+\r";
if ($mo->isNetmail()) {
if ($mo instanceof Netmail) {
if ($mo->via->count())
foreach ($mo->via as $via)
$reply .= sprintf("VIA: %s\r",$via);
foreach ($mo->via as $ao)
$reply .= sprintf("VIA: %s\r",$mo->via($ao));
else
$reply .= "No path information? This would be normal if this message came directly to the hub\r";

View File

@ -5,7 +5,7 @@
"keywords": ["framework","laravel"],
"license": "MIT",
"require": {
"php": "^8.1|8.2|8.3",
"php": "^8.2|8.3",
"ext-bz2": "*",
"ext-pcntl": "*",
"ext-sockets": "*",

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('netmails', function (Blueprint $table) {
$table->json('kludges')->nullable();
});
Schema::table('echomails', function (Blueprint $table) {
$table->tinyInteger('cost')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('netmails', function (Blueprint $table) {
$table->dropColumn('kludges');
});
Schema::table('echomails', function (Blueprint $table) {
$table->dropColumn('cost');
});
}
};

View File

@ -1,3 +1,8 @@
@php
use App\Models\Netmail;
use App\Classes\FTN\Message;
@endphp
@extends('layouts.app')
@section('htmlheader_title')
Verify Packet
@ -82,7 +87,7 @@
<div class="col-12">
<h4 class="accordion-header">
<span class="accordion-button" id="pktmsg" data-bs-toggle="collapse" data-bs-target="#collapse_msg_{{ $loop->parent->parent->index }}_{{ $loop->index }}" aria-expanded="false">
@if($msg->isNetmail()) Netmail @else Echomail&nbsp;<strong>{{ $msg->echoarea }}</strong> @endif : {{ $msg->msgid }}
@if($msg instanceof Netmail) Netmail @else Echomail&nbsp;<strong>{{ $msg->echoarea->name }}</strong> @endif : {{ $msg->msgid }}
</span>
</h4>
@ -98,48 +103,40 @@
<div class="row pb-2">
<div class="col-4">
DATE: <strong class="highlight">{{ $msg->date }}</strong>
DATE: <strong class="highlight">{{ $msg->datetime }}</strong>
</div>
<div class="col-4">
FLAGS: <strong class="highlight">{{ $msg->flags()->filter()->keys()->join(', ') }}</strong>
FLAGS: <strong class="highlight">{{ $msg->flags()->keys()->join(', ') }}</strong>
</div>
</div>
<div class="row pb-2">
<div class="col-4">
FROM: <strong class="highlight">{!! \App\Classes\FTN\Message::tr($msg->user_from) !!}</strong> (<strong class="highlight">{{ $msg->fftn }}</strong>)
FROM: <strong class="highlight">{!! Message::tr($msg->from) !!}</strong> (<strong class="highlight">{{ $msg->fftn->ftn }}</strong>)
</div>
<div class="col-4">
TO: <strong class="highlight">{!! \App\Classes\FTN\Message::tr($msg->user_to) !!}</strong>@if($msg->isNetmail()) (<strong class="highlight">{{ $msg->tftn }}</strong>) @endif
TO: <strong class="highlight">{!! Message::tr($msg->to) !!}</strong>@if($msg instanceof Netmail) (<strong class="highlight">{{ $msg->tftn->ftn }}</strong>) @endif
</div>
</div>
<div class="row pb-2">
<div class="col-8">
SUBJECT: <strong class="highlight">{!! \App\Classes\FTN\Message::tr($msg->subject) !!}</strong>
SUBJECT: <strong class="highlight">{!! Message::tr($msg->subject) !!}</strong>
</div>
</div>
<div class="row pb-2">
<div class="col-8">
<div class="pad pb-0">
<pre class="highlight">{!! \App\Classes\FTN\Message::tr($msg->message).sprintf("\r * Origin: %s",$msg->origin) !!}</pre>
<pre class="highlight">{!! Message::tr($msg->msg_src) !!}</pre>
</div>
</div>
</div>
@if($msg->tagline)
@if($msg instanceof Netmail)
<div class="row pb-2">
<div class="col-8">
TAGLINE: <br><strong class="highlight">{{ $msg->tagline }}</strong>
</div>
</div>
@endif
@if($msg->isNetmail())
<div class="row pb-2">
<div class="col-8">
VIA: <br><strong class="highlight">{!! $msg->via->join('</strong><br><strong class="highlight">') !!}</strong>
VIA: <br><strong class="highlight">{!! $msg->path->join('</strong> -> <strong class="highlight">') !!}</strong>
</div>
</div>
@else
@ -159,7 +156,7 @@
<div class="row pb-2">
<div class="col-8">
<strong>KLUDGES:</strong> <br>
@foreach ($msg->kludge->sort(function($v,$k) { return $k; })->reverse() as $k => $v)
@foreach ($msg->kludges->sort(function($v,$k) { return $k; })->reverse() as $k => $v)
<strong class="highlight">{{ $k }}</strong> {{ $v }}<br>
@endforeach
</div>

View File

@ -1,10 +1,11 @@
@php
use App\Classes\FTN\Message;
use App\Models\{Echomail,Netmail};
@endphp
<div class="row">
<div class="col-4">
TO: <strong class="highlight">{!! Message::tr($msg->to) !!}</strong> @if ($msg instanceof \App\Models\Netmail)(<strong class="highlight">{{ $msg->tftn->ftn }}</strong>)@endif
TO: <strong class="highlight">{!! Message::tr($msg->to) !!}</strong> @if ($msg instanceof Netmail)(<strong class="highlight">{{ $msg->tftn->ftn }}</strong>)@endif
</div>
<div class="col-4">
DATE: <strong class="highlight">{{ $msg->datetime->format('Y-m-d H:i:s') }}</strong>
@ -16,19 +17,30 @@ use App\Classes\FTN\Message;
FROM: <strong class="highlight">{!! Message::tr($msg->from) !!}</strong> (<strong class="highlight">{{ $msg->fftn->ftn }}</strong>)
</div>
<div class="col-4">
MSGID: <strong class="highlight">{{ $msg->msgid }}</strong>@if($x=\App\Models\Echomail::where('replyid',$msg->msgid)->count()) (<strong class="highlight">{{$x}}</strong> replies)@endif @if($msg->replyid)<br>REPLY: <strong class="highlight">{{ $msg->replyid }}</strong>@endif
MSGID: <strong class="highlight">{{ $msg->msgid }}</strong>@if($x=Echomail::where('replyid',$msg->msgid)->count()) (<strong class="highlight">{{$x}}</strong> replies)@endif @if($msg->replyid)<br>REPLY: <strong class="highlight">{{ $msg->replyid }}</strong>@endif
</div>
</div>
<div class="row pt-1 pb-2">
<div class="col-4">
SUBJECT: <strong class="highlight">{!! Message::tr($msg->subject) !!}</strong>
@if($msg->flags()->count())
<div class="row pt-1">
<div class="offset-4 col-8">
FLAGS: <strong class="highlight">{!! $msg->flags()->keys()->map(fn($item)=>strtoupper($item))->join('</strong>, <strong class="highlight">') !!}</strong>
</div>
</div>
@if ($msg instanceof \App\Models\Echomail)
<div class="col-4">
@endif
@if ($msg instanceof Echomail)
<div class="row pt-1 pb-2">
<div class="offset-4 col-4">
ECHOAREA: <strong class="highlight">{{ $msg->echoarea->name }}</strong> (<strong class="highlight">{{ $msg->echoarea->domain->name }}</strong>)
</div>
@endif
</div>
@endif
<div class="row pt-1 pb-2">
<div class="col-8">
SUBJECT: <strong class="highlight">{!! Message::tr($msg->subject) !!}</strong>
</div>
</div>
<div class="row pb-2">
@ -39,63 +51,63 @@ use App\Classes\FTN\Message;
</div>
</div>
@if ($msg instanceof \App\Models\Echomail)
<div class="row pb-2">
<div class="col-8">
KLUDGES: <br>
@foreach($msg->kludges as $k=>$v)
<strong class="highlight">{{ $k }}</strong> {{ $v }}<br>
@endforeach
</div>
</div>
@if ($msg instanceof Echomail)
<div class="row pb-2">
<div class="col-8">
SEENBY: <br><strong class="highlight">{!! $msg->seenby->pluck('ftn2d')->join('</strong>, <strong class="highlight">') !!}</strong>
</div>
@if ($msg->rogue_seenby->count())
<br><small>[<strong>NOTE</strong>: Some seen-by values couldnt be identified - ({{ $msg->rogue_seenby->join(',') }})]</small>
@if($msg->rogue_seenby->count())
<br><small>[<strong>NOTE</strong>: Some seen-by values couldnt be identified - ({{ $msg->rogue_seenby->transform(fn($item)=>str_replace('0:','',$item))->join(',') }})]</small>
@endif
</div>
@endif
@if ($msg->flags & Message::FLAG_LOCAL)
<div class="row pb-2">
<div class="col-8">
<strong class="highlight">Local message</strong>
</div>
<!-- @todo for the nodes we export to, highlight those that we have actually sent it, vs those that havent received it yet -->
<div class="row pb-2">
<div class="col-8">
PATH: <br><strong class="highlight">{!! $msg->pathorder()->join('</strong> -> <strong class="highlight">') !!}</strong>
@if(($msg instanceof Echomail) && $msg->rogue_path->count())
<br><small>[<strong>NOTE</strong>: Some path values couldnt be identified - ({{ $msg->rogue_path->join(',') }})]</small>
@endif
</div>
</div>
@elseif ((! $msg->flags) || ($msg->flags & (Message::FLAG_INTRANSIT|Message::FLAG_RECD)))
<!-- @todo for the nodes we export to, highlight those that we have actually sent it, vs those that havent received it yet -->
<div class="row pb-2">
<div class="col-8">
PATH: <br><strong class="highlight">{!! $msg->pathorder()->join('</strong> -> <strong class="highlight">') !!}</strong>
@if (($msg instanceof \App\Models\Echomail) && $msg->rogue_path->count())
<br><small>[<strong>NOTE</strong>: Some path values couldnt be identified - ({{ $msg->rogue_path->join(',') }})]</small>
@endif
</div>
</div>
<div class="row pb-2">
<div class="col-8">
<div class="row pb-2">
<div class="col-8">
@if($msg instanceof Netmail)
RECEIVED:<br>
@if ($msg instanceof \App\Models\Netmail)
@foreach ($msg->received as $path)
<strong class="highlight">{{ $path->pivot->recv_pkt }}</strong> from <strong class="highlight">{{ $path->ftn }}</strong> {{ $msg->created_at }}
@endforeach
@elseif ($msg instanceof \App\Models\Echomail)
<strong class="highlight">{{ ($x=$msg->path->sortBy('pivot.parent_id')->last())->pivot->recv_pkt }}</strong> from <strong class="highlight">{{ $x->ftn }}</strong> {{ $x->pivot->recv_at }}
@endif
</div>
@foreach ($msg->path as $path)
<strong class="highlight">{{ $path->pivot->recv_pkt }}</strong> from <strong class="highlight">{{ $path->ftn }}</strong> {{ $msg->created_at }}
@endforeach
@elseif ($msg instanceof Echomail)
RECEIVED:<br>
<strong class="highlight">{{ ($x=$msg->path->sortBy('pivot.parent_id')->last())->pivot->recv_pkt }}</strong> from <strong class="highlight">{{ $x->ftn }}</strong> {{ $x->pivot->recv_at }}
@endif
</div>
@endif
</div>
@section('page-scripts')
<script type="text/javascript" src="{{ asset('ansilove/ansilove.js') }}"></script>
<script type="text/javascript">
$(document).ready(function() {
var msg = new Uint8Array({!! json_encode(array_values(unpack('C*',str_replace("\r","\n",$msg->msg)))) !!});
var msg = new Uint8Array({!! json_encode(array_values(unpack('C*',str_replace("\r","\n",$msg->content)))) !!});
retina = window.devicePixelRatio > 1;
AnsiLove.renderBytes(
msg,
function (canvas, sauce) {
console.log(canvas);
document.getElementById("canvas").appendChild(canvas);
},
{'font': '80x25', 'bits': 8, 'icecolors': 0, 'columns': 80}