<?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\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,ParseAddresses; private Address $sender; private Message $msg; private Address $pktsrc; private Carbon $recvtime; private bool $skipbot; private string $packet; public function __construct(Message $msg,string $packet,Address $sender,Address $pktsrc,Carbon $recvtime,bool $skipbot=FALSE) { $this->msg = $msg; $this->packet = $packet; $this->sender = $sender; $this->pktsrc = $pktsrc; $this->recvtime = $recvtime; $this->skipbot = $skipbot; } public function __get($key): mixed { switch ($key) { case 'subject': return sprintf('%s-%s-%s',$this->packet,$this->sender->ftn,$this->msg->msgid); default: return NULL; } } /** * When calling MessageProcess - we assume that the packet is from a valid source, and * the destination (netmail/echomail) is also valid */ public function handle() { // Load our details $ftns = Setup::findOrFail(config('app.id'))->system->addresses; // If we are a netmail if ($this->msg->isNetmail()) { Log::info(sprintf('%s:- Processing Netmail [%s] to (%s) [%s] from (%s) [%s].', self::LOGKEY, $this->msg->msgid, $this->msg->user_to,$this->msg->tftn, $this->msg->user_from,$this->msg->fftn, )); // @todo Enable checks to reject old messages // Check for duplicate messages // FTS-0009.001 if ($this->msg->msgid) { $o = Netmail::where('msgid',$this->msg->msgid) ->where('fftn_id',($x=$this->msg->fboss_o) ? $x->id : NULL) ->where('datetime','>',Carbon::now()->subYears(3)) ->single(); Log::debug(sprintf('%s:- Checking for duplicate from host id [%d].',self::LOGKEY,($x=$this->msg->fboss_o) ? $x->id : NULL)); if ($o) { Log::alert(sprintf('%s:! Duplicate netmail [%s] in [%s] from (%s) [%s] to (%s) - ignorning.', self::LOGKEY, $this->msg->msgid, $this->msg->echoarea, $this->msg->user_from,$this->msg->fftn, $this->msg->user_to, )); if (! $o->msg_crc) $o->msg_crc = md5($this->msg->message); $o->save(); return; } } // @todo Enable checks to see if this is a file request or file send $o = new Netmail; $o->to = $this->msg->user_to; $o->from = $this->msg->user_from; $o->fftn_id = ($x=$this->msg->fftn_o) ? $x->id : NULL; $o->tftn_id = ($x=$this->msg->tftn_o) ? $x->id : NULL; $o->datetime = $this->msg->date; $o->tzoffset = $this->msg->date->utcOffset(); $o->flags = $this->msg->flags; $o->cost = $this->msg->cost; $o->msgid = $this->msg->msgid; $o->subject = $this->msg->subject; $o->msg = $this->msg->message; foreach ($this->msg->via as $v) $o->msg .= sprintf("\01Via %s\r",$v); $o->msg_src = $this->msg->message_src; $o->msg_crc = md5($this->msg->message); $o->set_pkt = $this->packet; $o->set_sender = $this->sender; $o->set_path = $this->msg->via; $o->set_recvtime = $this->recvtime; // Strip any local/transit flags $o->flags &= ~(Message::FLAG_LOCAL|Message::FLAG_INTRANSIT); // Determine if the message is to this system, or in transit if ($ftns->search(function($item) { return $this->msg->tftn === $item->ftn; }) !== FALSE) { // @todo Check if it is a duplicate message // @todo Check if the message is from a system we know about $processed = FALSE; // If the message is to a bot, we'll process it if (! $this->skipbot) foreach (config('process.robots') as $class) { if ($processed=$class::handle($this->msg)) { $o->flags |= Message::FLAG_RECD; $o->save(); Log::info(sprintf('%s:= Netmail [%s] from (%s:%s) - was processed by us internally [%d]', self::LOGKEY, $this->msg->msgid, $this->msg->user_from, $this->msg->fftn, $o->id, )); 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($this->msg->user_to))) ->orWhereRaw(sprintf("LOWER(alias)='%s'",strtolower($this->msg->user_to))); }) ->whereNotNull('system_id') ->single(); if ($uo && ($ao=$uo->system->match($this->msg->tftn_o->zone)?->pop())) { $note = "+--[ FORWARDED MESSAGE ]----------------------------------+\r"; $note .= "+ This message has been forwarded to you, it was originally sent to you\r"; $note .= sprintf("+ at [%s]\r",$this->msg->tftn_o->ftn); $note .= "+---------------------------------------------------------+\r\r"; $o->msg = $note.$this->msg->message; $o->tftn_id = $ao->id; $o->flags |= Message::FLAG_INTRANSIT; $o->save(); $processed = TRUE; // Dont send an advisement to an areabot if (! in_array(strtolower($this->msg->user_from),config('fido.areabots'))) Notification::route('netmail',$this->msg->fftn_o)->notify(new NetmailForward($this->msg,$ao)); // We'll ignore messages from *fix users } elseif (in_array(strtolower($this->msg->user_from),config('fido.areabots'))) { $o->flags |= Message::FLAG_RECD; $o->save(); Log::alert(sprintf('%s:! Ignoring Netmail [%s] to the Hub from (%s:%s) - its from a bot [%d]', self::LOGKEY, $this->msg->msgid, $this->msg->user_from, $this->msg->fftn, $o->id, )); $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->msg->user_from,$this->msg->fftn)); Notification::route('netmail',$this->msg->fftn_o)->notify(new Reject($this->msg)); } // If in transit, store for collection } else { // @todo Check if the message is to a system we know about // @todo In transit loop checking // @todo In transit TRACE response $o->flags |= Message::FLAG_INTRANSIT; $o->save(); Log::info(sprintf('%s:= Netmail [%s] in transit to (%s:%s) from (%s:%s) [%d].', self::LOGKEY, $this->msg->msgid, $this->msg->user_to,$this->msg->tftn, $this->msg->user_from,$this->msg->fftn, $o->id, )); } // Else we are echomail } else { Log::debug(sprintf('%s:- Looking for echomail area [%s] for mail from [%s]',self::LOGKEY,$this->msg->echoarea,$this->msg->fboss)); if (! $this->msg->fboss_o) { Log::error(sprintf('%s:! Cannot process message for echomail area [%s] for mail from [%s] with msgid [%s] - no boss object?',self::LOGKEY,$this->msg->echoarea,$this->msg->fboss,$this->msg->msgid)); return; } $ea = Echoarea::where('name',strtoupper($this->msg->echoarea)) ->where('domain_id',$this->msg->fboss_o->zone->domain_id) ->single(); if (! $ea) { Log::alert(sprintf('%s:! Echoarea [%s] doesnt exist for zone [%d-%d]',self::LOGKEY,$this->msg->echoarea,$this->msg->fboss_o->zone->domain_id,$this->msg->fboss_o->zone->zone_id)); Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNotExist($this->msg)); return; } Log::debug(sprintf('%s:- Processing echomail [%s] in [%s].',self::LOGKEY,$this->msg->msgid,$this->msg->echoarea)); if (! $this->pktsrc->zone->domain->zones->pluck('zone_id')->contains($this->msg->fboss_o->zone->zone_id)) { 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 // FTS-0009.001 if ($this->msg->msgid) { $o = Echomail::where('msgid',$this->msg->msgid) ->where('fftn_id',($x=$this->msg->fboss_o) ? $x->id : NULL) ->where('datetime','>=',$this->msg->date->subYears(3)) ->where('datetime','<=',$this->msg->date) ->single(); Log::debug(sprintf('%s:- Checking for duplicate from host id [%d].',self::LOGKEY,($x=$this->msg->fboss_o) ? $x->id : NULL)); if ($o) { Log::alert(sprintf('%s:! Duplicate echomail [%s] in [%s] from (%s) [%s] to (%s) - updating seenby.', self::LOGKEY, $this->msg->msgid, $this->msg->echoarea, $this->msg->user_from,$this->msg->fftn, $this->msg->user_to, )); if (! $o->msg_crc) $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. // If we didnt get the path on the original message, we'll override it if (! $o->path->count()) { $dummy = collect(); $path = $this->parseAddresses('path',$this->msg->path,$this->pktsrc->zone,$dummy); /* // If our sender is not in the path, add it if (! $path->contains($this->sender->id)) { Log::alert(sprintf('%s:? Echomail adding sender to PATH [%s] for [%d].',self::LOGKEY,$x->ftn,$o->id)); $path->push($this->sender->id); } */ $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 $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; } } // Find another message with the same msg_crc if ($this->msg->message) { $o = Echomail::where('msg_crc',$xx=md5($this->msg->message)) ->where('fftn_id',($x=$this->msg->fboss_o) ? $x->id : NULL) ->where('datetime','>',Carbon::now()->subWeek()) ->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 (! $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->ftn,$this->msg->msgid,$ea->name)); if (! $this->msg->rescanned->count()) Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNoWrite($this->msg)); return; } // If the node is not subscribed if ($this->pktsrc->echoareas->search(function($item) use ($ea) { return $item->id === $ea->id; }) === FALSE) { Log::alert(sprintf('%s:! FTN [%s] is not subscribed to [%s] for [%s].',self::LOGKEY,$this->pktsrc->ftn,$ea->name,$this->msg->msgid)); if (! $this->msg->rescanned->count()) Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNotSubscribed($this->msg)); } // We know about this area, store it $o = new Echomail; $o->init(); $o->to = $this->msg->user_to; $o->from = $this->msg->user_from; $o->subject = $this->msg->subject; $o->datetime = $this->msg->date; $o->tzoffset = $this->msg->date->utcOffset(); if ($x=$this->msg->fboss_o) { $o->fftn_id = $x->id; } else { $o->fftn_id = NULL; // @todo This should be the node that originated the message - but since that node is not in the DB it would be null } $o->echoarea_id = $ea->id; $o->msgid = $this->msg->msgid; $o->replyid = $this->msg->replyid; $o->msg = $this->msg->message_src."\r"; $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].', self::LOGKEY, $this->msg->msgid, $this->msg->echoarea, $this->msg->user_from,$this->msg->fftn, $this->msg->user_to, $o->id, )); // If the message is to a bot, but not rescanned, or purposely skipbot set, we'll process it if ((! $this->skipbot) && (! $this->msg->rescanned->count())) foreach (config('process.echomail') as $class) { if ($class::handle($this->msg)) { break; } } } } }