Compare commits

...

10 Commits

64 changed files with 1939 additions and 1708 deletions

View File

@ -93,6 +93,13 @@ jobs:
- name: Code Checkout - name: Code Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Record version
run: |
pwd
ls -al
echo ${GITHUB_SHA::8} > VERSION
cat VERSION
- name: Build and Push Docker Image - name: Build and Push Docker Image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:

View File

@ -28,7 +28,13 @@ class CompressedString implements CastsAttributes
? stream_get_contents($value) ? stream_get_contents($value)
: $value; : $value;
return $value ? zstd_uncompress(base64_decode($value)) : ''; // If we get an error decompressing, it might not be zstd (or its already been done)
try {
return $value ? zstd_uncompress(base64_decode($value)) : '';
} catch (\ErrorException $e) {
return $value;
}
} }
/** /**

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,30 +27,16 @@ 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);
} }
} }
/**
* Determine if a line is a kludge line.
*
* @param string $kludge
* @param string $string
* @return string
*/
protected function kludge(string $kludge,string $string)
{
return (preg_match("/^{$kludge}/",$string))
? chop(preg_replace("/^{$kludge}/",'',$string),"\r")
: FALSE;
}
/** /**
* This function creates our unpack header * This function creates our unpack header
* *

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,114 +42,28 @@ 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
protected $pass_p = NULL; // Overwrite the packet password (when packing messages)
/* ABSTRACT */
/** /**
* @param string|null $header * This function is intended to be implemented in child classes to test if the packet
* @throws \Exception * is defined by the child object
*/
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 * @see self::PACKET_TYPES
* @throws \Exception * @param string $header
* @return bool
*/ */
public function __toString(): string abstract public static function is_type(string $header): bool;
{ abstract protected function header(): 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 */
@ -156,19 +77,6 @@ class Packet extends FTNBase implements \Iterator, \Countable
return collect(static::HEADER)->sum(function($item) { return Arr::get($item,2); }); return collect(static::HEADER)->sum(function($item) { return Arr::get($item,2); });
} }
/**
* This function is intended to be implemented in child classes to test if the packet
* is defined by the child object
*
* @see self::PACKET_TYPES
* @param string $header
* @return bool
*/
public static function is_type(string $header): bool
{
return FALSE;
}
/** /**
* Process a packet file * Process a packet file
* *
@ -224,7 +132,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 +143,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 +156,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 +173,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 +189,112 @@ 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());
case 'messages':
return $this->{$key};
default:
throw new \Exception('Unknown key: '.$key);
}
}
/**
* Return the packet
*
* @return string
* @throws \Exception
*/
public function __toString(): string
{
if (empty($this->messages))
throw new InvalidPacketException('Refusing to make an empty packet');
if (empty($this->tftn_p) || empty($this->fftn_p))
throw new InvalidPacketException('Cannot generate a packet without a destination address');
$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 +307,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 +340,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 +374,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
*
* @return string
*/
public function generate(): string
{
return (string)$this;
}
public function mail(Collection $msgs): self
{
$this->messages = $msgs;
return $this;
}
/** /**
* Parse a message in a mail packet * Parse a message in a mail packet
* *
@ -393,7 +418,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 +427,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 +499,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 +513,24 @@ class Packet extends FTNBase implements \Iterator, \Countable
$this->messages->push($msg); $this->messages->push($msg);
} }
/**
* Overwrite the packet password
*
* @param string|null $password
* @return self
*/
public function password(string $password=NULL): self
{
if ($password && (strlen($password) < 9))
$this->pass_p = $password;
return $this;
}
/** @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

@ -62,34 +62,36 @@ final class FSC48 extends Packet
*/ */
protected function header(): string protected function header(): string
{ {
$oldest = $this->messages->sortBy('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->host_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->pass_p ?: $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->host_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,25 +2,23 @@
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
*/ */
abstract class Process abstract class Process
{ {
public static function canProcess(string $echoarea): bool public static function canProcess(Echoarea $eao): bool
{ {
$eao = Echoarea::where('name',$echoarea)->single(); return $eao->automsgs;
return $eao && $eao->automsgs;
} }
/** /**
* 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,$mo->from,$mo->fftn->ftn,$mo->echoarea->name));
Notification::route('echomail',$msg->echoarea)->notify(new TestNotification($msg)); Notification::route('echomail',$mo->echoarea->withoutRelations())->notify(new TestNotification($mo));
return TRUE; return TRUE;
} }

View File

@ -5,7 +5,8 @@ namespace App\Classes\FTN\Process\Netmail;
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\Netmails\Areafix as AreafixNotification; use App\Notifications\Netmails\Areafix as AreafixNotification;
use App\Notifications\Netmails\Areafix\NotConfiguredHere as AreafixNotConfiguredHereNotification; use App\Notifications\Netmails\Areafix\NotConfiguredHere as AreafixNotConfiguredHereNotification;
@ -18,18 +19,18 @@ final class Areafix extends Process
{ {
private const LOGKEY = 'RP-'; private const LOGKEY = 'RP-';
public static function handle(Message $msg): bool public static function handle(Echomail|Netmail $mo): bool
{ {
if (strtolower($msg->user_to) !== 'areafix') if (strtolower($mo->to) !== 'areafix')
return FALSE; return FALSE;
Log::info(sprintf('%s:- Processing AREAFIX message from (%s) [%s]',self::LOGKEY,$msg->user_from,$msg->fftn)); Log::info(sprintf('%s:- Processing AREAFIX message from (%s) [%s]',self::LOGKEY,$mo->from,$mo->fftn));
// If this is not a node we manage, then respond with a sorry can help you // If this is not a node we manage, then respond with a sorry can help you
if ($msg->fftn_o->system->sessions->count()) if ($mo->fftn->system->sessions->count())
Notification::route('netmail',$msg->fftn_o)->notify(new AreafixNotification($msg)); Notification::route('netmail',$mo->fftn)->notify(new AreafixNotification($mo));
else else
Notification::route('netmail',$msg->fftn_o)->notify(new AreafixNotConfiguredHereNotification($msg)); Notification::route('netmail',$mo->fftn)->notify(new AreafixNotConfiguredHereNotification($mo));
return TRUE; return TRUE;
} }

View File

@ -5,7 +5,8 @@ namespace App\Classes\FTN\Process\Netmail;
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\Netmails\Ping as PingNotification; use App\Notifications\Netmails\Ping as PingNotification;
/** /**
@ -17,14 +18,14 @@ final class Ping extends Process
{ {
private const LOGKEY = 'RP-'; private const LOGKEY = 'RP-';
public static function handle(Message $msg): bool public static function handle(Echomail|Netmail $mo): bool
{ {
if (strtolower($msg->user_to) !== 'ping') if (strtolower($mo->to) !== 'ping')
return FALSE; return FALSE;
Log::info(sprintf('%s:- Processing PING message from (%s) [%s]',self::LOGKEY,$msg->user_from,$msg->fftn)); Log::info(sprintf('%s:- Processing PING message from (%s) [%s]',self::LOGKEY,$mo->from,$mo->fftn->ftn));
Notification::route('netmail',$msg->fftn_o)->notify(new PingNotification($msg)); Notification::route('netmail',$mo->fftn)->notify(new PingNotification($mo));
return TRUE; return TRUE;
} }

View File

@ -32,7 +32,7 @@ final class Mail extends Send
public function __get($key) { public function __get($key) {
switch ($key) { switch ($key) {
case 'dbids': case 'dbids':
return $this->f->messages->pluck('dbid'); return $this->f->messages->pluck('id');
case 'name': case 'name':
return sprintf('%08x',timew($this->youngest())); return sprintf('%08x',timew($this->youngest()));
@ -111,7 +111,7 @@ final class Mail extends Send
return TRUE; return TRUE;
} }
public function youngest(): Carbon private function youngest(): Carbon
{ {
return $this->f->messages->pluck('date')->sort()->last(); return $this->f->messages->pluck('date')->sort()->last();
} }

View File

@ -128,13 +128,12 @@ class Receive extends Base
// If packet is greater than a size, lets queue it // If packet is greater than a size, lets queue it
if ($this->queue || ($this->receiving->size > config('fido.queue_size',0))) { if ($this->queue || ($this->receiving->size > config('fido.queue_size',0))) {
Log::info(sprintf('%s:- Packet [%s] will be sent to the queue for processing because its [%d] size, or queue forced',self::LOGKEY,$this->receiving->full_name,$this->receiving->size)); Log::info(sprintf('%s:- Packet [%s] will be sent to the queue for processing because its [%d] size, or queue forced',self::LOGKEY,$this->receiving->full_name,$this->receiving->size));
PacketProcess::dispatch($this->receiving,$this->ao->withoutRelations(),$rcvd_time); PacketProcess::dispatch($this->receiving->rel_name,$this->ao->zone->domain,FALSE,$rcvd_time);
} else } else
PacketProcess::dispatchSync($this->receiving,$this->ao->withoutRelations(),$rcvd_time); PacketProcess::dispatchSync($this->receiving->rel_name,$this->ao->zone->domain,TRUE,$rcvd_time);
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error(sprintf('%s:! Got error dispatching packet [%s] (%d:%s-%s).',self::LOGKEY,$this->receiving->full_name,$e->getLine(),$e->getFile(),$e->getMessage())); Log::error(sprintf('%s:! Got error dispatching packet [%s] (%d:%s-%s).',self::LOGKEY,$this->receiving->rel_name,$e->getLine(),$e->getFile(),$e->getMessage()));
} }
break; break;

View File

@ -249,11 +249,10 @@ class Send extends Base
* Add our mail to the send queue * Add our mail to the send queue
* *
* @param Address $ao * @param Address $ao
* @param bool $update
* @return bool * @return bool
* @throws Exception * @throws Exception
*/ */
public function mail(Address $ao,bool $update=TRUE): bool public function mail(Address $ao): bool
{ {
$mail = FALSE; $mail = FALSE;
@ -265,7 +264,7 @@ class Send extends Base
} }
// Netmail // Netmail
if ($x=$ao->getNetmail($update)) { if ($x=$ao->getNetmail()) {
Log::debug(sprintf('%s:- Netmail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn)); Log::debug(sprintf('%s:- Netmail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn));
$this->list->push(new Mail($x,self::T_NETMAIL)); $this->list->push(new Mail($x,self::T_NETMAIL));
@ -273,7 +272,7 @@ class Send extends Base
} }
// Echomail // Echomail
if ($x=$ao->getEchomail($update)) { if ($x=$ao->getEchomail()) {
Log::debug(sprintf('%s:- Echomail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn)); Log::debug(sprintf('%s:- Echomail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn));
$this->list->push(new Mail($x,self::T_ECHOMAIL)); $this->list->push(new Mail($x,self::T_ECHOMAIL));

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

@ -696,8 +696,13 @@ final class Binkp extends BaseProtocol
$this->node->ftn_other = $rem_aka; $this->node->ftn_other = $rem_aka;
continue; continue;
// If we only present limited AKAs dont validate password against akas outside of the domains we present
} elseif (is_null(our_address($o))) {
Log::alert(sprintf('%s:/ AKA domain [%s] is not in our domain(s) [%s] - ignoring',self::LOGKEY,$o->zone->domain->name,our_address()->pluck('zone.domain.name')->unique()->join(',')));
continue;
} elseif (! $o->active) { } elseif (! $o->active) {
Log::alert(sprintf('%s:/ AKA is not active [%s], ignoring',self::LOGKEY,$rem_aka)); Log::alert(sprintf('%s:/ AKA is not active [%s] - ignoring',self::LOGKEY,$rem_aka));
continue; continue;
} else { } else {
@ -705,7 +710,7 @@ final class Binkp extends BaseProtocol
} }
} catch (InvalidFTNException $e) { } catch (InvalidFTNException $e) {
Log::error(sprintf('%s:! AKA is INVALID [%s] (%s), ignoring',self::LOGKEY,$rem_aka,$e->getMessage())); Log::error(sprintf('%s:! AKA is INVALID [%s] (%s) - ignoring',self::LOGKEY,$rem_aka,$e->getMessage()));
continue; continue;
@ -1004,6 +1009,9 @@ final class Binkp extends BaseProtocol
$this->send->close(TRUE,$this->node); $this->send->close(TRUE,$this->node);
} }
} else {
Log::error(sprintf('%s:! M_got[skip] not for our file? [%s]',self::LOGKEY,$buf));
} }
} else { } else {

View File

@ -319,11 +319,23 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
Log::debug(sprintf('%s: - Parsing AKA [%s]',self::LOGKEY,$rem_aka)); Log::debug(sprintf('%s: - Parsing AKA [%s]',self::LOGKEY,$rem_aka));
try { try {
if (! ($o = Address::findFTN($rem_aka))) { if (! ($o = Address::findFTN($rem_aka,TRUE))) {
Log::debug(sprintf('%s: ? AKA is UNKNOWN [%s]',self::LOGKEY,$rem_aka)); Log::debug(sprintf('%s: ? AKA is UNKNOWN [%s]',self::LOGKEY,$rem_aka));
$this->node->ftn_other = $rem_aka; $this->node->ftn_other = $rem_aka;
continue; continue;
// If we only present limited AKAs dont validate password against akas outside of the domains we present
} elseif (is_null(our_address($o))) {
Log::alert(sprintf('%s:/ AKA domain [%s] is not in our domain(s) [%s] - ignoring',self::LOGKEY,$o->zone->domain->name,our_address()->pluck('zone.domain.name')->unique()->join(',')));
continue;
} elseif (! $o->active) {
Log::alert(sprintf('%s:/ AKA is not active [%s] - ignoring',self::LOGKEY,$rem_aka));
continue;
} else {
Log::info(sprintf('%s:- Got AKA [%s]',self::LOGKEY,$rem_aka));
} }
} catch (InvalidFTNException $e) { } catch (InvalidFTNException $e) {

View File

@ -50,8 +50,13 @@ class PacketAddress extends Command
exit(1); exit(1);
} }
$o = $o->where('id',$this->argument('dbid'))->get(); echo hex_dump($ao
->system
->packet($ao)
->mail($o->where('id',$this->argument('dbid'))->get())
->generate()
);
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());
@ -88,8 +99,8 @@ class PacketInfo extends Command
foreach ($pkt->errors as $msg) { foreach ($pkt->errors as $msg) {
$this->error(sprintf('- Date: %s',$msg->date)); $this->error(sprintf('- Date: %s',$msg->date));
$this->error(sprintf(' - FLAGS: %s',$msg->flags()->filter()->keys()->join(', '))); $this->error(sprintf(' - FLAGS: %s',$msg->flags()->filter()->keys()->join(', ')));
$this->error(sprintf(' - From: %s (%s)',$msg->user_from,$msg->fftn)); $this->error(sprintf(' - From: %s (%s)',$msg->from,$msg->fftn));
$this->error(sprintf(' - To: %s (%s)',$msg->user_to,$msg->tftn)); $this->error(sprintf(' - To: %s (%s)',$msg->to,$msg->tftn));
$this->error(sprintf(' - Subject: %s',$msg->subject)); $this->error(sprintf(' - Subject: %s',$msg->subject));
foreach ($msg->errors->errors()->all() as $error) foreach ($msg->errors->errors()->all() as $error)

View File

@ -2,15 +2,41 @@
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;
/**
* Things to test
* + Packet
* - Sender doesnt exist (try and send a bounce message in the same session)
* - Sender defined in DB by not ours
* - Sender has wrong password
* - Packet too old
*
* + Echomail
* - Area doesnt exist (to uplink)
* - Sender not subscribed (to uplink)
* - Sender cannot post (to uplink)
* - Sender has wrong address for echorea domain (to uplink)
* - Test message in echoarea
* - Echomail from address doesnt match packet envelope (to uplink)
* - Echomail too old (to uplink)
* - Rescanned dont generate notifications
* - Rescanned dont trigger bots
* - Some Notifications to an uplink should go to the admin instead?
*
* + Netmail
* - To hub, and user not defined (reject)
* - To hub, but user redirect (redirected)
* - To areafix (processed)
* - To ping (respond)
* - With trace turned on (respond)
*/
class PacketProcess extends Command class PacketProcess extends Command
{ {
/** /**
@ -35,38 +61,29 @@ 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()
{ {
$fs = Storage::disk(config('fido.local_disk')); //$fs = Storage::disk(config('fido.local_disk'));
$rel_name = sprintf('%s/%s',config('fido.dir'),$this->argument('file')); $rel_name = sprintf('%s/%s',config('fido.dir'),$this->argument('file'));
$f = new File($fs->path($rel_name)); //$f = new File($fs->path($rel_name));
$m = []; $m = [];
if ($this->argument('ftn')) { if ($this->argument('ftn')) {
$a = Address::findFTN($this->argument('ftn')); $ao = Address::findFTN($this->argument('ftn'));
} elseif (preg_match(sprintf('/^%s\.(.{3})$/',Packet::regex),$this->argument('file'),$m)) { } elseif (preg_match(sprintf('/^%s\.(.{3})$/',Packet::regex),$this->argument('file'),$m)) {
$a = Address::findOrFail(hexdec($m[1])); $ao = Address::findOrFail(hexdec($m[1]));
} else { } else {
$this->error('Unable to determine sender FTN address'); $this->error('Unable to determine sender FTN address');
exit(1); exit(1);
} }
foreach ($f as $packet) { Job::dispatchSync($rel_name,$ao->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. return Command::SUCCESS;
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

@ -4,7 +4,7 @@ namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Models\System; use App\Models\Address;
class PacketSystem extends Command class PacketSystem extends Command
{ {
@ -14,7 +14,7 @@ class PacketSystem extends Command
* @var string * @var string
*/ */
protected $signature = 'packet:system' protected $signature = 'packet:system'
.' {sid : System ID}'; .' {ftn : System address}';
/** /**
* The console command description. * The console command description.
@ -27,19 +27,19 @@ 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()
{ {
$so = System::findOrFail($this->argument('sid')); $ao = Address::findFTN($this->argument('ftn'));
foreach ($so->addresses as $ao) { foreach ($ao->system->addresses as $o) {
$pkt = $ao->getEchomail(FALSE); $pkt = $o->getEchomail();
$this->info(sprintf('System address [%s] has [%d] messages.',$ao->ftn,$pkt?->count())); $this->info(sprintf('System address [%s] has [%d] messages.',$ao->ftn,$pkt?->count()));
if ($pkt) { if ($pkt) {
foreach ($pkt as $msg) foreach ($pkt as $msg)
$this->warn(sprintf('- %s',$msg->msgid)); $this->warn(sprintf('- %s (%s)',$msg->msgid,$msg->id));
} }
} }
} }

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

@ -3,7 +3,9 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\QueryException; use Illuminate\Database\QueryException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -14,7 +16,7 @@ use Illuminate\Support\Facades\Notification;
use Illuminate\Support\ViewErrorBag; use Illuminate\Support\ViewErrorBag;
use App\Classes\FTN\Message; use App\Classes\FTN\Message;
use App\Http\Requests\{AddressMerge,AreafixRequest,SystemRegister}; use App\Http\Requests\{AddressMerge,AreafixRequest,SystemEchoareaRequest,SystemRegister,SystemSessionRequest};
use App\Jobs\AddressPoll; use App\Jobs\AddressPoll;
use App\Models\{Address,Echoarea,Echomail,Filearea,Netmail,Setup,System,Zone}; use App\Models\{Address,Echoarea,Echomail,Filearea,Netmail,Setup,System,Zone};
use App\Notifications\Netmails\AddressLink; use App\Notifications\Netmails\AddressLink;
@ -29,8 +31,6 @@ class SystemController extends Controller
*/ */
public function add_edit(SystemRegister $request,System $o) public function add_edit(SystemRegister $request,System $o)
{ {
$this->authorize('update',$o);
if ($request->post()) { if ($request->post()) {
foreach (['name','location','sysop','hold','phone','address','port','active','method','notes','zt_id','pkt_type','heartbeat'] as $key) foreach (['name','location','sysop','hold','phone','address','port','active','method','notes','zt_id','pkt_type','heartbeat'] as $key)
$o->{$key} = $request->post($key); $o->{$key} = $request->post($key);
@ -694,30 +694,20 @@ class SystemController extends Controller
/** /**
* Update the systems echoareas * Update the systems echoareas
* *
* @param Request $request * @param SystemEchoareaRequest $request
* @param System $o * @param System $o
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
*/ */
public function echoareas(Request $request,System $o) public function echoareas(SystemEchoareaRequest $request,System $o)
{ {
$ao = $o->addresses->firstWhere('id',$request->address_id); $ao = $o->addresses->firstWhere('id',$request->address_id);
if (($request->method() === 'POST') && $request->post()) { if (($request->method() === 'POST') && $request->validated()) {
session()->flash('accordion','echoarea'); $ao->echoareas()->syncWithPivotValues($request->validated('id',[]),['subscribed'=>Carbon::now()]);
if ($ao->trashed() && collect($request->get('id'))->diff($ao->echoareas->pluck('id'))->count())
return redirect()->back()->withErrors(sprintf('Address [%s] has been deleted, cannot add additional echos',$ao->ftn3d));
// Ensure we have session details for this address.
if (! $ao->session('sespass'))
return redirect()->back()->withErrors('System doesnt belong to this network');
$ao->echoareas()->syncWithPivotValues($request->get('id',[]),['subscribed'=>Carbon::now()]);
return redirect()->back()->with('success','Echoareas updated'); return redirect()->back()->with('success','Echoareas updated');
} }
// @todo Allow a NC/RC/ZC to override
$eo = Echoarea::active() $eo = Echoarea::active()
->where('domain_id',$ao->zone->domain_id) ->where('domain_id',$ao->zone->domain_id)
->where(function($query) use ($ao) { ->where(function($query) use ($ao) {
@ -836,26 +826,14 @@ class SystemController extends Controller
/** /**
* Add Session details * Add Session details
* *
* @param Request $request * @param SystemSessionRequest $request
* @param System $o * @param System $o
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException * @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function session_add(Request $request,System $o) public function session_add(SystemSessionRequest $request,System $o)
{ {
// @todo This should be admin of the zone $zo = Zone::findOrFail($request->zone_id);
$this->authorize('update',$o);
session()->flash('accordion','session');
$validate = $request->validate([
'zone_id' => 'required|exists:zones,id',
'sespass' => 'required|string|min:4',
'pktpass' => 'required|string|min:4|max:8',
'ticpass' => 'required|string|min:4',
'fixpass' => 'required|string|min:4',
]);
$zo = Zone::findOrFail($validate['zone_id']);
/* /*
// @todo Disabling this, it needs improvement. If the new node is the ZC it becomes the default for the zone (and therefore remove all defaults from other addresses in the same zone), otherwise default should be false // @todo Disabling this, it needs improvement. If the new node is the ZC it becomes the default for the zone (and therefore remove all defaults from other addresses in the same zone), otherwise default should be false
@ -866,7 +844,7 @@ class SystemController extends Controller
} }
*/ */
$o->sessions()->attach($zo,$validate); $o->sessions()->attach($zo,$request->validated());
return redirect()->to(sprintf('system/addedit/%d',$o->id)); return redirect()->to(sprintf('system/addedit/%d',$o->id));
} }
@ -874,13 +852,14 @@ class SystemController extends Controller
/** /**
* Delete address assigned to a host * Delete address assigned to a host
* *
* @param Address $o * @param System $o
* @return \Illuminate\Http\RedirectResponse * @param Zone $zo
* @throws \Illuminate\Auth\Access\AuthorizationException * @return RedirectResponse
* @throws AuthorizationException
*/ */
public function session_del(System $o,Zone $zo) public function session_del(System $o,Zone $zo)
{ {
$this->authorize('admin',$zo); $this->authorize('update_nn',$o);
session()->flash('accordion','session'); session()->flash('accordion','session');
$o->sessions()->detach($zo); $o->sessions()->detach($zo);

View File

@ -0,0 +1,71 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use App\Models\Domain;
/**
* Validation to register echoareas for a system
* o = System::class
*
* @note This validation only expects to be used with POST
* Variables:
* + "address_id" => "address" // address_id that will receive the echos
* + "domain_id" => "domain" // Of the echoareas
* + "id" => array of IDs // The echos being subscribed (or if absent, are removed)
*
* Rules:
* @see AddressAdd::class for description of authorisation
*/
class SystemEchoareaRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return request()->isMethod('get')
|| Gate::allows('update_nn',$this->route('o'));
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(Request $request): array
{
if (request()->isMethod('get'))
return [];
session()->flash('accordion','echoarea');
return [
'address_id'=>[
'exists:addresses,id',
// Make sure we have session details for the area
function ($attribute,$value,$fail) use ($request) {
$ao = request()->route('o')->addresses->firstWhere('id',$request->address_id);
if ((! $ao) || (! $ao->active) || (! $ao->system->zones->pluck('domain_id')->contains($request->domain_id)))
$fail('Address must be ACTIVE, and have session details for the domain');
},
],
'domain_id'=>'exists:domains,id',
'id'=>[
'array',
'min:1',
// Make sure the echoareas are in the domain
function ($attribute,$value,$fail) use ($request) {
$do = Domain::findOrFail($request->domain_id);
if ($do->echoareas->pluck('id')->intersect($value)->count() !== count($value))
$fail('Some echaoreas dont exist in the domain?');
},
]
];
}
}

View File

@ -22,17 +22,14 @@ class SystemRegister extends FormRequest
*/ */
public function authorize(Request $request) public function authorize(Request $request)
{ {
$this->so = new System; if (! $request->post())
return TRUE;
if (is_numeric($request->name)) { // Cannot claim this site
$this->so = System::findOrNew($request->name); if ($this->route('o')->id === Setup::findOrFail(config('app.id'))->system_id)
return FALSE;
// Cannot claim this site return Gate::allows($this->route('o')->users->count() ? 'update_nn' : 'register',$this->route('o'));
if ($this->so->id === Setup::findOrFail(config('app.id'))->system_id)
return FALSE;
}
return Gate::allows(is_numeric($request->name) && $this->so->users->count() ? 'update' : 'register',$this->so);
} }
public function messages(): array public function messages(): array

View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
class SystemSessionRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
session()->flash('accordion','session');
return Gate::allows('update_nn',$this->route('o'));
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'zone_id' => 'required|exists:zones,id',
'sespass' => 'required|string|min:4',
'pktpass' => 'required|string|min:4|max:8',
'ticpass' => 'required|string|min:4',
'fixpass' => 'required|string|min:4',
];
}
}

View File

@ -8,13 +8,12 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; 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\{Echomail,Netmail,User};
use App\Notifications\Netmails\{EchoareaNotExist,EchoareaNotSubscribed,EchoareaNoWrite,NetmailForward,Reject}; use App\Notifications\Netmails\{EchoareaNotExist,EchoareaNotSubscribed,EchoareaNoWrite,NetmailForward,NetmailHubNoUser};
use App\Traits\ParseAddresses; use App\Traits\ParseAddresses;
class MessageProcess implements ShouldQueue class MessageProcess implements ShouldQueue
@ -23,20 +22,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 +42,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 +50,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,85 +76,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->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;
} }
} }
@ -161,38 +129,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;
@ -201,99 +171,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 NetmailHubNoUser($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) {
@ -306,23 +264,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();
@ -334,70 +293,39 @@ class MessageProcess implements ShouldQueue
)); ));
} }
// If the node is not subscribed
if ($this->pktsrc->echoareas->search(function($item) use ($ea) { return $item->id === $ea->id; }) === FALSE) {
Log::alert(sprintf('%s:! FTN [%s] is not subscribed to [%s] for [%s].',self::LOGKEY,$this->pktsrc->ftn,$ea->name,$this->msg->msgid));
if (! $this->msg->rescanned->count())
Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNotSubscribed($this->msg));
}
// 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 // If the node is not subscribed, we'll accept it, but let them know
$o = new Echomail; if (! $sender->echoareas->contains($this->mo->echoarea)) {
$o->init(); Log::alert(sprintf('%s:! FTN [%s] is not subscribed to [%s] for [%s].',self::LOGKEY,$sender->ftn,$this->mo->echoarea->name,$this->mo->msgid));
$o->to = $this->msg->user_to; if (! $rescanned)
$o->from = $this->msg->user_from; Notification::route('netmail',$sender)->notify(new EchoareaNotSubscribed($this->mo));
$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; // We know about this area, store it
$o->msgid = $this->msg->msgid; $this->mo->save();
$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].', 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=TRUE,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,$f->pktName()));
break; break;
} }
@ -89,37 +110,37 @@ 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) if ($queue || (! $this->interactive))
* // @todo generate warning when netmail comes from a system not defined here Log::info(sprintf('%s:! Message [%s] will be sent to the queue to process',self::LOGKEY,$msg->msgid));
*
* // @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 +148,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

