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) public function __get(string $key)
{ {
switch ($key) { switch ($key) {
case 'fftn': case 'fftn_t':
return sprintf('%d:%d/%d.%d', return sprintf('%d:%d/%d.%d',
$this->fz, $this->fz,
$this->fn, $this->fn,
@ -19,7 +19,7 @@ abstract class FTN
$this->fp, $this->fp,
).($this->zone ? sprintf('@%s',$this->zone->domain->name) : ''); ).($this->zone ? sprintf('@%s',$this->zone->domain->name) : '');
case 'tftn': case 'tftn_t':
return sprintf('%d:%d/%d.%d', return sprintf('%d:%d/%d.%d',
$this->tz, $this->tz,
$this->tn, $this->tn,
@ -27,10 +27,10 @@ abstract class FTN
$this->tp, $this->tp,
).($this->zone ? sprintf('@%s',$this->zone->domain->name) : ''); ).($this->zone ? sprintf('@%s',$this->zone->domain->name) : '');
case 'fftn_o': case 'fftn':
return Address::findFTN($this->fftn); return Address::findFTN($this->fftn_t);
case 'tftn_o': case 'tftn':
return Address::findFTN($this->tftn); return Address::findFTN($this->tftn_t);
default: default:
throw new \Exception('Unknown key: '.$key); 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 Symfony\Component\HttpFoundation\File\File;
use App\Classes\FTN as FTNBase; 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; use App\Notifications\Netmails\EchomailBadAddress;
/** /**
* Represents a Fidonet Packet, that contains an array of messages. * 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'; private const LOGKEY = 'PKT';
protected const PACKED_MSG_LEAD = "\02\00"; protected const PACKED_MSG_LEAD = "\02\00";
protected const PACKED_END = "\00\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}))?-(.+)'; 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 = [ public const PACKET_TYPES = [
'2.2' => FTNBase\Packet\FSC45::class, '2.2' => FTNBase\Packet\FSC45::class,
'2+' => FTNBase\Packet\FSC48::class, '2+' => FTNBase\Packet\FSC48::class,
@ -35,115 +42,15 @@ class Packet extends FTNBase implements \Iterator, \Countable
]; ];
protected array $header; // Packet Header protected array $header; // Packet Header
protected ?string $name; // Packet name protected ?string $name = NULL; // Packet name
public File $file; // Packet filename 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 public Collection $errors; // Messages that fail validation
protected int $index; // Our array index 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 */ /* STATIC */
/** /**
@ -164,10 +71,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
* @param string $header * @param string $header
* @return bool * @return bool
*/ */
public static function is_type(string $header): bool abstract public static function is_type(string $header): bool;
{
return FALSE;
}
/** /**
* Process a packet file * Process a packet file
@ -224,7 +128,9 @@ class Packet extends FTNBase implements \Iterator, \Countable
// No message attached // No message attached
} else } 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 // Work out the packet zone
if ($o->fz && ($o->fd || $domain)) { if ($o->fz && ($o->fd || $domain)) {
@ -233,16 +139,10 @@ class Packet extends FTNBase implements \Iterator, \Countable
->where('zone_id',$o->fz) ->where('zone_id',$o->fz)
->where('name',$o->fd ?: $domain->name) ->where('name',$o->fd ?: $domain->name)
->single(); ->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 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)); 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) $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 $message = ''; // Current message we are building
$msgbuf = ''; $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 // We loop through reading from the buffer, to find our end of message tag
while ((! feof($f) && ($readbuf=fread($f,$leader)))) { while ((! feof($f) && ($readbuf=fread($f,$leader)))) {
@ -269,7 +169,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
$msgbuf = substr($msgbuf,$end+3); $msgbuf = substr($msgbuf,$end+3);
continue; continue;
// If we have more to read // If we have more to read
} elseif ($read_ptr < $size) { } elseif ($read_ptr < $size) {
continue; continue;
} }
@ -285,18 +185,103 @@ class Packet extends FTNBase implements \Iterator, \Countable
} }
/** /**
* Location of the version * @param string|null $header
* * @throws \Exception
* @return int
*/ */
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 */ /* INTERFACE */
@ -309,7 +294,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
return $this->messages->count(); return $this->messages->count();
} }
public function current(): Message public function current(): Echomail|Netmail
{ {
return $this->messages->get($this->index); return $this->messages->get($this->index);
} }
@ -342,6 +327,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
* @param Address $oo * @param Address $oo
* @param Address $o * @param Address $o
* @param string|null $passwd Override the password used in the packet * @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 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 * Add a message to this packet
* *
* @param Message $o * @param Message $o
* @deprecated No longer used when Address::class is updated
*/ */
public function addMail(Message $o): void public function addMail(Message $o): void
{ {
$this->messages->push($o); $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 * Parse a message in a mail packet
* *
@ -393,7 +405,8 @@ class Packet extends FTNBase implements \Iterator, \Countable
$msg = Message::parseMessage($message,$this->zone); $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 the message is invalid, we'll ignore it
if ($msg->errors) { 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 the messages is not for the right zone, we'll ignore it
if ($msg->errors->messages()->has('invalid-zone')) { 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()) if (! $msg->rescanned->count())
Notification::route('netmail',$this->fftn_o)->notify(new EchomailBadAddress($msg)); Notification::route('netmail',$this->fftn)->notify(new EchomailBadAddress($msg));
return; 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 the to address doenst exist, we'll create a new entry
if ($msg->errors->messages()->has('to') && $msg->tzone) { if ($msg->errors->messages()->has('to') && $msg->tzone) {
try { 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)); 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()))); Log::error(sprintf('%s:! Skipping message [%s] due to errors (%s)...',self::LOGKEY,$msg->msgid,join(',',$msg->errors->messages()->keys())));
$this->errors->push($msg); $this->errors->push($msg);
return; return;
} }
} }
@ -481,8 +500,10 @@ class Packet extends FTNBase implements \Iterator, \Countable
$this->messages->push($msg); $this->messages->push($msg);
} }
/** @deprecated Is this used? */
public function pluck(string $key): Collection public function pluck(string $key): Collection
{ {
throw new \Exception(sprintf('%s:! This function is deprecated - [%s]',self::LOGKEY,$key));
return $this->messages->pluck($key); return $this->messages->pluck($key);
} }
} }

View File

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

View File

