<?php namespace App\Jobs; use Carbon\Carbon; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Notification; use App\Classes\FTN\Message; use App\Models\{Echomail,Netmail,User}; use App\Notifications\Netmails\{EchoareaNotExist,EchoareaNotSubscribed,EchoareaNoWrite,NetmailForward,NetmailHubNoUser}; use App\Traits\ParseAddresses; class MessageProcess implements ShouldQueue { private const LOGKEY = 'JMP'; use Dispatchable,InteractsWithQueue,Queueable,SerializesModels,ParseAddresses; private Echomail|Netmail|string $mo; private bool $skipbot; /** * 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) { // @todo We need to serialize this model here, because laravel has an error unserializing it (Model Not Found) $this->mo = utf8_encode(serialize($mo->withoutRelations())); $this->skipbot = $skipbot; } public function __get($key): mixed { switch ($key) { case 'jobname': $mo = unserialize($this->mo); return sprintf('%s-%s-%s',$mo->set->get('set_pkt'),$mo->set->get('set_sender')->ftn,$mo->msgid); default: return NULL; } } /** * At this point, we know that the packet is from a system we know about, and the packet is to us: * + From a system that is configured with us, and the password has been validated * + From a system that is not configured with us, and it may have netmails for us */ public function handle() { $this->mo = unserialize(utf8_decode($this->mo)); // Load our details $ftns = our_address(); // If we are a netmail if ($this->mo instanceof Netmail) { // @todo generate exception when netmail to system that doesnt exist (node/point) and its this host's responsibility Log::info(sprintf('%s:- Processing Netmail [%s] to (%s) [%s] from (%s) [%s].', self::LOGKEY, $this->mo->msgid ?: '*NO MSGID*', $this->mo->to,$this->mo->tftn->ftn, $this->mo->from,$this->mo->fftn->ftn, )); // @todo Enable checks to reject old messages // Check for duplicate messages // FTS-0009.001 if ($this->mo->msgid) { Log::debug(sprintf('%s:- Checking for duplicate from host [%s].',self::LOGKEY,$this->mo->fftn->ftn)); $o = Netmail::where('msgid',$this->mo->msgid) ->where('fftn_id',$this->mo->fftn_id) ->where('datetime','>',Carbon::now()->subYears(3)) ->single(); if ($o) { Log::alert(sprintf('%s:! Duplicate netmail #%d [%s] from (%s) [%s] to (%s) - ignoring.', self::LOGKEY, $o->id, $this->mo->msgid, $this->mo->from,$this->mo->fftn->ftn, $this->mo->to, )); return; } } // @todo Enable checks to see if this is a file request or file send // Strip any local/transit flags $this->mo->flags &= ~(Message::FLAG_LOCAL|Message::FLAG_INTRANSIT); // Determine if the message is to this system, or in transit if ($ftns->contains($this->mo->tftn)) { $processed = FALSE; // If the message is to a bot, we'll process it if (! $this->skipbot) foreach (config('process.robots') as $class) { if ($processed=$class::handle($this->mo)) { $this->mo->flags |= Message::FLAG_RECD; $this->mo->save(); Log::info(sprintf('%s:= Netmail [%s] from (%s:%s) - was processed by us internally [%d]', self::LOGKEY, $this->mo->msgid, $this->mo->from, $this->mo->fftn->ftn, $this->mo->id, )); break; } } if (! $processed) { // Check if the netmail is to a user, with netmail forwarding enabled $uo = User::active() ->where(function($query) { return $query->whereRaw(sprintf("LOWER(name)='%s'",strtolower(utf8_encode($this->mo->to)))) ->orWhereRaw(sprintf("LOWER(alias)='%s'",strtolower(utf8_encode($this->mo->to)))); }) ->whereNotNull('system_id') ->single(); if ($uo && ($ao=$uo->system->match($this->mo->tftn->zone)?->pop())) { Log::info(sprintf('%s:- Forwarding Netmail [%s] to (%s) [%s] from (%s) [%s].', self::LOGKEY, $this->mo->msgid ?: '*NO MSGID*', $this->mo->to,$ao->ftn, $this->mo->from,$this->mo->fftn->ftn, )); $note = "+--[ FORWARDED MESSAGE ]----------------------------------+\r"; $note .= "+ This message has been forwarded to you, it was originally sent to you\r"; $note .= sprintf("+ at [%s]\r",$this->mo->tftn->ftn); $note .= "+---------------------------------------------------------+\r\r"; $this->mo->msg_src = $note.$this->mo->content; $this->mo->tftn_id = $ao->id; $this->mo->flags |= Message::FLAG_INTRANSIT; $this->mo->save(); $processed = TRUE; // Dont send an advisement to an areabot if (! in_array(strtolower($this->mo->from),config('fido.areabots'))) Notification::route('netmail',$this->mo->fftn)->notify(new NetmailForward($this->mo,$ao)); // We'll ignore messages from *fix users } elseif (in_array(strtolower($this->mo->from),config('fido.areabots'))) { $this->mo->flags |= Message::FLAG_RECD; $this->mo->save(); Log::alert(sprintf('%s:! Ignoring Netmail [%s] to the Hub from (%s:%s) - its from a bot [%d]', self::LOGKEY, $this->mo->msgid, $this->mo->from, $this->mo->fftn->ftn, $this->mo->id, )); $processed = TRUE; } } // If not processed, no users here! if (! $processed) { Log::alert(sprintf('%s:! Netmail to the Hub from (%s) [%s] but no users here.',self::LOGKEY,$this->mo->from,$this->mo->fftn->ftn)); Notification::route('netmail',$this->mo->fftn)->notify(new NetmailHubNoUser($this->mo)); } // If in transit, store for collection } else { // @todo In transit loop checking // @todo In transit TRACE response $this->mo->flags |= Message::FLAG_INTRANSIT; $this->mo->save(); Log::info(sprintf('%s:= Netmail [%s] in transit to (%s:%s) from (%s:%s) [%d].', self::LOGKEY, $this->mo->msgid, $this->mo->to,$this->mo->tftn->ftn, $this->mo->from,$this->mo->fftn->ftn, $this->mo->id, )); } // Else we are echomail } else { // The packet sender $sender = $this->mo->set->get('set_sender'); // @todo Check that this does evaluate to true if a message has been rescanned $rescanned = $this->mo->kludges->get('RESCANNED',FALSE); // Echoarea doesnt exist, cant import the message if (! $this->mo->echoarea) { Log::alert(sprintf('%s:! Echoarea [%s] doesnt exist for zone [%d@%s]',self::LOGKEY,$this->mo->set->get('set_echoarea'),$sender->zone->zone_id,$sender->zone->domain->name)); Notification::route('netmail',$sender)->notify(new EchoareaNotExist($this->mo)); return; } Log::debug(sprintf('%s:- Processing echomail [%s] in [%s] from [%s].',self::LOGKEY,$this->mo->msgid,$this->mo->echoarea->name,$sender->ftn)); // Message from zone is incorrect for echoarea if (! $this->mo->echoarea->domain->zones->contains($this->mo->fftn->zone)) { Log::alert(sprintf('%s:! The message [%s] is from a different zone [%d] than the packet sender [%d] - not importing', self::LOGKEY, $this->mo->msgid, $this->mo->fftn->zone->zone_id, $this->mo->fftn->zone->zone_id)); return; } // Check for duplicate messages // FTS-0009.001 if ($this->mo->msgid) { $o = ($x=Echomail::where('msgid',$this->mo->msgid) ->where('fftn_id',$this->mo->fftn_id) ->where('datetime','>=',$this->mo->datetime->clone()->subYears(3)) ->where('datetime','<=',$this->mo->datetime) ->dontCache()) ->single(); Log::debug(sprintf('%s:- Checking for duplicate from host id [%d], with msgid [%s] between [%s] and [%s].', self::LOGKEY, $this->mo->fftn_id, $this->mo->msgid, $this->mo->datetime->clone()->subYears(3), $this->mo->datetime, )); if ($x->count()) { // @todo Actually update seenby Log::alert(sprintf('%s:! Duplicate echomail (%s) in [%s] from (%s) [%s] to (%s) - ignoring.', self::LOGKEY, $this->mo->msgid, $this->mo->echoarea->name, $this->mo->from,$this->mo->fftn->ftn, $this->mo->to, )); // @todo This duplicate message may have gone via a different path, be nice to record it. // @todo if we have an export for any of the seenby addresses, remove it return; } } // Find another message with the same msg_crc if ($this->mo->msg_crc) { $o = Echomail::where('msg_crc',$xx=md5($this->mo->msg_crc)) ->where('fftn_id',$this->mo->fftn_id) ->where('datetime','>',Carbon::now()->subWeek()) ->dontCache() ->get(); if ($o->count()) Log::alert(sprintf('%s:! Duplicate message CRC [%s] in [%s].', self::LOGKEY, $xx, $o->pluck('id')->join('|') )); } // Can the system send messages to this area? if (! $this->mo->echoarea->can_write($sender->security)) { Log::alert(sprintf('%s:! FTN [%s] is not allowed to post [%s] to [%s].',self::LOGKEY,$sender->ftn,$this->mo->msgid,$this->mo->echoarea->name)); if (! $rescanned) Notification::route('netmail',$sender)->notify(new EchoareaNoWrite($this->mo)); return; } // If the node is not subscribed, we'll accept it, but let them know if (! $sender->echoareas->contains($this->mo->echoarea)) { 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 (! $rescanned) Notification::route('netmail',$sender)->notify(new EchoareaNotSubscribed($this->mo)); } // We know about this area, store it $this->mo->save(); Log::info(sprintf('%s:= Echomail [%s] in [%s] from (%s) [%s] to (%s) - [%s].', self::LOGKEY, $this->mo->msgid, $this->mo->echoarea->name, $this->mo->from,$this->mo->fftn->ftn, $this->mo->to, $this->mo->id, )); // If the message is to a bot, but not rescanned, or purposely skipbot set, we'll process it if ((! $this->skipbot) && (! $rescanned)) foreach (config('process.echomail') as $class) { if ($class::handle($this->mo)) { break; } } } } }