@ -978,47 +978,26 @@ class Address extends Model
/** /**
* Get echomail for this node * Get echomail for this node
* *
* @param bool $update
* @param Collection|null $echomail
* @return Packet|null * @return Packet|null
* @throws \Exception * @throws \Exception
* @todo If we export to uplink hubs without our address in the seenby, they should send the message back to * @todo If we export to uplink hubs without our address in the seenby, they should send the message back to
* us with their seenby's. * us with their seenby's.
*/ */
public function getEchomail(bool $update=TRUE,Collection $echomail=NULL): ?Packet public function getEchomail(): ?Packet
{ {
$pkt = NULL; if (($num=$this->echomailWaiting())->count()) {
if ($echomail) $s = Setup::findOrFail(config('app.id'));
return $this->getPacket($echomail);
$s = Setup::findOrFail(config('app.id'));
$num = self::UncollectedEchomail()
->select('echomails.id')
->where('addresses.id',$this->id)
->groupBy(['echomails.id'])
->get();
if ($num->count()) {
// Limit to max messages
Log::info(sprintf('%s:= Got [%d] echomails for [%s] for sending',self::LOGKEY,$num->count(),$this->ftn)); Log::info(sprintf('%s:= Got [%d] echomails for [%s] for sending',self::LOGKEY,$num->count(),$this->ftn));
// Limit to max messages
if ($num->count() > $s->msgs_pkt) if ($num->count() > $s->msgs_pkt)
Log::notice(sprintf('%s:= Only sending [%d] echomails for [%s]',self::LOGKEY,$s->msgs_pkt,$this->ftn)); Log::notice(sprintf('%s:= Only sending [%d] echomails for [%s]',self::LOGKEY,$s->msgs_pkt,$this->ftn));
$x = $this->echomailWaiting($s->msgs_pkt); return $this->system->packet($this)->mail($num->take($s->msgs_pkt));
$pkt = $this->getPacket($x);
if ($pkt && $pkt->count() && $update)
DB::table('echomail_seenby')
->whereIn('echomail_id',$x->pluck('id'))
->where('address_id',$this->id)
->whereNull('sent_at')
->whereNotNull('export_at')
->update(['sent_pkt'=>$pkt->name]);
} }
return $pkt; return NULL;
} }
/** /**
@ -1036,52 +1015,47 @@ class Address extends Model
/** /**
* Get netmail for this node (including it's children) * Get netmail for this node (including it's children)
* *
* @param bool $update
* @return Packet|null * @return Packet|null
* @throws \Exception * @throws \Exception
*/ */
public function getNetmail(bool $update=FALSE): ?Packet public function getNetmail(): ?Packet
{ {
$pkt = NULL; if (($num=$this->netmailAlertWaiting())->count()) {
Log::debug(sprintf('%s:= Packaging [%d] netmail alerts to [%s]',self::LOGKEY,$num->count(),$this->ftn));
$s = Setup::findOrFail(config('app.id')); // Find any message that defines a packet password
$pass = $num
->map(function($item) {
$passpos = strpos($item->subject,':');
return (($passpos > 0) && ($passpos < 8)) ? substr($item->subject,0,$passpos) : NULL;
})
->filter()
->pop();
if (($x=$this->netmailAlertWaiting())->count()) { Log::debug(sprintf('%s:= Overwriting system packet password with [%s] for [%s]',self::LOGKEY,$pass,$this->ftn));
Log::debug(sprintf('%s:= Packaging [%d] netmail alerts to [%s]',self::LOGKEY,$x->count(),$this->ftn));
$passpos = strpos($x->last()->subject,':');
if ($passpos > 8) return $this->system->packet($this,$pass)->mail(
Log::alert(sprintf('%s:! Password would be greater than 8 chars? [%d]',self::LOGKEY,$passpos)); $num->filter(fn($item)=>preg_match("/^{$pass}:/",$item->subject))
->transform(function($item) use ($pass) {
$pkt = $this->getPacket($x,substr($x->last()->subject,0,$passpos)); $item->subject = preg_replace("/^{$pass}:/",'',$item->subject);
return $item;
if ($pkt && $pkt->count() && $update) })
DB::table('netmails') );
->whereIn('id',$x->pluck('id'))
->update(['sent_pkt'=>$pkt->name]);
return $pkt;
} }
if (($x=$this->netmailWaiting()) if (($num=$this->netmailWaiting())->count()) {
->count()) $s = Setup::findOrFail(config('app.id'));
{
Log::debug(sprintf('%s:= Got [%d] netmails for [%s] for sending',self::LOGKEY,$x->count(),$this->ftn));
if ($x->count() > $s->msgs_pkt) { Log::debug(sprintf('%s:= Got [%d] netmails for [%s] for sending',self::LOGKEY,$num->count(),$this->ftn));
$x = $x->take($s->msgs_pkt);
Log::alert(sprintf('%s:= Only sending [%d] netmails for [%s]',self::LOGKEY,$x->count(),$this->ftn));
}
$pkt = $this->getPacket($x); // Limit to max messages
if ($num->count() > $s->msgs_pkt)
Log::alert(sprintf('%s:= Only sending [%d] netmails for [%s]',self::LOGKEY,$num->count(),$this->ftn));
if ($pkt && $pkt->count() && $update) return $this->system->packet($this)->mail($num->take($s->msgs_pkt));
DB::table('netmails')
->whereIn('id',$x->pluck('id'))
->update(['sent_pkt'=>$pkt->name]);
} }
return $pkt; return NULL;
} }
/** /**
@ -1091,8 +1065,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 +1079,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
@ -1151,6 +1126,7 @@ class Address extends Model
* *
* @return Collection * @return Collection
* @throws \Exception * @throws \Exception
* @note The packet password to use is on the subject line for these alerts
*/ */
public function netmailAlertWaiting(): Collection public function netmailAlertWaiting(): Collection
{ {

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,47 @@ 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
// <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
} elseif ($model->set->has('set_sender')) {
$nodes->push(['node'=>$model->set->get('set_sender'),'datetime'=>Carbon::now(),'program'=>'Unknown']);
} }
// Save the Path // Save the Path
@ -118,15 +142,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 +176,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,88 +187,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;
// 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