@ -2,7 +2,7 @@
namespace App\Classes\FTN; 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 * 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. * Return TRUE if the process class handled the message.
* *
* @param Message $msg * @param Echomail|Netmail $mo
* @return bool * @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\Notification;
use Illuminate\Support\Facades\Log; 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; use App\Notifications\Echomails\Test as TestNotification;
/** /**
@ -19,16 +20,16 @@ final class Test extends Process
private const testing = ['test','testing']; 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) if (! self::canProcess($mo->echoarea)
|| (strtolower($msg->user_to) !== 'all') || (strtolower($mo->to) !== 'all')
|| (! in_array(strtolower($msg->subject),self::testing))) || (! in_array(strtolower($mo->subject),self::testing)))
return FALSE; return FALSE;
Log::info(sprintf('%s:- Processing TEST message from (%s) [%s] in [%s]',self::LOGKEY,$msg->user_from,$msg->fftn,$msg->echoarea)); 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; return TRUE;
} }

View File

@ -11,6 +11,8 @@ use App\Classes\Sock\SocketClient;
use App\Classes\Sock\SocketException; use App\Classes\Sock\SocketException;
use App\Models\{Address,Mailer,Setup,System,SystemLog}; 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 abstract class Protocol
{ {
// Enable extra debugging // Enable extra debugging

View File

@ -50,8 +50,8 @@ class PacketAddress extends Command
exit(1); 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\File;
use App\Classes\FTN\Packet; use App\Classes\FTN\Packet;
use App\Models\Address; use App\Models\{Address,Echomail};
class PacketInfo extends Command class PacketInfo extends Command
{ {
@ -31,7 +31,7 @@ class PacketInfo extends Command
* Execute the console command. * Execute the console command.
* *
* @return mixed * @return mixed
* @throws \App\Classes\FTN\InvalidPacketException * @throws \App\Exceptions\InvalidPacketException
*/ */
public function handle() public function handle()
{ {
@ -55,27 +55,38 @@ class PacketInfo extends Command
$this->alert(sprintf('File Name: %s',$x)); $this->alert(sprintf('File Name: %s',$x));
$this->info(sprintf('Packet Type : %s (%s)',$pkt->type,get_class($pkt))); $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('From : %s to %s',$pkt->fftn->ftn,$pkt->tftn->ftn));
$this->info(sprintf('Dated : %s (%s)',$pkt->date,$pkt->date->timestamp)); $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('Password : %s (%s)',$pkt->password ?: '-',$pkt->password ? 'SET' : 'NOT set'));
$this->info(sprintf('Messages : %d',$pkt->messages->count())); $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('Tosser : %d (%s) version %s',$pkt->software->code,$pkt->software->name,$pkt->software_ver));
$this->info(sprintf('Capabilities: %x',$pkt->capability)); $this->info(sprintf('Capabilities: %x',$pkt->capability));
$this->info(sprintf('Has Errors : %s',$pkt->errors->count() ? 'YES' : 'No')); $this->info(sprintf('Has Errors : %s',$pkt->errors->count() ? 'YES' : 'No'));
$this->info(sprintf('Messages : %d',$pkt->count())); $this->info(sprintf('Messages : %d',$pkt->count()));
foreach ($pkt as $msg) { foreach ($pkt as $msg) {
try { echo "\n";
$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));
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) foreach ($msg->errors->errors()->all() as $error)
$this->line(' - '.$error); $this->error(' - '.$error);
}
} catch (\Exception $e) { } catch (\Exception $e) {
$this->error('! ERROR: '.$e->getMessage()); $this->error('! ERROR: '.$e->getMessage());

View File

@ -2,13 +2,12 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use App\Classes\File; use App\Classes\File;
use App\Classes\FTN\Packet; use App\Classes\FTN\Packet;
use App\Jobs\MessageProcess as Job; use App\Jobs\PacketProcess as Job;
use App\Models\Address; use App\Models\Address;
class PacketProcess extends Command class PacketProcess extends Command
@ -35,7 +34,7 @@ class PacketProcess extends Command
* Execute the console command. * Execute the console command.
* *
* @return void * @return void
* @throws \App\Classes\FTN\InvalidPacketException * @throws \App\Exceptions\InvalidPacketException
*/ */
public function handle() public function handle()
{ {
@ -56,17 +55,8 @@ class PacketProcess extends Command
exit(1); exit(1);
} }
foreach ($f as $packet) { $x = Job::dispatchSync($rel_name,$a->zone->domain,$this->option('dontqueue'));
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()));
// Dispatch job. dd(['job completed'=>$x]);
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'));
}
}
} }
} }

