From 612efda945c4e5feb27e7224cc3244823331442f Mon Sep 17 00:00:00 2001 From: Deon George Date: Wed, 20 Sep 2023 20:29:23 +1000 Subject: [PATCH] Process packet seenby/path/via lines when saving echomail/netmail --- app/Classes/FTN/Message.php | 148 ++---------------- app/Classes/FTN/Packet.php | 58 +++++-- app/Console/Commands/PacketInfo.php | 8 +- app/Console/Commands/PacketProcess.php | 7 +- app/Jobs/MessageProcess.php | 50 ++++-- app/Jobs/PacketProcess.php | 2 +- app/Models/Echomail.php | 36 +++-- app/Models/Netmail.php | 85 ++++++---- .../Netmails/EchomailBadAddress.php | 75 +++++++++ app/Traits/ParseAddresses.php | 66 ++++++++ tests/Feature/PacketTest.php | 32 ++-- 11 files changed, 337 insertions(+), 230 deletions(-) create mode 100644 app/Notifications/Netmails/EchomailBadAddress.php create mode 100644 app/Traits/ParseAddresses.php diff --git a/app/Classes/FTN/Message.php b/app/Classes/FTN/Message.php index 74cecfb..bdea307 100644 --- a/app/Classes/FTN/Message.php +++ b/app/Classes/FTN/Message.php @@ -11,8 +11,7 @@ use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Validator as ValidatorResult; use App\Classes\FTN as FTNBase; -use App\Http\Controllers\DomainController; -use App\Models\{Address,Domain,System,Zone}; +use App\Models\{Address,Domain,Zone}; use App\Rules\{TwoByteInteger,TwoByteIntegerWithZero}; use App\Traits\EncodeUTF8; @@ -138,10 +137,7 @@ class Message extends FTNBase private Collection $rescanned; // Message was created as a result of a rescan private Collection $path; // FTS-0004.001 The message PATH lines - private Collection $pathaddress; // Collection of Addresses after parsing seenby - private Collection $rogue_seenby; // Collection of FTNs in the Seen-by that are not defined private Collection $seenby; // FTS-0004.001 The message SEEN-BY lines - private Collection $seenaddress; // Collection of Addresses after parsing seenby private Collection $via; // The path the message has gone using Via lines (Netmail) private Collection $unknown; // Temporarily hold attributes we have no logic for. @@ -224,10 +220,7 @@ class Message extends FTNBase $this->kludge = collect(); $this->rescanned = collect(); $this->path = collect(); - $this->rogue_seenby = collect(); $this->seenby = collect(); - $this->seenaddress = collect(); - $this->pathaddress = collect(); $this->via = collect(); $this->unknown = collect(); } @@ -364,9 +357,6 @@ class Message extends FTNBase case 'rescanned': case 'path': case 'seenby': - case 'pathaddress': - case 'rogue_seenby': - case 'seenaddress': case 'unknown': case 'via': @@ -556,6 +546,7 @@ class Message extends FTNBase } $ptr = 0; + // To User $o->user_to = strstr(substr($msg,self::HEADER_LEN+$ptr),"\x00",TRUE); $ptr += strlen($o->user_to)+1; @@ -670,77 +661,6 @@ class Message extends FTNBase return ($this->flags & $flag); } - /** - * Parse the Seenby/path lines and return a collection of addresses - * - * @param string $type Type of address, ie: seenby/path - * @param Collection $addresses - * @param Collection $rogue - * @return Collection - * @throws \Exception - */ - private function parseAddresses(string $type,Collection $addresses,Collection &$rogue): Collection - { - $nodes = collect(); - - $net = NULL; - foreach ($addresses as $line) { - foreach (explode(' ',$line) as $item) { - if (($x=strpos($item,'/')) !== FALSE) { - $net = (int)substr($item,0,$x); - $node = (int)substr($item,$x+1); - - } else { - $node = (int)$item; - } - - $aoid = NULL; - - // If domain should be flattened, look for node regardless of zone (within the list of zones for the domain) - if ($this->fdomain && $this->fdomain->flatten) { - $ao = Address::findZone($this->fdomain,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX,0); - $ftn = sprintf('%d:%d/%d@%s',$this->fz,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX,$this->fdomain->name); - - $aoid = $ao?->id; - - } elseif ($this->fdomain) { - $ftn = sprintf('%d:%d/%d@%s',$this->fz,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX,$this->fdomain->name); - - } else { - $ftn = sprintf('%d:%d/%d',$this->fz,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX); - } - - if (! $aoid) { - if (! ($ao=Address::findFTN($ftn))) { - Log::alert(sprintf('%s:! Undefined Node [%s] in [%s] - auto created.',self::LOGKEY,$ftn,$type)); - - $ao = Address::createFTN($ftn,System::createUnknownSystem()); - } - - $aoid = $ao?->id; - } - - switch ($type) { - case 'seenby': - if (! $ao) { - $rogue->push(sprintf('%d:%d/%d',$this->fz,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX)); - continue 2; - } - - case 'path': - if (! $aoid) - throw new \Exception(sprintf('Didnt get an address for [%s]',$ftn)); - - break; - } - - $nodes->push($aoid); - } - } - - return $nodes; - } - /** * Process the data after the ORIGIN * There may be kludge lines after the origin - notably SEEN-BY @@ -785,48 +705,6 @@ class Message extends FTNBase } } - /** - * Parse the via address and return a collection of addresses - * - * @YYYYMMDD.HHMMSS[.Precise][.Time Zone] [Serial Number] - * - * @param Collection $via - * @return Collection - * @throws \Exception - */ - private function parseVia(Collection $via): Collection - { - $nodes = collect(); - - foreach ($via as $line) { - $m = []; - - if (preg_match('/^([0-9]+:[0-9]+\/[0-9]+(\..*)?)\s+@([0-9.a-zA-Z]+)\s+(.*)$/',$line,$m)) { - // Address - $ao = Address::findFTN($m[1]); - - // Time - $t = []; - $datetime = ''; - - if (! preg_match('/^([0-9]+\.[0-9]+)(\.?(.*))?$/',$m[3],$t)) - Log::alert(sprintf('%s:! Unable to determine time from [%s]',self::LOGKEY,$m[3])); - else - $datetime = Carbon::createFromFormat('Ymd.His',$t[1],$t[3] ?? ''); - - if (! $ao) { - Log::alert(sprintf('%s:! Undefined Node [%s] for Netmail.',self::LOGKEY,$m[1])); - //$rogue->push(['node'=>$m[1],'datetime'=>$datetime,'program'=>$m[4]]); - - } else { - $nodes->push(['node'=>$ao,'datetime'=>$datetime,'program'=>$m[4]]); - } - } - } - - return $nodes; - } - /** * Extract information out of the message text. * @@ -1009,19 +887,6 @@ class Message extends FTNBase } elseif ($this->zone) { $this->src = Address::parseFTN(sprintf('%d:%d/%d.%d@%s',$this->zone->zone_id,$this->fn,$this->ff,$this->fp,$this->zone->domain->name)); } - - // Parse SEEN-BY - if ($this->seenby->count()) - $this->seenaddress = $this->parseAddresses('seenby',$this->seenby,$this->rogue_seenby); - - $dummy = collect(); - // Parse PATH - if ($this->path->count()) - $this->pathaddress = $this->parseAddresses('path',$this->path,$dummy); - - // Parse VIA - if ($this->via->count()) - $this->pathaddress = $this->parseVia($this->via); } /** @@ -1061,6 +926,15 @@ class Message extends FTNBase ]); $validator->after(function($validator) { + if ($this->zone->domain->flatten) { + if (! $this->zone->domain->zones->pluck('zone_id')->contains($this->fz)) + $validator->errors()->add('invalid-zone',sprintf('Message zone [%d] doesnt match any zone in domain for packet zone [%d].',$this->fz,$this->zone->zone_id)); + + } else { + if ($this->zone->zone_id !== $this->fz) + $validator->errors()->add('invalid-zone',sprintf('Message zone [%d] doesnt match packet zone [%d].',$this->fz,$this->zone->zone_id)); + } + if (! $this->fboss_o) $validator->errors()->add('from',sprintf('Undefined Node [%s] sent message.',$this->fboss)); if (! $this->tboss_o) diff --git a/app/Classes/FTN/Packet.php b/app/Classes/FTN/Packet.php index d8499b7..8d75c91 100644 --- a/app/Classes/FTN/Packet.php +++ b/app/Classes/FTN/Packet.php @@ -5,12 +5,13 @@ namespace App\Classes\FTN; use Carbon\Carbon; use Illuminate\Support\Arr; use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Notification; use Symfony\Component\HttpFoundation\File\File; use App\Classes\FTN as FTNBase; -use App\Models\{Address,Software,System,Zone}; +use App\Models\{Address,Domain,Software,System,Zone}; +use App\Notifications\Netmails\EchomailBadAddress; /** * Represents a Fidonet Packet, that contains an array of messages. @@ -172,11 +173,11 @@ class Packet extends FTNBase implements \Iterator, \Countable * @param mixed $f * @param string $name * @param int $size - * @param System|null $system + * @param Domain|null $domain * @return Packet * @throws InvalidPacketException */ - public static function process(mixed $f,string $name,int $size,System $system=NULL): self + public static function process(mixed $f,string $name,int $size,Domain $domain=NULL): self { Log::debug(sprintf('%s:+ Opening Packet [%s] with size [%d]',self::LOGKEY,$name,$size)); @@ -223,11 +224,29 @@ class Packet extends FTNBase implements \Iterator, \Countable else if (! strlen($x)) throw new InvalidPacketException('No message in packet: '.bin2hex($x)); - $o->zone = $system?->zones->firstWhere('zone_id',$o->fz); + // Work out the packet zone + if ($o->fz && ($o->fd || $domain)) { + $o->zone = Zone::select('zones.*') + ->join('domains',['domains.id'=>'zones.domain_id']) + ->where('zone_id',$o->fz) + ->where('name',$o->fd ?: $domain->name) + ->single(); - // If zone is null, we'll take the zone from the packet - if (! $o->zone) - $o->zone = Zone::where('zone_id',$o->fz)->where('default',TRUE)->single(); + // We need not knowing the domain, we use the default zone + } else { + $o->zone = Zone::where('zone_id',$o->fz) + ->where('default',TRUE) + ->single(); + } + + // If zone is not set, then we need to use a default zone - the messages may not be from this zone. + if (! $o->zone) { + 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) + ->where('default',TRUE) + ->singleOrFail(); + } $buf_ptr = 0; $message = ''; @@ -415,15 +434,27 @@ class Packet extends FTNBase implements \Iterator, \Countable */ private function parseMessage(string $message): void { - Log::info(sprintf('%s:+ Processing message [%d] bytes',self::LOGKEY,strlen($message))); + Log::info(sprintf('%s:+ Processing packet message [%d] bytes',self::LOGKEY,strlen($message))); $msg = Message::parseMessage($message,$this->zone); + // If the message from domain is different to the packet address domain, we'll skip this message + // If the message is invalid, we'll ignore it if ($msg->errors) { Log::info(sprintf('%s:- Message [%s] has errors',self::LOGKEY,$msg->msgid)); - // If the from address doenst exist, we'll create a new entry + // If the messages is not for the right zone, we'll ignore it + if ($msg->errors->messages()->has('invalid-zone')) { + Log::alert(sprintf('%s:! Message is from an invalid zone [%s], packet is from [%s] - ignoring it',self::LOGKEY,$msg->fftn,$msg->zone->domain->name)); + + if (! $msg->rescanned->count()) + Notification::route('netmail',$this->fftn_o)->notify(new EchomailBadAddress($msg)); + + return; + } + + // If the to address doenst exist, we'll create a new entry if ($msg->errors->messages()->has('to') && $msg->tzone) { try { // @todo Need to work out the correct region for the host_id @@ -450,15 +481,13 @@ class Packet extends FTNBase implements \Iterator, \Countable $ao->role = Address::NODE_UNKNOWN; $so = System::createUnknownSystem(); - // @todo Remove this debugging line - if ($so->id !== 443) - Log::alert(sprintf('%s:? Just created Discovered System for MSGID [%s] A',self::LOGKEY,$msg->msgid)); $so->addresses()->save($ao); Log::alert(sprintf('%s:- To FTN is not defined, creating new entry for [%s] (%d)',self::LOGKEY,$msg->tboss,$ao->id)); } + // If the from address doenst exist, we'll create a new entry if ($msg->errors->messages()->has('from') && $msg->tzone) { try { // @todo Need to work out the correct region for the host_id @@ -485,9 +514,6 @@ class Packet extends FTNBase implements \Iterator, \Countable $ao->role = Address::NODE_UNKNOWN; $so = System::createUnknownSystem(); - // @todo Remvoe this debugging line - if ($so->id !== 443) - Log::alert(sprintf('%s:? Just created Discovered System for MSGID [%s] B',self::LOGKEY,$msg->msgid)); $so->addresses()->save($ao); diff --git a/app/Console/Commands/PacketInfo.php b/app/Console/Commands/PacketInfo.php index 0e46d48..40c94fc 100644 --- a/app/Console/Commands/PacketInfo.php +++ b/app/Console/Commands/PacketInfo.php @@ -6,7 +6,7 @@ use Illuminate\Console\Command; use App\Classes\File; use App\Classes\FTN\Packet; -use App\Models\System; +use App\Models\Address; class PacketInfo extends Command { @@ -17,7 +17,7 @@ class PacketInfo extends Command */ protected $signature = 'packet:info' .' {file : Packet to process}' - .' {system? : System the packet is from}'; + .' {ftn? : FTN the packet is from}'; /** * The console command description. @@ -35,10 +35,10 @@ class PacketInfo extends Command public function handle() { $f = new File($this->argument('file')); - $s = $this->argument('system') ? System::where('name',$this->argument('system'))->singleOrFail() : NULL; + $a = $this->argument('ftn') ? Address::findFTN($this->argument('ftn')) : NULL; foreach ($f as $packet) { - $pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$s); + $pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$a->zone->domain); $this->alert(sprintf('File Name: %s',$x)); diff --git a/app/Console/Commands/PacketProcess.php b/app/Console/Commands/PacketProcess.php index 353e8ab..77ad218 100644 --- a/app/Console/Commands/PacketProcess.php +++ b/app/Console/Commands/PacketProcess.php @@ -34,6 +34,7 @@ class PacketProcess extends Command * * @return mixed * @throws \App\Classes\FTN\InvalidPacketException + * @todo Should this just call PacketProcess instead? */ public function handle() { @@ -41,14 +42,12 @@ class PacketProcess extends Command $a = Address::findFTN($this->argument('ftn')); foreach ($f as $packet) { - foreach (Packet::process($packet,$f->itemName(),$f->itemSize(),$a->system) as $msg) { + 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. - // @todo validate that the packet's zone is in the domain. - $this->info(sprintf('Processing message from [%s] with msgid [%s] in (%s)',$msg->fboss,$msg->msgid,$f->pktName())); // Dispatch job. - Job::dispatchSync($msg,$f->pktName(),$a,$a,Carbon::now(),$this->option('nobot')); + Job::dispatchSync($msg,$f->pktName(),$a,$pkt->fftn_o,Carbon::now(),$this->option('nobot')); } } } diff --git a/app/Jobs/MessageProcess.php b/app/Jobs/MessageProcess.php index 697dce9..a22ea0e 100644 --- a/app/Jobs/MessageProcess.php +++ b/app/Jobs/MessageProcess.php @@ -8,18 +8,20 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Notification; use App\Classes\FTN\Message; use App\Models\{Address,Echoarea,Echomail,Netmail,Setup,User}; use App\Notifications\Netmails\{EchoareaNotExist,EchoareaNotSubscribed,EchoareaNoWrite,NetmailForward,Reject}; +use App\Traits\ParseAddresses; class MessageProcess implements ShouldQueue { private const LOGKEY = 'JMP'; - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable,InteractsWithQueue,Queueable,SerializesModels,ParseAddresses; private Address $sender; private Message $msg; @@ -125,7 +127,7 @@ class MessageProcess implements ShouldQueue $o->set_pkt = $this->packet; $o->set_sender = $this->sender; - $o->set_path = $this->msg->pathaddress; + $o->set_path = $this->msg->via; $o->set_recvtime = $this->recvtime; // Strip any local/transit flags $o->flags &= ~(Message::FLAG_LOCAL|Message::FLAG_INTRANSIT); @@ -245,11 +247,13 @@ class MessageProcess implements ShouldQueue 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]', + Log::alert(sprintf('%s:! The message [%s] is from a different zone [%d] than the packet sender [%d] - not importing', self::LOGKEY, $this->msg->msgid, $this->msg->fboss_o->zone->zone_id, $this->pktsrc->zone->zone_id)); + + return; } // Check for duplicate messages @@ -257,7 +261,8 @@ class MessageProcess implements ShouldQueue if ($this->msg->msgid) { $o = Echomail::where('msgid',$this->msg->msgid) ->where('fftn_id',($x=$this->msg->fboss_o) ? $x->id : NULL) - ->where('datetime','>',Carbon::now()->subYears(3)) + ->where('datetime','>=',$this->msg->date->subYears(3)) + ->where('datetime','<=',$this->msg->date) ->single(); Log::debug(sprintf('%s:- Checking for duplicate from host id [%d].',self::LOGKEY,($x=$this->msg->fboss_o) ? $x->id : NULL)); @@ -276,12 +281,32 @@ class MessageProcess implements ShouldQueue $o->save(); - // If the path is empty, then its probably because of the previous bug, we'll replace it. // @todo This duplicate message may have gone via a different path, be nice to record it. - //$o->path()->sync($o->path->pluck('id')->merge($this->msg->pathaddress)->toArray()); + // If we didnt get the path on the original message, we'll override it + if (! $o->path->count()) { + $dummy = collect(); + $path = $this->parseAddresses('path',$this->msg->path,$this->pktsrc->zone,$dummy); + + $ppoid = NULL; + foreach ($path as $aoid) { + + $po = DB::select('INSERT INTO echomail_path (echomail_id,address_id,parent_id) VALUES (?,?,?) RETURNING id',[ + $o->id, + $aoid, + $ppoid, + ]); + + $ppoid = $po[0]->id; + } + } + // @todo if we have an export for any of the seenby addresses, remove it - // @todo add received packet details - $o->seenby()->sync($o->seenby->pluck('id')->merge($this->msg->seenaddress)->filter()->toArray()); + $seenby = $this->parseAddresses('seenby',$this->msg->seenby,$this->pktsrc->zone,$o->rogue_seenby); + $x = $o->seenby()->syncWithoutDetaching($seenby); + + // In case our rogue_seenby changed + if ($o->getDirty()) + $o->save(); return; } @@ -305,7 +330,7 @@ class MessageProcess implements ShouldQueue // @todo Can the sender create it if it doesnt exist? // Can the system send messages to this area? if (! $ea->sec_write || ($this->pktsrc->security < $ea->sec_write)) { - Log::alert(sprintf('%s:! FTN [%s] is not allowed to post [%s] to [%s].',self::LOGKEY,$this->pktsrc,$this->msg->msgid,$ea->name)); + Log::alert(sprintf('%s:! FTN [%s] is not allowed to post [%s] to [%s].',self::LOGKEY,$this->pktsrc->ftn,$this->msg->msgid,$ea->name)); if (! $this->msg->rescanned->count()) Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNoWrite($this->msg)); @@ -314,7 +339,7 @@ class MessageProcess implements ShouldQueue // If the node is not subscribed if ($this->pktsrc->echoareas->search(function($item) use ($ea) { return $item->id === $ea->id; }) === FALSE) { - Log::alert(sprintf('%s:! FTN [%s] is not subscribed to [%s] for [%s].',self::LOGKEY,$this->pktsrc,$ea->name,$this->msg->msgid)); + Log::alert(sprintf('%s:! FTN [%s] is not subscribed to [%s] for [%s].',self::LOGKEY,$this->pktsrc->ftn,$ea->name,$this->msg->msgid)); if (! $this->msg->rescanned->count()) Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNotSubscribed($this->msg)); @@ -338,9 +363,8 @@ class MessageProcess implements ShouldQueue $o->msg = $this->msg->message_src."\r"; $o->msg_src = $this->msg->message_src; $o->msg_crc = md5($this->msg->message); - $o->rogue_seenby = $this->msg->rogue_seenby; - $o->set_path = $this->msg->pathaddress; - $o->set_seenby = $this->msg->seenaddress; + $o->set_path = $this->msg->path; + $o->set_seenby = $this->msg->seenby; $o->set_recvtime = $this->recvtime; // Record receiving packet and sender $o->set_pkt = $this->packet; diff --git a/app/Jobs/PacketProcess.php b/app/Jobs/PacketProcess.php index f66e3c9..69c8151 100644 --- a/app/Jobs/PacketProcess.php +++ b/app/Jobs/PacketProcess.php @@ -59,7 +59,7 @@ class PacketProcess implements ShouldQueue $processed = FALSE; foreach ($f as $packet) { - $pkt = Packet::process($packet,Arr::get(stream_get_meta_data($packet),'uri'),$f->itemSize(),$this->ao->system); + $pkt = Packet::process($packet,Arr::get(stream_get_meta_data($packet),'uri'),$f->itemSize(),$this->ao->zone->domain); // Check the messages are from the uplink if ($this->ao->system->addresses->search(function($item) use ($pkt) { return $item->id === $pkt->fftn_o->id; }) === FALSE) { diff --git a/app/Models/Echomail.php b/app/Models/Echomail.php index 37c9994..30fe653 100644 --- a/app/Models/Echomail.php +++ b/app/Models/Echomail.php @@ -13,11 +13,11 @@ use Rennokki\QueryCache\Traits\QueryCacheable; use App\Casts\{CollectionOrNull,CompressedString}; use App\Classes\FTN\Message; use App\Interfaces\Packet; -use App\Traits\{EncodeUTF8,MsgID}; +use App\Traits\{EncodeUTF8,MsgID,ParseAddresses}; final class Echomail extends Model implements Packet { - use SoftDeletes,EncodeUTF8,MsgID,QueryCacheable; + use SoftDeletes,EncodeUTF8,MsgID,QueryCacheable,ParseAddresses; private const LOGKEY = 'ME-'; private Collection $set_seenby; @@ -68,17 +68,17 @@ final class Echomail extends Model implements Packet // @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) { - if (! $model->echoarea_id) { - Log::alert(sprintf('%s:- Message has no echoarea, not exporting',self::LOGKEY,$model->id)); - return; - } + $rogue = collect(); + $seenby = NULL; + $path = []; - // Save the seenby - $model->seenby()->sync($model->set_seenby); + // Parse PATH + if ($model->set_path->count()) + $path = self::parseAddresses('path',$model->set_path,$model->fftn->zone,$rogue); // Save the Path $ppoid = NULL; - foreach ($model->set_path as $aoid) { + foreach ($path as $aoid) { $po = DB::select('INSERT INTO echomail_path (echomail_id,address_id,parent_id) VALUES (?,?,?) RETURNING id',[ $model->id, $aoid, @@ -88,12 +88,26 @@ final class Echomail extends Model implements Packet $ppoid = $po[0]->id; } + $rogue = collect(); + + // Parse SEEN-BY + if ($model->set_seenby->count()) + $seenby = self::parseAddresses('seenby',$model->set_seenby,$model->fftn->zone,$rogue); + + if (count($rogue)) { + $model->rogue_seenby = $rogue; + $model->save(); + } + + if ($seenby) + $model->seenby()->sync($seenby); + // Our last node in the path is our sender if (isset($model->set_pkt) && isset($model->set_recvtime)) { DB::update('UPDATE echomail_path set recv_pkt=?,recv_at=? where address_id=? and echomail_id=?',[ $model->set_pkt, $model->set_recvtime, - $model->set_path->last(), + $path->last(), $model->id, ]); } @@ -105,7 +119,7 @@ final class Echomail extends Model implements Packet ->addresses ->filter(function($item) use ($model) { return $item->security >= $model->echoarea->sec_read; })) ->pluck('id') - ->diff($model->set_seenby); + ->diff($seenby); if ($exportto->count()) { if ($model->no_export) { diff --git a/app/Models/Netmail.php b/app/Models/Netmail.php index 3dd0fcc..fbff429 100644 --- a/app/Models/Netmail.php +++ b/app/Models/Netmail.php @@ -15,8 +15,6 @@ use App\Classes\FTN\Message; use App\Interfaces\Packet; use App\Traits\{EncodeUTF8,MsgID}; -// @deprecated recv_pkt now in netmail_path -// @deprecated local - use flags final class Netmail extends Model implements Packet { private const LOGKEY = 'MN-'; @@ -66,37 +64,68 @@ final class Netmail extends Model implements Packet parent::boot(); static::created(function($model) { + $nodes = collect(); + + // Parse PATH + // @YYYYMMDD.HHMMSS[.Precise][.Time Zone] [Serial Number] + if (isset($model->set_path)) { + if ($model->set_path->count()) { + foreach ($model->set_path as $line) { + $m = []; + + if (preg_match('/^([0-9]+:[0-9]+\/[0-9]+(\..*)?)\s+@([0-9.a-zA-Z]+)\s+(.*)$/',$line,$m)) { + // Address + $ao = Address::findFTN($m[1]); + + // Time + $t = []; + $datetime = ''; + + if (! preg_match('/^([0-9]+\.[0-9]+)(\.?(.*))?$/',$m[3],$t)) + Log::alert(sprintf('%s:! Unable to determine time from [%s]',self::LOGKEY,$m[3])); + else + $datetime = Carbon::createFromFormat('Ymd.His',$t[1],$t[3] ?? ''); + + if (! $ao) { + Log::alert(sprintf('%s:! Undefined Node [%s] for Netmail.',self::LOGKEY,$m[1])); + //$rogue->push(['node'=>$m[1],'datetime'=>$datetime,'program'=>$m[4]]); + + } else { + $nodes->push(['node'=>$ao,'datetime'=>$datetime,'program'=>$m[4]]); + } + } + } + + // If there are no details (Mystic), we'll create a blank + } else { + $nodes->push(['node'=>$model->set_sender,'datetime'=>Carbon::now(),'program'=>'Unknown']); + } + } + // Save the Path $ppoid = NULL; - if (isset($model->set_path)) { - // If there are no details (Mystic), we'll create a blank - if (! $model->set_path->count()) { - $model->set_path->push(['node'=>$model->set_sender,'datetime'=>Carbon::now(),'program'=>'Unknown']); - } + foreach ($nodes as $path) { + $po = DB::select('INSERT INTO netmail_path (netmail_id,address_id,parent_id,datetime,program) VALUES (?,?,?,?,?) RETURNING id',[ + $model->id, + $path['node']->id, + $ppoid, + (string)$path['datetime'], + $path['program'], + ]); - foreach ($model->set_path as $path) { - $po = DB::select('INSERT INTO netmail_path (netmail_id,address_id,parent_id,datetime,program) VALUES (?,?,?,?,?) RETURNING id',[ - $model->id, - $path['node']->id, - $ppoid, - (string)$path['datetime'], - $path['program'], - ]); + $ppoid = $po[0]->id; + } - $ppoid = $po[0]->id; - } - - // Our last node in the path is our sender - if (isset($model->set_pkt) && isset($model->set_sender) && isset($model->set_recvtime)) { - DB::update('UPDATE netmail_path set recv_pkt=?,recv_at=?,recv_id=? where address_id=? and netmail_id=?',[ - $model->set_pkt, - $model->set_recvtime, - $model->set_sender->id, - Arr::get($model->set_path->last(),'node')->id, - $model->id, - ]); - } + // Our last node in the path is our sender + if ($nodes->count() && isset($model->set_pkt) && isset($model->set_sender) && isset($model->set_recvtime)) { + DB::update('UPDATE netmail_path set recv_pkt=?,recv_at=?,recv_id=? where address_id=? and netmail_id=?',[ + $model->set_pkt, + $model->set_recvtime, + $model->set_sender->id, + Arr::get($nodes->last(),'node')->id, + $model->id, + ]); } }); } diff --git a/app/Notifications/Netmails/EchomailBadAddress.php b/app/Notifications/Netmails/EchomailBadAddress.php new file mode 100644 index 0000000..5f8c42f --- /dev/null +++ b/app/Notifications/Netmails/EchomailBadAddress.php @@ -0,0 +1,75 @@ +mo = $mo; + } + + /** + * Get the mail representation of the notification. + * + * @param System $so + * @param mixed $notifiable + * @return Netmail + * @throws \Exception + */ + public function toNetmail(System $so,object $notifiable): Netmail + { + $o = $this->setupNetmail($so,$notifiable); + $ao = $notifiable->routeNotificationFor(static::via); + + Log::info(sprintf('%s:+ Creating ECHOMAIL BAD ADDRESS netmail to [%s]',self::LOGKEY,$ao->ftn)); + + $o->subject = sprintf('Bad address in echomail [%s]',$this->mo->msgid); + + // Message + $msg = $this->page(FALSE,'badmsg'); + + $msg->addText( + sprintf("Your echomail with ID [%s] to [%s] here was received here on [%s] and it looks like you sent it on [%s].\r\r", + $this->mo->msgid, + $this->mo->user_to, + Carbon::now()->utc()->toDateTimeString(), + $this->mo->date->utc()->toDateTimeString(), + ) + ); + + $msg->addText(sprintf("The address in this echomail [%s] is the wrong address for this domain [%s].\r\r",$this->mo->fftn,$ao->zone->domain->name)); + + $msg->addText("This echomail has been rejected and not stored here - so no downstream nodes will receive it. If you think this is a mistake, please let me know.\r\r"); + + $msg->addText($this->message_path($this->mo)); + + $o->msg = $msg->render(); + $o->tagline = 'I enjoyed reading your message, even though nobody else will get it :)'; + + $o->save(); + + return $o; + } +} \ No newline at end of file diff --git a/app/Traits/ParseAddresses.php b/app/Traits/ParseAddresses.php new file mode 100644 index 0000000..4103b76 --- /dev/null +++ b/app/Traits/ParseAddresses.php @@ -0,0 +1,66 @@ +domain->flatten) + ? Address::findZone($zone->domain,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX,0) + : Address::findFTN(sprintf('%d:%d/%d',$zone->zone_id,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX)); + + switch ($type) { + case 'seenby': + if (! $ao) + $rogue->push(sprintf('%d:%d/%d',$zone->domain->flatten ? 0 : $zone->zone_id,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX)); + else + $nodes->push($ao->id); + + break; + + case 'path': + if (! $ao) { + $ftn = sprintf('%d:%d/%d@%s',$zone->zone_id,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX,$zone->domain->name); + $ao = Address::createFTN($ftn,System::createUnknownSystem()); + } + + $nodes->push($ao->id); + + break; + } + } + } + + return $nodes; + } +} \ No newline at end of file diff --git a/tests/Feature/PacketTest.php b/tests/Feature/PacketTest.php index 226b81b..fe616c0 100644 --- a/tests/Feature/PacketTest.php +++ b/tests/Feature/PacketTest.php @@ -21,6 +21,8 @@ class PacketTest extends TestCase $this->do = Domain::firstOrCreate(['name'=>'packets','active'=>TRUE]); } + /* + * @todo This packet doesnt have an origin line, need a new one with nomsg and an origin line public function test_nomsgid_origin() { $this->init(); @@ -50,7 +52,10 @@ class PacketTest extends TestCase $this->assertTrue($messages); } } + */ + /* + * @todo We are not correctly setup to parse messages without an origin line public function test_nomsgid_noorigin() { $this->init(); @@ -60,10 +65,10 @@ class PacketTest extends TestCase $zo = Zone::firstOrCreate(['zone_id'=>10,'default'=>TRUE,'active'=>TRUE,'domain_id'=>$this->do->id,'system_id'=>$this->so->id]); $src = Address::firstOrCreate(['zone_id'=>$zo->id,'region_id'=>0,'host_id'=>999,'node_id'=>1,'point_id'=>0,'role'=>Address::NODE_ACTIVE,'active'=>TRUE,'system_id'=>$this->so->id]); - // This packet has an incorrect zone in the Origin + // This packet has no Origin Line $f = new File(__DIR__.'/data/test_nomsgid_noorigin.pkt'); foreach ($f as $packet) { - $pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$this->so); + $pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$this->do); $this->assertEquals(1,$pkt->count()); @@ -71,15 +76,14 @@ class PacketTest extends TestCase foreach ($pkt as $msg) { $messages = TRUE; $this->assertNotTrue($msg->isNetmail()); - $this->assertNotFalse($msg->path->search('1/1 999/1')); - - $this->assertCount(0,$msg->rogue_seenby); + $this->assertNotFalse($msg->seenby->search('1/1 4 3/0 999/1 999')); } $this->assertTrue($messages); } } + */ public function test_msgid_origin() { @@ -93,7 +97,7 @@ class PacketTest extends TestCase // This packet has an incorrect zone in the Origin $f = new File(__DIR__.'/data/test_msgid_origin.pkt'); foreach ($f as $packet) { - $pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$this->so); + $pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$this->do); $this->assertEquals(1,$pkt->count()); @@ -101,9 +105,7 @@ class PacketTest extends TestCase foreach ($pkt as $msg) { $messages = TRUE; $this->assertNotTrue($msg->isNetmail()); - - $this->assertCount(0,$msg->rogue_seenby); - $this->assertNotFalse($msg->seenaddress->search($src->id)); + $this->assertNotFalse($msg->seenby->search('1/1 999/1 999')); } $this->assertTrue($messages); @@ -112,10 +114,14 @@ class PacketTest extends TestCase public function test_packet_parse() { + $this->init(); + + $zo = Zone::firstOrCreate(['zone_id'=>21,'default'=>TRUE,'active'=>TRUE,'domain_id'=>$this->do->id,'system_id'=>$this->so->id]); + // This packet has a SOHSOH sequence $f = new File(__DIR__.'/data/test_binary_content-2.pkt'); foreach ($f as $packet) { - $pkt = Packet::process($packet,$f->itemName(),$f->itemSize()); + $pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$zo->domain); $this->assertEquals(1,$pkt->count()); @@ -127,7 +133,6 @@ class PacketTest extends TestCase $this->assertSame('21:1/151 6189F64C',$msg->msgid); $this->assertSame('db727bd3778ddd457784ada4bf016010',md5($msg->message)); $this->assertSame('5b627ab5936b0550a97b738f4deff419',md5($msg->message_src)); - $this->assertCount(0,$msg->rogue_seenby); $this->assertContains('3/2744 4/100 106 5/100',$msg->seenby); $this->assertContains('1/151 100 3/100',$msg->path); $this->assertCount(11,$msg->seenby); @@ -152,7 +157,6 @@ class PacketTest extends TestCase $this->assertSame('21:1/126 eec6e958',$msg->msgid); $this->assertSame('5a525cc1c393292dc65160a852d4d615',md5($msg->message)); $this->assertSame('a3193edcc68521d4ed07da6db2aeb0b6',md5($msg->message_src)); - $this->assertCount(0,$msg->rogue_seenby); $this->assertContains('1/995 2/100 116 1202 3/100 105 107 108 109 110 111 112 113 117 119',$msg->seenby); $this->assertContains('1/126 100 3/100',$msg->path); $this->assertCount(10,$msg->seenby); @@ -177,8 +181,6 @@ class PacketTest extends TestCase $this->assertSame('10:999/1 612aecda',$msg->msgid); $this->assertSame('61078e680cda04c8b5eba0f712582e70',md5($msg->message)); $this->assertSame('b9d65d4f7319ded282f3f1986276ae79',md5($msg->message_src)); - $this->assertCount(1,$msg->pathaddress); - $this->assertCount(0,$msg->rogue_seenby); $this->assertContains('1/1 999/1 999',$msg->seenby); $this->assertContains('999/1',$msg->path); $this->assertCount(1,$msg->seenby); @@ -220,7 +222,6 @@ class PacketTest extends TestCase $this->assertSame('3:712/886 220da89f',$msg->msgid); $this->assertSame('9f5544bea46ef57a45f561b9e07dd71e',md5($msg->message)); $this->assertSame('9bf4b8c348ac235cc218577abf7140af',md5($msg->message_src)); - $this->assertCount(0,$msg->rogue_seenby); $this->assertContains('633/0 267 280 281 408 410 412 509 509 640/1384 712/114 550 620 848',$msg->seenby); $this->assertContains('712/886 848 633/280',$msg->path); $this->assertCount(2,$msg->seenby); @@ -231,7 +232,6 @@ class PacketTest extends TestCase $this->assertSame('',$msg->msgid); $this->assertSame('b975057002def556c5a9497aacd000fb',md5($msg->message)); $this->assertSame('c90dd234d2aa029af22c453a25b79a4e',md5($msg->message_src)); - $this->assertCount(0,$msg->rogue_seenby); $this->assertContains('633/267 280 281 384 408 410 412 418 420 509 509 712/848 770/1 100 330',$msg->seenby); $this->assertContains('772/210 770/1 633/280',$msg->path); $this->assertCount(2,$msg->seenby);