@ -110,7 +110,8 @@ class System extends Model
public function sessions() public function sessions()
{ {
return $this->belongsToMany(Zone::class) return $this->belongsToMany(Zone::class)
->withPivot(['sespass','pktpass','ticpass','fixpass','zt_ipv4','zt_ipv6','default']); ->withPivot(['sespass','pktpass','ticpass','fixpass','zt_ipv4','zt_ipv6','default'])
->dontCache();
} }
/** /**
@ -251,12 +252,19 @@ class System extends Model
/** /**
* Return the packet that this system uses * Return the packet that this system uses
* *
* @param Address $ao
* @param string|null $password
* @return Packet * @return Packet
*/ */
public function packet(): Packet public function packet(Address $ao,string $password=NULL): 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)
->password($password);
} }
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->name));
} }
} }

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

@ -8,7 +8,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification; use Illuminate\Notifications\Notification;
use App\Classes\FTN\Message; use App\Classes\FTN\Message;
use App\Models\{Echoarea, Echomail, Setup, System}; use App\Models\{Echoarea,Echomail,Setup};
abstract class Echomails extends Notification //implements ShouldQueue abstract class Echomails extends Notification //implements ShouldQueue
{ {
@ -46,26 +46,24 @@ abstract class Echomails extends Notification //implements ShouldQueue
*/ */
abstract public function toEchomail(object $notifiable): Echomail; abstract public function toEchomail(object $notifiable): Echomail;
protected function setupEchomail(Message $mo,object $notifiable): Echomail protected function setupEchomail(Echomail $mo,object $notifiable): Echomail
{ {
$echoarea = $notifiable->routeNotificationFor(static::via); $echoarea = $notifiable->routeNotificationFor(static::via);
$eo = Echoarea::where('name',$echoarea)->singleOrFail();
$o = new Echomail; $o = new Echomail;
$o->init();
$o->from = Setup::PRODUCT_NAME; $o->from = Setup::PRODUCT_NAME;
$o->replyid = $mo->msgid; $o->replyid = $mo->msgid;
$o->echoarea_id = $eo->id; $o->echoarea_id = $echoarea->id;
$o->datetime = Carbon::now(); $o->datetime = Carbon::now();
$o->tzoffset = $o->datetime->utcOffset(); $o->tzoffset = $o->datetime->utcOffset();
$o->fftn_id = ($x=our_address($mo->fboss_o))->id; $o->fftn_id = ($x=our_address($mo->fftn))->id;
$o->flags = (Message::FLAG_LOCAL); $o->flags = (Message::FLAG_LOCAL);
$o->tearline = sprintf('%s (%04X)',Setup::PRODUCT_NAME,Setup::PRODUCT_ID); $o->tearline = sprintf('%s (%04X)',Setup::PRODUCT_NAME,Setup::PRODUCT_ID);
$o->origin = sprintf('%s (%s)',Setup::PRODUCT_NAME,$x->ftn4d); $o->origin = sprintf('%s (%s)',Setup::PRODUCT_NAME,$x->ftn4d);
$o->kludges = collect(['chrs'=>$mo->kludge->get('chrs') ?: 'CP437 2']); $o->kludges->put('CHRS:',$mo->kludges->get('chrs') ?: 'CP437 2');
return $o; return $o;
} }

View File

@ -6,8 +6,8 @@ use Carbon\Carbon;
use Carbon\CarbonInterface; use Carbon\CarbonInterface;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Classes\{Fonts\Thick,Fonts\Thin,FTN\Message,Page}; use App\Classes\{Fonts\Thick,Fonts\Thin,Page};
use App\Models\{Echomail,System}; use App\Models\Echomail;
use App\Notifications\Echomails; use App\Notifications\Echomails;
use App\Traits\MessagePath; use App\Traits\MessagePath;
@ -17,14 +17,14 @@ class Test extends Echomails
private const LOGKEY = 'NNP'; private const LOGKEY = 'NNP';
private Message $mo; private Echomail $mo;
/** /**
* Reply to a netmail ping request. * Reply to a netmail ping request.
* *
* @param Message $mo * @param Echomail $mo
*/ */
public function __construct(Message $mo) public function __construct(Echomail $mo)
{ {
parent::__construct(); parent::__construct();
@ -43,9 +43,9 @@ class Test extends Echomails
$o = $this->setupEchomail($this->mo,$notifiable); $o = $this->setupEchomail($this->mo,$notifiable);
$echoarea = $notifiable->routeNotificationFor(static::via); $echoarea = $notifiable->routeNotificationFor(static::via);
Log::info(sprintf('%s:+ Creating test echomail to [%s]',self::LOGKEY,$echoarea)); Log::info(sprintf('%s:+ Creating TEST echomail in [%s]',self::LOGKEY,$echoarea->name));
$o->to = $this->mo->user_from; $o->to = $this->mo->from;
$o->subject = 'Test Reply'; $o->subject = 'Test Reply';
// Message // Message

View File

@ -6,7 +6,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,User}; use App\Models\{Netmail,User};
use App\Traits\PageTemplate; use App\Traits\PageTemplate;
class AddressLink extends Netmails class AddressLink extends Netmails

View File

@ -4,9 +4,8 @@ namespace App\Notifications\Netmails;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Message;
use App\Notifications\Netmails; use App\Notifications\Netmails;
use App\Models\{Netmail,System}; use App\Models\Netmail;
use App\Traits\{MessagePath,PageTemplate}; use App\Traits\{MessagePath,PageTemplate};
class Areafix extends Netmails class Areafix extends Netmails
@ -15,14 +14,14 @@ class Areafix extends Netmails
private const LOGKEY = 'NAF'; private const LOGKEY = 'NAF';
private Message $mo; private Netmail $mo;
/** /**
* Reply to an areafix request. * Reply to an areafix request.
* *
* @param Message $mo * @param Netmail $mo
*/ */
public function __construct(Message $mo) public function __construct(Netmail $mo)
{ {
parent::__construct(); parent::__construct();
@ -43,7 +42,7 @@ class Areafix extends Netmails
Log::info(sprintf('%s:+ Responding to areafix with netmail to [%s]',self::LOGKEY,$ao->ftn)); Log::info(sprintf('%s:+ Responding to areafix with netmail to [%s]',self::LOGKEY,$ao->ftn));
$o->to = $this->mo->user_from; $o->to = $this->mo->from;
$o->replyid = $this->mo->msgid; $o->replyid = $this->mo->msgid;
$o->subject = 'Areafix Reply'; $o->subject = 'Areafix Reply';

View File

@ -4,9 +4,8 @@ namespace App\Notifications\Netmails\Areafix;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Message;
use App\Notifications\Netmails; use App\Notifications\Netmails;
use App\Models\{Netmail,System}; use App\Models\Netmail;
use App\Traits\{MessagePath,PageTemplate}; use App\Traits\{MessagePath,PageTemplate};
class NotConfiguredHere extends Netmails class NotConfiguredHere extends Netmails
@ -15,14 +14,14 @@ class NotConfiguredHere extends Netmails
private const LOGKEY = 'NCH'; private const LOGKEY = 'NCH';
private Message $mo; private Netmail $mo;
/** /**
* Reply to a areafix, but the system isnt configured here. * Reply to a areafix, but the system isnt configured here.
* *
* @param Message $mo * @param Netmail $mo
*/ */
public function __construct(Message $mo) public function __construct(Netmail $mo)
{ {
parent::__construct(); parent::__construct();
@ -43,7 +42,7 @@ class NotConfiguredHere extends Netmails
Log::info(sprintf('%s:+ Responding to areafix for a node [%s] not configured here',self::LOGKEY,$ao->ftn)); Log::info(sprintf('%s:+ Responding to areafix for a node [%s] not configured here',self::LOGKEY,$ao->ftn));
$o->to = $this->mo->user_from; $o->to = $this->mo->from;
$o->replyid = $this->mo->msgid; $o->replyid = $this->mo->msgid;
$o->subject = 'Areafix - Not Configured Here'; $o->subject = 'Areafix - Not Configured Here';

View File

@ -5,9 +5,8 @@ namespace App\Notifications\Netmails;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
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 EchoareaNoWrite extends Netmails class EchoareaNoWrite extends Netmails
@ -16,14 +15,14 @@ class EchoareaNoWrite 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 they dont have permission. * Send a sysop a message if they attempt to write to an area that they dont have permission.
* *
* @param Message $mo * @param Echomail $mo
*/ */
public function __construct(Message $mo) public function __construct(Echomail $mo)
{ {
parent::__construct(); parent::__construct();
@ -42,9 +41,9 @@ class EchoareaNoWrite extends Netmails
$o = $this->setupNetmail($notifiable); $o = $this->setupNetmail($notifiable);
$ao = $notifiable->routeNotificationFor(static::via); $ao = $notifiable->routeNotificationFor(static::via);
Log::info(sprintf('%s:+ Creating ECHOMAIL NO WRITE netmail to [%s]',self::LOGKEY,$ao->ftn)); Log::info(sprintf('%s:+ Creating ECHOAREA NO WRITE netmail to [%s]',self::LOGKEY,$ao->ftn));
$o->subject = 'Echomail rejected - '.$this->mo->msgid; $o->subject = sprintf('Echomail #%s rejected to %s',$this->mo->msgid,$this->mo->echoarea->name);
// Message // Message
$msg = $this->page(FALSE,'nowrite'); $msg = $this->page(FALSE,'nowrite');
@ -52,13 +51,13 @@ class EchoareaNoWrite 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(),
) )
); );
$msg->addText(sprintf("It appears that you do not have permission to post in this echoarea using the address [%s], so the message from your system was rejected.\r\r",$ao->ftn)); $msg->addText(sprintf("It appears that you do not have permission to post in [%s] using the address [%s], so the message from your system was rejected.\r\r",$this->mo->echoarea->name,$ao->ftn));
$msg->addText("Please contact the ZC if you think this is a mistake.\r\r"); $msg->addText("Please contact the ZC if you think this is a mistake.\r\r");
$msg->addText($this->message_path($this->mo)); $msg->addText($this->message_path($this->mo));

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();
@ -42,9 +42,9 @@ class EchoareaNotExist extends Netmails
$o = $this->setupNetmail($notifiable); $o = $this->setupNetmail($notifiable);
$ao = $notifiable->routeNotificationFor(static::via); $ao = $notifiable->routeNotificationFor(static::via);
Log::info(sprintf('%s:+ Creating ECHOMAIL NOT EXIST netmail to [%s]',self::LOGKEY,$ao->ftn)); Log::info(sprintf('%s:+ Creating ECHOAREA 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,13 +52,13 @@ 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(),
) )
); );
$msg->addText("It appears that the echoarea that this message is for doesnt exist, so the message from your system was rejected.\r\r"); $msg->addText(sprintf("It appears that the echoarea [%s] that this message is for doesnt exist, so the message from your system was rejected.\r\r",$this->mo->set->get('set_echoarea')));
$msg->addText("Please contact the ZC if you think this is a mistake.\r\r"); $msg->addText("Please contact the ZC if you think this is a mistake.\r\r");
$msg->addText($this->message_path($this->mo)); $msg->addText($this->message_path($this->mo));

View File

@ -5,9 +5,8 @@ namespace App\Notifications\Netmails;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
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 EchoareaNotSubscribed extends Netmails class EchoareaNotSubscribed extends Netmails
@ -16,14 +15,14 @@ class EchoareaNotSubscribed extends Netmails
private const LOGKEY = 'NNW'; private const LOGKEY = 'NNW';
private Message $mo; private Echomail $mo;
/** /**
* Send a sysop a message if they write to an area that they hadnt previously subscribed to. * Send a sysop a message if they write to an area that they hadnt previously subscribed to.
* *
* @param Message $mo * @param Echomail $mo
*/ */
public function __construct(Message $mo) public function __construct(Echomail $mo)
{ {
parent::__construct(); parent::__construct();
@ -42,17 +41,17 @@ class EchoareaNotSubscribed extends Netmails
$o = $this->setupNetmail($notifiable); $o = $this->setupNetmail($notifiable);
$ao = $notifiable->routeNotificationFor(static::via); $ao = $notifiable->routeNotificationFor(static::via);
Log::info(sprintf('%s:+ Creating ECHOMAIL NOT SUBSCRIBED netmail to [%s]',self::LOGKEY,$ao->ftn)); Log::info(sprintf('%s:+ Creating ECHOAREA NOT SUBSCRIBED netmail to [%s]',self::LOGKEY,$ao->ftn));
$o->subject = 'Echoarea not subscribed - '.$this->mo->echoarea; $o->subject = 'Echoarea not subscribed - '.$this->mo->echoarea->name;
// Message // Message
$msg = $this->page(FALSE,'nothere'); $msg = $this->page(FALSE,'nosub');
$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(),
) )

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\Netmail;
use App\Traits\{MessagePath,PageTemplate}; use App\Traits\{MessagePath,PageTemplate};
class EchomailBadAddress extends Netmails class EchomailBadAddress extends Netmails
@ -52,7 +52,7 @@ class EchomailBadAddress 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(),
) )

View File

@ -4,9 +4,8 @@ namespace App\Notifications\Netmails;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Message;
use App\Notifications\Netmails; use App\Notifications\Netmails;
use App\Models\{Address,Netmail,System}; use App\Models\{Address,Netmail};
use App\Traits\{MessagePath,PageTemplate}; use App\Traits\{MessagePath,PageTemplate};
class NetmailForward extends Netmails class NetmailForward extends Netmails
@ -16,15 +15,15 @@ class NetmailForward extends Netmails
private const LOGKEY = 'NNP'; private const LOGKEY = 'NNP';
private Address $ao; private Address $ao;
private Message $mo; private Netmail $mo;
/** /**
* Reply to a netmail ping request. * Reply to a netmail ping request.
* *
* @param Message $mo * @param Netmail $mo
* @param Address $ao * @param Address $ao
*/ */
public function __construct(Message $mo,Address $ao) public function __construct(Netmail $mo,Address $ao)
{ {
parent::__construct(); parent::__construct();
@ -44,9 +43,9 @@ class NetmailForward extends Netmails
$o = $this->setupNetmail($notifiable); $o = $this->setupNetmail($notifiable);
$ao = $notifiable->routeNotificationFor(static::via); $ao = $notifiable->routeNotificationFor(static::via);
Log::info(sprintf('%s:+ Advising [%s@%s] that netmail to [%s] will be forwarded to [%s].',self::LOGKEY,$this->mo->user_from,$ao->ftn,$this->mo->user_to,$this->ao->ftn)); Log::info(sprintf('%s:+ Advising [%s@%s] that netmail to [%s] will be forwarded to [%s].',self::LOGKEY,$this->mo->from,$ao->ftn,$this->mo->to,$this->ao->ftn));
$o->to = $this->mo->user_from; $o->to = $this->mo->from;
$o->replyid = $this->mo->msgid; $o->replyid = $this->mo->msgid;
$o->subject = sprintf('Your netmail is being forwarded to %s',$this->ao->ftn3d); $o->subject = sprintf('Your netmail is being forwarded to %s',$this->ao->ftn3d);
@ -56,10 +55,10 @@ class NetmailForward extends Netmails
$msg->addText("Howdy, Clrghouz is not a BBS, so users cannot login to collect netmail.\r\r\r"); $msg->addText("Howdy, Clrghouz is not a BBS, so users cannot login to collect netmail.\r\r\r");
$msg->addText(sprintf("Never fear, your msg [%s] to [%s] has been forwarded, to [%s].\r\r", $msg->addText(sprintf("Never fear, your msg [%s] to [%s] has been forwarded, to [%s].\r\r",
$this->mo->msgid, $this->mo->msgid,
$this->mo->user_to, $this->mo->to,
$this->ao->ftn3d, $this->ao->ftn3d,
)); ));
$msg->addText(sprintf("To avoid receiving this netmail, send messages to [%s] to [%s].\r\r",$this->mo->user_to,$this->ao->ftn3d)); $msg->addText(sprintf("To avoid receiving this netmail, send messages to [%s] to [%s].\r\r",$this->mo->to,$this->ao->ftn3d));
$o->msg = $msg->render(); $o->msg = $msg->render();
$o->tagline = 'Thank you so much for your mail. I love it already.'; $o->tagline = 'Thank you so much for your mail. I love it already.';

View File

@ -5,25 +5,24 @@ namespace App\Notifications\Netmails;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Message;
use App\Notifications\Netmails; use App\Notifications\Netmails;
use App\Models\{Netmail,System}; use App\Models\Netmail;
use App\Traits\{MessagePath,PageTemplate}; use App\Traits\{MessagePath,PageTemplate};
class Reject extends Netmails class NetmailHubNoUser extends Netmails
{ {
use MessagePath,PageTemplate; use MessagePath,PageTemplate;
private const LOGKEY = 'NNP'; private const LOGKEY = 'NNP';
private Message $mo; private Netmail $mo;
/** /**
* Reply to a netmail ping request. * Reply to a netmail ping request.
* *
* @param Message $mo * @param Netmail $mo
*/ */
public function __construct(Message $mo) public function __construct(Netmail $mo)
{ {
parent::__construct(); parent::__construct();
@ -42,9 +41,9 @@ class Reject extends Netmails
$o = $this->setupNetmail($notifiable); $o = $this->setupNetmail($notifiable);
$ao = $notifiable->routeNotificationFor(static::via); $ao = $notifiable->routeNotificationFor(static::via);
Log::info(sprintf('%s:+ Creating reject netmail to [%s]',self::LOGKEY,$ao->ftn)); Log::info(sprintf('%s:+ Creating HUB NO USER netmail to [%s]',self::LOGKEY,$ao->ftn));
$o->to = $this->mo->user_from; $o->to = $this->mo->from;
$o->replyid = $this->mo->msgid; $o->replyid = $this->mo->msgid;
$o->subject = 'Message Undeliverable - '.$this->mo->msgid; $o->subject = 'Message Undeliverable - '.$this->mo->msgid;
@ -54,7 +53,7 @@ class Reject extends Netmails
$msg->addText( $msg->addText(
sprintf("Your netmail with ID [%s] to [%s] here was received here on [%s] and it looks like you sent it on [%s].\r\r", sprintf("Your netmail 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(),
) )

View File

@ -6,7 +6,7 @@ use Illuminate\Support\Facades\Log;
use App\Notifications\Netmails; use App\Notifications\Netmails;
use App\Classes\FTN\Message; use App\Classes\FTN\Message;
use App\Models\{Netmail,System}; use App\Models\Netmail;
use App\Traits\PageTemplate; use App\Traits\PageTemplate;
class PacketPasswordInvalid extends Netmails class PacketPasswordInvalid extends Netmails

View File

@ -8,7 +8,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\Netmail;
use App\Traits\{MessagePath,PageTemplate}; use App\Traits\{MessagePath,PageTemplate};
class Ping extends Netmails class Ping extends Netmails
@ -17,14 +17,14 @@ class Ping extends Netmails
private const LOGKEY = 'NNP'; private const LOGKEY = 'NNP';
private Message $mo; private Netmail $mo;
/** /**
* Reply to a netmail ping request. * Reply to a netmail ping request.
* *
* @param Message $mo * @param Message $mo
*/ */
public function __construct(Message $mo) public function __construct(Netmail $mo)
{ {
parent::__construct(); parent::__construct();
@ -45,7 +45,7 @@ class Ping extends Netmails
Log::info(sprintf('%s:+ Creating PING netmail to [%s]',self::LOGKEY,$ao->ftn)); Log::info(sprintf('%s:+ Creating PING netmail to [%s]',self::LOGKEY,$ao->ftn));
$o->to = $this->mo->user_from; $o->to = $this->mo->from;
$o->replyid = $this->mo->msgid; $o->replyid = $this->mo->msgid;
$o->subject = 'Ping Reply'; $o->subject = 'Ping Reply';

View File

@ -5,9 +5,8 @@ namespace App\Notifications\Netmails;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Message;
use App\Notifications\Netmails; use App\Notifications\Netmails;
use App\Models\{Netmail,System}; use App\Models\Netmail;
use App\Traits\{MessagePath,PageTemplate}; use App\Traits\{MessagePath,PageTemplate};
class PollingFailed extends Netmails class PollingFailed extends Netmails

View File

@ -5,7 +5,7 @@ namespace App\Notifications\Netmails;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Notifications\Netmails; use App\Notifications\Netmails;
use App\Models\{Netmail,System}; use App\Models\Netmail;
use App\Traits\PageTemplate; use App\Traits\PageTemplate;
class Test extends Netmails class Test extends Netmails

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

@ -6,6 +6,21 @@ use Illuminate\Auth\Access\HandlesAuthorization;
use App\Models\{System,User}; use App\Models\{System,User};
/**
* This handles updating system records
*
* Authorisation is defined by function_role_only, where
* - function = create,delete,update
* - role = admin,zc,rc,nc,hc,nn,pt
* - only = only that role can do (no hierarchy permission)
* ie:
* - admin - only site admin can do (user = admin)
* - zc - only ZC can perform (user has an address that is a ZC)
* - rc - only RC (or ZC) ...
* - hc - only HC (or ZC/RC) ...
* - nn - only NN (or ZC/RC/HC) ...
* - pt - only PT (or ZC/RC/HC/NN) ...
*/
class SystemPolicy class SystemPolicy
{ {
use HandlesAuthorization; use HandlesAuthorization;
@ -49,7 +64,7 @@ class SystemPolicy
* @param System $system * @param System $system
* @return bool * @return bool
*/ */
public function update(User $user, System $system): bool public function update_nn(User $user,System $system): bool
{ {
// Site Admins can always edit // Site Admins can always edit
if ($user->isAdmin()) if ($user->isAdmin())
@ -59,7 +74,8 @@ class SystemPolicy
if (! $system->exists) if (! $system->exists)
return FALSE; return FALSE;
return $system->users->contains($user) // @todo Permit ZC, RC, NC, HUB user
&& (($system->addresses->count() === 0) || ($system->addresses->where('validated',TRUE)->count()));
return $system->users->contains($user) && $system->akas->count();
} }
} }

View File

@ -89,7 +89,7 @@ trait EncodeUTF8
return $this->attributes[$key]; return $this->attributes[$key];
} }
return parent::getAttribute($key); return Arr::get($this->_encoded,$key) ? $this->attributes[$key] : parent::getAttribute($key);
} }
public function setAttribute($key,$value) public function setAttribute($key,$value)

View File

@ -0,0 +1,197 @@
<?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 ($this->exists && (! $val))
return $val;
// If $val is not set, then it may be an unsaved object
return sprintf(' * Origin: %s',
((! $this->exists) && $this->set->has('set_origin'))
? $this->set->get('set_origin')
: $val);
}
public function getTaglineAttribute(string $val=NULL): ?string
{
if ($this->exists && (! $val))
return $val;
// If $val is not set, then it may be an unsaved object
return sprintf('... %s',
((! $this->exists) && $this->set->has('set_tagline'))
? $this->set->get('set_tagline')
: $val);
}
public function getTearlineAttribute(string $val=NULL): ?string
{
if ($this->exists && (! $val))
return $val;
// If $val is not set, then it may be an unsaved object
return sprintf('--- %s',
((! $this->exists) && $this->set->has('set_tearline'))
? $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),
'pktpasswd' => $this->isFlagSet(Message::FLAG_PKTPASSWD),
])->filter();
}
public 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,42 +5,40 @@
*/ */
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->path->count())
if ($mo->via->count()) if ($mo instanceof Netmail) {
foreach ($mo->via as $via) foreach ($mo->path as $o)
$reply .= sprintf("VIA: %s\r",$via); $reply .= sprintf("VIA: %s\r",$mo->via($o));
else
$reply .= "No path information? This would be normal if this message came directly to the hub\r";
} else { } else {
if ($mo->path->count()) foreach ($mo->path as $o)
foreach ($mo->path as $via) $reply .= sprintf("VIA: %s\r",$o->ftn);
$reply .= sprintf("VIA: %s\r",$via); }
else
$reply .= "No path information? This would be normal if this message came directly to the hub\r"; else
} $reply .= "No path information? This would be normal if this message came directly to the hub\r";
$reply .= "+--[ END MESSAGE ]------------------------------------+\r\r"; $reply .= "+--[ END MESSAGE ]------------------------------------+\r\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,28 @@
<?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->string('origin')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('netmails', function (Blueprint $table) {
$table->dropColumn('origin');
});
}
};

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