View File

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

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Classes\FTN; namespace App\Exceptions;
use Exception; 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 Illuminate\Support\Facades\Notification;
use App\Classes\FTN\Message; 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\Notifications\Netmails\{EchoareaNotExist,EchoareaNotSubscribed,EchoareaNoWrite,NetmailForward,Reject};
use App\Traits\ParseAddresses; use App\Traits\ParseAddresses;
@ -23,20 +23,19 @@ class MessageProcess implements ShouldQueue
use Dispatchable,InteractsWithQueue,Queueable,SerializesModels,ParseAddresses; use Dispatchable,InteractsWithQueue,Queueable,SerializesModels,ParseAddresses;
private Address $sender; private Echomail|Netmail|string $mo;
private Message $msg;
private Address $pktsrc;
private Carbon $recvtime;
private bool $skipbot; 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; // @todo We need to serialize this model here, because laravel has an error unserializing it (Model Not Found)
$this->packet = $packet; $this->mo = serialize($mo);
$this->sender = $sender;
$this->pktsrc = $pktsrc;
$this->recvtime = $recvtime;
$this->skipbot = $skipbot; $this->skipbot = $skipbot;
} }
@ -44,7 +43,7 @@ class MessageProcess implements ShouldQueue
{ {
switch ($key) { switch ($key) {
case 'subject': 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: default:
return NULL; return NULL;
@ -52,21 +51,25 @@ class MessageProcess implements ShouldQueue
} }
/** /**
* When calling MessageProcess - we assume that the packet is from a valid source, and * At this point, we know that the packet is from a system we know about, and the packet is to us:
* the destination (netmail/echomail) is also valid * + 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() public function handle()
{ {
$this->mo = unserialize($this->mo);
// Load our details // Load our details
$ftns = our_address(); $ftns = our_address();
// If we are a netmail // 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].', Log::info(sprintf('%s:- Processing Netmail [%s] to (%s) [%s] from (%s) [%s].',
self::LOGKEY, self::LOGKEY,
$this->msg->msgid, $this->mo->msgid,
$this->msg->user_to,$this->msg->tftn, $this->mo->to,$this->mo->tftn->ftn,
$this->msg->user_from,$this->msg->fftn, $this->mo->from,$this->mo->fftn->ftn,
)); ));
// @todo Enable checks to reject old messages // @todo Enable checks to reject old messages
@ -74,89 +77,51 @@ class MessageProcess implements ShouldQueue
// Check for duplicate messages // Check for duplicate messages
// FTS-0009.001 // FTS-0009.001
if ($this->msg->msgid) { if ($this->mo->msgid) {
$o = Netmail::where('msgid',$this->msg->msgid) Log::debug(sprintf('%s:- Checking for duplicate from host [%s].',self::LOGKEY,$this->mo->fftn->ftn));
->where('fftn_id',($x=$this->msg->fboss_o) ? $x->id : NULL)
$o = Netmail::where('msgid',$this->mo->msgid)
->where('fftn_id',$this->mo->fftn->id)
->where('datetime','>',Carbon::now()->subYears(3)) ->where('datetime','>',Carbon::now()->subYears(3))
->single(); ->single();
Log::debug(sprintf('%s:- Checking for duplicate from host id [%d].',self::LOGKEY,($x=$this->msg->fboss_o) ? $x->id : NULL));
if ($o) { 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, self::LOGKEY,
$this->msg->msgid, $o->id,
$this->msg->echoarea, $this->mo->msgid,
$this->msg->user_from,$this->msg->fftn, $this->mo->from,$this->mo->fftn->ftn,
$this->msg->user_to, $this->mo->to,
)); ));
if (! $o->msg_crc)
$o->msg_crc = md5($this->msg->message);
$o->save();
return; return;
} }
} }
// @todo Enable checks to see if this is a file request or file send // @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 // 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 // Determine if the message is to this system, or in transit
if ($ftns->search(function($item) { return $this->msg->tftn === $item->ftn; }) !== FALSE) { if ($ftns->contains($this->mo->tftn)) {
// @todo Check if it is a duplicate message
// @todo Check if the message is from a system we know about
$processed = FALSE; $processed = FALSE;
// If the message is to a bot, we'll process it // If the message is to a bot, we'll process it
if (! $this->skipbot) if (! $this->skipbot)
foreach (config('process.robots') as $class) { foreach (config('process.robots') as $class) {
if ($processed=$class::handle($this->msg)) { if ($processed=$class::handle($this->mo)) {
$o->flags |= Message::FLAG_RECD; $this->mo->flags |= Message::FLAG_RECD;
$o->save(); $this->mo->save();
Log::info(sprintf('%s:= Netmail [%s] from (%s:%s) - was processed by us internally [%d]', Log::info(sprintf('%s:= Netmail [%s] from (%s:%s) - was processed by us internally [%d]',
self::LOGKEY, self::LOGKEY,
$this->msg->msgid, $this->mo->msgid,
$this->msg->user_from, $this->mo->from,
$this->msg->fftn, $this->mo->fftn->ftn,
$o->id, $this->mo->id,
)); ));
break; break;
} }
} }
@ -165,38 +130,40 @@ class MessageProcess implements ShouldQueue
// Check if the netmail is to a user, with netmail forwarding enabled // Check if the netmail is to a user, with netmail forwarding enabled
$uo = User::active() $uo = User::active()
->where(function($query) { ->where(function($query) {
return $query->whereRaw(sprintf("LOWER(name)='%s'",strtolower($this->msg->user_to))) return $query->whereRaw(sprintf("LOWER(name)='%s'",strtolower($this->mo->to)))
->orWhereRaw(sprintf("LOWER(alias)='%s'",strtolower($this->msg->user_to))); ->orWhereRaw(sprintf("LOWER(alias)='%s'",strtolower($this->mo->to)));
}) })
->whereNotNull('system_id') ->whereNotNull('system_id')
->single(); ->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 = "+--[ FORWARDED MESSAGE ]----------------------------------+\r";
$note .= "+ This message has been forwarded to you, it was originally sent to you\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"; $note .= "+---------------------------------------------------------+\r\r";
$o->msg = $note.$this->msg->message;
$o->tftn_id = $ao->id; $this->mo->msg = $note.$this->mo->content;
$o->flags |= Message::FLAG_INTRANSIT; $this->mo->tftn_id = $ao->id;
$o->save(); $this->mo->flags |= Message::FLAG_INTRANSIT;
$this->mo->save();
$processed = TRUE; $processed = TRUE;
// Dont send an advisement to an areabot // Dont send an advisement to an areabot
if (! in_array(strtolower($this->msg->user_from),config('fido.areabots'))) if (! in_array(strtolower($this->mo->from),config('fido.areabots')))
Notification::route('netmail',$this->msg->fftn_o)->notify(new NetmailForward($this->msg,$ao)); Notification::route('netmail',$this->mo->fftn)->notify(new NetmailForward($this->mo,$ao));
// We'll ignore messages from *fix users // We'll ignore messages from *fix users
} elseif (in_array(strtolower($this->msg->user_from),config('fido.areabots'))) { } elseif (in_array(strtolower($this->mo->from),config('fido.areabots'))) {
$o->flags |= Message::FLAG_RECD; $this->mo->flags |= Message::FLAG_RECD;
$o->save(); $this->mo->save();
Log::alert(sprintf('%s:! Ignoring Netmail [%s] to the Hub from (%s:%s) - its from a bot [%d]', Log::alert(sprintf('%s:! Ignoring Netmail [%s] to the Hub from (%s:%s) - its from a bot [%d]',
self::LOGKEY, self::LOGKEY,
$this->msg->msgid, $this->mo->msgid,
$this->msg->user_from, $this->mo->from,
$this->msg->fftn, $this->mo->fftn->ftn,
$o->id, $this->mo->id,
)); ));
$processed = TRUE; $processed = TRUE;
@ -205,99 +172,87 @@ class MessageProcess implements ShouldQueue
// If not processed, no users here! // If not processed, no users here!
if (! $processed) { 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 // If in transit, store for collection
} else { } else {
// @todo Check if the message is to a system we know about
// @todo In transit loop checking // @todo In transit loop checking
// @todo In transit TRACE response // @todo In transit TRACE response
$o->flags |= Message::FLAG_INTRANSIT; $this->mo->flags |= Message::FLAG_INTRANSIT;
$o->save(); $this->mo->save();
Log::info(sprintf('%s:= Netmail [%s] in transit to (%s:%s) from (%s:%s) [%d].', Log::info(sprintf('%s:= Netmail [%s] in transit to (%s:%s) from (%s:%s) [%d].',
self::LOGKEY, self::LOGKEY,
$this->msg->msgid, $this->mo->msgid,
$this->msg->user_to,$this->msg->tftn, $this->mo->to,$this->mo->tftn->ftn,
$this->msg->user_from,$this->msg->fftn, $this->mo->from,$this->mo->fftn->ftn,
$o->id, $this->mo->id,
)); ));
} }
// Else we are echomail // Else we are echomail
} else { } 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) { // @todo Check that this does evaulate to true if a message has been rescanned
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)); $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; return;
} }
$ea = Echoarea::where('name',strtoupper($this->msg->echoarea)) Log::debug(sprintf('%s:- Processing echomail [%s] in [%s] from [%s].',self::LOGKEY,$this->mo->msgid,$this->mo->echoarea->name,$sender->ftn));
->where('domain_id',$this->msg->fboss_o->zone->domain_id)
->single();
if (! $ea) { // Message from zone is incorrect for echoarea
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)); if (! $this->mo->echoarea->domain->zones->contains($this->mo->fftn->zone)) {
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)) {
Log::alert(sprintf('%s:! The message [%s] is from a different zone [%d] than the packet sender [%d] - not importing', Log::alert(sprintf('%s:! The message [%s] is from a different zone [%d] than the packet sender [%d] - not importing',
self::LOGKEY, self::LOGKEY,
$this->msg->msgid, $this->mo->msgid,
$this->msg->fboss_o->zone->zone_id, $this->mo->fftn->zone->zone_id,
$this->pktsrc->zone->zone_id)); $this->mo->fftn->zone->zone_id));
return; return;
} }
// Check for duplicate messages // Check for duplicate messages
// FTS-0009.001 // FTS-0009.001
if ($this->msg->msgid) { if ($this->mo->msgid) {
$o = Echomail::where('msgid',$this->msg->msgid) $o = Echomail::where('msgid',$this->mo->msgid)
->where('fftn_id',($x=$this->msg->fboss_o) ? $x->id : NULL) ->where('fftn_id',$this->mo->fftn->id)
->where('datetime','>=',$this->msg->date->subYears(3)) ->where('datetime','>=',$this->mo->date->subYears(3))
->where('datetime','<=',$this->msg->date) ->where('datetime','<=',$this->mo->date)
->single(); ->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) { if ($o) {
// @todo Actually update seenby
Log::alert(sprintf('%s:! Duplicate echomail [%s] in [%s] from (%s) [%s] to (%s) - updating seenby.', Log::alert(sprintf('%s:! Duplicate echomail [%s] in [%s] from (%s) [%s] to (%s) - updating seenby.',
self::LOGKEY, self::LOGKEY,
$this->msg->msgid, $this->mo->msgid,
$this->msg->echoarea, $this->mo->echoarea->name,
$this->msg->user_from,$this->msg->fftn, $this->mo->from,$this->mo->fftn->ftn,
$this->msg->user_to, $this->mo->to,
)); ));
if (! $o->msg_crc) //$o->save();
$o->msg_crc = md5($this->msg->message);
$o->save();
// @todo This duplicate message may have gone via a different path, be nice to record it. // @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 we didnt get the path on the original message, we'll override it
if (! $o->path->count()) { if (! $o->path->count()) {
$dummy = collect(); $dummy = collect();
$path = $this->parseAddresses('path',$this->msg->path,$this->pktsrc->zone,$dummy); $path = $this->parseAddresses('path',$this->mo->path,$sender->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);
}
*/
$ppoid = NULL; $ppoid = NULL;
foreach ($path as $aoid) { foreach ($path as $aoid) {
@ -310,23 +265,24 @@ class MessageProcess implements ShouldQueue
$ppoid = $po[0]->id; $ppoid = $po[0]->id;
} }
} }
*/
// @todo if we have an export for any of the seenby addresses, remove it // @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 // In case our rogue_seenby changed
if ($o->getDirty()) $this->mo->save();
$o->save();
return; return;
} }
} }
// Find another message with the same msg_crc // Find another message with the same msg_crc
if ($this->msg->message) { if ($this->mo->msg_crc) {
$o = Echomail::where('msg_crc',$xx=md5($this->msg->message)) $o = Echomail::where('msg_crc',$xx=md5($this->mo->msg_crc))
->where('fftn_id',($x=$this->msg->fboss_o) ? $x->id : NULL) ->where('fftn_id',$this->mo->fftn->id)
->where('datetime','>',Carbon::now()->subWeek()) ->where('datetime','>',Carbon::now()->subWeek())
->get(); ->get();
@ -339,73 +295,38 @@ class MessageProcess implements ShouldQueue
} }
// If the node is not subscribed // If the node is not subscribed
if ($this->pktsrc->echoareas->search(function($item) use ($ea) { return $item->id === $ea->id; }) === FALSE) { 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,$this->pktsrc->ftn,$ea->name,$this->msg->msgid)); 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()) if (! $rescanned)
Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNotSubscribed($this->msg)); Notification::route('netmail',$sender)->notify(new EchoareaNotSubscribed($this->mo));
} }
// Can the system send messages to this area? // Can the system send messages to this area?
if (! $ea->can_write($this->pktsrc->security)) { if (! $this->mo->echoarea->can_write($sender->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)); 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 (! $this->msg->rescanned->count()) if (! $rescanned)
Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNoWrite($this->msg)); Notification::route('netmail',$sender)->notify(new EchoareaNoWrite($this->mo));
return; return;
} }
// We know about this area, store it // We know about this area, store it
$o = new Echomail; $this->mo->save();
$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();
Log::info(sprintf('%s:= Echomail [%s] in [%s] from (%s) [%s] to (%s) - [%s].', Log::info(sprintf('%s:= Echomail [%s] in [%s] from (%s) [%s] to (%s) - [%s].',
self::LOGKEY, self::LOGKEY,
$this->msg->msgid, $this->mo->msgid,
$this->msg->echoarea, $this->mo->echoarea->name,
$this->msg->user_from,$this->msg->fftn, $this->mo->from,$this->mo->fftn->ftn,
$this->msg->user_to, $this->mo->to,
$o->id, $this->mo->id,
)); ));
// If the message is to a bot, but not rescanned, or purposely skipbot set, we'll process it // 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) { foreach (config('process.echomail') as $class) {
if ($class::handle($this->msg)) { if ($class::handle($this->mo)) {
break; break;
} }
} }