@ -217,7 +217,9 @@
@if(($x=$oo->systems->where('pivot.default',TRUE))->count() && ($x->first()->id !== $o->id)) @if(($x=$oo->systems->where('pivot.default',TRUE))->count() && ($x->first()->id !== $o->id))
<i class="bi bi-dash-square"></i> <i class="bi bi-dash-square"></i>
@else @else
<span class="default" itemid="{{ $oo->id }}"><i class="bi bi-{{ $x->count() ? 'check-square' : 'square' }}"></i></span> @can('admin')
<span class="default" itemid="{{ $oo->id }}"><i class="bi bi-{{ $x->count() ? 'check-square' : 'square' }}"></i></span>
@endcan
@endif @endif
</td> </td>
<td>{{ $oo->pivot->sespass }}</td> <td>{{ $oo->pivot->sespass }}</td>
@ -239,7 +241,9 @@
<p>No session details exist</p> <p>No session details exist</p>
@endif @endif
@include('system.widget.form-session') @can('update_nn',$o)
@include('system.widget.form-session')
@endcan
</div> </div>
</div> </div>
</div> </div>
@ -603,6 +607,7 @@
@section('page-scripts') @section('page-scripts')
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { $(document).ready(function () {
@can('admin')
$('.default').click(function () { $('.default').click(function () {
var item = this; var item = this;
icon = $(item).find('i'); icon = $(item).find('i');
@ -634,6 +639,7 @@
cache: false cache: false
}) })
}); });
@endcan
$('data.validated').on('click',function(item) { $('data.validated').on('click',function(item) {
that = $(this); that = $(this);
var values = item.delegateTarget.value.split(':'); var values = item.delegateTarget.value.split(':');

View File

@ -42,7 +42,7 @@
<tbody> <tbody>
@foreach (\App\Models\System::active()->with(['addresses.zone.domain'])->get() as $oo) @foreach (\App\Models\System::active()->with(['addresses.zone.domain'])->get() as $oo)
<tr> <tr>
<td><a href="{{ url('system/addedit',[$oo->id]) }}" @cannot('update',$oo)class="disabled" @endcannot>{{ $oo->id }}</a></td> <td><a href="{{ url('system/addedit',[$oo->id]) }}" @cannot('update_nn',$oo)class="disabled" @endcannot>{{ $oo->id }}</a></td>
<td>{{ $oo->name }} @if(! $oo->active)<span class="float-end"><small>[i]</small></span>@endif</td> <td>{{ $oo->name }} @if(! $oo->active)<span class="float-end"><small>[i]</small></span>@endif</td>
<td>{{ $oo->sysop }}</td> <td>{{ $oo->sysop }}</td>
<td>{{ $oo->location }}</td> <td>{{ $oo->location }}</td>

View File

@ -36,7 +36,7 @@
@endif @endif
</span> </span>
@can('update',$o) @can('update_nn',$o)
<div class="col-2"> <div class="col-2">
<button type="submit" name="submit" class="btn btn-success float-end">Add</button> <button type="submit" name="submit" class="btn btn-success float-end">Add</button>
</div> </div>

View File

@ -36,7 +36,7 @@
@endif @endif
</span> </span>
@can('update',$o) @can('update_nn',$o)
<div class="col-2"> <div class="col-2">
<button type="submit" name="submit" class="btn btn-success float-end">Add</button> <button type="submit" name="submit" class="btn btn-success float-end">Add</button>
</div> </div>

View File

@ -1,6 +1,10 @@
<!-- $o = System::class --> <!-- $o = System::class -->
@if(($x=\App\Models\Zone::active() @php
->whereIn('id',$o->zones->pluck('id')) use App\Models\Zone;
@endphp
@if(($x=Zone::active()
->whereIn('id',$o->addresses->pluck('zone.id'))
->whereNotIn('id',$o->sessions->pluck('id')) ->whereNotIn('id',$o->sessions->pluck('id'))
->with(['domain']) ->with(['domain'])
->get())->count()) ->get())->count())
@ -42,7 +46,7 @@
<label for="sespass" class="form-label">Session Password</label> <label for="sespass" class="form-label">Session Password</label>
<div class="input-group has-validation"> <div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-lock"></i></span> <span class="input-group-text"><i class="bi bi-lock"></i></span>
<input type="text" style="width: 35%;" class="form-control @error('sespass') is-invalid @enderror" id="sespass" placeholder="Session" name="sespass" value="{{ old('sespass') }}" @cannot('update',$o)disabled @endcannot required> <input type="text" style="width: 35%;" class="form-control @error('sespass') is-invalid @enderror" id="sespass" placeholder="Session" name="sespass" value="{{ old('sespass') }}" required>
<span class="invalid-feedback" role="alert"> <span class="invalid-feedback" role="alert">
@error('sespass') @error('sespass')
{{ $message }} {{ $message }}
@ -58,7 +62,7 @@
<label for="pktpass" class="form-label">Packet Password</label> <label for="pktpass" class="form-label">Packet Password</label>
<div class="input-group has-validation"> <div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-box"></i></span> <span class="input-group-text"><i class="bi bi-box"></i></span>
<input type="text" style="width: 35%;" class="form-control @error('pktpass') is-invalid @enderror" id="pktpass" placeholder="Packet" name="pktpass" value="{{ old('pktpass') }}" @cannot('update',$o)disabled @endcannot required> <input type="text" style="width: 35%;" class="form-control @error('pktpass') is-invalid @enderror" id="pktpass" placeholder="Packet" name="pktpass" value="{{ old('pktpass') }}" required>
<span class="invalid-feedback" role="alert"> <span class="invalid-feedback" role="alert">
@error('pktpass') @error('pktpass')
{{ $message }} {{ $message }}
@ -76,7 +80,7 @@
<label for="fixpass" class="form-label">Areafix Password</label> <label for="fixpass" class="form-label">Areafix Password</label>
<div class="input-group has-validation"> <div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-card-text"></i></span> <span class="input-group-text"><i class="bi bi-card-text"></i></span>
<input type="text" style="width: 35%;" class="form-control @error('fixpass') is-invalid @enderror" id="fixpass" placeholder="Areafix" name="fixpass" value="{{ old('fixpass') }}" @cannot('update',$o)disabled @endcannot required> <input type="text" style="width: 35%;" class="form-control @error('fixpass') is-invalid @enderror" id="fixpass" placeholder="Areafix" name="fixpass" value="{{ old('fixpass') }}" required>
<span class="invalid-feedback" role="alert"> <span class="invalid-feedback" role="alert">
@error('fixpass') @error('fixpass')
{{ $message }} {{ $message }}
@ -92,7 +96,7 @@
<label for="ticpass" class="form-label">TIC Password</label> <label for="ticpass" class="form-label">TIC Password</label>
<div class="input-group has-validation"> <div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-archive"></i></span> <span class="input-group-text"><i class="bi bi-archive"></i></span>
<input type="text" style="width: 35%;" class="form-control @error('ticpass') is-invalid @enderror" id="ticpass" placeholder="TIC" name="ticpass" value="{{ old('ticpass') }}" @cannot('update',$o)disabled @endcannot required> <input type="text" style="width: 35%;" class="form-control @error('ticpass') is-invalid @enderror" id="ticpass" placeholder="TIC" name="ticpass" value="{{ old('ticpass') }}" required>
<span class="invalid-feedback" role="alert"> <span class="invalid-feedback" role="alert">
@error('ticpass') @error('ticpass')
{{ $message }} {{ $message }}
@ -118,11 +122,9 @@
@endif @endif
</span> </span>
@can('update',$o) <div class="col-2">
<div class="col-2"> <button type="submit" name="submit" class="btn btn-success float-end">Add</button>
<button type="submit" name="submit" class="btn btn-success float-end">Add</button> </div>
</div>
@endcan
</div> </div>
</div> </div>
</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,48 +51,60 @@ 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>
@if($msg instanceof Netmail)
<div class="col-8">
SENT:
@if($msg->sent_pkt && $msg->sent_at)
<strong class="highlight">{{ $msg->sent_at }}</strong> (<strong class="highlight">{{ $msg->sent_pkt }}.pkt</strong>)
@else
<strong class="highlight">NOT SENT</strong>
@endif
</div> </div>
@endif @endif
@ -89,18 +113,16 @@ use App\Classes\FTN\Message;
<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}
); );
}); });
</script> </script>
@append @append