View File

@ -15,9 +15,9 @@ use Illuminate\Support\Facades\Storage;
use League\Flysystem\UnableToMoveFile; use League\Flysystem\UnableToMoveFile;
use App\Classes\File; use App\Classes\File;
use App\Classes\File\Item; use App\Classes\FTN\Packet;
use App\Classes\FTN\{InvalidPacketException,Packet}; use App\Exceptions\InvalidPacketException;
use App\Models\Address; use App\Models\{Domain,Echomail,Netmail};
use App\Notifications\Netmails\PacketPasswordInvalid; use App\Notifications\Netmails\PacketPasswordInvalid;
class PacketProcess implements ShouldQueue class PacketProcess implements ShouldQueue
@ -26,22 +26,26 @@ class PacketProcess implements ShouldQueue
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private Item $file; private string $filename;
private Address $ao; private Domain $do;
private Carbon $rcvd_time; 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->filename = $filename;
$this->ao = $ao; $this->do = $do;
$this->rcvd_time = $rcvd_time; $this->interactive = $interactive;
$this->rcvd_time = $rcvd_time ?: Carbon::now();
$this->nobot = $nobot;
} }
public function __get($key): mixed public function __get($key): mixed
{ {
switch ($key) { switch ($key) {
case 'subject': case 'subject':
return $this->file->name; return $this->filename;
default: default:
return NULL; return NULL;
@ -54,30 +58,47 @@ class PacketProcess implements ShouldQueue
*/ */
public function handle() 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')); $fs = Storage::disk(config('fido.local_disk'));
// @todo Catch files that we cannot process, eg: ARJ bundles. // @todo Catch files that we cannot process, eg: ARJ bundles.
try { try {
$f = new File($this->file->full_name); $f = new File($fs->path($this->filename));
$processed = FALSE; $processed = FALSE;
foreach ($f as $packet) { 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 // Check that the packet is from a system that is defined in the DB
if ($this->ao->system->addresses->search(function($item) use ($pkt) { return $item->id === $pkt->fftn_o->id; }) === FALSE) { if (! $pkt->fftn) {
Log::error(sprintf('%s:! Packet [%s] is not from this link? [%d]',self::LOGKEY,$pkt->fftn_o->ftn,$this->ao->system_id)); Log::error(sprintf('%s:! Packet [%s] is not from a system we know about? [%s]',self::LOGKEY,$this->filename,$pkt->fftn_t));
break; break;
} }
// Check the packet password if (! our_nodes($this->do)->contains($pkt->fftn)) {
if (strtoupper($this->ao->session('pktpass')) !== strtoupper($pkt->password)) { 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));
Log::error(sprintf('%s:! Packet from [%s] with password [%s] is invalid.',self::LOGKEY,$this->ao->ftn,$pkt->password));
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; break;
} }
@ -89,37 +110,34 @@ class PacketProcess implements ShouldQueue
$count = 0; $count = 0;
foreach ($pkt as $msg) { 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. if ($msg->errors) {
// @todo validate that the packet's zone is in the domain. Log::error(sprintf('%s:! Message [%s] has [%d] errors, unable to process',self::LOGKEY,$msg->msgid,$msg->errors->errors()->count()));
/* continue;
* // @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 $msg->set_sender = $pkt->fftn->withoutRelations();
* // @todo generate exception when echomail comes from a system not defined here // Record receiving packet and sender
* // @todo generate exception when echomail comes from a system doesnt exist $msg->set_pkt = $f->pktName();
* $msg->set_recvtime = $this->rcvd_time;
* // @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
*/
try { try {
// Dispatch job. // Dispatch job.
if ($queue) if ($queue || (! $this->interactive))
MessageProcess::dispatch($msg,$f->pktName(),$this->ao->withoutRelations(),$pkt->fftn_o->withoutRelations(),$this->rcvd_time); MessageProcess::dispatch($msg->withoutRelations(),$this->nobot);
else 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) { } 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())); 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()) if ($count === $pkt->count())
@ -127,42 +145,41 @@ class PacketProcess implements ShouldQueue
} }
if (! $processed) { 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 { } else {
// If we want to keep the packet, we could do that logic here // If we want to keep the packet, we could do that logic here
if (config('fido.packet_keep')) { 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')); $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 { try {
if ($fs->makeDirectory($dir)) { if ($fs->makeDirectory($dir)) {
$fs->move($this->file->rel_name,$x=sprintf('%s/%s',$dir,$this->file->pref_name)); $fs->move($this->filename,$x=sprintf('%s/%s',$dir,$f->itemName()));
Log::info(sprintf('%s:- Moved processed packet [%s] to [%s]',self::LOGKEY,$this->file->rel_name,$x)); Log::info(sprintf('%s:- Moved processed packet [%s] to [%s]',self::LOGKEY,$this->filename,$x));
} else } else
Log::error(sprintf('%s:! Unable to create dir [%s]',self::LOGKEY,$dir)); Log::error(sprintf('%s:! Unable to create dir [%s]',self::LOGKEY,$dir));
} catch (UnableToMoveFile $e) { } 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) { } 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 { } 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() $fs->delete($this->filename);
unlink($this->file->full_name);
} }
} }
} catch (InvalidPacketException $e) { } 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) { } 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) if ($passpos > 8)
Log::alert(sprintf('%s:! Password would be greater than 8 chars? [%d]',self::LOGKEY,$passpos)); 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)); $pkt = $this->getPacket($x,substr($x->last()->subject,0,$passpos));
if ($pkt && $pkt->count() && $update) if ($pkt && $pkt->count() && $update)
@ -1091,8 +1093,9 @@ class Address extends Model
* @param string|null $passwd Override password used in packet * @param string|null $passwd Override password used in packet
* @return Packet|null * @return Packet|null
* @throws \Exception * @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')); $s = Setup::findOrFail(config('app.id'));
$ao = our_address($this); $ao = our_address($this);
@ -1104,7 +1107,7 @@ class Address extends Model
} }
// Get packet type // Get packet type
$o = $ao->system->packet(); $o = $ao->system->packet($this);
$o->addressHeader($ao,$this,$passwd); $o->addressHeader($ao,$this,$passwd);
// $oo = Netmail/Echomail Model // $oo = Netmail/Echomail Model

View File

@ -10,54 +10,77 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Casts\{CollectionOrNull,CompressedString}; use App\Casts\{CollectionOrNull,CompressedString};
use App\Classes\FTN\Message;
use App\Interfaces\Packet; 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 final class Echomail extends Model implements Packet
{ {
use SoftDeletes,EncodeUTF8,MsgID,ParseAddresses,QueryCacheableConfig; use SoftDeletes,MessageAttributes,MsgID,ParseAddresses,QueryCacheableConfig;
private const LOGKEY = 'ME-'; 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 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 = [ protected $casts = [
'datetime' => 'datetime:Y-m-d H:i:s', 'datetime' => 'datetime:Y-m-d H:i:s',
'kludges' => CollectionOrNull::class, 'kludges' => CollectionOrNull::class,
'msg' => CompressedString::class, 'msg' => CompressedString::class,
'msg_src' => CompressedString::class, 'msg_src' => CompressedString::class,
'rogue_seenby' => CollectionOrNull::class, 'rogue_seenby' => CollectionOrNull::class,
'rogue_path' => CollectionOrNull::class, 'rogue_path' => CollectionOrNull::class, // @deprecated?
];
private const cast_utf8 = [
'to',
'from',
'subject',
'msg',
'msg_src',
'origin',
'tearline',
'tagline',
]; ];
public function __set($key,$value) public function __set($key,$value)
{ {
switch ($key) { 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 'no_export':
case 'set_path':
case 'set_pkt':
case 'set_sender':
case 'set_recvtime':
case 'set_seenby':
$this->{$key} = $value; $this->{$key} = $value;
break; 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: default:
parent::__set($key,$value); parent::__set($key,$value);
} }
@ -67,6 +90,12 @@ final class Echomail extends Model implements Packet
{ {
parent::boot(); 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) // @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) { static::created(function($model) {
$rogue = collect(); $rogue = collect();
@ -74,8 +103,10 @@ final class Echomail extends Model implements Packet
$path = collect(); $path = collect();
// Parse PATH // Parse PATH
if ($model->set_path->count()) if ($model->set->has('set_path'))
$path = self::parseAddresses('path',$model->set_path,$model->fftn->zone,$rogue); $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 // Make sure our sender is first in the path
if (! $path->contains($model->fftn_id)) { 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 // Make sure our pktsrc is last in the path
if (isset($model->set_sender) && (! $path->contains($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_sender)); Log::alert(sprintf('%s:? Echomail adding pktsrc to end of PATH [%s].',self::LOGKEY,$model->set->get('set_sender')->ftn));
$path->push($model->set_sender); $path->push($model->set->get('set_sender')->id);
} }
// Save the Path // 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) // @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 // Parse SEEN-BY
if ($model->set_seenby->count()) if ($model->set->has('set_seenby'))
$seenby = self::parseAddresses('seenby',$model->set_seenby,$model->fftn->zone,$rogue); $seenby = self::parseAddresses('seenby',$model->set->get('set_seenby'),$model->fftn->zone,$rogue);
// Make sure our sender is in the seenby // Make sure our sender is in the seenby
if (! $seenby->contains($model->fftn_id)) { 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 // Make sure our pktsrc is in the seenby
if (isset($model->set_sender) && (! $seenby->contains($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_sender)); Log::alert(sprintf('%s:? Echomail adding pktsrc to SEENBY [%s].',self::LOGKEY,$model->set->get('set_sender')->ftn));
$seenby->push($model->set_sender); $seenby->push($model->set->get('set_sender')->id);
} }
if (count($rogue)) { if (count($rogue)) {
@ -129,26 +160,26 @@ final class Echomail extends Model implements Packet
$model->seenby()->sync($seenby); $model->seenby()->sync($seenby);
// Our last node in the path is our sender // 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()) { if ($path->count()) {
DB::update('UPDATE echomail_path set recv_pkt=?,recv_at=? where address_id=? and echomail_id=?',[ DB::update('UPDATE echomail_path set recv_pkt=?,recv_at=? where address_id=? and echomail_id=?',[
$model->set_pkt, $model->set->get('set_pkt'),
$model->set_recvtime, $model->set->get('set_recvtime'),
$path->last(), $path->last(),
$model->id, $model->id,
]); ]);
} else { } 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. // See if we need to export this message.
if ($model->echoarea->sec_read) { if ($model->echoarea->sec_read) {
$exportto = ($x=$model $exportto = $model
->echoarea ->echoarea
->addresses ->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') ->pluck('id')
->diff($seenby); ->diff($seenby);
@ -193,90 +224,19 @@ final class Echomail extends Model implements Packet
->withPivot(['id','parent_id','recv_pkt','recv_at']); ->withPivot(['id','parent_id','recv_pkt','recv_at']);
} }
/* METHODS */ /* ATTRIBUTES */
public function init(): void public function getSeenByAttribute(): Collection
{ {
$this->set_path = collect(); return ((! $this->exists) && $this->set->has('set_seenby'))
$this->set_seenby = collect(); ? $this->set->get('set_seenby')
: $this->getRelationValue('seenby');
} }
public function jsonSerialize(): array public function getPathAttribute(): Collection
{ {
return $this->encode(); return ((! $this->exists) && $this->set->has('set_path'))
} ? $this->set->get('set_path')
: $this->getRelationValue('path');
/**
* 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();
} }
} }

View File

@ -10,35 +10,30 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Casts\CompressedString; use App\Casts\{CollectionOrNull,CompressedString};
use App\Classes\FTN\Message;
use App\Interfaces\Packet; 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 final class Netmail extends Model implements Packet
{ {
use SoftDeletes,MsgID,MessageAttributes;
private const LOGKEY = 'MN-'; 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; /**
* Kludges that we absorb in this model
private Collection $set_path; */
private Address $set_sender; private const kludges = [
private Carbon $set_recvtime; 'MSGID:'=>'msgid',
private string $set_pkt; 'REPLY:'=>'replyid',
'Via' => 'set_path',
private const cast_utf8 = [
'to',
'from',
'subject',
'msg',
'msg_src',
'origin',
'tearline',
'tagline',
]; ];
protected $casts = [ protected $casts = [
'datetime' => 'datetime:Y-m-d H:i:s', 'datetime' => 'datetime:Y-m-d H:i:s',
'kludges' => CollectionOrNull::class,
'msg' => CompressedString::class, 'msg' => CompressedString::class,
'msg_src' => CompressedString::class, 'msg_src' => CompressedString::class,
'sent_at' => 'datetime:Y-m-d H:i:s', '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) public function __set($key,$value)
{ {
switch ($key) { 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_pkt':
case 'set_recvtime': case 'set_recvtime':
case 'set_sender': 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; break;
default: default:
@ -63,43 +83,48 @@ final class Netmail extends Model implements Packet
{ {
parent::boot(); parent::boot();
static::creating(function($model) {
if (! is_null($model->errors))
throw new \Exception('Cannot save, validation errors exist');
});
static::created(function($model) { static::created(function($model) {
$nodes = collect(); $nodes = collect();
// Parse PATH // 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] // <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone] <Program Name> <Version> [Serial Number]
if (isset($model->set_path)) { if ($model->set->has('set_path')) {
if ($model->set_path->count()) { foreach ($model->set->get('set_path') as $line) {
foreach ($model->set_path as $line) { $m = [];
$m = [];
if (preg_match('/^([0-9]+:[0-9]+\/[0-9]+(\..*)?)\s+@([0-9.a-zA-Z]+)\s+(.*)$/',$line,$m)) { if (preg_match(self::PATH_REGEX,$line,$m)) {
// Address // Address
$ao = Address::findFTN($m[1]); // @todo Do we need to add a domain here, since the path line may not include one
$ao = Address::findFTN($m[1]);
// Time // Time
$t = []; $t = [];
$datetime = ''; $datetime = '';
if (! preg_match('/^([0-9]+\.[0-9]+)(\.?(.*))?$/',$m[3],$t)) if (! preg_match('/^([0-9]+\.[0-9]+)(\.?(.*))?$/',$m[3],$t))
Log::alert(sprintf('%s:! Unable to determine time from [%s]',self::LOGKEY,$m[3])); Log::alert(sprintf('%s:! Unable to determine time from [%s]',self::LOGKEY,$m[3]));
else else
$datetime = Carbon::createFromFormat('Ymd.His',$t[1],$t[3] ?? ''); $datetime = Carbon::createFromFormat('Ymd.His',$t[1],$t[3] ?? '');
if (! $ao) { if (! $ao) {
Log::alert(sprintf('%s:! Undefined Node [%s] for Netmail.',self::LOGKEY,$m[1])); Log::alert(sprintf('%s:! Undefined Node [%s] in netmail path.',self::LOGKEY,$m[1]));
//$rogue->push(['node'=>$m[1],'datetime'=>$datetime,'program'=>$m[4]]); //$rogue->push(['node'=>$m[1],'datetime'=>$datetime,'program'=>$m[4]]);
} else { } else {
$nodes->push(['node'=>$ao,'datetime'=>$datetime,'program'=>$m[4]]); $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 // Save the Path
@ -118,15 +143,25 @@ final class Netmail extends Model implements Packet
} }
// Our last node in the path is our sender // 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=?',[ DB::update('UPDATE netmail_path set recv_pkt=?,recv_at=?,recv_id=? where address_id=? and netmail_id=?',[
$model->set_pkt, $model->set->get('set_pkt'),
$model->set_recvtime, $model->set->get('set_recvtime'),
$model->set_sender->id, $model->set->get('set_sender')->id,
Arr::get($nodes->last(),'node')->id, Arr::get($nodes->last(),'node')->id,
$model->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() public function path()
{ {
return $this->belongsToMany(Address::class,'netmail_path') return $this->belongsToMany(Address::class,'netmail_path')
->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 received()
{
return $this->belongsToMany(Address::class,'netmail_path','netmail_id','recv_id')
->withPivot(['id','parent_id','datetime','program','recv_pkt','recv_id']);
} }
public function tftn() public function tftn()
@ -158,89 +188,41 @@ final class Netmail extends Model implements Packet
->withTrashed(); ->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 */ /* 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 return sprintf('%s @%s.UTC %s',
$o = new Message; $ao->ftn3d,
$ao->pivot->datetime->format('Ymd.His'),
try { $ao->pivot->program);
$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();
} }
} }

View File

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

View File

@ -41,6 +41,6 @@ class EchomailChannel
$o = $notification->toEchomail($notifiable); $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); $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); $ao = $notifiable->routeNotificationFor(static::via);
$o = new Netmail; $o = new Netmail;
$o->set_sender = our_address($ao);
$o->to = $ao->system->sysop; $o->to = $ao->system->sysop;
$o->from = Setup::PRODUCT_NAME; $o->from = Setup::PRODUCT_NAME;
$o->datetime = Carbon::now(); $o->datetime = Carbon::now();
$o->tzoffset = $o->datetime->utcOffset(); $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->tftn_id = $ao->id;
$o->flags = (Message::FLAG_LOCAL|Message::FLAG_PRIVATE); $o->flags = (Message::FLAG_LOCAL|Message::FLAG_PRIVATE);
$o->cost = 0; $o->cost = 0;

View File

@ -7,7 +7,7 @@ use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Message; use App\Classes\FTN\Message;
use App\Notifications\Netmails; use App\Notifications\Netmails;
use App\Models\{Netmail,System}; use App\Models\{Echomail,Netmail};
use App\Traits\{MessagePath,PageTemplate}; use App\Traits\{MessagePath,PageTemplate};
class EchoareaNotExist extends Netmails class EchoareaNotExist extends Netmails
@ -16,14 +16,14 @@ class EchoareaNotExist extends Netmails
private const LOGKEY = 'NNW'; 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. * Send a sysop a message if they attempt to write to an area that doesnt exist.
* *
* @param Message $mo * @param Message $mo
*/ */
public function __construct(Message $mo) public function __construct(Echomail $mo)
{ {
parent::__construct(); 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)); 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 // Message
$msg = $this->page(FALSE,'nothere'); $msg = $this->page(FALSE,'nothere');
@ -52,7 +52,7 @@ class EchoareaNotExist extends Netmails
$msg->addText( $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", 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->msgid,
$this->mo->user_to, $this->mo->to,
Carbon::now()->utc()->toDateTimeString(), Carbon::now()->utc()->toDateTimeString(),
$this->mo->date->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; namespace App\Traits;
use App\Classes\FTN\Message; use App\Models\{Echomail,Netmail};
trait MessagePath 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 = "This is your original message:\r\r";
$reply .= "+--[ BEGIN MESSAGE ]----------------------------------+\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 .= 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 .= "+--[ CONTROL LINES ]----------------------------------+\r";
$reply .= sprintf("DATE: %s\r",$mo->date->format('Y-m-d H:i:s')); $reply .= sprintf("DATE: %s\r",$mo->date->format('Y-m-d H:i:s'));
$reply .= sprintf("MSGID: %s\r",$mo->msgid); $reply .= sprintf("MSGID: %s\r",$mo->msgid);
foreach ($mo->kludge as $k=>$v) foreach ($mo->kludges as $k=>$v)
$reply .= sprintf("@%s: %s\r",strtoupper($k),$v); $reply .= sprintf("%s %s\r",$k,$v);
$reply .= "+--[ PATH ]-------------------------------------------+\r"; $reply .= "+--[ PATH ]-------------------------------------------+\r";
if ($mo->isNetmail()) { if ($mo instanceof Netmail) {
if ($mo->via->count()) if ($mo->via->count())
foreach ($mo->via as $via) foreach ($mo->via as $ao)
$reply .= sprintf("VIA: %s\r",$via); $reply .= sprintf("VIA: %s\r",$mo->via($ao));
else else
$reply .= "No path information? This would be normal if this message came directly to the hub\r"; $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"], "keywords": ["framework","laravel"],
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^8.1|8.2|8.3", "php": "^8.2|8.3",
"ext-bz2": "*", "ext-bz2": "*",
"ext-pcntl": "*", "ext-pcntl": "*",
"ext-sockets": "*", "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') @extends('layouts.app')
@section('htmlheader_title') @section('htmlheader_title')
Verify Packet Verify Packet
@ -82,7 +87,7 @@
<div class="col-12"> <div class="col-12">
<h4 class="accordion-header"> <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"> <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> </span>
</h4> </h4>
@ -98,48 +103,40 @@
<div class="row pb-2"> <div class="row pb-2">
<div class="col-4"> <div class="col-4">
DATE: <strong class="highlight">{{ $msg->date }}</strong> DATE: <strong class="highlight">{{ $msg->datetime }}</strong>
</div> </div>
<div class="col-4"> <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> </div>
<div class="row pb-2"> <div class="row pb-2">
<div class="col-4"> <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>
<div class="col-4"> <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> </div>
<div class="row pb-2"> <div class="row pb-2">
<div class="col-8"> <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> </div>
<div class="row pb-2"> <div class="row pb-2">
<div class="col-8"> <div class="col-8">
<div class="pad pb-0"> <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> </div>
</div> </div>
@if($msg->tagline) @if($msg instanceof Netmail)
<div class="row pb-2"> <div class="row pb-2">
<div class="col-8"> <div class="col-8">
TAGLINE: <br><strong class="highlight">{{ $msg->tagline }}</strong> VIA: <br><strong class="highlight">{!! $msg->path->join('</strong> -> <strong class="highlight">') !!}</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>
</div> </div>
</div> </div>
@else @else
@ -159,7 +156,7 @@
<div class="row pb-2"> <div class="row pb-2">
<div class="col-8"> <div class="col-8">
<strong>KLUDGES:</strong> <br> <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> <strong class="highlight">{{ $k }}</strong> {{ $v }}<br>
@endforeach @endforeach
</div> </div>

View File

@ -1,10 +1,11 @@
@php @php
use App\Classes\FTN\Message; use App\Classes\FTN\Message;
use App\Models\{Echomail,Netmail};
@endphp @endphp
<div class="row"> <div class="row">
<div class="col-4"> <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>
<div class="col-4"> <div class="col-4">
DATE: <strong class="highlight">{{ $msg->datetime->format('Y-m-d H:i:s') }}</strong> 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>) FROM: <strong class="highlight">{!! Message::tr($msg->from) !!}</strong> (<strong class="highlight">{{ $msg->fftn->ftn }}</strong>)
</div> </div>
<div class="col-4"> <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> </div>
<div class="row pt-1 pb-2"> @if($msg->flags()->count())
<div class="col-4"> <div class="row pt-1">
SUBJECT: <strong class="highlight">{!! Message::tr($msg->subject) !!}</strong> <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> </div>
@if ($msg instanceof \App\Models\Echomail) @endif
<div class="col-4">
@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>) ECHOAREA: <strong class="highlight">{{ $msg->echoarea->name }}</strong> (<strong class="highlight">{{ $msg->echoarea->domain->name }}</strong>)
</div> </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>
<div class="row pb-2"> <div class="row pb-2">
@ -39,63 +51,63 @@ use App\Classes\FTN\Message;
</div> </div>
</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="row pb-2">
<div class="col-8"> <div class="col-8">
SEENBY: <br><strong class="highlight">{!! $msg->seenby->pluck('ftn2d')->join('</strong>, <strong class="highlight">') !!}</strong> SEENBY: <br><strong class="highlight">{!! $msg->seenby->pluck('ftn2d')->join('</strong>, <strong class="highlight">') !!}</strong>
</div> </div>
@if ($msg->rogue_seenby->count()) @if($msg->rogue_seenby->count())
<br><small>[<strong>NOTE</strong>: Some seen-by values couldnt be identified - ({{ $msg->rogue_seenby->join(',') }})]</small> <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 @endif
</div> </div>
@endif @endif
@if ($msg->flags & Message::FLAG_LOCAL) <!-- @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="row pb-2">
<div class="col-8"> <div class="col-8">
<strong class="highlight">Local message</strong> PATH: <br><strong class="highlight">{!! $msg->pathorder()->join('</strong> -> <strong class="highlight">') !!}</strong>
</div>
@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>
</div>
@elseif ((! $msg->flags) || ($msg->flags & (Message::FLAG_INTRANSIT|Message::FLAG_RECD))) <div class="row pb-2">
<!-- @todo for the nodes we export to, highlight those that we have actually sent it, vs those that havent received it yet --> <div class="col-8">
<div class="row pb-2"> @if($msg instanceof Netmail)
<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">
RECEIVED:<br> RECEIVED:<br>
@if ($msg instanceof \App\Models\Netmail) @foreach ($msg->path as $path)
@foreach ($msg->received as $path) <strong class="highlight">{{ $path->pivot->recv_pkt }}</strong> from <strong class="highlight">{{ $path->ftn }}</strong> {{ $msg->created_at }}
<strong class="highlight">{{ $path->pivot->recv_pkt }}</strong> from <strong class="highlight">{{ $path->ftn }}</strong> {{ $msg->created_at }} @endforeach
@endforeach @elseif ($msg instanceof Echomail)
@elseif ($msg instanceof \App\Models\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 }} <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 @endif
</div>
</div> </div>
@endif </div>
@section('page-scripts') @section('page-scripts')
<script type="text/javascript" src="{{ asset('ansilove/ansilove.js') }}"></script> <script type="text/javascript" src="{{ asset('ansilove/ansilove.js') }}"></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(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; retina = window.devicePixelRatio > 1;
AnsiLove.renderBytes( AnsiLove.renderBytes(
msg, msg,
function (canvas, sauce) { function (canvas, sauce) {
console.log(canvas);
document.getElementById("canvas").appendChild(canvas); document.getElementById("canvas").appendChild(canvas);
}, },
{'font': '80x25', 'bits': 8, 'icecolors': 0, 'columns': 80} {'font': '80x25', 'bits': 8, 'icecolors': 0, 'columns': 80}