Compare commits

..

2 Commits

Author SHA1 Message Date
e65e664792 Add system_id when listing AKAs, so that we can reference the system relation.
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 38s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m54s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-06-21 14:44:28 +10:00
4f6e1e90c6 Fix file sending, as a result of moving $size into Send::class when optimising mail sending
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 45s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m52s
Create Docker Image / Final Docker Image Manifest (push) Successful in 12s
2024-06-21 12:15:22 +10:00
185 changed files with 3414 additions and 4966 deletions

View File

@ -1,25 +1,55 @@
APP_NAME="Clearing Houz"
APP_ENV=production
APP_KEY=
APP_MAINTENANCE_DRIVER=cache
APP_MAINTENANCE_STORE=memcached
APP_DEBUG=false
APP_URL=http://clrghouz
APP_TIMEZONE=
APP_URL=
AUTH_PASSWORD_RESET_TOKEN_TABLE=password_resets
CACHE_STORE=memcached
MEMCACHED_HOST=memcached
LOG_CHANNEL=stack
LOG_LEVEL=info
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=clrghouz
DB_USERNAME=clrghouz
DB_PASSWORD=
#DB_SSLMODE=prefer
#DB_SSLROOTCERT=/var/www/html/config/ssl/ca.crt
#DB_SSLCERT=/var/www/html/config/ssl/client.crt
#DB_SSLKEY=/var/www/html/config/ssl/client.key
BROADCAST_DRIVER=log
MEMCACHED_HOST=memcached
CACHE_DRIVER=memcached
QUEUE_CONNECTION=database
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=
REDIS_PORT=6379
MAIL_DRIVER=smtp
MAIL_HOST=mail.dege.lan
MAIL_PORT=25
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=
MAIL_AUTO_EMBED_METHOD=base64
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
FIDO_DIR=fido
FIDO_PACKET_KEEP=
FIDO_STRICT=false
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
@ -28,23 +58,6 @@ AWS_ENDPOINT=
AWS_DEFAULT_REGION=home
AWS_USE_PATH_STYLE_ENDPOINT=true
LOG_CHANNEL=daily
LOG_LEVEL=info
LOG_DAILY_DAYS=93
MAIL_MAILER=smtp
MAIL_HOST=smtp
MAIL_PORT=25
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=
MAIL_AUTO_EMBED_METHOD=base64
SESSION_DRIVER=file
# Clrghouz configuration
FIDO_DNS_NS=
MATRIX_SERVER=
MATRIX_AS_TOKEN=
MATRIX_HS_TOKEN=

View File

@ -74,7 +74,8 @@ jobs:
( dockerd --host=tcp://0.0.0.0:2375 --tls=false & ) && sleep 3
## Some debugging info
# docker info && docker version
# env|sort
env|sort
echo "PRT: ${{ secrets.PKG_WRITE_TOKEN }}"
- name: Registry FQDN Setup
id: registry
@ -92,10 +93,12 @@ jobs:
- name: Code Checkout
uses: actions/checkout@v4
- name: Record version and Delete Unnecessary files
- name: Record version
run: |
pwd
ls -al
echo ${GITHUB_SHA::8} > VERSION
rm -rf .git* tests/ storage/app/test/
cat VERSION
- name: Build and Push Docker Image
uses: docker/build-push-action@v5

View File

@ -1,9 +1,8 @@
<?php
namespace App\Models\Casts;
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
class CollectionOrNull implements CastsAttributes
@ -11,13 +10,13 @@ class CollectionOrNull implements CastsAttributes
/**
* Cast the given value.
*
* @param Model $model
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return Collection
*/
public function get(Model $model,string $key,$value,array $attributes): Collection
public function get($model,string $key,$value,array $attributes): Collection
{
return collect(json_decode($value, true));
}
@ -31,7 +30,7 @@ class CollectionOrNull implements CastsAttributes
* @param array $attributes
* @return string|null
*/
public function set(Model $model,string $key,$value,array $attributes): ?string
public function set($model,string $key,$value,array $attributes): ?string
{
return ($value->count()) ? json_encode($value) : NULL;
}

View File

@ -1,6 +1,6 @@
<?php
namespace App\Models\Casts;
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;

View File

@ -1,6 +1,6 @@
<?php
namespace App\Models\Casts;
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;

View File

@ -100,7 +100,7 @@ class HubStats extends Dynamic
$o->uncollected_echomail ?? 0,
$o->uncollected_netmail ?? 0,
$o->uncollected_files ?? 0,
$o->system->last_seen?->format('Y-m-d H:i') ?: '-',
$o->system->last_session?->format('Y-m-d H:i'),
is_null($o->system->pollmode) ? 'HOLD' : ($o->system->pollmode ? 'CRASH' : 'DAILY'),
$o->system->autohold ? 'YES' : 'NO');
}

View File

@ -59,7 +59,7 @@ class NodelistSegment extends Dynamic
$result->push('CM');
if ($ao->system->address) {
$result->push(sprintf('INA:%s',our_address($ao->domain)->contains($ao->id) ? our_hostname($ao) : $ao->system->address));
$result->push(sprintf('INA:%s',$ao->system->address));
if (($x=$ao->system->mailers->pluck('name')->search('BINKP')) !== FALSE)
$result->push(sprintf('IBN%s',(($y=$ao->system->mailers->get($x)->pivot->port) !== 24554) ? ':'.$y : ''));

View File

@ -17,7 +17,7 @@ abstract class FTN
$this->fn,
$this->ff,
$this->fp,
).((isset($this->zone) && $this->zone) ? sprintf('@%s',$this->zone->domain->name) : '');
).($this->zone ? sprintf('@%s',$this->zone->domain->name) : '');
case 'tftn_t':
return sprintf('%d:%d/%d.%d',
@ -25,7 +25,7 @@ abstract class FTN
$this->tn,
$this->tf,
$this->tp,
).((isset($this->zone) && $this->zone) ? sprintf('@%s',$this->zone->domain->name) : '');
).($this->zone ? sprintf('@%s',$this->zone->domain->name) : '');
case 'fftn':
return Address::findFTN($this->fftn_t);

View File

@ -89,7 +89,7 @@ class Message extends FTNBase
public const AREATAG_LEN = 35; //
private array $header; // Message Header
private Collection $kludges; // TZUTC that needs to be converted to be used by Carbon @see self::kludges
private int $tzutc = 0; // TZUTC that needs to be converted to be used by Carbon @see self::kludges
private Echomail|Netmail $mo; // The object storing this packet message
private Address $us; // Our address for this message
@ -233,7 +233,7 @@ class Message extends FTNBase
$o->mo->from = $o->header['user_from'];
$o->mo->subject = $o->header['subject'];
$o->mo->datetime = $o->datetime->clone()->utc();
$o->mo->datetime = $o->datetime;
$o->mo->tzoffset = $o->datetime->utcOffset();
$o->mo->flags = $o->header['flags'];
$o->mo->cost = $o->header['cost'];
@ -294,7 +294,6 @@ class Message extends FTNBase
public function __construct(Zone $zone)
{
$this->zone = $zone;
$this->kludges = collect();
}
public function __get($key)
@ -307,7 +306,7 @@ class Message extends FTNBase
case 'fz': return (int)Arr::get($this->src,'z');
case 'fn': return (int)($x=$this->src) ? Arr::get($x,'n') : Arr::get($this->header,'onet');
case 'ff': return (int)($x=$this->src) ? Arr::get($x,'f') : Arr::get($this->header,'onode');
case 'fp': return (int)$this->mo->kludges->get('FMPT') ?: Arr::get($this->src,'p',Arr::get($this->header,'opoint',0));
case 'fp': return (int)$this->mo->kludges->get('FMPT:') ?: Arr::get($this->src,'p',Arr::get($this->header,'opoint',0));
case 'fd': return Arr::get($this->src,'d');
case 'fzone':
@ -324,7 +323,6 @@ class Message extends FTNBase
return Zone::where('zone_id',$this->fz)
->where('default',TRUE)
->single();
case 'fdomain':
// We'll use the zone's domain if this method class was called with a zone
if ($this->zone && (($this->zone->domain->name === Arr::get($this->src,'d')) || ! Arr::get($this->src,'d')))
@ -342,7 +340,7 @@ class Message extends FTNBase
case 'tz': return (int)Arr::get($this->isEchomail() ? $this->src : $this->dst,'z');
case 'tn': return (int)Arr::get($this->header,'dnet');
case 'tf': return (int)Arr::get($this->header,'dnode');
case 'tp': return (int)$this->mo->kludges->get('TOPT') ?: Arr::get($this->header,'dpoint',0);
case 'tp': return (int)$this->mo->kludges->get('TOPT:') ?: Arr::get($this->header,'dpoint',0);
case 'tzone':
// Use the zone if this class was called with it.
@ -477,25 +475,11 @@ class Message extends FTNBase
break;
case 'tzutc':
return $this->kludges->get($key);
default:
throw new \Exception('Unknown key: '.$key);
}
}
public function __set(string $key,mixed $value): void
{
switch ($key) {
case 'tzutc':
if (! is_numeric($value))
throw new InvalidPacketException('TZUTC is not numeric '.$value);
$this->kludges->put($key,$value);
}
}
/**
* Export an FTN message, ready for sending.
*
@ -504,6 +488,8 @@ class Message extends FTNBase
*/
public function __toString(): string
{
$s = Setup::findOrFail(config('app.id'));
$return = pack(collect(self::HEADER)->pluck(1)->join(''),
$this->mo->fftn->node_id, // Originating Node
$this->mo->tftn->node_id, // Destination Node
@ -519,6 +505,9 @@ class Message extends FTNBase
$return .= $this->mo->subject."\00";
if (($this->mo instanceof Netmail) && $this->mo->isFlagSet(self::FLAG_LOCAL)) {
// If there isnt an INTL kludge, we'll add it
if (! $this->mo->kludges->has('INTL'))
$this->mo->kludges->put('INTL',sprintf('%s %s',$this->mo->tftn->ftn3d,$this->mo->fftn->ftn3d));
if ((! $this->mo->kludges->has('FMPT')) && $this->mo->fftn->point_id)
$this->mo->kludges->put('FMPT',$this->mo->fftn->point_id);
@ -527,16 +516,12 @@ class Message extends FTNBase
$this->mo->kludges->put('TOPT',$this->mo->tftn->point_id);
}
$this->mo->kludges->put($this->mo->isFlagSet(self::FLAG_LOCAL) ? 'PID:' : 'TID:',sprintf('%s %s',Setup::PRODUCT_NAME_SHORT,Setup::version()));
$this->mo->kludges->put($this->mo->isFlagSet(self::FLAG_LOCAL) ? 'PID:' : 'TID:',sprintf('%s %s',Setup::PRODUCT_NAME_SHORT,$s->version));
$this->mo->kludges->put('DBID:',$this->mo->id);
if ($this->mo instanceof Echomail)
$return .= sprintf("AREA:%s\r",strtoupper($this->mo->echoarea->name));
// Rebuild the INTL kludge line
elseif ($this->mo instanceof Netmail)
$this->mo->kludges->put('INTL',sprintf('%s %s',$this->mo->tftn->ftn3d,$this->mo->fftn->ftn3d));
// Add some kludges
$return .= sprintf("\01TZUTC: %s\r",str_replace('+','',$this->mo->date->getOffsetString('')));
@ -559,19 +544,14 @@ class Message extends FTNBase
$return .= sprintf("\x01Via %s @%s.UTC %s %s\r",
$this->us->ftn3d,
Carbon::now()->format('Ymd.His'),
Setup::PRODUCT_NAME_SHORT,Setup::version());
Setup::PRODUCT_NAME_SHORT,$s->version);
} else {
// FTS-0004.001/FSC-0068.001 The message SEEN-BY lines
// FTS-0004.001/FSC-0068.001 The message PATH lines
// @todo This unique() function here shouldnt be required, but is while system generated messages are storing path/seenby
$path = $this
->mo
->path
->push($this->us)
->unique('ftn')
->filter(fn($item)=>is_null($item->point_id) || ($item->point_id === 0));
$path = $this->mo->path->push($this->us)->unique('ftn')->filter(fn($item)=>($item->point_id === 0));
// Create our rogue seenby objects
$seenby = $this->mo->seenby;
@ -585,7 +565,7 @@ class Message extends FTNBase
$seenby = $seenby
->push($this->us)
->filter(fn($item)=>is_null($item->point_id) || ($item->point_id === 0))
->filter(fn($item)=>($item->point_id === 0))
->unique('ftn')
->sortBy(function($item) { return sprintf('%05d%05d',$item->host_id,$item->node_id);});
@ -667,158 +647,147 @@ class Message extends FTNBase
// First find our kludge lines
$ptr_start = 0;
$ptr_end = 0;
try {
while (substr($message,$ptr_start,1) === "\x01") {
$ptr_end = strpos($message,"\r",$ptr_start);
while (substr($message,$ptr_start,1) === "\x01") {
$ptr_end = strpos($message,"\r",$ptr_start);
$m = [];
$kludge = ($x=substr($message,$ptr_start+1,$ptr_end-$ptr_start-1));
preg_match('/^([^\s]+:?)+\s+(.*)$/',$kludge,$m);
$ptr_start = $ptr_end+1;
if (! $m) {
Log::alert(sprintf('%s:! Invalid Kluge Line [%s]',self::LOGKEY,$x));
continue;
}
// Catch any kludges we need to process here
if (array_key_exists($m[1],self::kludges)) {
// Some earlier mystic message had a blank value for TZUTC
if ((($m[1]) === 'TZUTC:') && (! $m[2]))
$m[2] = '0000';
$this->{self::kludges[$m[1]]} = $m[2];
} else
$o->kludges = [$m[1],$m[2]];
}
// Next our message content ends with '\r * Origin: ... \r' or <soh>...
// FTS-0004.001
if ($ptr_end=strrpos($message,"\r * Origin: ",$ptr_start)) {
// Find the <cr>
$ptr_end = strpos($message,"\r",$ptr_end+1);
// If there is no ptr_end, then this is not an origin
if (! $ptr_end)
throw new InvalidPacketException('Couldnt find the end of the origin');
} elseif (! $ptr_end=strpos($message,"\r\x01",$ptr_start)) {
$ptr_end = strlen($message);
}
$remaining = substr($message,$ptr_end+1);
// At this point, the remaining part of the message should start with \x01, PATH or SEEN-BY
if ((substr($remaining,0,9) !== 'SEEN-BY: ') && (substr($remaining,0,5) !== 'PATH:') && ($x=strpos($remaining,"\x01")) !== 0) {
if ($x)
$ptr_end += $x;
else
$ptr_end += strlen($remaining);
}
// Process the message content
if ($content=substr($message,$ptr_start,$ptr_end-$ptr_start)) {
$o->msg_src = $content;
$o->msg_crc = md5($content);
$ptr_content_start = 0;
// See if we have a tagline
if ($ptr_content_end=strrpos($content,"\r... ",$ptr_content_start)) {
$o->msg = substr($content,$ptr_content_start,$ptr_content_end+1);
$ptr_content_start = $ptr_content_end+5;
$ptr_content_end = strpos($content,"\r",$ptr_content_start);
// If there is no terminating "\r", then that's it
if (! $ptr_content_end) {
$o->set_tagline = substr($content,$ptr_content_start);
$ptr_content_start = strlen($content);
} else {
$o->set_tagline = substr($content,$ptr_content_start,$ptr_content_end-$ptr_content_start);
$ptr_content_start = $ptr_content_end;
}
}
// See if we have a tearline
if ($ptr_content_end=strrpos($content,"\r--- ",$ptr_content_start)) {
if (! $ptr_content_start)
$o->msg = substr($content,$ptr_content_start,$ptr_content_end+1);
$ptr_content_start = $ptr_content_end+5;
$ptr_content_end = strpos($content,"\r",$ptr_content_start);
// If there is no terminating "\r", then that's it
if (! $ptr_content_end) {
$o->set_tearline = substr($content,$ptr_content_start);
$ptr_content_start = strlen($content);
} else {
$o->set_tearline = substr($content,$ptr_content_start,$ptr_content_end-$ptr_content_start);
$ptr_content_start = $ptr_content_end;
}
}
// See if we have an origin
if ($ptr_content_end=strrpos($content,"\r * Origin: ",$ptr_content_start)) {
if (! $ptr_content_start)
$o->msg = substr($content,$ptr_content_start,$ptr_content_end);
$ptr_content_start = $ptr_content_end+12;
$ptr_content_end = strpos($content,"\r",$ptr_content_start);
// If there is no terminating "\r", then that's it
if (! $ptr_content_end) {
$o->set_origin = substr($content,$ptr_content_start);
$ptr_content_start = strlen($content);
} else {
$o->set_origin = substr($content,$ptr_content_start,$ptr_content_end-$ptr_content_start);
$ptr_content_start = $ptr_content_end+1;
}
}
// If there wasnt any tagline/tearline/origin, then the whole content is the message
if (! $ptr_content_start) {
$o->msg = $content;
$ptr_content_start = $ptr_end-$ptr_start;
}
// Trim any right \r from the message
$o->msg = rtrim($o->msg,"\r");
// Quick validation that we are done
if ($ptr_content_start !== strlen($content)) {
Log::alert(sprintf('%s:! We failed parsing the message.',self::LOGKEY));
$o->msg = substr($message,0,$ptr_end);
}
}
$m = [];
$kludge = ($x=substr($message,$ptr_start+1,$ptr_end-$ptr_start-1));
preg_match('/^([^\s]+:?)+\s+(.*)$/',$kludge,$m);
$ptr_start = $ptr_end+1;
// Finally work out control kludges
foreach (collect(explode("\r",substr($message,$ptr_start)))->filter() as $line) {
// If the line starts with <soh> ignore it
if (substr($line,0,1) === "\x01")
$line = ltrim($line,"\x01");
$m = [];
preg_match('/^([^\s]+:?)+\s+(.*)$/',$line,$m);
$o->kludges = [$m[1],$m[2]];
if (! $m) {
Log::alert(sprintf('%s:! Invalid Kluge Line [%s]',self::LOGKEY,$x));
continue;
}
} catch (\Exception $e) {
Log::error(sprintf('%s:! Error parsing message, now at offset [0x%02x] (%s)',
self::LOGKEY,
$ptr_start,
$e->getMessage()),['dump'=>hex_dump($message)]);
// Catch any kludges we need to process here
if (array_key_exists($m[1],self::kludges)) {
// Some earlier mystic message had a blank value for TZUTC
if ((($m[1]) === 'TZUTC:') && (! $m[2]))
$m[2] = '0000';
throw new InvalidPacketException('Error parsing message');
$this->{self::kludges[$m[1]]} = $m[2];
} else
$o->kludges = [$m[1],$m[2]];
}
// Next our message content ends with '\r * Origin: ... \r' or <soh>...
// FTS-0004.001
if ($ptr_end=strrpos($message,"\r * Origin: ",$ptr_start)) {
// Find the <cr>
$ptr_end = strpos($message,"\r",$ptr_end+1);
// If there is no ptr_end, then this is not an origin
if (! $ptr_end)
throw new InvalidPacketException('Couldnt find the end of the origin');
} elseif (! $ptr_end=strpos($message,"\r\x01",$ptr_start)) {
$ptr_end = strlen($message);
}
$remaining = substr($message,$ptr_end+1);
// At this point, the remaining part of the message should start with \x01, PATH or SEEN-BY
if ((substr($remaining,0,9) !== 'SEEN-BY: ') && (substr($remaining,0,5) !== 'PATH:') && ($x=strpos($remaining,"\x01")) !== 0) {
if ($x)
$ptr_end += $x;
else
$ptr_end += strlen($remaining);
}
// Process the message content
if ($content=substr($message,$ptr_start,$ptr_end-$ptr_start)) {
$o->msg_src = $content;
$o->msg_crc = md5($content);
$ptr_content_start = 0;
// See if we have a tagline
if ($ptr_content_end=strrpos($content,"\r... ",$ptr_content_start)) {
$o->msg = substr($content,$ptr_content_start,$ptr_content_end+1);
$ptr_content_start = $ptr_content_end+5;
$ptr_content_end = strpos($content,"\r",$ptr_content_start);
// If there is no terminating "\r", then that's it
if (! $ptr_content_end) {
$o->set_tagline = substr($content,$ptr_content_start);
$ptr_content_start = strlen($content);
} else {
$o->set_tagline = substr($content,$ptr_content_start,$ptr_content_end-$ptr_content_start);
$ptr_content_start = $ptr_content_end;
}
}
// See if we have a tearline
if ($ptr_content_end=strrpos($content,"\r--- ",$ptr_content_start)) {
if (! $ptr_content_start)
$o->msg = substr($content,$ptr_content_start,$ptr_content_end+1);
$ptr_content_start = $ptr_content_end+5;
$ptr_content_end = strpos($content,"\r",$ptr_content_start);
// If there is no terminating "\r", then that's it
if (! $ptr_content_end) {
$o->set_tearline = substr($content,$ptr_content_start);
$ptr_content_start = strlen($content);
} else {
$o->set_tearline = substr($content,$ptr_content_start,$ptr_content_end-$ptr_content_start);
$ptr_content_start = $ptr_content_end;
}
}
// See if we have an origin
if ($ptr_content_end=strrpos($content,"\r * Origin: ",$ptr_content_start)) {
if (! $ptr_content_start)
$o->msg = substr($content,$ptr_content_start,$ptr_content_end);
$ptr_content_start = $ptr_content_end+12;
$ptr_content_end = strpos($content,"\r",$ptr_content_start);
// If there is no terminating "\r", then that's it
if (! $ptr_content_end) {
$o->set_origin = substr($content,$ptr_content_start);
$ptr_content_start = strlen($content);
} else {
$o->set_origin = substr($content,$ptr_content_start,$ptr_content_end-$ptr_content_start);
$ptr_content_start = $ptr_content_end+1;
}
}
// If there wasnt any tagline/tearline/origin, then the whole content is the message
if (! $ptr_content_start) {
$o->msg = $content;
$ptr_content_start = $ptr_end-$ptr_start;
}
// Trim any right \r from the message
$o->msg = rtrim($o->msg,"\r");
// Quick validation that we are done
if ($ptr_content_start !== strlen($content)) {
Log::alert(sprintf('%s:! We failed parsing the message.',self::LOGKEY));
$o->msg = substr($message,0,$ptr_end);
}
}
$ptr_start = $ptr_end+1;
// Finally work out control kludges
foreach (collect(explode("\r",substr($message,$ptr_start)))->filter() as $line) {
// If the line starts with <soh> ignore it
if (substr($line,0,1) === "\x01")
$line = ltrim($line,"\x01");
$m = [];
preg_match('/^([^\s]+:?)+\s+(.*)$/',$line,$m);
$o->kludges = [$m[1],$m[2]];
}
return $o;
@ -876,17 +845,11 @@ class Message extends FTNBase
$validator->after(function($validator) {
if ($this->zone->domain->flatten) {
if (! $this->zone->domain->zones->pluck('zone_id')->contains($this->fz))
$validator->errors()->add('invalid-zone',sprintf('Message from zone [%d] doesnt match any zone in domain for packet zone [%d].',$this->fz,$this->zone->zone_id));
if (! $this->zone->domain->zones->pluck('zone_id')->contains($this->tz))
$validator->errors()->add('invalid-zone',sprintf('Message to zone [%d] doesnt match any zone in domain for packet zone [%d].',$this->fz,$this->zone->zone_id));
$validator->errors()->add('invalid-zone',sprintf('Message zone [%d] doesnt match any zone in domain for packet zone [%d].',$this->fz,$this->zone->zone_id));
} else {
if ($this->zone->zone_id !== $this->fz)
$validator->errors()->add('invalid-zone',sprintf('Message from zone [%d] doesnt match packet zone [%d].',$this->fz,$this->zone->zone_id));
if ($this->zone->zone_id !== $this->tz)
$validator->errors()->add('invalid-zone',sprintf('Message to zone [%d] doesnt match packet zone [%d].',$this->tz,$this->zone->zone_id));
$validator->errors()->add('invalid-zone',sprintf('Message zone [%d] doesnt match packet zone [%d].',$this->fz,$this->zone->zone_id));
}
if (! $this->fftn)

View File

@ -3,7 +3,7 @@
namespace App\Classes\FTN;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
@ -12,8 +12,8 @@ use Symfony\Component\HttpFoundation\File\File;
use App\Classes\FTN as FTNBase;
use App\Exceptions\InvalidPacketException;
use App\Models\{Address,Echomail,Netmail,Software,System,Zone};
use App\Notifications\Netmails\{EchomailBadAddress,NetmailBadAddress};
use App\Models\{Address,Domain,Echomail,Netmail,Software,System,Zone};
use App\Notifications\Netmails\EchomailBadAddress;
/**
* Represents a Fidonet Packet, that contains an array of messages.
@ -88,11 +88,11 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
* @param mixed $f File handler returning packet data
* @param string $name
* @param int $size
* @param System|null $so - The system that sent us the packet, used to figure out domains if the packet is for a different zone
* @param Domain|null $domain
* @return Packet
* @throws InvalidPacketException
*/
public static function process(mixed $f,string $name,int $size,System $so=NULL): self
public static function process(mixed $f,string $name,int $size,Domain $domain=NULL): self
{
Log::debug(sprintf('%s:+ Opening Packet [%s] with size [%d]',self::LOGKEY,$name,$size));
@ -139,35 +139,26 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
} else
throw new InvalidPacketException('Not a valid packet, not EOP or SOM:'.bin2hex($x));
Log::info(sprintf('%s:- Packet [%s] is a [%s] packet',self::LOGKEY,$o->name,get_class($o)));
Log::info(sprintf('%s:- Packet [%s] is a [%s] packet, dated [%s]',self::LOGKEY,$o->name,get_class($o),$o->date));
if ($o->fz && ($o->fd || $so)) {
Log::alert(sprintf('%s:! No domain in the packet, work it out from the system [%d] for zone [%d]',self::LOGKEY,$so->name,$o->fz));
if (($x=$so->zones->where('zone_id',$o->fz)->unique('domain_id'))->count() === 1) {
$o->zone = $x->pop();
} else {
Log::alert(sprintf('%s:! Node [%s] has two zones with [%d]',self::LOGKEY,$so->name,$o->fz));
}
// Work out the packet zone
if ($o->fz && ($o->fd || $domain)) {
$o->zone = Zone::select('zones.*')
->join('domains',['domains.id'=>'zones.domain_id'])
->where('zone_id',$o->fz)
->where('name',$o->fd ?: $domain->name)
->single();
}
// If zone is not set, then we need to use a default zone - the messages may not be from this 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));
try {
$o->zone = Zone::where('zone_id',$o->fz)
->where('default',TRUE)
->singleOrFail();
} catch (ModelNotFoundException $e) {
throw new InvalidPacketException(sprintf('%s:! We couldnt work out the packet zone, and there isnt a default for[%d]',self::LOGKEY,$o->fz));
}
$o->zone = Zone::where('zone_id',$o->fz)
->where('default',TRUE)
->singleOrFail();
}
Log::info(sprintf('%s:- Packet Dated [%s] from [%s] to [%s]',self::LOGKEY,$o->date,$o->fftn_t,$o->tftn_t));
$message = ''; // Current message we are building
$msgbuf = '';
$leader = Message::header_len()+strlen(self::PACKED_MSG_LEAD);
@ -182,7 +173,6 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
|| (($end=strpos($msgbuf,"\x00".self::PACKED_END,$leader)) !== FALSE))
{
// Parse our message
Log::debug(sprintf('%s:- Message at offset [%d] in [%s]',self::LOGKEY,$read_ptr-strlen($readbuf),$name));
$o->parseMessage(substr($msgbuf,0,$end));
$msgbuf = substr($msgbuf,$end+3);
@ -194,7 +184,7 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
}
// If we get here
throw new InvalidPacketException(sprintf('Cannot determine END of message/packet: %s|%s',get_class($o),hex_dump($message)));
throw new InvalidPacketException(sprintf('Cannot determine END of message/packet: %s|%s',get_class($o),hex_dump($message)));;
}
if ($msgbuf)
@ -371,7 +361,7 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
$this->content .= "\00\00";
$this->messages = $msgs->map(fn($item)=>$item->only(['id','datetime']));
$this->messages = $msgs->map(fn($item)=>$item->only(['id','date']));
return $this;
}
@ -394,10 +384,10 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
// If the messages is not for the right zone, we'll ignore it
if ($msg->errors->has('invalid-zone')) {
Log::alert(sprintf('%s:! Message [%s] is from|to an invalid zone [%s|%s], packet is from [%s] - ignoring it',self::LOGKEY,$msg->msgid,$msg->get_fftn,$msg->get_tftn,$this->fz));
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->kludges->get('RESCANNED'))
Notification::route('netmail',$this->fftn)->notify(($msg instanceof Echomail) ? new EchomailBadAddress($msg) : new NetmailBadAddress($msg));
Notification::route('netmail',$this->fftn)->notify(new EchomailBadAddress($msg));
return;
}
@ -406,7 +396,7 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
if ($msg->errors->has('from') && $this->fftn && $this->fftn->zone_id) {
Log::debug(sprintf('%s:^ From address [%s] doesnt exist, it needs to be created',self::LOGKEY,$msg->set->get('set_fftn')));
$ao = Address::findFTN($msg->set->get('set_fftn'),TRUE,TRUE);
$ao = Address::findFTN($msg->set->get('set_fftn'),TRUE);
if ($ao?->exists && ($ao->zone?->domain_id !== $this->fftn->zone->domain_id)) {
Log::alert(sprintf('%s:! From address [%s] domain [%d] doesnt match packet domain [%d]?',self::LOGKEY,$msg->set->get('set_fftn'),$ao->zone?->domain_id,$this->fftn->zone->domain_id));
@ -417,20 +407,17 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
if (! $ao) {
$so = System::createUnknownSystem();
$ao = Address::createFTN($msg->set->get('set_fftn'),$so);
Log::alert(sprintf('%s:- From FTN [%s] is not defined, created new entry for (%d)',self::LOGKEY,$msg->set->get('set_fftn'),$ao->id));
}
$msg->fftn_id = $ao->id;
$msg->errors->forget('from');
$msg->errors->forget('fftn_id');
Log::alert(sprintf('%s:- From FTN [%s] is not defined, created new entry for (%d)',self::LOGKEY,$msg->set->get('set_fftn'),$ao->id));
}
// If the $msg->tftn doesnt exist, we'll need to create it
if ($msg->errors->has('to') && $this->tftn && $this->tftn->zone_id) {
Log::debug(sprintf('%s:^ To address [%s] doesnt exist, it needs to be created',self::LOGKEY,$msg->set->get('set_tftn')));
$ao = Address::findFTN($msg->set->get('set_tftn'),TRUE,TRUE);
$ao = Address::findFTN($msg->set->get('set_tftn'),TRUE);
if ($ao?->exists && ($ao->zone?->domain_id !== $this->tftn->zone->domain_id)) {
Log::alert(sprintf('%s:! To address [%s] domain [%d] doesnt match packet domain [%d]?',self::LOGKEY,$msg->set->get('set_tftn'),$ao->zone?->domain_id,$this->fftn->zone->domain_id));
@ -441,13 +428,10 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
if (! $ao) {
$so = System::createUnknownSystem();
$ao = Address::createFTN($msg->set->get('set_fftn'),$so);
Log::alert(sprintf('%s:- To FTN [%s] is not defined, created new entry for (%d)',self::LOGKEY,$msg->set->get('set_tftn'),$ao->id));
}
$msg->tftn_id = $ao->id;
$msg->errors->forget('to');
$msg->errors->forget('tftn_id');
Log::alert(sprintf('%s:- To FTN [%s] is not defined, created new entry for (%d)',self::LOGKEY,$msg->set->get('set_tftn'),$ao->id));
}
// If there is no fftn, then its from a system that we dont know about

View File

@ -65,7 +65,7 @@ final class FSC39 extends Packet
*/
protected function header(Collection $msgs): string
{
$oldest = $this->messages->sortBy('date')->last();
$oldest = $this->messages->sortBy('datetime')->last();
return pack(collect(self::HEADER)->pluck(1)->join(''),
$this->fftn_p->node_id, // Orig Node

View File

@ -55,10 +55,6 @@ final class FSC48 extends Packet
case 'capability':
return sprintf('%016b',Arr::get($this->header,'capword'));
case 'fn':
// If the packet is from a point, then onet will be 0xffff
return ($x=Arr::get($this->header,'onet')) === 0xffff ? Arr::get($this->header,'auxnet') : $x;
default:
return parent::__get($key);
}

View File

@ -6,10 +6,9 @@ use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Process;
use App\Classes\FTN\Process\Netmail\Robot\Unknown;
use App\Models\{Echomail,Netmail};
use App\Notifications\Netmails\FixCantHandle;
use App\Notifications\Netmails\Areafix\{CommandsProcessed,InvalidPassword,NotConfiguredHere};
use App\Notifications\Netmails\Areafix as AreafixNotification;
use App\Notifications\Netmails\Areafix\NotConfiguredHere as AreafixNotConfiguredHereNotification;
/**
* Process messages to Ping
@ -20,102 +19,18 @@ final class Areafix extends Process
{
private const LOGKEY = 'RP-';
public const areafix_commands = 'App\\Classes\\FTN\\Process\\Netmail\\Robot\\Areafix\\';
public static function handle(Echomail|Netmail $mo): bool
{
if (((strtolower($mo->to) !== 'areafix') && (strtolower($mo->to) !== 'filefix')) || (! ($mo instanceof Netmail)))
if (strtolower($mo->to) !== 'areafix')
return FALSE;
Log::info(sprintf('%s:- Processing *FIX [%s] message from (%s) [%s]',self::LOGKEY,$mo->to,$mo->from,$mo->fftn->ftn));
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 (! $mo->fftn->system->sessions->count()) {
Notification::route('netmail',$mo->fftn)->notify(new NotConfiguredHere($mo));
return TRUE;
}
// If this nodes password is not correct
if ($mo->fftn->pass_fix !== strtoupper($mo->subject)) {
Notification::route('netmail',$mo->fftn)->notify(new InvalidPassword($mo));
return TRUE;
}
if ((strtolower($mo->to) === 'areafix'))
return self::areafix($mo);
if ((strtolower($mo->to) === 'filefix'))
return self::filefix($mo);
return FALSE;
}
public static function areafix(Netmail $mo): bool
{
$result = collect();
$result->push('--> BEGIN <--');
foreach ($mo->body_lines as $command) {
// Skip empty lines
if (! $command)
continue;
$command = explode(' ',strtoupper(trim($command)));
// If command starts with '...' or '---', its a tear/tag line, and we have reached the end
if (str_starts_with($command[0],'...') || str_starts_with($command[0],'---')) {
Log::debug(sprintf('%s:= We got a tearline/tagline, end of processing',self::LOGKEY));
$result->push('--> END OF PROCESSING <--');
break;
// If command doesnt start with %, its an area
} elseif (! str_starts_with($command[0],'%')) {
Log::info(sprintf('%s:= Assuming command [%s] is an AREA command',self::LOGKEY,$command[0]));
array_unshift($command,'%AREA');
}
// Some commands are reserved words
switch ($x=strtolower(substr($command[0],1))) {
case 'list':
$class = self::areafix_commands.'AreaList';
break;
default:
// Parse the message body and pluck out the commands on each line
$class = self::areafix_commands.ucfirst($x);
}
if (! class_exists($class)) {
$result->push(sprintf('%-25s <-- **COMMAND UNKNOWN**',join(' ',$command)));
Log::info(sprintf('%s:! Command UNKNOWN [%s] ',self::LOGKEY,join('|',$command)),['class'=>$class]);
continue;
}
// Drop the command from the array, the rest are arguments
array_shift($command);
// Refresh our echoareas
$mo->fftn->load('echoareas');
$o = new $class($mo,$command);
$result->push($o->process());
}
// Reply with a confirmation of what commands were processed
Notification::route('netmail',$mo->fftn)->notify(new CommandsProcessed($mo,$result));
return TRUE;
}
public static function filefix(Netmail $mo): bool
{
Notification::route('netmail',$mo->fftn)->notify(new FixCantHandle($mo));
if ($mo->fftn->system->sessions->count())
Notification::route('netmail',$mo->fftn)->notify(new AreafixNotification($mo));
else
Notification::route('netmail',$mo->fftn)->notify(new AreafixNotConfiguredHereNotification($mo));
return TRUE;
}

View File

@ -1,143 +0,0 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Areafix;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Jobs\AreafixRescan;
// Echoarea Processing Command
class Area extends Base
{
private const LOGKEY = 'AFA';
private const command = '%AREA';
public static function help(): array
{
return [
self::command.' [-|+]<ECHOAREA> [R|D=<DAYS>]',
' Use the area command to subscribe (+) or unsubscribe (-) to an ECHOAREA',
' Arguments:',
' - ECHOAREA (required) name of area to subscribe or unsubscribe',
' - D=DAYS (optional) number of days to resend mail from this area that you',
' havent already received (useful if you are resubscribing to an area and',
' have received mail in the past)',
' - R=DAYS (optional) number of days to resend mail from this area (even if',
' it was sent to you previously)',
' Notes:',
' * "+" is optional, and is implied if "-" is not used',
' * "R" and "D" options only apply to subscribing',
];
}
public function process(): string
{
$command = self::command.' '.join(' ',$this->arguments);
// If command starts with '-', its to unsubscribe
if (str_starts_with($this->arguments[0],'-')) {
$sub = FALSE;
$area = substr($this->arguments[0],1);
} elseif (str_starts_with($this->arguments[0],'+')) {
$sub = TRUE;
$area = substr($this->arguments[0],1);
} else {
$sub = TRUE;
$area = $this->arguments[0];
}
Log::debug(sprintf('%s:- Processing [%s] for [%s]',self::LOGKEY,$sub ? 'ADD' : 'REMOVE',$area));
// Drop the area from the arguments, the rest are options
array_shift($this->arguments);
// Area exists
if ($ea=$this->mo->fftn->domain->echoareas->where('name',$area)->pop()) {
// If already subscribed
if ($nea=$this->mo->fftn->echoareas->where('name',$area)->pop()) {
// requesting to subscribe "You already are since..., arguments ignored
if ($sub) {
Log::debug(sprintf('%s:- FTN [%s] ALREADY subscribed to [%s] since [%s]',self::LOGKEY,$this->mo->fftn->ftn,$area,$nea->pivot->subscribed->format('Y-m-d H:i')));
return sprintf('%-25s <-- ALREADY subscribed since %s',$command,$nea->pivot->subscribed->format('Y-m-d H:i'));
// requesting to unsubscribe
} else {
$this->mo->fftn->echoareas()->detach($ea->id);
// Remove sub, clear queue
$x = DB::table('echomail_seenby')
->where('address_id',$this->mo->fftn->id)
->join('echomails',['echomails.id'=>'echomail_seenby.echomail_id'])
->where('echoarea_id',$nea->id)
->whereNotNull('export_at')
->whereNull('sent_at')
->orderBy('echomails.datetime')
->skip($this->mo->fftn->system->pkt_msgs)
->delete();
Log::debug(sprintf('%s:- FTN [%s] UNSUBSCRIBED from [%s] clearing [%s]',self::LOGKEY,$this->mo->fftn->ftn,$area,$x));
return sprintf('%-25s <-- UNSUBSCRIBED, CLEARED [%d] MSGS from queue',$command,$x);
}
// If not subscribed
} else {
// requesting to subscribe, subsubsribe and rescan if arguments
if ($sub) {
$this->mo->fftn->echoareas()->attach([$ea->id=>['subscribed'=>Carbon::now()]]);
// If we have arguments, they are to rescan
if (count($this->arguments) === 1) {
$m = [];
if (preg_match('/^([DR])=([0-9]+)$/',$this->arguments[0],$m)) {
switch ($m[1]) {
// Scan
case 'D':
AreafixRescan::dispatch($this->mo->fftn,$ea,$m[2])
->onQueue('mail');
return sprintf('%-25s <-- SUBSCRIBED, RESCAN [%d] DAYS queued',$command,$m[2]);
// Scan
case 'R':
AreafixRescan::dispatch($this->mo->fftn,$ea,$m[2],TRUE)
->onQueue('mail');
return sprintf('%-25s <-- SUBSCRIBED, FORCE RESCAN [%d] DAYS queued',$command,$m[2]);
}
}
return sprintf('%-25s <-- SUBSCRIBED, INVALID OPTIONS',$command);
} elseif (count($this->arguments) > 1) {
Log::debug(sprintf('%s:- FTN [%s] subscribed to [%s], extra commands [%s] ignored',self::LOGKEY,$this->mo->fftn->ftn,$area,join('|',$this->arguments)));
return sprintf('%-25s <-- SUBSCRIBED, OPTIONS IGNORED',$command);
} else {
Log::debug(sprintf('%s:- FTN [%s] subscribed to [%s]',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- SUBSCRIBED',$command);
}
// If not subscribed, "you arent subscribed, arguments ignored"
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
}
} else {
Log::debug(sprintf('%s:- FTN [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@ -1,38 +0,0 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Areafix;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use App\Notifications\Netmails\Areafix\AreaList as AreaListNotification;
// LIST - List echoareas in a domain
class AreaList extends Base
{
private const LOGKEY = 'AFS';
private const command = '%LIST';
public static function help(): array
{
return [
self::command,
' List the available echoareas in this network',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Areafix [%s] for [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn,join('|',$this->arguments)));
if (count($this->arguments) > 1)
return sprintf('%-25s <-- INVALID COMMAND',self::command);
else {
Notification::route('netmail',$this->mo->fftn)
->notify(new AreaListNotification($this->mo));
return sprintf('%-25s <-- COMMAND PROCESSED',self::command);
}
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Areafix;
use Illuminate\Support\Facades\Log;
use App\Models\Netmail;
// Our base areafix commands class
abstract class Base
{
private const LOGKEY = 'AB-';
protected Netmail $mo;
protected array $arguments;
public function __construct(Netmail $mo,array $arguments) {
Log::debug(sprintf('%s:- Areafix [%s] command with arguments [%s] for [%s]',self::LOGKEY,get_class($this),implode('|',$arguments),$mo->fftn->ftn));
$this->mo = $mo;
$this->arguments = $arguments;
}
abstract public static function help(): array;
abstract public function process(): string;
}

View File

@ -1,48 +0,0 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Areafix;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use App\Classes\FTN\Process\Netmail\Areafix;
use App\Notifications\Netmails\Areafix\Help as HelpNotification;
// A Help Index Command
class Help extends Base
{
private const LOGKEY = 'AFH';
private const areafix_classes = 'app/Classes/FTN/Process/Netmail/Robot/Areafix';
private const command = '%HELP';
public static function help(): array
{
return [
self::command,
' This message!',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Processing [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn));
$result = collect();
foreach (preg_grep('/^([^.])/',scandir(self::areafix_classes)) as $file) {
if (($file === 'Base.php') || (! str_ends_with(strtolower($file),'.php')))
continue;
$class = Areafix::areafix_commands.preg_replace('/\.php$/','',$file);
if ($result->count())
$result->push('');
$result = $result
->merge($class::help());
}
Notification::route('netmail',$this->mo->fftn)->notify(new HelpNotification($this->mo,$result));
return sprintf('%-25s <-- COMMAND PROCESSED',self::command);
}
}

View File

@ -1,61 +0,0 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Areafix;
use Illuminate\Support\Facades\Log;
use App\Jobs\AreafixRescan;
// RESCAN - Resend echomail
class Rescan extends Base
{
private const LOGKEY = 'AFR';
private const command = '%RESCAN';
public static function help(): array
{
return [
self::command.' [-|+]<ECHOAREA> [<DAYS>]',
' Use the rescan command to resend mail from an echoarea.',
' This is will resend mail again, even if you have received it in the past.',
' Arguments:',
' - ECHOAREA (required) name of area to subscribe or unsubscribe',
' - DAYS (optional) number of days to resend mail from this area that you',
' If DAYS is omitted, the default is 30',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Areafix [%s] for [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn,join('|',$this->arguments)));
$command = self::command.' '.join(' ',$this->arguments);
if (! is_numeric($this->arguments[1]))
return sprintf('%-25s <-- INVALID, DAYS [%s] NOT NUMERIC',$command,$this->arguments[1]);
// Area exists
if ($ea=$this->mo->fftn->domain->echoareas->where('name',$this->arguments[0])->pop()) {
// If already subscribed
if ($this->mo->fftn->echoareas->pluck('name')->contains($this->arguments[0])) {
AreafixRescan::dispatch($this->mo->fftn,$ea,$this->arguments[1],TRUE)
->onQueue('mail');
Log::debug(sprintf('%s:- FTN [%s] RESCAN [%s] DAYS [%d]',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0],$this->arguments[1]));
return sprintf('%-25s <-- RESCAN [%d] DAYS queued',$command,$this->arguments[1]);
// If not subscribed
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0]));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
} else {
Log::debug(sprintf('%s:- FTN [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0]));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@ -1,62 +0,0 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Areafix;
use Illuminate\Support\Facades\Log;
use App\Jobs\AreafixRescan;
// SCAN - Send unsent echomail
class Scan extends Base
{
private const LOGKEY = 'AFS';
private const command = '%SCAN';
public static function help(): array
{
return [
self::command.' [-|+]<ECHOAREA> [<DAYS>]',
' Use the scan command to resend mail that you havent received yet from an',
' echoarea. This is useful if you are rejoining an echoarea, and only want',
' to get mail that you dont already have.',
' Arguments:',
' - ECHOAREA (required) name of area to subscribe or unsubscribe',
' - DAYS (optional) number of days to resend mail from this area that you',
' If DAYS is omitted, the default is 30',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Areafix [%s] for [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn,join('|',$this->arguments)));
$command = self::command.' '.join(' ',$this->arguments);
if (! is_numeric($this->arguments[1]))
return sprintf('%-25s <-- INVALID, DAYS [%s] NOT NUMERIC',$command,$this->arguments[1]);
// Area exists
if ($ea=$this->mo->fftn->domain->echoareas->where('name',$this->arguments[0])->pop()) {
// If already subscribed
if ($this->mo->fftn->echoareas->pluck('name')->contains($this->arguments[0])) {
AreafixRescan::dispatch($this->mo->fftn,$ea,$this->arguments[1])
->onQueue('mail');
Log::debug(sprintf('%s:- FTN [%s] SCAN [%s] DAYS [%d]',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0],$this->arguments[1]));
return sprintf('%-25s <-- SCAN [%d] DAYS queued',$command,$this->arguments[1]);
// If not subscribed
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0]));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
} else {
Log::debug(sprintf('%s:- FTN [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0]));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@ -83,11 +83,6 @@ class File extends FileBase implements \Iterator
/* METHODS */
public function isArchive(): bool
{
return $this->isArchive;
}
/**
* Determine if the file is a mail packet
*

View File

@ -11,7 +11,7 @@ use Illuminate\Support\Collection;
* When sending, we can queue up a list of items, and mark one active (the one being sent) at a time.
*
* + Netmail/Echomail/TIC
* + name is the hex ID of the youngest item in the mail bundle
* + name is dynamically calculated, based on timew() of the youngest item in the mail bundle
* + size is dynamically calculated based on the size of the bundle
* + mtime is dynamically calculated, based on the age of the youngest item
* + sendas (nameas) is name + [.pkt|.tic]

View File

@ -86,18 +86,21 @@ final class File extends Send
$this->size = $this->f->size;
// If sending file is a File::class, then our file is s3
$this->fd = ($this->nameas && $this->f instanceof FileModel)
? Storage::readStream($this->f->rel_name)
: fopen($this->full_name,'rb');
if ($this->nameas && $this->f instanceof FileModel) {
$this->fd = Storage::readStream($this->f->rel_name);
if (! $this->fd) {
Log::error(sprintf('%s:! Unable to open file [%s] for reading',self::LOGKEY,$this->full_name));
} else {
$this->fd = fopen($this->full_name,'rb');
return FALSE;
if (! $this->fd) {
Log::error(sprintf('%s:! Unable to open file [%s] for reading',self::LOGKEY,$this->full_name));
return FALSE;
}
Log::info(sprintf('%s:= File [%s] opened with size [%d]',self::LOGKEY,$this->full_name,$this->size));
}
Log::info(sprintf('%s:= File [%s] opened with size [%d]',self::LOGKEY,$this->full_name,$this->size));
return TRUE;
}
@ -108,6 +111,6 @@ final class File extends Send
public function seek(int $pos): bool
{
return (fseek($this->fd,$pos,SEEK_SET) === 0);
return (fseek($this->f,$pos,SEEK_SET) === 0);
}
}

View File

@ -4,7 +4,6 @@ namespace App\Classes\File;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
@ -37,13 +36,13 @@ final class Mail extends Send
return $this->f->messages->pluck('id');
case 'name':
return sprintf('%08x',$this->youngest_id());
return sprintf('%08x',timew($this->youngest()));
case 'nameas':
return sprintf('%s.pkt',$this->name);
case 'mtime':
return $this->youngest_date()->timestamp;
return $this->youngest()->timestamp;
case 'type':
return ($this->ftype&0xff00)>>8;
@ -115,18 +114,8 @@ final class Mail extends Send
return TRUE;
}
private function youngest(): array
private function youngest(): Carbon
{
return $this->f->messages->sortBy(fn($item)=>Arr::get($item,'datetime'))->first();
}
private function youngest_id(): int
{
return Arr::get($this->youngest(),'id',0);
}
private function youngest_date(): Carbon
{
return Arr::get($this->youngest(),'datetime',Carbon::now());
return $this->f->messages->pluck('date')->sort()->last();
}
}

View File

@ -128,9 +128,9 @@ class Receive extends Base
// If packet is greater than a size, lets queue it
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));
PacketProcess::dispatch($this->receiving->rel_name,$this->ao->system,FALSE,$rcvd_time);
PacketProcess::dispatch($this->receiving->rel_name,$this->ao->zone->domain,FALSE,$rcvd_time);
} else
PacketProcess::dispatchSync($this->receiving->rel_name,$this->ao->system,TRUE,$rcvd_time);
PacketProcess::dispatchSync($this->receiving->rel_name,$this->ao->zone->domain,TRUE,$rcvd_time);
} catch (\Exception $e) {
Log::error(sprintf('%s:! Got error dispatching packet [%s] (%d:%s-%s).',self::LOGKEY,$this->receiving->rel_name,$e->getLine(),$e->getFile(),$e->getMessage()));

View File

@ -125,9 +125,6 @@ class Send extends Base
if ($successful) {
$end = time()-$this->start;
Log::info(sprintf('%s:- Closing [%s], sent in [%d] with [%s] items',self::LOGKEY,$this->sending->nameas,$end,$this->sending->dbids->count()));
} else {
Log::alert(sprintf('%s:- Closing [%s], file NOT SENT successfully',self::LOGKEY,$this->sending->nameas));
}
$this->sending->close($successful,$node);
@ -204,7 +201,7 @@ class Send extends Base
// Files
if (($x=$ao->filesWaiting())->count()) {
Log::info(sprintf('%s:- [%d] Files(s) added for sending to [%s]',self::LOGKEY,$x->count(),$ao->ftn));
Log::debug(sprintf('%s:- [%d] Files(s) added for sending to [%s]',self::LOGKEY,$x->count(),$ao->ftn));
// Add Files
foreach ($x as $fo) {
@ -227,12 +224,12 @@ class Send extends Base
*/
public function open(string $compress=''): bool
{
Log::debug(sprintf('%s:+ File Open to send',self::LOGKEY));
Log::debug(sprintf('%s:+ Opening file to send',self::LOGKEY));
if ((($this->index=$this->list->search(function($item) { return $item->complete === FALSE; })) !== FALSE)
&& $this->sending->open())
{
Log::info(sprintf('%s:- Content Send item [#%d] (%s) with size [%d]',self::LOGKEY,$this->index,$this->sending->nameas,$this->sending->size));
Log::info(sprintf('%s:- Sending item [%d] (%s)',self::LOGKEY,$this->index,$this->sending->nameas));
$this->pos = 0;
$this->start = time();
@ -245,10 +242,7 @@ class Send extends Base
return TRUE;
} else {
Log::error(sprintf('%s:- No files to open',self::LOGKEY));
$this->index = NULL;
return FALSE;
throw new Exception('No files to open');
}
}
@ -309,7 +303,7 @@ class Send extends Base
$this->pos += strlen($data);
Log::debug(sprintf('%s:- Content Read [%d] bytes, pos now [%d]',self::LOGKEY,strlen($data),$this->pos));
Log::debug(sprintf('%s:- Read [%d] bytes, file pos now [%d]',self::LOGKEY,strlen($data),$this->pos));
return $data;
}
@ -329,7 +323,7 @@ class Send extends Base
if ($this->sending->seek($pos)) {
$this->pos = $pos;
Log::debug(sprintf('%s:= Content Seek to [%d]',self::LOGKEY,$this->pos));
Log::debug(sprintf('%s:= Seeked to [%d]',self::LOGKEY,$this->pos));
return TRUE;

View File

@ -47,7 +47,7 @@ final class Dynamic extends Send
return $this->sent->timestamp;
case 'size':
return $this->{$key};
return strlen($this->buffer);
default:
return NULL;
@ -104,7 +104,6 @@ final class Dynamic extends Send
public function open(string $compress=''): bool
{
$this->buffer = (string)$this->item;
$this->size = strlen($this->buffer);
return TRUE;
}

View File

@ -38,7 +38,6 @@ class Node
private Collection $ftns_authed; // The FTNs we have validated
private Collection $ftns_other; // Other FTN addresses presented
private bool $authed; // Have we authenticated the remote.
private Address $originate; // When we originate a call, this is who we are after
private int $options; // This nodes capabilities/options
@ -85,10 +84,6 @@ class Node
// The nodes password
case 'password':
// If we are originating a session, we'll use that password.
if (isset($this->originate))
return $this->originate->pass_session;
// If we have already authed, we'll use that password.
if ($this->ftns_authed->count())
return $this->ftns_authed->first()->pass_session;
@ -199,8 +194,6 @@ class Node
throw new Exception('Already authed');
foreach ($this->ftns as $o) {
Log::debug(sprintf('%s:- Attempting to authenticate [%s] with [%s]',self::LOGKEY,$o->ftn,$o->pass_session));
if (! $sespass=$o->pass_session)
continue;
@ -276,8 +269,7 @@ class Node
*/
public function originate(Address $o): void
{
$this->originate = $o;
$this->ftns_authed = $o->system->match($o->zone,Address::NODE_ALL);
$this->ftns_authed->push($o);
}
/**
@ -291,11 +283,19 @@ class Node
if ($this->authed)
return TRUE;
Log::debug(sprintf('%s:- Making sure we called [%s] from [%s]',self::LOGKEY,$this->originate->ftn,$this->ftns->pluck('ftn')->join(',')));
if ($this->ftns_authed->count() !== 1 || ! $this->ftns->count())
return FALSE;
$this->authed = $this->ftns->pluck('ftn')->contains($this->originate->ftn);
$ftn = $this->ftns_authed->first()->ftn;
return $this->authed;
return $this->ftns->search(function($item) use ($ftn) {
if ($item->ftn === $ftn) {
$item->system->last_session = Carbon::now();
$item->system->save();
$this->authed = TRUE;
return TRUE;
}
}) !== FALSE;
}
public function optionClear(int $key): void

View File

@ -332,13 +332,13 @@ class Page
$subtext = substr($this->text,$current_pos,$space_pos);
}
// If the rest of the string will fit on the current line
} elseif ($text_length-$current_pos < static::MSG_WIDTH-$this->x-$buffer) {
// If the reset of the string will fit on the current line
} elseif ($text_length-$current_pos < static::MSG_WIDTH-$buffer) {
$subtext = substr($this->text,$current_pos);
// Get the next lines worth of chars, breaking on a space
} else {
$subtext = $this->text_substr(substr($this->text,$current_pos),static::MSG_WIDTH-$this->x-$buffer);
$subtext = $this->text_substr(substr($this->text,$current_pos),static::MSG_WIDTH-$buffer);
// Include the text up to the last space
if (substr($this->text,$current_pos+strlen($subtext),1) !== ' ')

View File

@ -3,7 +3,6 @@
namespace App\Classes;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use App\Classes\File\{Receive,Send};
@ -108,7 +107,7 @@ abstract class Protocol
public const TCP_SPEED = 115200;
protected SocketClient $client; /* Our socket details */
protected Setup $setup; /* Our setup */
protected ?Setup $setup; /* Our setup */
protected Node $node; /* The node we are communicating with */
/** The list of files we are sending */
protected Send $send;
@ -135,9 +134,12 @@ abstract class Protocol
abstract protected function protocol_session(bool $force_queue=FALSE): int;
public function __construct()
public function __construct(Setup $o=NULL)
{
$this->setup = Config::get('setup',Setup::findOrFail(config('app.id')));
if ($o && ! $o->system->akas->count())
throw new \Exception('We dont have any ACTIVE FTN addresses assigned');
$this->setup = $o;
}
/**
@ -257,7 +259,7 @@ abstract class Protocol
throw new SocketException(SocketException::CANT_ACCEPT,'Could not fork process');
if ($pid)
Log::info(sprintf('%s:+ New connection from [%s], thread [%d] created',self::LOGKEY,$client->address_remote,$pid));
Log::info(sprintf('%s:+ New connection, thread [%d] created',self::LOGKEY,$pid));
// Parent return ready for next connection
return $pid;
@ -316,7 +318,7 @@ abstract class Protocol
Log::debug(sprintf('%s:- Presenting limited AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(',')));
} else {
$addresses = our_address();
$addresses = $this->setup->system->akas;
Log::debug(sprintf('%s:- Presenting ALL our AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(',')));
}
@ -429,8 +431,8 @@ abstract class Protocol
if ($so && $so->exists) {
foreach ($this->node->aka_other as $aka)
// @todo For disabled zones, we shouldnt refuse to record the address
if ((! Address::findFTN($aka)) && ($oo=Address::createFTN($aka,$so))) {
if (! Address::findFTN($aka)) {
$oo = Address::createFTN($aka,$so);
$oo->validated = TRUE;
$oo->save();
}

View File

@ -14,7 +14,7 @@ use App\Classes\Protocol as BaseProtocol;
use App\Classes\Sock\Exception\SocketException;
use App\Classes\Sock\SocketClient;
use App\Exceptions\{FileGrewException,InvalidFTNException};
use App\Models\{Address,Mailer,Setup};
use App\Models\{Address,Mailer};
final class Binkp extends BaseProtocol
{
@ -177,8 +177,8 @@ final class Binkp extends BaseProtocol
$this->msgs(self::BPM_BSY,'RETRY 0600: Down for maintenance, back soon...');
// @note Sometimes the remote drops the connection when we send the busy
while (($this->tx_left || $this->mqueue->count()) && $this->binkp_send()) {}
while ($this->tx_left || $this->mqueue->count())
$this->binkp_send();
return FALSE;
}
@ -195,7 +195,7 @@ final class Binkp extends BaseProtocol
$this->msgs(self::BPM_NUL,sprintf('NDL %d,TCP,BINKP',$this->client->speed));
$this->msgs(self::BPM_NUL,sprintf('TIME %s',Carbon::now()->toRfc2822String()));
$this->msgs(self::BPM_NUL,
sprintf('VER %s/%s %s/%s',Setup::PRODUCT_NAME_SHORT,Setup::version(),self::PROT,self::VERSION));
sprintf('VER %s-%s %s/%s',config('app.name'),$this->setup->version,self::PROT,self::VERSION));
if ($this->originate) {
$opt = $this->capGet(self::F_NOREL,self::O_WANT) ? ' NR' : '';
@ -393,13 +393,11 @@ final class Binkp extends BaseProtocol
if ($this->capGet(self::F_CRYPT,self::O_YES)) {
Log::debug(sprintf('%s:%% Decrypting data from remote.',self::LOGKEY));
$this->rx_buf .= ($x=$this->crypt_in->decrypt($rx_buf));
$this->rx_buf .= $this->crypt_in->decrypt($rx_buf);
} else {
$this->rx_buf .= ($x=$rx_buf);
$this->rx_buf .= $rx_buf;
}
Log::debug(sprintf('%s:- We read [%d] chars from remote',self::LOGKEY,strlen($x)),['rx_buf'=>hex_dump($x)]);
}
Log::debug(sprintf('%s:- Read buffer has [%d] chars to process.',self::LOGKEY,strlen($this->rx_buf)));
@ -436,7 +434,7 @@ final class Binkp extends BaseProtocol
}
if (static::DEBUG)
Log::debug(sprintf('%s:- rx_buf size [%d]',self::LOGKEY,strlen($this->rx_buf)));
Log::debug(sprintf('%s: - binkp_recv BUFFER [%d]',self::LOGKEY,strlen($this->rx_buf)));
$msg = ord(substr($this->rx_buf,0,1));
@ -489,11 +487,6 @@ final class Binkp extends BaseProtocol
$rc = $this->M_get($data);
break;
case self::BPM_SKIP:
Log::debug(sprintf('%s:- SKIP:Remote requested to skip file [%s]',self::LOGKEY,$data));
$rc = $this->M_skip($data);
break;
case self::BPM_GOTSKIP:
Log::debug(sprintf('%s:- GOT:Remote received, or already has a file [%s]',self::LOGKEY,$data));
$rc = $this->M_gotskip($data);
@ -663,7 +656,7 @@ final class Binkp extends BaseProtocol
$offs = (int)$this->strsep($str,' ');
$flags = $this->strsep($str,' ');
if ($name && is_numeric($size) && $time) {
if ($name && $size && $time) {
return [
'file'=>['name'=>$name,'size'=>$size,'mtime'=>$time],
'offs'=>$offs,
@ -718,7 +711,7 @@ final class Binkp extends BaseProtocol
// If we only present limited AKAs dont validate password against akas outside of the domains we present
} elseif (is_null(our_address($o))) {
Log::debug(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(',')));
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(',')));
$this->node->ftn_other = $rem_aka;
continue;
@ -733,7 +726,6 @@ final class Binkp extends BaseProtocol
Log::info(sprintf('%s:- Got AKA [%s]',self::LOGKEY,$rem_aka));
// We'll update this address status
// @todo this shouldnt be here, since we havent authenticated the node
$o->validated = TRUE;
$o->role &= ~(Address::NODE_HOLD|Address::NODE_DOWN);
$o->save();
@ -894,13 +886,16 @@ final class Binkp extends BaseProtocol
//if ($this->recv->fd)
// $this->recv->close();
// If we cannot understand the file, we'll send back a SKIP
if (! ($file=$this->file_parse($buf))) {
Log::error(sprintf('%s:! UNPARSABLE file info [%s]',self::LOGKEY,$buf));
$this->msgs(self::BPM_ERR,sprintf('M_FILE: unparsable file info: "%s", what are you on?',$buf));
$this->msgs(self::BPM_SKIP,$buf);
return TRUE;
if ($this->sessionGet(self::SE_SENDFILE))
$this->send->close(FALSE,$this->node);
$this->rc = self::S_FAILURE;
return FALSE;
}
// In NR mode, when we got -1 for the file offsite, the reply to our get will confirm our requested offset.
@ -918,18 +913,6 @@ final class Binkp extends BaseProtocol
$this->recv->new($file['file'],$this->node->address,$this->force_queue);
// If the file is zero byte size, we'll skip it
if ($this->recv->recvsize === 0) {
Log::alert(sprintf('%s:! SKIPPING zero byte file info [%s]',self::LOGKEY,$this->recv->nameas));
$this->msgs(self::BPM_SKIP,$this->recv->name_size_time);
// Close the file, since we are skipping it.
$this->recv->close();
return TRUE;
}
try {
switch ($this->recv->open($file['offs']<0,$file['flags'])) {
case self::FOP_ERROR:
@ -1023,46 +1006,7 @@ final class Binkp extends BaseProtocol
}
/**
* M_SKIP commands
*
* @param string $buf
* @return bool
* @throws \Exception
* @todo We need to not add more files this session if a node skips a file
*/
private function M_skip(string $buf): bool
{
Log::alert(sprintf('%s:+ Remote request to skip the file for now [%s]',self::LOGKEY,$buf));
if ($file = $this->file_parse($buf)) {
if ($this->send->nameas
&& ! strncasecmp(Arr::get($file,'file.name'),$this->send->nameas,self::MAX_PATH)
&& $this->send->mtime === Arr::get($file,'file.mtime')
&& $this->send->size === Arr::get($file,'file.size'))
{
if ((! $this->sessionGet(self::SE_SENDFILE)) && (! $this->sessionGet(self::SE_WAITGOT))) {
Log::error(sprintf('%s:! M_skip for unknown file [%s]',self::LOGKEY,$buf));
} else {
Log::info(sprintf('%s:= Packet/File [%s], type [%d] skipped.',self::LOGKEY,$this->send->nameas,$this->send->type));
$this->sessionClear(self::SE_WAITGOT|self::SE_SENDFILE);
$this->send->close(FALSE,$this->node);
}
} else {
Log::error(sprintf('%s:! M_skip not for our file? [%s]',self::LOGKEY,$buf));
}
} else {
Log::error(sprintf('%s:! UNPARSABLE file info [%s]',self::LOGKEY,$buf));
}
return TRUE;
}
/**
* M_GOTSKIP command
* M_GOT/M_SKIP commands
*
* @param string $buf
* @return bool
@ -1286,13 +1230,9 @@ final class Binkp extends BaseProtocol
}
}
if ($this->optionGet(self::O_PWD)) {
if ($this->optionGet(self::O_PWD))
Log::info(sprintf('%s:- SECURE',self::LOGKEY));
// @todo Since we have connected, if the node was marked down/hold reset that
// Notification::route('netmail',$ao->system->aka_unknown()->first()->withoutRelations())->notify(new NodeMarkedDownNetmail($ao->withoutRelations()));
}
return $this->binkp_hsdone();
}
@ -1388,9 +1328,6 @@ final class Binkp extends BaseProtocol
if ($this->node->aka_authed) {
$this->msgs(self::BPM_OK,sprintf('%ssecure',$have_pwd ? '' : 'non-'));
// @todo Since we have connected, if the node was marked down/hold reset that
// Notification::route('netmail',$ao->system->aka_unknown()->first()->withoutRelations())->notify(new NodeMarkedDownNetmail($ao->withoutRelations()));
} else {
$this->msgs(self::OK,'non-secure');
}
@ -1573,7 +1510,12 @@ final class Binkp extends BaseProtocol
Log::info(sprintf('%s:- We have authed these AKAs [%s]',self::LOGKEY,$node->aka_remote_authed->pluck('ftn')->join(',')));
foreach ($node->aka_remote_authed as $ao) {
Log::info(sprintf('%s:- Checking for any new mail and files to [%s]',self::LOGKEY,$ao->ftn));
Log::debug(sprintf('%s:- Checking for any new mail and files to [%s]',self::LOGKEY,$ao->ftn));
if (! $ao->validated) {
Log::alert(sprintf('%s:! Address [%s] is not validated, so we wont bundle mail for it',self::LOGKEY,$ao->ftn));
continue;
}
$this->send->mail($ao);
$this->send->files($ao);
@ -1590,11 +1532,11 @@ final class Binkp extends BaseProtocol
*/
}
Log::info(sprintf('%s:- We have [%d] items to send to [%s]',self::LOGKEY,$this->send->togo_count,$ao->ftn));
Log::info(sprintf('%s:- We have [%d] items to send to [%s]',self::LOGKEY,$this->send->togo_count,$ao->system->name));
} else {
// @todo We should only send netmail if unauthenticated - netmail that is direct to this node (no routing)
Log::alert(sprintf('%s:- Not AUTHed so not looking for mail, but we know these akas [%s]',self::LOGKEY,$node->aka_remote->pluck('ftn')->join(',')));
Log::debug(sprintf('%s:- Not AUTHed so not looking for mail, but we know these akas [%s]',self::LOGKEY,$node->aka_remote->pluck('ftn')->join(',')));
}
}

View File

@ -73,7 +73,7 @@ final class DNS extends BaseProtocol
$this->client = $client;
$this->protocol_session();
Log::debug(sprintf('%s:= onConnect - Connection closed [%s]',self::LOGKEY,$client->address_remote));
Log::info(sprintf('%s:= onConnect - Connection closed [%s]',self::LOGKEY,$client->address_remote));
exit(0);
}
@ -167,7 +167,7 @@ final class DNS extends BaseProtocol
case self::DNS_TYPE_AAAA:
case self::DNS_TYPE_SRV:
case self::DNS_TYPE_TXT:
Log::debug(sprintf('%s:= Looking for record [%s] for [%s]',self::LOGKEY,$this->query->type,$this->query->domain));
Log::info(sprintf('%s:= Looking for record [%s] for [%s]',self::LOGKEY,$this->query->type,$this->query->domain));
$labels = clone($this->query->labels);
$mailer = '';
@ -232,15 +232,14 @@ final class DNS extends BaseProtocol
// Check we have the right record
if ((! $ao) || (($rootdn !== self::TLD) && ((! $ao->zone->domain->dnsdomain) || ($ao->zone->domain->dnsdomain !== $rootdn)))) {
Log::alert(sprintf('%s:= No DNS record for [%d:%d/%d.%d@%s]',self::LOGKEY,$z,$n,$f,$p,$d));
return $this->nameerr();
}
switch ($this->query->type) {
case self::DNS_TYPE_SRV:
if (($ao->system->address) && ($xx=$ao->system->mailers->where('id',$mailer->id)->pop())) {
Log::info(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->address,$ao->ftn));
Log::info(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->address,$ao->ftn));
if (($ao->system->address) && ($xx=$ao->system->mailers->where('id',$mailer->id)->pop())) {
return $this->reply(
self::DNS_NOERROR,
[serialize([
@ -251,8 +250,6 @@ final class DNS extends BaseProtocol
]) => self::DNS_TYPE_SRV]);
} else {
Log::alert(sprintf('%s:! No/incomplete hostname/port details for [%d] for DNS query [%s]',self::LOGKEY,$ao->system->id,$ao->ftn));
return $this->nodata();
}
@ -264,7 +261,7 @@ final class DNS extends BaseProtocol
[serialize($ao->system->name) => self::DNS_TYPE_TXT]);
default:
Log::info(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->address ?: 'NO ADDRESS',$ao->ftn));
Log::info(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->address,$ao->ftn));
return (! $ao->system->address)
? $this->nodata()

View File

@ -207,8 +207,8 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
// Mailer Details
$makedata .= sprintf('{%s}{%s}{%s}{%s}',
Setup::product_id(),
Setup::PRODUCT_NAME_SHORT,
Setup::version(),
config('app.name'),
$this->setup->version,
'#000000' // Serial Numbers
);
@ -1061,9 +1061,6 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
return (self::S_REDIAL|self::S_ADDTRY);
}
// @todo Since we have connected, if the node was marked down/hold reset that
// Notification::route('netmail',$ao->system->aka_unknown()->first()->withoutRelations())->notify(new NodeMarkedDownNetmail($ao->withoutRelations()));
// @todo Lock Node AKAs
Log::info(sprintf('%s:- We have [%lu%s] mail, [%lu%s] files',self::LOGKEY,$this->send->mail_size,'b',$this->send->files_size,'b'));
@ -1237,6 +1234,11 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
// Add our mail to the queue if we have authenticated
if ($this->node->aka_authed)
foreach ($this->node->aka_remote_authed as $ao) {
if (! $ao->validated) {
Log::alert(sprintf('%s:! Address [%s] is not validated, so we wont bundle mail for it',self::LOGKEY,$ao->ftn));
continue;
}
// Send mail
while ($this->send->mail($ao)) {
$z = new Zmodem;

View File

@ -48,7 +48,7 @@ final class SocketClient {
/** @var string Data in the RX buffer */
private string $rx_buf = '';
public function __construct (\Socket $connection,bool $originate=FALSE) {
public function __construct (\Socket $connection) {
$this->connection = $connection;
if ($this->type === SOCK_STREAM) {
@ -56,138 +56,87 @@ final class SocketClient {
socket_getpeername($connection,$this->address_remote,$this->port_remote);
// If HAPROXY is used, work get the clients address
if ((! $originate) && config('fido.haproxy')) {
if (config('fido.haproxy')) {
Log::debug(sprintf('%s:+ HAPROXY connection host [%s] on port [%d] (%s)',self::LOGKEY,$this->address_remote,$this->port_remote,$this->type));
if (($x=$this->read(5,6)) === 'PROXY ')
$vers = 1;
elseif (($x === "\x0d\x0a\x0d\x0a\x00\x0d") && ($this->read('5,6') === "\x0aQUIT\x0a"))
$vers = 2;
else
if ($this->read(5,12) !== "\x0d\x0a\x0d\x0a\x00\x0d\x0aQUIT\x0a")
throw new HAproxyException('Failed to initialise HAPROXY connection');
switch ($vers) {
// Version/Command
$vc = $this->read_ch(5);
if (($x=($vc>>4)&0x7) !== 2)
throw new HAproxyException(sprintf('Unknown HAPROXY version [%d]',$x));
switch ($x=($vc&0x7)) {
// HAPROXY internal
case 0:
throw new HAproxyException('HAPROXY internal health-check');
// PROXY connection
case 1:
// Protocol/Address Family
switch ($x=$this->read(5,5)) {
case 'TCP4 ':
$p = 4;
break;
case 'TCP6 ':
$p = 6;
break;
default:
throw new HAproxyException(sprintf('HAPROXY protocol [%d] is not handled',$x));
}
$read = $this->read(5,104-11);
// IPv4
if (($p === 4) || ($p === 6)) {
$parse = collect(sscanf($read,'%s %s %s %s'));
$src = Arr::get($parse,0);
$dst = Arr::get($parse,1);
$src_port = (int)Arr::get($parse,2);
$dst_port = (int)Arr::get($parse,3);
$len = $parse->map(fn($item)=>strlen($item))->sum()+3;
// The last 2 chars should be "\r\n"
if (($x=substr($read,$len)) !== "\r\n")
throw new HAproxyException(sprintf('HAPROXY parsing failed for version [%d] [%s] (%s)',$p,$read,hex_dump($x)));
} else {
throw new HAproxyException(sprintf('HAPROXY version [%d] is not handled [%s]',$p,$read));
}
$this->port_remote = $src_port;
break;
case 2:
// Version/Command
$vc = $this->read_ch(5);
if (($x=($vc>>4)&0x7) !== 2)
throw new HAproxyException(sprintf('Unknown HAPROXY version [%d]',$x));
switch ($x=($vc&0x7)) {
// HAPROXY internal
case 0:
throw new HAproxyException('HAPROXY internal health-check');
// PROXY connection
case 1:
break;
default:
throw new HAproxyException(sprintf('HAPROXY command [%d] is not handled',$x));
}
// Protocol/Address Family
$pa = $this->read_ch(5);
switch ($x=($pa>>4)&0x7) {
case 1: // AF_INET
$p = 4;
break;
case 2: // AF_INET6
$p = 6;
break;
}
switch ($x=($pa&0x7)) {
case 1: // STREAM
break;
default:
throw new HAproxyException(sprintf('HAPROXY address family [%d] is not handled',$x));
}
$len = Arr::get(unpack('n',$this->read(5,2)),1);
// IPv4
if (($p === 4) && ($len === 12)) {
$src = inet_ntop($this->read(5,4));
$dst = inet_ntop($this->read(5,4));
} elseif (($p === 6) && ($len === 36)) {
$src = inet_ntop($this->read(5,16));
$dst = inet_ntop($this->read(5,16));
} else {
throw new HAproxyException(sprintf('HAPROXY address len [%d:%d] is not handled',$p,$len));
}
$src_port = unpack('n',$this->read(5,2));
$dst_port = Arr::get(unpack('n',$this->read(5,2)),1);
$this->port_remote = Arr::get($src_port,1);
break;
default:
throw new HAproxyException('Failed to initialise HAPROXY connection');
throw new HAproxyException(sprintf('HAPROXY command [%d] is not handled',$x));
}
$this->address_remote = $src;
// Protocol/Address Family
$pa = $this->read_ch(5);
$p = NULL;
Log::debug(sprintf('%s:- HAPROXY src [%s:%d] dst [%s:%d]',
switch ($x=($pa>>4)&0x7) {
case 1: // AF_INET
$p = 4;
break;
case 2: // AF_INET6
$p = 6;
break;
default:
throw new HAproxyException(sprintf('HAPROXY protocol [%d] is not handled',$x));
}
switch ($x=($pa&0x7)) {
case 1: // STREAM
break;
default:
throw new HAproxyException(sprintf('HAPROXY address family [%d] is not handled',$x));
}
$len = Arr::get(unpack('n',$this->read(5,2)),1);
// IPv4
if (($p === 4) && ($len === 12)) {
$src = inet_ntop($this->read(5,4));
$dst = inet_ntop($this->read(5,4));
} elseif (($p === 6) && ($len === 36)) {
$src = inet_ntop($this->read(5,16));
$dst = inet_ntop($this->read(5,16));
} else {
throw new HAproxyException(sprintf('HAPROXY address len [%d:%d] is not handled',$p,$len));
}
$src_port = unpack('n',$this->read(5,2));
$dst_port = unpack('n',$this->read(5,2));
$this->address_remote = $src;
$this->port_remote = Arr::get($src_port,1);
Log::info(sprintf('%s:! HAPROXY src [%s:%d] dst [%s:%d]',
self::LOGKEY,
$this->address_remote,
$this->port_remote,
$dst,
$dst_port,
Arr::get($dst_port,1),
));
}
Log::debug(sprintf('%s:+ Connection host [%s] on port [%d] (%s)',self::LOGKEY,$this->address_remote,$this->port_remote,$this->type));
Log::info(sprintf('%s:+ Connection host [%s] on port [%d] (%s)',self::LOGKEY,$this->address_remote,$this->port_remote,$this->type));
}
}
@ -235,14 +184,13 @@ final class SocketClient {
* @param string $address
* @param int $port
* @return static
* @throws SocketException|HAproxyException
* @throws SocketException
*/
public static function create(string $address,int $port): self
{
Log::info(sprintf('%s:+ Creating connection to [%s:%d]',self::LOGKEY,$address,$port));
$type = collect(config('fido.ip'))
->filter(fn($item)=>$item['enabled']);
$sort = collect(['AAAA','A']);
if (filter_var($address,FILTER_VALIDATE_IP))
$resolved = collect([[
@ -251,15 +199,14 @@ final class SocketClient {
]]);
else
// We only look at AAAA/A records
$resolved = collect(dns_get_record($address,$type->map(fn($item)=>$item['type'])->sum()))
->filter(fn($item)=>$type->has(Arr::get($item,'type')))
->sort(fn($a,$b)=>$type->get(Arr::get($a,'type'))['order'] < $type->get(Arr::get($b,'type'))['order']);
$resolved = collect(dns_get_record($address,DNS_AAAA|DNS_A))
->filter(function($item) use ($sort) { return $sort->search(Arr::get($item,'type')) !== FALSE; })
->sort(function($item) use ($sort) { return $sort->search(Arr::get($item,'type')); });
if (! $resolved->count())
throw new SocketException(SocketException::CANT_CONNECT,sprintf('%s doesnt resolved to an IPv4/IPv6 address',$address));
$result = FALSE;
$socket = NULL;
foreach ($resolved as $address) {
try {
@ -289,7 +236,7 @@ final class SocketClient {
if ($result === FALSE)
throw new SocketException(SocketException::CANT_CONNECT,socket_strerror(socket_last_error($socket)));
return new self($socket,TRUE);
return new self($socket);
}
/**
@ -405,7 +352,7 @@ final class SocketClient {
Log::error(sprintf('%s:! Closing socket [%s]',self::LOGKEY,$e->getMessage()));
}
Log::debug(sprintf('%s:= Connection closed with [%s]',self::LOGKEY,$this->address_remote));
Log::info(sprintf('%s:= Connection closed with [%s]',self::LOGKEY,$this->address_remote));
}
/**

View File

@ -1,42 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Jobs\AddressClearQueue as Job;
use App\Models\Address;
class AddressClearQueue extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'address:clear:queue'
.' {ftn : FTN}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Clear up anything queued for an FTN';
/**
* Execute the console command.
*/
public function handle(): int
{
$ao = Address::findFTN($this->argument('ftn'),TRUE,TRUE);
if (! $ao) {
$this->error('FTN not found: '.$this->argument('ftn'));
return self::FAILURE;
}
return Job::dispatchSync($ao);
}
}

View File

@ -2,10 +2,10 @@
namespace App\Console\Commands\Areafix;
use Carbon\Carbon;
use Illuminate\Console\Command;
use App\Jobs\AreafixRescan;
use App\Models\{Address,Echoarea};
use App\Models\{Address,Echoarea,Echomail};
class Rescan extends Command
{
@ -14,13 +14,7 @@ class Rescan extends Command
*
* @var string
*/
protected $signature = 'areafix:rescan'
.' {ftn : FTN Address}'
.' {area : Echoarea Tag}'
.' {days? : Limit to messages authored days ago}'
.' {--j|queue : Queue the Job}'
.' {--Q|queuename=default : Queue on queue}'
.' {--R|export : Re-export previously sent messages}';
protected $signature = 'areafix:rescan {ftn} {area} {days?}';
/**
* The console command description.
@ -49,12 +43,50 @@ class Rescan extends Command
if (! $this->argument('area'))
throw new \Exception('Areaname is required');
$eo = Echoarea::where('name',$this->argument('area'))->sole();
$eao = Echoarea::where('name',$this->argument('area'))->singleOrFail();
if ($eao->domain_id !== $ao->zone->domain_id)
throw new \Exception(sprintf('Echo area [%s] is not in domain [%s] for FTN [%s]',$eao->name,$ao->zone->domain->name,$ao->ftn));
if ($this->option('queue'))
AreafixRescan::dispatch($ao,$eo,$this->argument('days'))->onQueue($this->option('queuename'));
else
AreafixRescan::dispatchSync($ao,$eo,$this->argument('days'));
// Check that the user is subscribed
if (! $ao->echoareas->contains($eao->id))
throw new \Exception(sprintf('FTN [%s] is not subscribed to [%s]',$ao->ftn,$eao->name));
// Check that an FTN can read the area
if (! $eao->can_read($ao->security))
throw new \Exception(sprintf('FTN [%s] doesnt have permission to receive [%s]',$ao->ftn,$eao->name));
foreach (Echomail::select('id')
->where('echoarea_id',$eao->id)
->when($this->argument('days'),function($query) {
return $query->where('created_at','>=',Carbon::now()->subDays($this->argument('days'))->startOfDay());
})
->orderBy('datetime')
->cursor() as $eo) {
// Echomail hasnt been exported before
if (! $eo->seenby->count()) {
$eo->seenby()->attach($ao->id,['export_at'=>Carbon::now()]);
$this->info(sprintf('Exported [%d] to [%s]',$eo->id,$ao->ftn3d));
} else {
$export = $eo->seenby->where('id',$ao->id)->pop();
// Echomail is pending export
if ($export && $export->pivot->export_at && is_null($export->pivot->sent_at) && is_null($export->pivot->sent_pkt)) {
$this->warn(sprintf('Not exporting [%d] already queued for [%s]',$eo->id,$ao->ftn3d));
// Echomail has been exported
} elseif ($export) {
$eo->seenby()->updateExistingPivot($ao,['export_at'=>Carbon::now(),'sent_at'=>NULL,'sent_pkt'=>NULL]);
$this->info(sprintf('Re-exported [%d] to [%s]',$eo->id,$ao->ftn3d));
// Echomail has not been exported
} else {
$eo->seenby()->attach($ao,['export_at'=>Carbon::now(),'sent_at'=>NULL,'sent_pkt'=>NULL]);
$this->info(sprintf('Exported [%d] to [%s]',$eo->id,$ao->ftn3d));
}
}
}
return self::SUCCESS;
}

View File

@ -38,7 +38,7 @@ class CommBinkpReceive extends Command
$o = Setup::findOrFail(config('app.id'));
$server = new SocketServer($o->binkp_port,$o->binkp_bind);
$server->handler = [new Binkp,'onConnect'];
$server->handler = [new Binkp($o),'onConnect'];
try {
$server->listen();

View File

@ -16,9 +16,7 @@ class CommBinkpSend extends Command
*
* @var string
*/
protected $signature = 'comm:binkp:send'
.'{--N|now : Dont queue}'
.'{ftn : FTN to Send to}';
protected $signature = 'comm:binkp:send {ftn : FTN to Send to}';
/**
* The console command description.
@ -34,7 +32,7 @@ class CommBinkpSend extends Command
*
* @throws \Exception
*/
public function handle()
public function handle(): void
{
$ao = Address::findFTN($this->argument('ftn'));
if (! $ao)
@ -44,11 +42,6 @@ class CommBinkpSend extends Command
$mo = Mailer::where('name',self::ID)->singleOrFail();
if ($this->option('now'))
Job::dispatchSync($ao,$mo);
else
Job::dispatch($ao,$mo);
return self::SUCCESS;
Job::dispatch($ao,$mo);
}
}

View File

@ -38,7 +38,7 @@ class CommEMSIReceive extends Command
$o = Setup::findOrFail(config('app.id'));
$server = new SocketServer($o->emsi_port,$o->emsi_bind);
$server->handler = [new EMSI,'onConnect'];
$server->handler = [new EMSI($o),'onConnect'];
try {
$server->listen();

View File

@ -16,9 +16,7 @@ class CommEMSISend extends Command
*
* @var string
*/
protected $signature = 'comm:emsi:send'
.'{--N|now : Dont queue}'
.'{ftn : FTN to Send to}';
protected $signature = 'comm:emsi:send {ftn : FTN to Send to}';
/**
* The console command description.
@ -34,7 +32,7 @@ class CommEMSISend extends Command
*
* @throws \Exception
*/
public function handle()
public function handle(): void
{
$ao = Address::findFTN($this->argument('ftn'));
if (! $ao)
@ -44,11 +42,6 @@ class CommEMSISend extends Command
$mo = Mailer::where('name',self::ID)->singleOrFail();
if ($this->option('now'))
Job::dispatchSync($ao,$mo);
else
Job::dispatch($ao,$mo);
return self::SUCCESS;
Job::dispatch($ao,$mo);
}
}

View File

@ -49,11 +49,6 @@ class PacketDump extends Command
throw new \Exception('Unknown type: '.$this->argument('type'));
}
if (is_null($pkt)) {
$this->info(sprintf('No packet for [%s] of type [%s]',$this->argument('ftn'),$this->argument('type')));
return self::SUCCESS;
}
if (! $this->argument('file')) {
$this->info('Item Name:'.$pkt->name);
$this->info('Item Type:'.get_class($pkt));

View File

@ -49,8 +49,8 @@ class MailList extends Command
return [
'id'=>$item->id,
'msgid'=>$item->msgid,
'from'=>sprintf('%s (%s)',$item->from,$item->fftn->ftn3d),
'to'=>sprintf('%s (%s)',$item->to,$item->tftn->ftn3d),
'from'=>$item->from,
'to'=>$item->to,
'subject'=>$item->subject,
];
}));

View File

@ -1,50 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\Address;
use Carbon\Carbon;
use Illuminate\Console\Command;
use App\Jobs\NodesNew as Job;
use App\Models\Domain;
class NodesNew extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'nodes:new'
.' {domain : Domain}'
.' {--date= : From a specific date (default 1 since last Saturday)}'
.' {--netmail= : Send a Netmail to FTN}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'List new nodes since last Saturday (or a specific date)';
/**
* Execute the console command.
*/
public function handle(): int
{
$do = Domain::where('name',$this->argument('domain'))->singleOrFail();
$ao = NULL;
if ($this->option('netmail')) {
$ao = Address::findFTN($this->option('netmail'));
if (! $ao) {
$this->error('Address not found: '.$this->option('netmail'));
return self::FAILURE;
}
}
return Job::dispatchSync($do,$this->option('date') ? Carbon::parse($this->option('date')) : Carbon::parse('last saturday'),$ao);
}
}

View File

@ -50,7 +50,7 @@ class PacketInfo extends Command
}
foreach ($f as $packet) {
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$a?->system);
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$a?->zone->domain);
$this->alert(sprintf('File Name: %s',$x));
@ -68,7 +68,7 @@ class PacketInfo extends Command
echo "\n";
try {
$this->warn(sprintf('- Date : %s (%s)',$msg->date,$msg->date->tz->toOffsetName()));
$this->warn(sprintf('- Date : %s (%s)',$msg->datetime,$msg->datetime->tz->toOffsetName()));
$this->warn(sprintf(' - Errors : %s',$msg->errors->count() ? 'YES' : 'No'));
$this->warn(sprintf(' - Flags : %s',$msg->flags()->keys()->join(', ')));
$this->warn(sprintf(' - Cost : %d',$msg->cost));
@ -97,7 +97,7 @@ class PacketInfo extends Command
}
foreach ($pkt->errors as $msg) {
$this->error(sprintf('- Date: %s',$msg->datetime));
$this->error(sprintf('- Date: %s',$msg->date));
$this->error(sprintf(' - FLAGS: %s',$msg->flags()->filter()->keys()->join(', ')));
$this->error(sprintf(' - From: %s (%s)',$msg->from,$msg->fftn));
$this->error(sprintf(' - To: %s (%s)',$msg->to,$msg->tftn));

View File

@ -78,7 +78,7 @@ class PacketProcess extends Command
return self::FAILURE;
}
Job::dispatchSync($rel_name,$ao->system,$this->option('dontqueue'));
Job::dispatchSync($rel_name,$ao->zone->domain,$this->option('dontqueue'));
return self::SUCCESS;
}

View File

@ -3,12 +3,12 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use App\Classes\Protocol\{Binkp,DNS,EMSI};
use App\Classes\Sock\Exception\SocketException;
use App\Classes\Sock\SocketServer;
use App\Models\Setup;
class ServerStart extends Command
{
@ -37,11 +37,7 @@ class ServerStart extends Command
public function handle(): int
{
Log::info(sprintf('%s:+ Server Starting (%d)',self::LOGKEY,getmypid()));
if (! our_address()->count())
throw new \Exception('We dont have any ACTIVE FTN addresses assigned');
$o = Config::get('setup');
$o = Setup::findOrFail(config('app.id'));
$start = collect();
@ -50,7 +46,7 @@ class ServerStart extends Command
'address'=>$o->binkp_bind,
'port'=>$o->binkp_port,
'proto'=>SOCK_STREAM,
'class'=>new Binkp,
'class'=>new Binkp($o),
]);
if ($o->emsi_active)
@ -58,7 +54,7 @@ class ServerStart extends Command
'address'=>$o->emsi_bind,
'port'=>$o->emsi_port,
'proto'=>SOCK_STREAM,
'class'=>new EMSI,
'class'=>new EMSI($o),
]);
if ($o->dns_active)
@ -66,7 +62,7 @@ class ServerStart extends Command
'address'=>$o->dns_bind,
'port'=>$o->dns_port,
'proto'=>SOCK_DGRAM,
'class'=>new DNS,
'class'=>new DNS(),
]);
$children = collect();

46
app/Console/Kernel.php Normal file
View File

@ -0,0 +1,46 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use App\Jobs\{AddressIdleDomain,MailSend,SystemHeartbeat};
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
//
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->job(new MailSend(TRUE))->everyMinute()->withoutOverlapping();
$schedule->job(new MailSend(FALSE))->twiceDaily(1,13);
$schedule->job(new SystemHeartbeat)->hourly();
$schedule->job(new AddressIdleDomain)->weeklyOn(0,'01:00');
}
/**
* Register the commands for the application.
*
* @return void
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

View File

@ -42,13 +42,13 @@ abstract class Base
case 'room':
$room_alias = Http::withToken(config('matrix.as_token'))
->get(sprintf('%s/_matrix/client/v3/rooms/%s/state/m.room.canonical_alias',config('matrix.server'),$this->room_id));
->get(sprintf('https://%s/_matrix/client/v3/rooms/%s/state/m.room.canonical_alias',config('matrix.server'),$this->room_id));
return $room_alias->json('alias',$this->room_id);
case 'topic':
$subject = Http::withToken(config('matrix.as_token'))
->get(sprintf('%s/_matrix/client/v3/rooms/%s/state/m.room.topic',config('matrix.server'),$this->room_id));
->get(sprintf('https://%s/_matrix/client/v3/rooms/%s/state/m.room.topic',config('matrix.server'),$this->room_id));
return $subject->json('topic','Message from Matrix');

View File

@ -0,0 +1,40 @@
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* @var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
{
$this->reportable(function (Throwable $e) {
//
});
}
}

View File

@ -1,213 +0,0 @@
<?php
namespace App\Helpers;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
/**
* Useful tools (js/css) used when rendering pages
*/
class PageAssets
{
// Types that we can handle
private const array types = [
'css',
'js',
];
public const array assets = [
'datatables' => [
'base' => [
'css' => [
'//cdn.datatables.net/2.1.2/css/dataTables.bootstrap4.css',
//'//cdn.datatables.net/2.1.2/css/dataTables.dataTables.min.css',
],
'js' => [
'//cdn.datatables.net/2.1.2/js/dataTables.min.js',
'//cdn.datatables.net/2.1.2/js/dataTables.bootstrap4.min.js',
],
],
'buttons' => [
'css' => [
'//cdn.datatables.net/buttons/3.1.0/css/buttons.bootstrap4.min.css',
//'//cdn.datatables.net/buttons/3.1.0/css/buttons.dataTables.min.css',
],
'js' => [
'//cdn.datatables.net/buttons/3.1.0/js/dataTables.buttons.min.js',
//'//cdn.datatables.net/buttons/3.1.0/js/buttons.dataTables.min.js',
'//cdn.datatables.net/buttons/3.1.0/js/buttons.bootstrap4.min.js',
'//cdnjs.cloudflare.com/ajax/libs/jszip/3.2.0/jszip.min.js',
],
],
'conditionalpaging' => [
'js' => [
'//cdn.datatables.net/plug-ins/2.0.5/features/conditionalPaging/dataTables.conditionalPaging.min.js',
],
],
'fixedheader' => [
'css' => [
'//cdn.datatables.net/fixedheader/4.0.1/css/fixedHeader.bootstrap4.min.css',
//'//cdn.datatables.net/fixedheader/4.0.1/css/fixedHeader.dataTables.min.css',
],
'js' => [
'//cdn.datatables.net/fixedheader/4.0.1/js/dataTables.fixedHeader.min.js',
//'//cdn.datatables.net/fixedheader/4.0.1/js/fixedHeader.dataTables.min.js',
'//cdn.datatables.net/fixedheader/4.0.1/js/fixedHeader.bootstrap4.min.js',
]
],
'responsive' => [
'css' => [
'//cdn.datatables.net/responsive/3.0.2/css/responsive.bootstrap4.min.css',
//'//cdn.datatables.net/responsive/3.0.2/css/responsive.dataTables.min.css',
],
'js' => [
'//cdn.datatables.net/responsive/3.0.2/js/dataTables.responsive.min.js',
//'//cdn.datatables.net/responsive/3.0.2/js/responsive.bootstrap.min.js',
'//cdn.datatables.net/responsive/3.0.2/js/responsive.bootstrap4.min.js',
]
],
'rowgroup' => [
'css' => [
'//cdn.datatables.net/rowgroup/1.5.0/css/rowGroup.bootstrap4.min.css',
//'//cdn.datatables.net/rowgroup/1.5.0/css/rowGroup.dataTables.min.css',
],
'js' => [
'//cdn.datatables.net/rowgroup/1.5.0/js/dataTables.rowGroup.min.js',
//'//cdn.datatables.net/rowgroup/1.5.0/js/rowGroup.dataTables.min.js',
'//cdn.datatables.net/rowgroup/1.5.0/js/rowGroup.bootstrap4.min.js',
],
],
'searchpanes' => [
'css' => [
'//cdn.datatables.net/searchpanes/2.3.1/css/searchPanes.bootstrap4.min.css',
//'//cdn.datatables.net/searchpanes/2.3.1/css/searchPanes.dataTables.min.css',
],
'js' => [
'//cdn.datatables.net/searchpanes/2.3.1/js/dataTables.searchPanes.min.js',
//'//cdn.datatables.net/searchpanes/2.3.1/js/searchPanes.dataTables.min.js',
'//cdn.datatables.net/searchpanes/2.3.1/js/searchPanes.bootstrap4.min.js',
],
],
'searchpanes-left' => [
'css' => [
'/plugin/dataTables/leftSearchPanes.css',
],
],
'select' => [
'css' => [
'//cdn.datatables.net/select/2.0.3/css/select.bootstrap4.min.css',
//'//cdn.datatables.net/select/2.0.3/css/select.dataTables.min.css',
],
'js' => [
'//cdn.datatables.net/select/2.0.3/js/dataTables.select.min.js',
//'//cdn.datatables.net/select/2.0.3/js/select.dataTables.min.js',
'//cdn.datatables.net/select/2.0.3/js/select.bootstrap4.min.js',
]
],
],
'select2' => [
'base' => [
'css' => [
'//cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css',
],
'js' => [
'//cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js',
],
],
'autofocus' => [
'js' => [
'/plugin/select2/fix-autofocus.js',
],
]
],
'simplemde' => [
'base' => [
'css' => [
'//cdn.jsdelivr.net/simplemde/latest/simplemde.min.css',
],
'js' => [
'//cdn.jsdelivr.net/simplemde/latest/simplemde.min.js',
],
],
],
];
// Items to manage
public static Collection $items;
// Add an item to the list
public static function add(string $type,Collection|array|string $asset): void
{
if (! in_array($type,self::types))
throw new \Exception('Invalid type: '.$type);
if (! isset(self::$items))
self::init();
if (is_string($asset))
self::$items
->get($type)
->push($asset)
->unique();
else
self::$items->put($type,
self::$items
->get($type)
->merge($asset->values())
->unique());
}
// Add a predefined asset
public static function asset(string $id): void
{
if (! isset(self::$items))
self::init();
if (str_contains($id,',')) {
[$item,$arguments] = explode(',',$id,2);
$arguments = collect(explode('|',$arguments));
} else {
$item = $id;
$arguments = collect();
}
$arguments = $arguments->prepend('base');
$asset = collect(Arr::get(self::assets,$item))->only($arguments);
foreach (self::types as $type)
if ($x=$asset->pluck($type)->filter()->flatten())
self::add($type,$x);
}
// Render the CSS items
public static function css(): string
{
return isset(self::$items)
? self::$items
->get('css')
->map(fn($item)=>sprintf('<link rel="stylesheet" href="%s">',$item))
->join('')
: '';
}
public static function init(): void
{
self::$items = collect([
'js' => collect(),
'css' => collect(),
]);
}
// Render the JS items
public static function js(): string
{
return isset(self::$items)
? self::$items
->get('js')
->map(fn($item)=>sprintf('<script type="text/javascript" src="%s"></script>',$item))
->join('')
: '';
}
}

View File

@ -2,9 +2,10 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\ConfirmsPasswords;
use App\Http\Controllers\Controller;
class ConfirmPasswordController extends Controller
{
/*
@ -25,7 +26,7 @@ class ConfirmPasswordController extends Controller
*
* @var string
*/
protected $redirectTo = '/';
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.

View File

@ -2,12 +2,11 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use App\Http\Controllers\Controller;
class ForgotPasswordController extends Controller
{
/*

View File

@ -8,6 +8,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
class LoginController extends Controller
{
@ -29,7 +30,7 @@ class LoginController extends Controller
*
* @var string
*/
protected $redirectTo = '/';
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
@ -38,41 +39,40 @@ class LoginController extends Controller
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
$this->middleware('auth')->only('logout');
$this->middleware('guest')
->except('logout');
}
public function login(Request $request)
{
$this->validateLogin($request);
public function login(Request $request)
{
$this->validateLogin($request);
if (Auth::attempt(array_merge($this->credentials($request),['active'=>TRUE]),TRUE)) {
$request->session()->regenerate();
if (Auth::attempt(array_merge($this->credentials($request),['active'=>TRUE]),TRUE)) {
$request->session()->regenerate();
return $this->sendLoginResponse($request);
}
return $this->sendLoginResponse($request);
}
return $this->sendFailedLoginResponse($request);
}
return $this->sendFailedLoginResponse($request);
}
// Record our last logged in time
protected function authenticated(Request $request, $user)
{
$user->last_on = Carbon::now();
$user->save();
}
protected function authenticated(Request $request, $user)
{
$user->last_on = Carbon::now();
$user->save();
}
/**
* Show our themed login page
*/
public function showLoginForm()
{
$login_note = '';
/**
* Show our themed login page
*/
public function showLoginForm()
{
$login_note = '';
if (file_exists('login_note.txt'))
$login_note = file_get_contents('login_note.txt');
if (file_exists('login_note.txt'))
$login_note = file_get_contents('login_note.txt');
return view('auth.login')
->with('login_note',$login_note);
}
return view('auth.login')
->with('login_note',$login_note);
}
}

View File

@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\User;
class RegisterController extends Controller
@ -29,7 +30,7 @@ class RegisterController extends Controller
*
* @var string
*/
protected $redirectTo = '/';
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.

View File

@ -2,9 +2,9 @@
namespace App\Http\Controllers\Auth;
use Illuminate\Foundation\Auth\ResetsPasswords;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\ResetsPasswords;
class ResetPasswordController extends Controller
{
@ -26,5 +26,15 @@ class ResetPasswordController extends Controller
*
* @var string
*/
protected $redirectTo = '/';
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
}

View File

@ -2,9 +2,9 @@
namespace App\Http\Controllers\Auth;
use Illuminate\Foundation\Auth\VerifiesEmails;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\VerifiesEmails;
class VerificationController extends Controller
{
@ -26,7 +26,7 @@ class VerificationController extends Controller
*
* @var string
*/
protected $redirectTo = '/';
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.

View File

@ -2,10 +2,12 @@
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
abstract class Controller extends \Illuminate\Routing\Controller
class Controller extends BaseController
{
use AuthorizesRequests,ValidatesRequests;
}
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

View File

@ -18,7 +18,7 @@ class EchoareaController extends Controller
$request->validate([
'domain_id' => 'required|exists:domains,id',
'name' => 'required|min:4|max:35|regex:/^[a-zA-Z0-9\-_~.]{4,}$/|unique:echoareas,name,'.($o->exists ? $o->id : 0),
'name' => 'required|min:4|max:35|regex:/^[a-zA-Z0-9\-_~]{4,}$/|unique:echoareas,name,'.($o->exists ? $o->id : 0),
'description' => 'required',
'active' => 'required|boolean',
'show' => 'required|boolean',

View File

@ -10,7 +10,7 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use App\Classes\File;
use App\Classes\FTN\{Message,Packet};
use App\Classes\FTN\Packet;
use App\Http\Requests\SetupRequest;
use App\Models\File as FileModel;
use App\Models\{Address,Echomail,Netmail,Setup,System};
@ -169,7 +169,7 @@ class HomeController extends Controller
->orWhere('replyid','like','%'.$request->query('term').'%')
->get() as $o)
{
$result->push(['id'=>$o->id,'name'=>sprintf('%s (%s)',Message::tr($o->from),$o->fftn->ftn3d),'value'=>url('echomail/view',[$o->id]),'category'=>'Echomail']);
$result->push(['id'=>$o->id,'name'=>sprintf('%s (%s)',$o->from,$o->fftn->ftn3d),'value'=>url('echomail/view',[$o->id]),'category'=>'Echomail']);
}
// Look for Netmail

View File

@ -36,7 +36,7 @@ class SystemController extends Controller
// Sometimes items
foreach (['sysop','hold','notes','zt_id','heartbeat'] as $key)
if ($request->has($key))
if ($request->validated($key))
$o->{$key} = $request->validated($key);
switch ($request->validated('pollmode')) {
@ -66,17 +66,7 @@ class SystemController extends Controller
->with('saved',TRUE);
}
$o->loadMissing([
'zcs',
// For 'ftn' to work
'addresses.zone:id,zone_id,domain_id,active',
'addresses.zone.domain:id,name,active',
// For 'role'
'addresses.system:id,address',
// For system addedit
'sessions.domain:id,name,active',
'sessions.systems:id',
]);
$o->load(['addresses.zone.domain','addresses.nodes_hub','addresses.system','sessions.domain','sessions.systems']);
return view('system.addedit')
->with('action',$o->exists ? 'update_nn' : 'create')
@ -498,16 +488,10 @@ class SystemController extends Controller
public function api_autohold_toggle(Request $request,string $state): array
{
$o = System::findOrFail($request->id);
$o->autohold = $state === 'off' ? FALSE : TRUE;
$o->save();
if ($request->user()->can('update_nn',$o)) {
$o->autohold = !($state === 'off');
$o->save();
Log::debug(sprintf('%s:- Autohold set to [%s]',self::LOGKEY,$o->autohold ? 'ON' : 'OFF'));
} else {
abort(403);
}
Log::debug(sprintf('%s:- Autohold set to [%s]',self::LOGKEY,$o->autohold ? 'ON' : 'OFF'));
return ['autohold'=>$o->autohold];
}
@ -621,12 +605,31 @@ class SystemController extends Controller
*/
public function register(SystemRegisterRequest $request)
{
// Step 1, show the user a form to select an existing defined system
if ($request->isMethod('GET'))
return view('user.system.register');
if ($request->action === 'register' && $request->name && is_numeric($request->name))
return view('user.system.widget.register_confirm')
->with('o',System::findOrFail($request->name));
$o = System::findOrNew(is_numeric($request->system_id) ? $request->system_id : NULL);
// If the system exists, and we are 'register', we'll start the address claim process
if ($o->exists && $request->action === 'Link') {
$validate = Setup::findOrFail(config('app.id'))->system->inMyZones($o->addresses);
// If we have addresses, we'll trigger the routed netmail
if ($validate->count()) {
Notification::route('netmail',$x=$validate->first())->notify(new AddressLink(Auth::user()));
AddressPoll::dispatch($x)->delay(15);
}
return view('user.system.widget.register_send')
->with('validate',$validate)
->with('o',$o);
}
// If the system doesnt exist, we'll create it
if (! $o->exist) {
$o->sysop = Auth::user()->name;

83
app/Http/Kernel.php Normal file
View File

@ -0,0 +1,83 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\AddUserToView::class,
],
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
'activeuser' => \App\Http\Middleware\ActiveUser::class,
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
/**
* The priority-sorted list of middleware.
*
* This forces the listed middleware to always be in the given order.
*
* @var array
*/
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\Authenticate::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
}

View File

@ -5,7 +5,6 @@ namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\Config;
use App\Models\Setup;
@ -46,10 +45,8 @@ class AddUserToView
*/
public function handle($request, Closure $next)
{
Config::set('setup',$x=Setup::find(config('app.id'))->load('system'));
$this->factory->share('user',$this->user);
$this->factory->share('setup',$x);
$this->factory->share('setup',Setup::find(config('app.id')));
return $next($request);
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware;
class CheckForMaintenanceMode extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Providers\RouteServiceProvider;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle(Request $request,\Closure $next,?string $guard=NULL)
{
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
return $next($request);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array
*/
protected $except = [
'password',
'password_confirmation',
];
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* Indicates whether the XSRF-TOKEN cookie should be set on the response.
*
* @var bool
*/
protected $addHttpCookie = true;
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
//
];
}

View File

@ -36,7 +36,7 @@ class AddressMerge extends FormRequest
$src = Address::withTrashed()->findOrFail($request->src);
if ((! $dst->active) && ($dst->system_id !== $src->system_id) && ($src->system->name !== System::default))
$fail('Destination must be active, or be from the same system');
$fail('Destination must be active, or be from the system system');
},
function ($attribute,$value,$fail) use ($request) {
$dst = Address::withTrashed()->findOrFail($value);

View File

@ -23,21 +23,14 @@ class SystemRegisterRequest extends FormRequest
// @todo Also disallow claiming this hosts system
return Gate::allows(
$this->route('o')->users->count()
? 'update_nn'
: 'register',
$this->route('o')
);
return Gate::allows($this->route('o')->users->count() ? 'update_nn' : 'register',$this->route('o'));
}
public function messages(): array
{
return [
'heartbeat' => 'Sorry, only an admin can set this below 12',
'hold' => 'Must be Yes or No',
'pollmode' => 'Must be Hold, Normal or Crash',
'pkt_msgs' => 'Sorry, only an admin can increase this above 100',
];
}
@ -46,7 +39,7 @@ class SystemRegisterRequest extends FormRequest
*
* If the system exists (POST & action="register" & system_id=<value>), then no validation required
* If the system doesnt exist (POST & action="register" & system_id undefined) then we need just a name to start the process (action="create")
* Then, full validation
* Then, full validation
* @return array
*/
public function rules(Request $request)
@ -76,26 +69,8 @@ class SystemRegisterRequest extends FormRequest
'active' => 'required|boolean',
'hold' => 'sometimes|boolean',
'pollmode' => 'required|integer|min:0|max:2',
'heartbeat' => [
'nullable',
'integer',
function ($attribute,$value,$fail) {
if (($value < 12) && (! Gate::allows('admin')))
$fail(true);
},
'min:0',
'max:48'
],
'pkt_msgs' => [
'nullable',
'integer',
function ($attribute,$value,$fail) {
if (($value > 100) && (! Gate::allows('admin')))
$fail(true);
},
'min:5',
'max:65535',
],
'heartbeat' => 'nullable|integer|min:0|max:48',
'pkt_msgs' => 'nullable|integer|min:5',
] : []));
}
}

View File

@ -1,67 +0,0 @@
<?php
namespace App\Jobs;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use App\Classes\FTN\Message;
use App\Models\{Address,Domain,System};
use App\Notifications\Echomails\AbsentNodes;
use App\Notifications\Emails\NodeMarkedDown as NodeMarkedDownEmail;
use App\Notifications\Netmails\NodeMarkedDown as NodeMarkedDownNetmail;
use App\Notifications\Emails\NodeMarkedHold as NodeMarkedHoldEmail;
use App\Notifications\Netmails\NodeMarkedHold as NodeMarkedHoldNetmail;
use App\Notifications\Emails\NodeDelisted as NodeDelistedEmail;
use App\Notifications\Netmails\NodeDelisted as NodeDelistedNetmail;
class AddressClearQueue implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private const LOGKEY = 'JAC';
private Address $ao; // System address
/**
* Create a new job instance.
*/
public function __construct(Address $ao)
{
$this->ao = $ao->withoutRelations();
}
/**
* Execute the job.
*/
public function handle(): void
{
// Remove echomail not collected from echomail_seenby
DB::table('echomail_seenby')
->where('address_id',$this->ao->id)
->whereNotNull('export_at')
->whereNull('sent_at')
->delete();
// Remove FLAG_INTRANSIT from netmail that hasnt been delivered
DB::table('netmails')
->where('tftn_id',$this->ao->id)
->whereRaw(sprintf('(flags & %d) > 0',Message::FLAG_INTRANSIT))
->update(['flags'=>DB::raw(sprintf('(flags & ~%d)',Message::FLAG_INTRANSIT))]);
// Remove files not collected
DB::table('file_seenby')
->where('address_id',$this->ao->id)
->whereNotNull('export_at')
->whereNull('sent_at')
->delete();
}
}

View File

@ -37,8 +37,8 @@ class AddressIdle implements ShouldQueue
*/
public function __construct(Domain $do,Address $ao=NULL)
{
$this->do = $do->withoutRelations();
$this->ao = $ao?->withoutRelations();
$this->do = $do;
$this->ao = $ao;
}
/**
@ -49,167 +49,168 @@ class AddressIdle implements ShouldQueue
$result = collect();
// Delist DOWN nodes
if (config('fido.idle.delist')) {
$age = Carbon::now()->subDays(config('fido.idle.delist'));
foreach ($this->old($this->do,config('fido.idle.delist'),Address::NODE_DOWN,$this->ao) as $ao) {
// Only delist system that has been marked down
// Only mark delist them if its been 7 days since they were marked DOWN
if ((! $ao->is_down) || ($ao->updated_at->isPast(Carbon::now()->subWeek())))
continue;
foreach ($this->old($this->do,config('fido.idle.delist'),Address::NODE_DOWN,$this->ao) as $ao) {
Log::debug(sprintf('%s:- Evaluating DOWN node [%s], not seen for at least [%d] days, last update [%d] days',self::LOGKEY,$ao->ftn,$ao->system->last_seen?->diffInDays(),$ao->updated_at->diffInDays()));
Log::info(sprintf('%s:- Delisting [%s], not seen for [%d] days',self::LOGKEY,$ao->ftn,config('fido.idle.delist')));
$contact = FALSE;
// Only delist system that has been marked down
// Only delist them if its been 14 days since they were marked DOWN
if ((! $ao->is_down) || ($ao->updated_at->greaterThan(Carbon::now()->subWeeks(2))))
continue;
// Remove echomail not collected from echomail_seenby
DB::table('echomail_seenby')
->where('address_id',$ao->id)
->whereNotNull('export_at')
->whereNull('sent_at')
->delete();
// Validate that the last seen was infact that long ago
if ($ao->system->last_seen && $ao->system->last_seen->greaterThan($age))
continue;
// Remove FLAG_INTRANSIT from netmail that hasnt been delivered
DB::table('netmails')
->where('tftn_id',$ao->id)
->whereRaw(sprintf('(flags & %d) > 0',Message::FLAG_INTRANSIT))
->update(['flags'=>DB::raw(sprintf('(flags & ~%d)',Message::FLAG_INTRANSIT))]);
Log::info(sprintf('%s:- Delisting [%s], not seen for at least [%d] days',self::LOGKEY,$ao->ftn,$ao->system->last_seen?->diffInDays()));
$contact = FALSE;
// Remove files not collected
DB::table('file_seenby')
->where('address_id',$ao->id)
->whereNotNull('export_at')
->whereNull('sent_at')
->delete();
// Remove subscribed echoareas
$ao->echoareas()->detach();
// Remove subscribed echoareas
$ao->echoareas()->detach();
// Remove subscribed fileareas
$ao->fileareas()->detach();
// Remove subscribed fileareas
$ao->fileareas()->detach();
$ao->active = FALSE;
$ao->validated = FALSE;
$ao->save();
$ao->active = FALSE;
$ao->validated = FALSE;
$ao->save();
// Clear the queue
AddressClearQueue::dispatchSync($ao);
// Email Alert
if ($ao->system->users->count()) {
Notification::send($ao->system->users,new NodeDelistedEmail($ao->withoutRelations()));
$contact = TRUE;
}
// Netmail Alert (to othernet network address)
if ($ao->system->aka_unknown()->count()) {
Notification::route('netmail',$ao->system->aka_unknown()->first()->withoutRelations())->notify(new NodeDelistedNetmail($ao->withoutRelations()));
$contact = TRUE;
}
$ao->contacted = $contact;
$result->push($ao);
// Email Alert
if ($ao->system->users->count()) {
Notification::send($ao->system->users,new NodeDelistedEmail($ao->withoutRelations()));
$contact = TRUE;
}
// Netmail Alert (to othernet network address)
if ($ao->system->uncommon()->count()) {
Notification::route('netmail',$ao->system->uncommon()->first()->withoutRelations())->notify(new NodeDelistedNetmail($ao->withoutRelations()));
$contact = TRUE;
}
$ao->contacted = (! $contact);
$result->push($ao);
}
// Mark nodes DOWN
if (config('fido.idle.down')) {
$age = Carbon::now()->subDays(config('fido.idle.down'));
foreach ($this->old($this->do,config('fido.idle.down'),Address::NODE_HOLD,$this->ao) as $ao) {
Log::info(sprintf('%s:- Marking [%s] as DOWN, not seen for [%d] days',self::LOGKEY,$ao->ftn,config('fido.idle.down')));
$contact = FALSE;
foreach ($this->old($this->do,config('fido.idle.down'),Address::NODE_HOLD,$this->ao) as $ao) {
Log::debug(sprintf('%s:- Evaluating HOLD node [%s], not seen for at least [%d] days, last update [%d] days',self::LOGKEY,$ao->ftn,$ao->system->last_seen?->diffInDays(),$ao->updated_at->diffInDays()));
// Only mark down system that has been marked down
// Only mark down them if its been 14 days since they were marked HOLD
if ((! $ao->is_hold) || ($ao->updated_at->greaterThan(Carbon::now()->subWeeks(2))))
continue;
// Validate that the last seen was infact that long ago
if ($ao->system->last_seen && $ao->system->last_seen->greaterThan($age))
continue;
Log::info(sprintf('%s:- Marking [%s] as DOWN, not seen for at least [%d] days',self::LOGKEY,$ao->ftn,$ao->system->last_seen?->diffInDays()));
$contact = FALSE;
// Email Alert
if ($ao->system->users->count()) {
Notification::send($ao->system->users,new NodeMarkedDownEmail($ao->withoutRelations()));
$contact = TRUE;
}
// Netmail Alert (to othernet network address)
if ($ao->system->aka_unknown()->count()) {
Notification::route('netmail',$ao->system->aka_unknown()->first()->withoutRelations())->notify(new NodeMarkedDownNetmail($ao->withoutRelations()));
$contact = TRUE;
}
// Mark as DOWN
$ao->role &= ~Address::NODE_HOLD;
$ao->role |= Address::NODE_DOWN;
$ao->save();
$ao->contacted = $contact;
$result->push($ao);
// Email Alert
if ($ao->system->users->count()) {
Notification::send($ao->system->users,new NodeMarkedDownEmail($ao->withoutRelations()));
$contact = TRUE;
}
// Netmail Alert (to othernet network address)
if ($ao->system->uncommon()->count()) {
Notification::route('netmail',$ao->system->uncommon()->first()->withoutRelations())->notify(new NodeMarkedDownNetmail($ao->withoutRelations()));
$contact = TRUE;
}
// Mark as DOWN
$ao->role &= ~Address::NODE_HOLD;
$ao->role |= Address::NODE_DOWN;
$ao->save();
$ao->contacted = (! $contact);
$result->push($ao);
}
// @todo Make sure we only process addresses that we are responsible for, eg: 1/999 shouldnt have been processed even though 3/999 as eligible (and they are were connected to the same system)
// Mark nodes as HOLD
if (config('fido.idle.hold')) {
$age = Carbon::now()->subDays(config('fido.idle.hold'));
foreach ($this->old($this->do,config('fido.idle.hold'),Address::NODE_ALL,$this->ao) as $ao) {
Log::debug(sprintf('%s:- Evaluating IDLE node [%s], not seen for at least [%d] days',self::LOGKEY,$ao->ftn,$ao->system->last_seen?->diffInDays()));
// Ignore any systems that are a Discoverd System
if ($ao->system->name === System::default) {
Log::alert(sprintf('%s:! Ignoring HOLD for discovered System [%s]',self::LOGKEY,$ao->ftn));
continue;
}
// Ignore any systems already marked hold or down
if ($ao->role & (Address::NODE_DOWN|Address::NODE_HOLD))
continue;
// Validate that the last seen was infact that long ago
if ($ao->system->last_seen && $ao->system->last_seen->greaterThan($age))
continue;
$contact = FALSE;
Log::info(sprintf('%s:- Marking [%s] as HOLD, not seen for at least [%d] days',self::LOGKEY,$ao->ftn,$ao->system->last_seen?->diffInDays()));
// Email Alert
if ($ao->system->users->count()) {
Notification::send($ao->system->users,new NodeMarkedHoldEmail($ao->withoutRelations()));
$contact = TRUE;
}
// Netmail Alert (to othernet network address)
if ($ao->system->aka_unknown()->count()) {
Notification::route('netmail',$ao->system->aka_unknown()->first()->withoutRelations())->notify(new NodeMarkedHoldNetmail($ao->withoutRelations()));
$contact = TRUE;
}
// Mark as DOWN
$ao->role |= Address::NODE_HOLD;
$ao->save();
$ao->contacted = $contact;
$result->push($ao);
foreach ($this->old($this->do,config('fido.idle.hold'),0,$this->ao) as $ao) {
// Ignore any systems that are a Discoverd System
if ($ao->system->name === System::default) {
Log::alert(sprintf('%s:! Ignoring HOLD for discovered System [%s]',self::LOGKEY,$ao->ftn));
continue;
}
// Ignore any systems already marked hold or down
if ($ao->role & (Address::NODE_DOWN|Address::NODE_HOLD))
continue;
$contact = FALSE;
Log::info(sprintf('%s:- Marking [%s] as HOLD, not seen for [%d] days',self::LOGKEY,$ao->ftn,config('fido.idle.hold')));
// Email Alert
if ($ao->system->users->count()) {
Notification::send($ao->system->users,new NodeMarkedHoldEmail($ao->withoutRelations()));
$contact = TRUE;
}
// Netmail Alert (to othernet network address)
if ($ao->system->uncommon()->count()) {
Notification::route('netmail',$ao->system->uncommon()->first()->withoutRelations())->notify(new NodeMarkedHoldNetmail($ao->withoutRelations()));
$contact = TRUE;
}
// Mark as DOWN
$ao->role |= Address::NODE_HOLD;
$ao->save();
$ao->contacted = (! $contact);
$result->push($ao);
}
if ($result->count())
Notification::route('echomail',$this->do->nodestatus_echoarea)->notify(new AbsentNodes($result));
Notification::route('echomail',$this->do->nodestatusarea)->notify(new AbsentNodes($result));
}
private function old(Domain $do,int $days,int $flags=0,Address $ao=NULL): Collection
{
// Ignore dates that are zero
if (! $days)
return collect();
$age = Carbon::now()->subDays($days)->endOfDay();
$ours = our_address($do)->pluck('ftn');
return Address::FTN()
->ActiveFTN()
->addSelect(['addresses.updated_at'])
->where(fn($query)=>$query->where('point_id',0)->orWhereNull('point_id'))
->whereIn('addresses.id',our_nodes($do)->pluck('id'))
return Address::select([
'a.id',
'a.system_id',
'a.zone_id',
'addresses.region_id',
'a.host_id',
'a.node_id',
'a.point_id',
'addresses.active',
'addresses.hub_id',
'addresses.role',
'addresses.updated_at',
DB::raw('sum(a.uncollected_echomail) as uncollected_echomail'),
DB::raw('sum(a.uncollected_netmail) as uncollected_netmail'),
DB::raw('sum(a.uncollected_files) as uncollected_files')
])
->from(
Address::UncollectedEchomailTotal()
->union(Address::UncollectedNetmailTotal())
->union(Address::UncollectedFilesTotal()),'a')
->where('systems.active',TRUE)
->where('addresses.active',TRUE)
->where('zones.active',TRUE)
->where('domains.active',TRUE)
->whereNotIn('a.id',our_address()->pluck('id'))
->when($ao,fn($query)=>$query->where('addresses.id',$ao->id))
->where(fn($q)=>$q->where('last_session','<',$age)->orWhereNull('last_session'))
->whereRaw(sprintf('((role IS NULL) OR (role=0) OR ((role & %d) > 0))',$flags))
->whereRaw(sprintf('((role IS NULL) OR ((role & %d) = 0))',Address::NODE_KEEP))
->join('systems',['systems.id'=>'addresses.system_id'])
//->with(['system','zone.domain'])
->get()
->filter(fn($item)=>$ours->contains($item->parent()?->ftn));
->where('last_session','<',$age)
->where('domains.id',$do->id)
->whereRaw(sprintf('((role IS NULL) OR ((role & %d) > 0))',$flags))
->join('addresses',['addresses.id'=>'a.id'])
->join('systems',['systems.id'=>'a.system_id'])
->join('zones',['zones.id'=>'addresses.zone_id'])
->join('domains',['domains.id'=>'zones.domain_id'])
->ftnOrder()
->groupBy('a.system_id','a.id','a.zone_id','addresses.region_id','a.host_id','a.node_id','a.point_id','addresses.hub_id','addresses.role','addresses.active','addresses.updated_at')
->with(['system','zone.domain'])
->dontCache()
->get();
}
}

View File

@ -14,6 +14,13 @@ class AddressIdleDomain implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*/
public function __construct()
{
}
/**
* Execute the job.
*/

View File

@ -27,7 +27,7 @@ class AddressPoll implements ShouldQueue, ShouldBeUnique
private const LOGKEY = 'JAP';
public int $tries = 10;
public int $tries = 5;
public int $maxExceptions = 1;
public bool $failOnTimeout = TRUE;
@ -38,8 +38,8 @@ class AddressPoll implements ShouldQueue, ShouldBeUnique
public function __construct(Address $ao,Mailer $mo=NULL)
{
$this->ao = $ao->withoutRelations();
$this->mo = $mo?->withoutRelations();
$this->ao = $ao;
$this->mo = $mo;
$this->onQueue(self::QUEUE);
}
@ -93,13 +93,13 @@ class AddressPoll implements ShouldQueue, ShouldBeUnique
switch ($o->name) {
case 'BINKP':
$s = new Binkp;
$s = new Binkp(Setup::findOrFail(config('app.id')));
$mo = Mailer::where('name','BINKP')->singleOrFail();
break;
case 'EMSI':
$s = new EMSI;
$s = new EMSI(Setup::findOrFail(config('app.id')));
$mo = Mailer::where('name','EMSI')->singleOrFail();
break;

View File

@ -1,169 +0,0 @@
<?php
namespace App\Jobs;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\Skip;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use App\Models\{Address,Echoarea,Echomail};
use App\Notifications\Netmails\Areafix\Scan;
class AreafixRescan implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private const LOGKEY = 'JAR';
private Address $ao; // System address
private Echoarea $eao; // Domain we are processing
private int $days;
private bool $rescan;
/**
* Create a new job instance.
*/
public function __construct(Address $ao,Echoarea $eao,int $days=30,bool $rescan=FALSE)
{
$this->ao = $ao->withoutRelations();
$this->eao = $eao->withoutRelations();
$this->days = $days;
$this->rescan = $rescan;
}
public function __get(string $key): mixed
{
switch ($key) {
case 'jobname':
return sprintf('%s %s (%d)',$this->ao->ftn,$this->eao->name,$this->days);
default:
$this->fail('Unkown key:'.$key);
return NULL;
}
}
public function middleware(): array
{
return [
Skip::when(function(): bool {
if ($this->eao->domain_id !== $this->ao->zone->domain_id) {
Log::error(sprintf('%s:! Echo area [%s] is not in domain [%s] for FTN [%s]',self::LOGKEY,$this->eao->name,$this->ao->zone->domain->name,$this->ao->ftn));
return TRUE;
// Check that the user is subscribed
} elseif (! $this->ao->echoareas->contains($this->eao->id)) {
Log::error(sprintf('%s:! FTN [%s] is not subscribed to [%s]',self::LOGKEY,$this->ao->ftn,$this->eao->name));
return TRUE;
// Check that an FTN can read the area
} elseif (! $this->eao->can_read($this->ao->security)) {
Log::error(sprintf('%s:! FTN [%s] doesnt have permission to receive [%s]',self::LOGKEY,$this->ao->ftn,$this->eao->name));
return TRUE;
}
return FALSE;
})
];
}
public function handle(): void
{
$c = 0;
$s = 0;
$earliest = NULL;
$latest = NULL;
foreach (Echomail::select(['id','datetime'])
->where('echoarea_id',$this->eao->id)
->where('datetime','>=',
Carbon::now()
->subDays($this->days)
->startOfDay()
)
->orderBy('datetime')
->cursor() as $eo) {
// Echomail hasnt been exported before
if (! $eo->seenby->count()) {
$eo->seenby()->attach($this->ao->id,['export_at'=>Carbon::now()]);
$c++;
if (($eo->datetime < $earliest) || (! $earliest))
$earliest = $eo->datetime;
if (($latest < $eo->datetime) || (! $latest))
$latest = $eo->datetime;
Log::debug(sprintf('Exported [%d] MSG (%s) dated (%s) to [%s]',$eo->id,$eo->msgid ?: '*NO MSGID*',$eo->datetime->format('Y-m-d H:i:s'),$this->ao->ftn3d));
} else {
$export = $eo->seenby->where('id',$this->ao->id)->pop();
if ($export) {
// Echomail is pending export
if ($export->pivot->export_at && is_null($export->pivot->sent_at) && is_null($export->pivot->sent_pkt)) {
$s++;
Log::debug(sprintf('Not exporting [%d] MSG (%s) dated (%s) already queued for [%s]',$eo->id,$eo->msgid ?: '*NO MSGID*',$eo->datetime->format('Y-m-d H:i:s'),$this->ao->ftn3d));
// Echomail has been exported
} elseif ($this->rescan) {
$eo->seenby()->updateExistingPivot($this->ao,['export_at'=>Carbon::now(),'sent_at'=>NULL]);
$c++;
if (($eo->datetime < $earliest) || (! $earliest))
$earliest = $eo->datetime;
if (($latest < $eo->datetime) || (! $latest))
$latest = $eo->datetime;
Log::debug(sprintf('Re-exported [%d] MSG (%s) dated (%s) to [%s]',$eo->id,$eo->msgid ?: '*NO MSGID*',$eo->datetime,$this->ao->ftn3d));
} else {
$s++;
Log::debug(sprintf('Not resending previously sent message [%d], MSGID (%s) - sent in Pkt [%s] on [%s]',
$eo->id,
$eo->msgid ?: '* NO MSGID*',
$export->pivot->sent_pkt ?: '-',
$export->pivot->sent_at ?: '-',
));
}
// Echomail has not been exported
} else {
$eo->seenby()->attach($this->ao,['export_at'=>Carbon::now(),'sent_at'=>NULL,'sent_pkt'=>NULL]);
$c++;
if (($eo->datetime < $earliest) || (! $earliest))
$earliest = $eo->datetime;
if (($latest < $eo->datetime) || (! $latest))
$latest = $eo->datetime;
Log::debug(sprintf('Exported [%d] to [%s]',$eo->id,$this->ao->ftn3d));
}
}
}
Notification::route('netmail',$this->ao)
->notify(new Scan(collect([
'area'=>$this->eao->name,
'queued'=>$c,
'skipped'=>$s,
'earliest'=>$earliest,
'latest'=>$latest,
])));
Log::info(sprintf('%s:= Queued [%d], Skipped [%d] echomails for [%s] in [%s]',self::LOGKEY,$c,$s,$this->ao->ftn,$this->eao->name));
}
}

View File

@ -36,7 +36,7 @@ class EchoareaImport implements ShouldQueue
public function __construct(string $file,Domain $do,string $prefix='',bool $delete_recs=FALSE,bool $delete_file=FALSE)
{
$this->file = $file;
$this->do = $do->withoutRelations();
$this->do = $do;
$this->prefix = $prefix ?: '';
$this->delete_file = $delete_file;
$this->delete_recs = $delete_recs;

View File

@ -36,7 +36,7 @@ class FileareaImport implements ShouldQueue
public function __construct(string $file,Domain $do,string $prefix='',bool $delete_recs=FALSE,bool $delete_file=FALSE)
{
$this->file = $file;
$this->do = $do->withoutRelations();
$this->do = $do;
$this->prefix = $prefix ?: '';
$this->delete_file = $delete_file;
$this->delete_recs = $delete_recs;

View File

@ -48,6 +48,21 @@ class MailSend #implements ShouldQueue
->join('systems',['systems.id'=>'a.system_id'])
->join('zones',['zones.id'=>'a.zone_id'])
->join('domains',['domains.id'=>'zones.domain_id'])
->where(function($query) {
return $query->whereNull('autohold')
->orWhere('autohold',FALSE);
})
->when(! is_null($this->crash),function($query) {
return $query->when(
$this->crash,
function($query) {
return $query->where('pollmode',$this->crash);
},
function($query) {
return $query->whereNotNull('pollmode');
}
);
})
->groupBy('a.system_id','a.id','a.zone_id','addresses.region_id','a.host_id','a.node_id','a.point_id','addresses.hub_id','addresses.role')
->with(['system','zone.domain'])
->dontCache()
@ -65,13 +80,7 @@ class MailSend #implements ShouldQueue
} else {
return $item;
}
})
->filter(function($item) {
if ($item->system->autohold)
return NULL;
return is_null($this->crash) || ($item->system->pollmode) || ($item->system->pollmode === $this->crash) ? $item : NULL;
});
});
foreach ($u->groupBy('ftn') as $oo) {
if (Job::where('queue','poll')->get()->pluck('command.address.id')->search(($x=$oo->first())->id) === FALSE) {

View File

@ -34,7 +34,7 @@ class MessageProcess implements ShouldQueue
public function __construct(Echomail|Netmail $mo,bool $skipbot=FALSE)
{
// @todo We need to serialize this model here, because laravel has an error unserializing it (Model Not Found)
$this->mo = utf8_encode(serialize($mo->withoutRelations()));
$this->mo = utf8_encode(serialize($mo));
$this->skipbot = $skipbot;
}
@ -67,7 +67,7 @@ class MessageProcess implements ShouldQueue
// @todo generate exception when netmail to system that doesnt exist (node/point) and its this host's responsibility
Log::info(sprintf('%s:- Processing Netmail [%s] to (%s) [%s] from (%s) [%s].',
self::LOGKEY,
$this->mo->msgid ?: '*NO MSGID*',
$this->mo->msgid,
$this->mo->to,$this->mo->tftn->ftn,
$this->mo->from,$this->mo->fftn->ftn,
));
@ -81,7 +81,7 @@ class MessageProcess implements ShouldQueue
Log::debug(sprintf('%s:- Checking for duplicate from host [%s].',self::LOGKEY,$this->mo->fftn->ftn));
$o = Netmail::where('msgid',$this->mo->msgid)
->where('fftn_id',$this->mo->fftn_id)
->where('fftn_id',$this->mo->fftn->id)
->where('datetime','>',Carbon::now()->subYears(3))
->single();
@ -137,19 +137,12 @@ class MessageProcess implements ShouldQueue
->single();
if ($uo && ($ao=$uo->system->match($this->mo->tftn->zone)?->pop())) {
Log::info(sprintf('%s:- Forwarding Netmail [%s] to (%s) [%s] from (%s) [%s].',
self::LOGKEY,
$this->mo->msgid ?: '*NO MSGID*',
$this->mo->to,$ao->ftn,
$this->mo->from,$this->mo->fftn->ftn,
));
$note = "+--[ FORWARDED MESSAGE ]----------------------------------+\r";
$note .= "+ This message has been forwarded to you, it was originally sent to you\r";
$note .= sprintf("+ at [%s]\r",$this->mo->tftn->ftn);
$note .= "+---------------------------------------------------------+\r\r";
$this->mo->msg_src = $note.$this->mo->content;
$this->mo->msg = $note.$this->mo->content;
$this->mo->tftn_id = $ao->id;
$this->mo->flags |= Message::FLAG_INTRANSIT;
$this->mo->save();
@ -206,7 +199,7 @@ class MessageProcess implements ShouldQueue
// The packet sender
$sender = $this->mo->set->get('set_sender');
// @todo Check that this does evaluate to true if a message has been rescanned
// @todo Check that this does evaulate to true if a message has been rescanned
$rescanned = $this->mo->kludges->get('RESCANNED',FALSE);
// Echoarea doesnt exist, cant import the message
@ -233,24 +226,17 @@ class MessageProcess implements ShouldQueue
// Check for duplicate messages
// FTS-0009.001
if ($this->mo->msgid) {
$o = ($x=Echomail::where('msgid',$this->mo->msgid)
->where('fftn_id',$this->mo->fftn_id)
->where('datetime','>=',$this->mo->datetime->clone()->subYears(3))
->where('datetime','<=',$this->mo->datetime)
->dontCache())
$o = Echomail::where('msgid',$this->mo->msgid)
->where('fftn_id',$this->mo->fftn->id)
->where('datetime','>=',$this->mo->date->subYears(3))
->where('datetime','<=',$this->mo->date)
->single();
Log::debug(sprintf('%s:- Checking for duplicate from host id [%d], with msgid [%s] between [%s] and [%s].',
self::LOGKEY,
$this->mo->fftn_id,
$this->mo->msgid,
$this->mo->datetime->clone()->subYears(3),
$this->mo->datetime,
));
Log::debug(sprintf('%s:- Checking for duplicate from host id [%d].',self::LOGKEY,$this->mo->fftn->id));
if ($x->count()) {
if ($o) {
// @todo Actually update seenby
Log::alert(sprintf('%s:! Duplicate echomail (%s) in [%s] from (%s) [%s] to (%s) - ignoring.',
Log::alert(sprintf('%s:! Duplicate echomail [%s] in [%s] from (%s) [%s] to (%s) - updating seenby.',
self::LOGKEY,
$this->mo->msgid,
$this->mo->echoarea->name,
@ -258,9 +244,37 @@ class MessageProcess implements ShouldQueue
$this->mo->to,
));
//$o->save();
// @todo This duplicate message may have gone via a different path, be nice to record it.
/*
// If we didnt get the path on the original message, we'll override it
if (! $o->path->count()) {
$dummy = collect();
$path = $this->parseAddresses('path',$this->mo->path,$sender->zone,$dummy);
$ppoid = NULL;
foreach ($path as $aoid) {
$po = DB::select('INSERT INTO echomail_path (echomail_id,address_id,parent_id) VALUES (?,?,?) RETURNING id',[
$o->id,
$aoid,
$ppoid,
]);
$ppoid = $po[0]->id;
}
}
*/
// @todo if we have an export for any of the seenby addresses, remove it
//$seenby = $this->parseAddresses('seenby',$this->mo->seenby,$sender->zone,$o->rogue_seenby);
//$this->mo->seenby()->syncWithoutDetaching($seenby);
// In case our rogue_seenby changed
//$this->mo->save();
return;
}
}
@ -268,9 +282,8 @@ class MessageProcess implements ShouldQueue
// Find another message with the same msg_crc
if ($this->mo->msg_crc) {
$o = Echomail::where('msg_crc',$xx=md5($this->mo->msg_crc))
->where('fftn_id',$this->mo->fftn_id)
->where('fftn_id',$this->mo->fftn->id)
->where('datetime','>',Carbon::now()->subWeek())
->dontCache()
->get();
if ($o->count())

View File

@ -1,65 +0,0 @@
<?php
namespace App\Jobs;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use App\Models\{Address, Domain, System};
use App\Notifications\Netmails\NodesNew as NotificationNodesNew;
class NodesNew implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private const LOGKEY = 'JNN';
private ?Carbon $since; // New nodes since this date
private Address $ao; // Domain we are processing
private Domain $do; // Domain we are processing
/**
* Create a new job instance.
*/
public function __construct(Domain $do,Carbon $since=NULL,Address $ao=NULL)
{
$this->do = $do->withoutRelations();
$this->ao = $ao?->withoutRelations();
$this->since = $since;
}
/**
* Execute the job.
*/
public function handle(): void
{
$since = ($this->since ?: Carbon::parse('last saturday'))->startOfDay();
$result = Address::FTN()
->ActiveFTN()
->join('systems',['systems.id'=>'addresses.system_id'])
->join('system_zone',['system_zone.system_id'=>'systems.id','system_zone.zone_id'=>'zones.id'])
->whereIn('zones.id',$this->do->zones->pluck('id'))
->where('systems.active',TRUE)
->where('systems.created_at','>=',$since)
->get();
if ($result->count()) {
Log::notice(sprintf('%s:- Sending new nodes since [%s] (%d)',self::LOGKEY,$since,$result->count()));
Notification::route('netmail',$this->ao->withoutRelations())
->notify(new NotificationNodesNew(
$since,
$result,
));
} else
Log::notice(sprintf('%s:- No nodes since [%s]',self::LOGKEY,$since));
}
}

View File

@ -17,7 +17,7 @@ use League\Flysystem\UnableToMoveFile;
use App\Classes\File;
use App\Classes\FTN\Packet;
use App\Exceptions\InvalidPacketException;
use App\Models\{Echomail,Netmail,System};
use App\Models\{Domain,Echomail,Netmail};
use App\Notifications\Netmails\PacketPasswordInvalid;
class PacketProcess implements ShouldQueue
@ -27,15 +27,15 @@ class PacketProcess implements ShouldQueue
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private string $filename;
private System $so;
private Domain $do;
private Carbon $rcvd_time;
private bool $interactive;
private bool $nobot;
public function __construct(string $filename,System $so,bool $interactive=TRUE,Carbon $rcvd_time=NULL,bool $nobot=FALSE)
public function __construct(string $filename,Domain $do,bool $interactive=TRUE,Carbon $rcvd_time=NULL,bool $nobot=FALSE)
{
$this->filename = $filename;
$this->so = $so->withoutRelations();
$this->do = $do;
$this->interactive = $interactive;
$this->rcvd_time = $rcvd_time ?: Carbon::now();
$this->nobot = $nobot;
@ -67,103 +67,87 @@ class PacketProcess implements ShouldQueue
$f = new File($fs->path($this->filename));
$processed = FALSE;
$bad_archive = FALSE;
foreach ($f as $packet) {
try {
$pkt = Packet::process($packet,Arr::get(stream_get_meta_data($packet),'uri'),$f->itemSize(),$this->so);
$pkt = Packet::process($packet,Arr::get(stream_get_meta_data($packet),'uri'),$f->itemSize(),$this->do);
// Check that the packet is from a system that is defined in the DB
if (! $pkt->fftn) {
Log::error(sprintf('%s:! Packet [%s] is not from a system we know about? [%s]',self::LOGKEY,$this->filename,$pkt->fftn_t));
// Check that the packet is from a system that is defined in the DB
if (! $pkt->fftn) {
Log::error(sprintf('%s:! Packet [%s] is not from a system we know about? [%s]',self::LOGKEY,$this->filename,$pkt->fftn_t));
break;
}
if (! our_nodes($pkt->fftn->zone->domain)->contains($pkt->fftn)) {
Log::error(sprintf('%s:! Packet [%s] is from a system that is not configured with us? [%s]',self::LOGKEY,$this->filename,$pkt->fftn_t));
// @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;
}
// If we dont have the tftn in the DB, then packet cannot be to us
if (! $pkt->tftn) {
Log::error(sprintf('%s:! Packet [%s] is from a system [%s] we dont know about?',self::LOGKEY,$this->filename,$pkt->tftn_t));
// @todo Notification::route('netmail',$pkt->fftn)->notify(new UnexpectedPacketToUs($this->filename));
break;
}
// Check the packet is to our address, if not we'll reject it.
if (! our_address($pkt->tftn->zone->domain)->contains($pkt->tftn)) {
Log::error(sprintf('%s:! Packet [%s] is not to our address? [%s]',self::LOGKEY,$this->filename,$pkt->tftn->ftn));
// @todo Notification::route('netmail',$pkt->fftn)->notify(new UnexpectedPacketToUs($this->filename));
break;
}
// Check the packet password
if ($pkt->fftn->pass_packet !== 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;
}
Log::info(sprintf('%s:- Packet has [%d] messages',self::LOGKEY,$pkt->count()));
// Queue messages if there are too many in the packet.
if ($queue = ($pkt->count() > config('fido.queue_msgs')))
Log::info(sprintf('%s:- Messages will be sent to the queue for processing',self::LOGKEY));
$count = 0;
foreach ($pkt as $msg) {
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));
if ($msg->errors->count()) {
Log::error(sprintf('%s:! Message [%s] has [%d] errors, unable to process',self::LOGKEY,$msg->msgid,$msg->errors->count()));
continue;
}
$msg->set_sender = $pkt->fftn->withoutRelations();
// Record receiving packet and sender
$msg->set_pkt = $f->pktName();
$msg->set_recvtime = $this->rcvd_time;
if ($queue || (! $this->interactive))
Log::info(sprintf('%s:! Message [%s] will be sent to the queue to process',self::LOGKEY,$msg->msgid));
try {
// Dispatch job.
if ($queue || (! $this->interactive))
MessageProcess::dispatch($msg->withoutRelations(),$this->nobot);
else
MessageProcess::dispatchSync($msg->withoutRelations(),$this->nobot);
$count++;
} catch (\Exception $e) {
Log::error(sprintf('%s:! Got error [%s] dispatching message [%s] (%d:%s-%s).',self::LOGKEY,get_class($e),$msg->msgid,$e->getLine(),$e->getFile(),$e->getMessage()));
}
}
if ($count === $pkt->count())
$processed = TRUE;
} catch (\Exception $e) {
Log::error(sprintf('%s:! Got an exception [%s] processing packet',self::LOGKEY,$e->getMessage()));
$bad_archive = TRUE;
break;
}
if (! our_nodes($pkt->fftn->zone->domain)->contains($pkt->fftn)) {
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));
// @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($pkt->tftn->zone->domain)->contains($pkt->tftn)) {
Log::error(sprintf('%s:! Packet [%s] is not to our address? [%s]',self::LOGKEY,$this->filename,$pkt->tftn->ftn));
// @todo Notification::route('netmail',$pkt->fftn)->notify(new UnexpectedPacketToUs($this->filename));
break;
}
// Check the packet password
if ($pkt->fftn->pass_packet !== 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;
}
Log::info(sprintf('%s:- Packet has [%d] messages',self::LOGKEY,$pkt->count()));
// Queue messages if there are too many in the packet.
if ($queue = ($pkt->count() > config('fido.queue_msgs')))
Log::info(sprintf('%s:- Messages will be sent to the queue for processing',self::LOGKEY));
$count = 0;
foreach ($pkt as $msg) {
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));
if ($msg->errors->count()) {
Log::error(sprintf('%s:! Message [%s] has [%d] errors, unable to process',self::LOGKEY,$msg->msgid,$msg->errors->count()));
continue;
}
$msg->set_sender = $pkt->fftn->withoutRelations();
// Record receiving packet and sender
$msg->set_pkt = $f->pktName();
$msg->set_recvtime = $this->rcvd_time;
if ($queue || (! $this->interactive))
Log::info(sprintf('%s:! Message [%s] will be sent to the queue to process',self::LOGKEY,$msg->msgid));
try {
// Dispatch job.
if ($queue || (! $this->interactive))
MessageProcess::dispatch($msg->withoutRelations(),$this->nobot);
else
MessageProcess::dispatchSync($msg->withoutRelations(),$this->nobot);
$count++;
} catch (\Exception $e) {
Log::error(sprintf('%s:! Got error [%s] dispatching message [%s] (%d:%s-%s).',self::LOGKEY,get_class($e),$msg->msgid,$e->getLine(),$e->getFile(),$e->getMessage()));
}
}
if ($count === $pkt->count())
$processed = TRUE;
}
if ((! $processed) || $bad_archive) {
if (! $processed) {
Log::alert(sprintf('%s:- Not deleting packet [%s], it doesnt seem to be processed?',self::LOGKEY,$this->filename));
} else {

View File

@ -60,15 +60,15 @@ class SystemHeartbeat #implements ShouldQueue
// If we havent polled in heatbeat hours, poll system
foreach ($l as $oo) {
if (Job::where('queue','poll')->get()->pluck('command.address.id')->search($oo->id) === FALSE) {
if ((! $oo->system->last_seen)
|| ($oo->system->hearbeat && ($oo->system->last_seen->addHours($oo->system->heartbeat) < Carbon::now()))
|| ((! $oo->system->hearbeat) && ($oo->role_id < Address::NODE_NN) && ($oo->system->last_seen->addHours(6) < Carbon::now())))
if ((! $oo->system->last_session)
|| ($oo->system->hearbeat && ($oo->system->last_session->addHours($oo->system->heartbeat) < Carbon::now()))
|| ((! $oo->system->hearbeat) && ($oo->role_id < Address::NODE_NN) && ($oo->system->last_session->addHours(6) < Carbon::now())))
{
Log::info(sprintf('%s:- Polling [%s] (%s) - we havent seen them since [%s], heartbeat [%d]',
self::LOGKEY,
$oo->ftn,
$oo->system->name,
$oo->system->last_seen ?: 'Never',
$oo->system->last_session ?: 'Never',
$oo->system->heartbeat,
));
@ -78,7 +78,7 @@ class SystemHeartbeat #implements ShouldQueue
Log::debug(sprintf('%s:= Not scheduling poll to [%s], we saw them [%s], heartbeat [%d]',
self::LOGKEY,
$oo->ftn,
$oo->system->last_seen,
$oo->system->last_session,
$oo->system->heartbeat
));
}

View File

@ -29,7 +29,7 @@ class EchomailListener implements ShouldQueue
$ea = $event->eo->echoarea;
// Catch our messages that we've posted, so they dont go back
if (preg_match('/^@.+:/',$event->eo->from))
if (str_ends_with($event->eo->from,':'.config('matrix.server')))
return;
if ($ea && collect(config('matrix.rooms'))->contains($ea->name)) {

View File

@ -12,37 +12,37 @@ use App\Models\{Setup,User};
class TestEmail extends Mailable
{
use Queueable, SerializesModels;
use Queueable, SerializesModels;
/* User to send mail to */
public User $user;
/* Our setup object */
public Setup $setup;
/* User to send mail to */
public User $user;
/* Our setup object */
public Setup $setup;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct(User $o)
{
$this->user = $o;
}
/**
* Create a new message instance.
*
* @return void
*/
public function __construct(User $o)
{
$this->user = $o;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
$this->setup = Setup::findOrFail(config('app.id'));
/**
* Build the message.
*
* @return $this
*/
public function build()
{
$this->setup = Setup::findOrFail(config('app.id'));
return $this
->markdown('mail.system.test_email')
->subject('Just a test...')
->with([
'url'=>sprintf('https://%s',gethostname()),
return $this
->markdown('mail.system.test_email')
->subject('Just a test...')
->with([
'url'=>'https://localhost',
]);
}
}
}
}

View File

@ -24,28 +24,19 @@ use App\Traits\{QueryCacheableConfig,ScopeActive};
*
* If an address is validated, we know that the system is using the address (we've confirmed that during a session).
* validated is update/removed is during a mailer session, confirming if the node has the address
* @todo Remove validated, if that system hasnt used the address for a defined period (eg: 30)
*
* We'll only trigger a poll to a system that we have mail for if active && validated, unless "forced".
*
* Any mail for that address will be delivered, if active && validated.
*
* Address status:
* + Active (active=true/validated=true) - mail can flow and in one of our networks (we have session details)
* + Pending (active=true/validated=false) - remote hasnt configured address during a session and in one of our networks
* + Known (active=true/validated=true) - the node presented this address, but we didnt auth it, and its a network we are not in
* + Unconfirm (active=true/validated=true) - the node presented this address, but we dont manage the address (it uses a different hub)
* + Nodelist (active=true/validated=true) - the node presented this address, but we dont manage the address and its in a recent nodelist
* + Delisted (active=false/validated=true) - this node shouldnt use this address any more
* + Freed (active=false/validated=false) - this node shouldnt is known to not be using this address any more
*
* Other Status
* + citizen - The address belongs to one of our jurisdiction (hub_id = our address, NC,RC,ZC)
* + foreign - The address doesnt belong to our jurisdiction
*
* @see \App\Http\Requests\AddressAdd::class for rules about AKA and role
*/
// Need to add
// Calculated Region for an FTN, ie: find the parent and use their region id
// - if creating an RC, then the region is part of the creation, ZC region is 0
// Then use this in createFTN
class Address extends Model
{
use QueryCacheableConfig,ScopeActive,SoftDeletes;
@ -61,10 +52,9 @@ class Address extends Model
public const NODE_HC = 1<<3; // Hub
public const NODE_NN = 1<<4; // Node
public const NODE_PVT = 1<<5; // Pvt (we dont have address information) @todo
public const NODE_HOLD = 1<<6; // Hold (user has requested hold, we havent heard from the node for 7 days
public const NODE_DOWN = 1<<7; // Down we havent heard from the node for 30 days
public const NODE_HOLD = 1<<6; // Hold (user has requested hold, we havent heard from the node for 7 days @todo
public const NODE_DOWN = 1<<7; // Down we havent heard from the node for 30 days @todo
public const NODE_POINT = 1<<8; // Point
public const NODE_KEEP = 1<<9; // Dont mark an address hold/down or de-list automatically
public const NODE_UNKNOWN = 1<<15; // Unknown
public const NODE_ALL = 0x811f; // Mask to catch all nodes, excluding their status
@ -185,11 +175,10 @@ class Address extends Model
*
* @param string $address
* @param bool $trashed
* @param bool $recent
* @return Address|null
* @throws \Exception
*/
public static function findFTN(string $address,bool $trashed=FALSE,bool $recent=FALSE): ?self
public static function findFTN(string $address,bool $trashed=FALSE): ?self
{
$ftn = self::parseFTN($address);
$o = NULL;
@ -198,11 +187,8 @@ class Address extends Model
->select('addresses.*')
->join('zones',['zones.id'=>'addresses.zone_id'])
->join('domains',['domains.id'=>'zones.domain_id'])
->when($trashed,function($query) use ($recent) {
return $query->withTrashed()
->orderBy('updated_at','DESC')
->when($recent,fn($query)=>$query->where(fn($query)=>$query
->where('deleted_at','>=',Carbon::now()->subMonth())->orWhereNull('deleted_at')));
->when($trashed,function($query) {
$query->withTrashed();
},function($query) {
$query->active();
})
@ -223,7 +209,7 @@ class Address extends Model
$o = $query
->where('region_id',$ftn['n'])
->where('host_id',$ftn['n'])
->with(['system:id,active,name,address,pkt_msgs,last_session,hold'])
->with(['system:id,active'])
->first();
// Look for a normal address
@ -238,7 +224,7 @@ class Address extends Model
})
->orWhere('host_id',$ftn['n']);
})
->with(['system:id,active,name,address,pkt_msgs,last_session,hold'])
->with(['system:id,active'])
->first();
// Check and see if we are a flattened domain, our address might be available with a different zone.
@ -255,7 +241,7 @@ class Address extends Model
$o = self::findZone($do,$ftn['n'],$ftn['f'],$ftn['p'],$trashed);
}
return ($o && ($trashed || $o->system->active)) ? $o : NULL;
return ($o && $o->system->active) ? $o : NULL;
}
public static function newFTN(string $address): self
@ -263,26 +249,16 @@ class Address extends Model
$ftn = self::parseFTN($address);
$do = $ftn['d'] ? Domain::where('name',$ftn['d'])->single() : NULL;
$o = new self;
$o->region_id = $ftn['r'];
$o->host_id = $ftn['n'];
$o->node_id = $ftn['f'];
$o->point_id = $ftn['p'];
$zo = Zone::where('zone_id',$ftn['z'])
->when($do,fn($query)=>$query->where('domain_id',$do->id))
->single();
$o = new self;
$o->zone_id = $zo?->id;
if (($ftn['z'] === 0) || (! $zo)) {
Log::alert(sprintf('%s:! newFTN was parsed an FTN [%s] with a zero zone, adding empty zone in domain',self::LOGKEY,$address));
$zo = new Zone;
$zo->domain_id = $do?->id;
}
$o->zone()->associate($zo);
$o->region_id = $ftn['r'];
$o->host_id = $ftn['n'];
$o->node_id = $ftn['f'];
$o->point_id = $ftn['p'];
return $o;
}
@ -353,42 +329,37 @@ class Address extends Model
throw new InvalidFTNException(sprintf('Invalid FTN: [%s] - point address invalid [%d]',$ftn,$matches[4]));
// Work out region
$region_id = NULL;
$region_id = 0;
$zone_id = NULL;
// We can only work out region/zone if we have a domain - this is for 2D parsing
// We can only work out region if we have a domain
if ($matches[5] ?? NULL) {
$o = new self;
$o->host_id = $matches[2];
$o->node_id = $matches[3];
$o->point_id = empty($matches[4]) ? 0 : (int)$matches[4];
if ($matches[1] !== "0") {
$zo = Zone::select('zones.*')
->where('zone_id',$matches[1])
->join('domains',['domains.id'=>'zones.domain_id'])
->where('domains.name',$matches[5])
->single();
// Try and find out the zone from the host_id
} else {
$zo = Zone::select('zones.*')
->where(fn($query)=>$query->where('host_id',$matches[2])->orWhere('region_id',$matches[2]))
->join('domains',['domains.id'=>'zones.domain_id'])
->join('addresses',['addresses.zone_id'=>'zones.id'])
->where('domains.name',$matches[5])
->first();
}
$zo = Zone::select('zones.*')->where('zone_id',$matches[1])->join('domains',['domains.id'=>'zones.domain_id'])->where('domains.name',$matches[5])->single();
$o->zone_id = $zo?->id;
$parent = $o->parent();
$zone_id = $parent?->zone->zone_id;
// For flattened domains
if ($zo?->domain->flatten && is_null($zone_id))
foreach ($zo->domain->zones as $zoo) {
$o->zone_id = $zoo->id;
$parent = $o->parent();
if ($parent)
break;
}
$region_id = $parent?->region_id;
$zone_id = $parent?->zone->zone_id;
}
return [
'z' => (int)($zone_id ?: $matches[1]),
'z' => (int)$zone_id ?: $matches[1],
'r' => (int)$region_id,
'n' => (int)$matches[2],
'f' => (int)$matches[3],
@ -404,14 +375,16 @@ class Address extends Model
*
* @param $query
* @return mixed
* @note zones and domains needs to be joined in the base call, or use FTN()
*/
public function scopeActiveFTN($query)
{
return $query
return $query->select($this->getTable().'.*')
->join('zones',['zones.id'=>'addresses.zone_id'])
->join('domains',['domains.id'=>'zones.domain_id'])
->where('zones.active',TRUE)
->where('domains.active',TRUE)
->active();
->active()
->FTNorder();
}
/**
@ -423,21 +396,13 @@ class Address extends Model
public function scopeFTN($query)
{
return $query
->select(['addresses.id','region_id','host_id','node_id','point_id','addresses.zone_id','addresses.active','role','security','addresses.system_id','addresses.active','validated','deleted_at'])
->join('zones',['zones.id'=>'addresses.zone_id'])
->join('domains',['domains.id'=>'zones.domain_id'])
->orderBy('domains.name')
->orderBy('region_id')
->orderBy('host_id')
->orderBy('node_id')
->orderBy('point_id')
->select(['id','addresses.zone_id','host_id','node_id','point_id','system_id'])
->with([
'zone:zones.id,domain_id,zone_id,active',
'zone.domain:domains.id,name,active,public',
'zone:zones.id,domain_id,zone_id',
'zone.domain:domains.id,name',
]);
}
/** @deprecated use FTN() */
public function scopeFTNOrder($query)
{
return $query
@ -483,7 +448,7 @@ class Address extends Model
'node_id',
'point_id',
'system_id',
DB::raw('count(addresses.id) as uncollected_echomail'),
DB::raw('count(*) as uncollected_echomail'),
DB::raw('0 as uncollected_netmail'),
DB::raw('0 as uncollected_files'),
])
@ -520,7 +485,7 @@ class Address extends Model
'system_id',
DB::raw('0 as uncollected_echomail'),
DB::raw('0 as uncollected_netmail'),
DB::raw('count(addresses.id) as uncollected_files')
DB::raw('count(*) as uncollected_files')
])
->UncollectedFiles();
}
@ -558,7 +523,7 @@ class Address extends Model
'point_id',
'system_id',
DB::raw('0 as uncollected_echomail'),
DB::raw('count(addresses.id) as uncollected_netmail'),
DB::raw('count(*) as uncollected_netmail'),
DB::raw('0 as uncollected_files')
])
->UncollectedNetmail();
@ -584,7 +549,6 @@ class Address extends Model
public function echoareas()
{
return $this->belongsToMany(Echoarea::class)
->using(AddressEchoarea::class)
->orderBy('name')
->withPivot(['subscribed']);
}
@ -784,9 +748,6 @@ class Address extends Model
if (! $this->relationLoaded('zone'))
$this->load(['zone:id,domain_id,zone_id']);
if (! $this->zone)
throw new InvalidFTNException(sprintf('Invalid Zone for FTN address [%d/%d.%d@%s]',$this->host_id ?: $this->region_id,$this->node_id,$this->point_id,$this->domain?->name));
return sprintf('%d:%s',$this->zone->zone_id,$this->getFTN2DAttribute());
}
@ -1174,8 +1135,8 @@ class Address extends Model
*/
public function getNetmail(): ?Packet
{
if ($count=($num=$this->netmailAlertWaiting())->count()) {
Log::info(sprintf('%s:= Packaging [%d] netmail alerts to [%s]',self::LOGKEY,$count,$this->ftn));
if (($num=$this->netmailAlertWaiting())->count()) {
Log::debug(sprintf('%s:= Packaging [%d] netmail alerts to [%s]',self::LOGKEY,$num->count(),$this->ftn));
$msgs = $num->get();
@ -1199,11 +1160,11 @@ class Address extends Model
);
}
if ($count=($num=$this->netmailWaiting())->count()) {
Log::info(sprintf('%s:= Got [%d] netmails for [%s] for sending',self::LOGKEY,$count,$this->ftn));
if (($num=$this->netmailWaiting())->count()) {
Log::debug(sprintf('%s:= Got [%d] netmails for [%s] for sending',self::LOGKEY,$num->count(),$this->ftn));
// Limit to max messages
if ($count > $this->system->pkt_msgs)
if ($num->count() > $this->system->pkt_msgs)
Log::alert(sprintf('%s:= Only sending [%d] netmails for [%s]',self::LOGKEY,$this->system->pkt_msgs,$this->ftn));
return $this->system->packet($this)->mail($num->take($this->system->pkt_msgs)->get());
@ -1228,17 +1189,10 @@ class Address extends Model
*/
public function netmailWaiting(): Builder
{
// Addresses that our downstream of this address, except anybody that has session details with us
$ours = our_nodes($this->zone->domain)->pluck('id');
$addresses = $this->downstream()
->filter(fn($item)=>! $ours->contains($item->id))
->merge($this->system->match($this->zone,Address::NODE_ALL));
$netmails = $this
->UncollectedNetmail()
->select('netmails.id')
->whereIn('addresses.id',$addresses->pluck('id'))
->whereIn('addresses.id',$this->downlinks()->add($this)->pluck('id'))
->groupBy(['netmails.id'])
->get();

View File

@ -1,12 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
class AddressEchoarea extends Pivot
{
protected $casts = [
'subscribed' => 'datetime:Y-m-d H:i',
];
}

View File

@ -11,16 +11,9 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use App\Models\Casts\CompressedStringOrNull;
use App\Casts\CompressedStringOrNull;
use App\Traits\{QueryCacheableConfig,ScopeActive};
/**
* This represents an FTN Domain.
*
* We are either a member of the domain (because we have an AKA in it) or NOT.
*
* The assumption is, if we are a member of the domain, we receive/send mail to an uplink and possibly downlinks
*/
class Domain extends Model
{
use HasFactory,QueryCacheableConfig,ScopeActive;
@ -35,30 +28,26 @@ class Domain extends Model
/* SCOPES */
/**
* A domain is public (visible), if the user is an admin or, the domain is marked public)
* Only query active records
*/
public function scopePublic($query)
{
$user = Auth::user();
return $query
->active()
->when((! $user) || (! $user->isAdmin()),
fn($query)=>$query->where('public',TRUE));
->when(((! $user) || (! $user->isAdmin())),function($query) { return $query->where('public',TRUE)->active(); });
}
/* RELATIONS */
public function echoareas()
{
return $this->hasMany(Echoarea::class)
->orderBy('name');
return $this->hasMany(Echoarea::class);
}
public function fileareas()
{
return $this->hasMany(Filearea::class)
->orderBy('name');
return $this->hasMany(Filearea::class);
}
public function nodelist_filearea()
@ -66,7 +55,7 @@ class Domain extends Model
return $this->belongsTo(Filearea::class);
}
public function nodestatus_echoarea()
public function nodestatusarea()
{
return $this->belongsTo(Echoarea::class,'nodestatus_id');
}
@ -74,76 +63,60 @@ class Domain extends Model
public function zones()
{
return $this->hasMany(Zone::class)
->select(['id','zone_id','domain_id','active'])
->orderBy('zone_id');
->select(['id','zone_id','domain_id']);
}
/* ATTRIBUTES */
/**
* We can accept applications if we have an address in the domain
*
* @return bool
* @throws \Exception
*/
public function getCanAcceptAppAttribute(): bool
{
return $this->isManaged()
return ($x=our_address($this))
&& $x->count()
&& $this->active
&& $this->accept_app
&& Auth::id();
&& Auth::id()
&& $this->userHasSystemsNotInNet(Auth::user())->count();
}
public function getHomePageAttribute(?string $value): string
{
//0xFD2FB528
return $this->castAttribute('homepage',$value) ?: 'No available information at the moment.';
}
/* METHODS */
/**
* Show count of messages by day/week/month/all stats for each echoarea in this domain
*
* @return Collection
*/
public function echoarea_stats(): Collection
{
return Cache::remember(md5(sprintf('%d-%s',$this->id,__METHOD__)),self::CACHE_TIME,function() {
$dt = Carbon::now()->startOfday();
$case = CaseBuilder::whenRaw("datetime >= '?'",$dt->subDay()->format('Y-m-d'))->thenRaw("'day'")
->whenRaw("datetime >= '?'",$dt->subDays(7)->format('Y-m-d'))->thenRaw("'week'")
->whenRaw("datetime >= '?'",$dt->subMonth()->format('Y-m-d'))->thenRaw("'month'")
->elseRaw("'all'");
$dt = Carbon::now()->startOfday();
$case = CaseBuilder::whenRaw("datetime >= '?'",$dt->subDay()->format('Y-m-d'))->thenRaw("'day'")
->whenRaw("datetime >= '?'",$dt->subDays(7)->format('Y-m-d'))->thenRaw("'week'")
->whenRaw("datetime >= '?'",$dt->subMonth()->format('Y-m-d'))->thenRaw("'month'")
->elseRaw("'all'");
return Echoarea::select(['echoareas.id','name','description','active',DB::raw('count(echomails.id) AS count'),DB::raw('min(datetime) as first_message'),DB::raw('max(datetime) as last_message')])
->selectRaw($case->toRaw().' AS stats')
->join('echomails',['echomails.echoarea_id'=>'echoareas.id'],NULL,NULL,'left outer')
->where('domain_id',$this->id)
->groupBy('echoareas.id')
->groupBy('echoareas.name')
->groupBy('stats')
->orderBy('echoareas.name')
->orderBy('last_message','DESC')
->get();
});
return Echoarea::select(['echoareas.id','name','description','active',DB::raw('count(echomails.id) AS count'),DB::raw('max(datetime) as last_message')])
->selectRaw($case->toRaw().' AS stats')
->join('echomails',['echomails.echoarea_id'=>'echoareas.id'],NULL,NULL,'left outer')
->where('domain_id',$this->id)
->groupBy('echoareas.id')
->groupBy('echoareas.name')
->groupBy('stats')
->orderBy('echoareas.name')
->orderBy('last_message','DESC')
->get();
}
/**
* Show daily total of messages by echoarea in this domain
*
* @param Collection|NULL $systems
* @return Collection
*/
public function echoarea_total_daily(Collection $systems=NULL): Collection
{
return Cache::remember(md5(sprintf('%d-%s',$this->id,$systems?->pluck('id')->join(','))),self::CACHE_TIME,function() use ($systems) {
return DB::query()
->select(['echoareas.name','echoareas.show',DB::raw('COUNT(echoareas.name) AS count'),DB::raw('datetime::date AS date')])
->select(['echoareas.name','echoareas.show',DB::raw('COUNT(*) AS count'),DB::raw('datetime::date AS date')])
->from($this->getTable())
->join('echoareas',['echoareas.domain_id'=>'domains.id'])
->join('echomails',['echomails.echoarea_id'=>'echoareas.id'])
->where('domains.id',$this->id)
->where('echomails.datetime','>=',Carbon::now()->subMonths(self::STATS_MONTHS)->startOfMonth())
->when($systems?->count(),fn($query)=>$query->whereIn('echomails.fftn_id',$systems->pluck('addresses')->flatten()->pluck('id')))
->when($systems?->count(),function($query) use ($systems) { return $query->whereIn('echomails.fftn_id',$systems->pluck('addresses')->flatten()->pluck('id')); })
->groupBy(['echoareas.name','echoareas.show','date'])
->orderBy('echoareas.name')
->orderBy('date')
@ -151,30 +124,6 @@ class Domain extends Model
});
}
/**
* Show count of files by week/month/all status for each filearea in this domain
*/
public function filearea_stats()
{
return Cache::remember(md5(sprintf('%d-%s',$this->id,__METHOD__)),self::CACHE_TIME,function() {
$dt = Carbon::now()->startOfday();
$case = CaseBuilder::whenRaw("datetime >= '?'",$dt->subDays(7)->format('Y-m-d'))->thenRaw("'week'")
->whenRaw("datetime >= '?'",$dt->subMonth()->format('Y-m-d'))->thenRaw("'month'")
->elseRaw("'all'");
return Filearea::select(['fileareas.id','fileareas.name','description','active',DB::raw('count(files.id) AS count'),DB::raw('min(datetime) as first_file'),DB::raw('max(datetime) as last_file')])
->selectRaw($case->toRaw().' AS stats')
->join('files',['files.filearea_id'=>'fileareas.id'],NULL,NULL,'left outer')
->where('domain_id',$this->id)
->groupBy('fileareas.id')
->groupBy('fileareas.name')
->groupBy('stats')
->orderBy('fileareas.name')
->orderBy('last_file','DESC')
->get();
});
}
/**
* Determine if this zone is managed by this host
*
@ -183,6 +132,28 @@ class Domain extends Model
*/
public function isManaged(): bool
{
return our_address($this)->count() > 0;
return ($x=our_address())
&& $x->pluck('zone.domain')
->pluck('id')
->contains($this->id);
}
/**
* Work out which of the users systems are not in this domain
*
* @param User $o
* @return Collection
*/
public function userHasSystemsNotInNet(User $o): Collection
{
$o->load('systems.akas.zone');
$result = collect();
foreach ($o->systems->filter(function($item) { return $item->active; }) as $so) {
if (! $so->akas->pluck('zone')->unique('domain_id')->pluck('domain_id')->contains($this->id))
$result->push($so);
}
return $result;
}
}

View File

@ -5,7 +5,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Casts\CollectionOrNull;
use App\Casts\CollectionOrNull;
class Dynamic extends Model
{

View File

@ -46,8 +46,7 @@ class Echoarea extends Model
private const CACHE_TIME = 3600;
protected $casts = [
'first_message' => 'datetime:Y-m-d H:i:s',
'last_message' => 'datetime:Y-m-d H:i:s',
'last_message' => 'datetime:Y-m-d H:i:s'
];
/* RELATIONS */

View File

@ -9,10 +9,10 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Casts\{CollectionOrNull,CompressedStringOrNull,UTF8StringOrNull};
use App\Classes\FTN\Message;
use App\Events\Echomail as EchomailEvent;
use App\Interfaces\Packet;
use App\Models\Casts\{CompressedStringOrNull,CollectionOrNull,UTF8StringOrNull};
use App\Traits\{MessageAttributes,MsgID,ParseAddresses,QueryCacheableConfig};
final class Echomail extends Model implements Packet
@ -150,7 +150,7 @@ final class Echomail extends Model implements Packet
// Make sure our origin contains our FTN
$m = [];
if ((preg_match('#^(.*)\s+\(([0-9]+:[0-9]+/[0-9]+.*)\)+\s*$#',$model->set_origin,$m))
&& (Address::findFTN(sprintf('%s@%s',$m[2],$model->fftn->domain->name),TRUE,TRUE)?->id === $model->fftn_id))
&& (Address::findFTN(sprintf('%s@%s',$m[2],$model->fftn->domain->name))?->id === $model->fftn_id))
{
$x = Origin::where('value',utf8_encode($m[1]))->single();
@ -184,13 +184,13 @@ final class Echomail extends Model implements Packet
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
if (($model->fftn->point_id === 0) && (! $model->isFlagSet(Message::FLAG_LOCAL)) && (! $path->contains($model->fftn_id))) {
if ((! $model->isFlagSet(Message::FLAG_LOCAL)) && (! $path->contains($model->fftn_id))) {
Log::alert(sprintf('%s:? Echomail adding sender to start of PATH [%s].',self::LOGKEY,$model->fftn_id));
$path->prepend($model->fftn_id);
}
// Make sure our pktsrc is last in the path
if ($model->set->has('set_sender') && (! $path->contains($model->set->get('set_sender')->id)) && ($model->set->get('set_sender')->point_id === 0)) {
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->get('set_sender')->ftn));
$path->push($model->set->get('set_sender')->id);
}
@ -215,13 +215,13 @@ final class Echomail extends Model implements Packet
$seenby = self::parseAddresses('seenby',$model->set->get('set_seenby'),$model->fftn->zone,$rogue);
// Make sure our sender is in the seenby
if (($model->fftn->point_id === 0) && (! $model->isFlagSet(Message::FLAG_LOCAL)) && (! $seenby->contains($model->fftn_id))) {
if ((! $model->isFlagSet(Message::FLAG_LOCAL)) && (! $seenby->contains($model->fftn_id))) {
Log::alert(sprintf('%s:? Echomail adding sender to SEENBY [%s].',self::LOGKEY,$model->fftn_id));
$seenby->push($model->fftn_id);
}
// Make sure our pktsrc is in the seenby
if ($model->set->has('set_sender') && (! $seenby->contains($model->set->get('set_sender')->id)) && ($model->set->get('set_sender')->point_id === 0)) {
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->get('set_sender')->ftn));
$seenby->push($model->set->get('set_sender')->id);
}
@ -250,7 +250,6 @@ final class Echomail extends Model implements Packet
}
// See if we need to export this message.
// @todo We need to limit exporting if address/system is not active
if ($model->echoarea->sec_read) {
$exportto = $model
->echoarea
@ -291,7 +290,6 @@ final class Echomail extends Model implements Packet
return $this->belongsToMany(Address::class,'echomail_seenby')
->select(['id','zone_id','host_id','node_id'])
->withPivot(['export_at','sent_at','sent_pkt'])
->dontCache()
->FTN2DOrder();
}

View File

@ -11,7 +11,7 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use App\Models\Casts\{CompressedStringOrNull,CollectionOrNull};
use App\Casts\{CollectionOrNull,CompressedStringOrNull};
class File extends Model
{

View File

@ -11,11 +11,6 @@ class Filearea extends Model
{
use SoftDeletes,ScopeActive,AreaSecurity;
protected $casts = [
'first_file' => 'datetime:Y-m-d H:i:s',
'last_file' => 'datetime:Y-m-d H:i:s',
];
protected $fillable = [
'name',
];
@ -31,9 +26,4 @@ class Filearea extends Model
{
return $this->belongsTo(Domain::class);
}
public function files()
{
return $this->hasMany(File::class);
}
}

View File

@ -10,9 +10,9 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Casts\{CollectionOrNull,CompressedStringOrNull,UTF8StringOrNull};
use App\Interfaces\Packet;
use App\Models\Casts\{CompressedStringOrNull,CollectionOrNull,UTF8StringOrNull};
use App\Models\Pivots\ViaPivot;
use App\Pivots\ViaPivot;
use App\Traits\{MessageAttributes,MsgID};
final class Netmail extends Model implements Packet
@ -21,7 +21,7 @@ final class Netmail extends Model implements Packet
private const LOGKEY = 'MN-';
public const UPDATED_AT = NULL;
private const PATH_REGEX = '/^([0-9]+:[0-9]+\/[0-9]+(\.?[0-9@a-zA-Z]*)?)\s+@([0-9.a-zA-Z]+)\s+(.*)$/';
private const PATH_REGEX = '/^([0-9]+:[0-9]+\/[0-9]+(\..*)?)\s+@([0-9.a-zA-Z]+)\s+(.*)$/';
/**
* Kludges that we absorb in this model
@ -46,12 +46,6 @@ final class Netmail extends Model implements Packet
public function __get($key)
{
switch ($key) {
case 'get_fftn':
return $this->set->get('set_fftn') ?: $this->fftn->ftn;
case 'get_tftn':
return $this->set->get('set_tftn') ?: $this->tftn->ftn;
case 'set_fftn':
case 'set_tftn':
case 'set_path':
@ -148,7 +142,7 @@ final class Netmail extends Model implements Packet
// Make sure our origin contains our FTN
$m = [];
if ((preg_match('#^(.*)\s+\(([0-9]+:[0-9]+/[0-9]+.*)\)+\s*$#',$model->set_origin,$m))
&& (Address::findFTN(sprintf('%s@%s',$m[2],$model->fftn->domain->name),TRUE,TRUE)?->id === $model->fftn_id))
&& (Address::findFTN(sprintf('%s@%s',$m[2],$model->fftn->domain->name))?->id === $model->fftn_id))
{
$x = Origin::where('value',utf8_encode($m[1]))->single();
@ -247,11 +241,6 @@ final class Netmail extends Model implements Packet
->using(ViaPivot::class);
}
public function sent()
{
return $this->belongsTo(Address::class);
}
public function tftn()
{
return $this
@ -277,16 +266,6 @@ final class Netmail extends Model implements Packet
: $this->getRelationValue('path');
}
/**
* Split the body of a message into a collection of lines.
*
* @return array
*/
public function getBodyLinesAttribute(): array
{
return explode("\r",$this->msg_src);
}
/* METHODS */
/**

View File

@ -2,9 +2,10 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\Casts\UTF8StringOrNull;
use App\Casts\UTF8StringOrNull;
class Origin extends Model
{

View File

@ -71,6 +71,9 @@ class Setup extends Model
case 'msgs_pkt':
return Arr::get($this->options,$key,self::MAX_MSGS_PKT);
case 'version':
return File::exists('VERSION') ? chop(File::get('VERSION')) : 'dev';
default:
return parent::__get($key);
}
@ -110,16 +113,6 @@ class Setup extends Model
return hexstr($c);
}
/**
* Application version
*
* @return string
*/
public static function version(): string
{
return File::exists('VERSION') ? chop(File::get('VERSION')) : 'dev';
}
/* RELATIONS */
/**

View File

@ -2,7 +2,6 @@
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
@ -48,39 +47,35 @@ class System extends Model
$uo = Auth::user();
return $query
->when(
$uo && ! $uo->isAdmin(),
fn($query)=>
$query
->whereIn('id',$uo->systems->pluck('id'))
->orWhere($this->getTable().'.active',TRUE),
fn($query)=>$query->where($this->getTable().'.active',TRUE))
->when($uo && ! $uo->isAdmin(),function($query) use ($uo) {
return $query->whereIn('id',$uo->systems->pluck('id'))
->orWhere($this->getTable().'.active',TRUE);
},function($query) { $query->where($this->getTable().'.active',TRUE); })
->orderBy('name');
}
/* RELATIONS */
/**
* All addresses assigned to a system, including addresses pending deletion
* @return mixed
*/
public function addresses()
{
return $this->hasMany(Address::class)
->FTN()
->withTrashed();
->withTrashed()
->FTNorder();
}
/**
* System addresses that are active
*
* @return mixed
*/
public function akas()
{
return $this->hasMany(Address::class)
->FTN()
->ActiveFTN();
->select('addresses.*')
->join('zones',['zones.id'=>'addresses.zone_id'])
->join('domains',['domains.id'=>'zones.domain_id'])
->where('addresses.active',TRUE)
->where('zones.active',TRUE)
->where('domains.active',TRUE)
->orderBy('domains.name')
->with(['zone.domain'])
->FTNorder()
->orderBy('role','ASC');
}
public function mailers()
@ -115,10 +110,9 @@ class System extends Model
public function sessions()
{
return $this->belongsToMany(Zone::class)
->select(['zones.id','zones.zone_id','domain_id','zones.active'])
->join('domains',['domains.id'=>'zones.domain_id'])
->select(['id','zones.zone_id','domain_id','active'])
->withPivot(['sespass','pktpass','ticpass','fixpass','zt_ipv4','zt_ipv6','default'])
->orderBy('domains.name');
->dontCache();
}
/**
@ -141,14 +135,7 @@ class System extends Model
*/
public function zcs()
{
return $this->hasMany(Zone::class)
->select(['zones.id','zone_id','domain_id','system_id','zones.active'])
->join('domains',['domains.id'=>'zones.domain_id'])
->orderBy('domains.name')
->orderBy('zone_id')
->with([
'domain:id,name,active',
]);
return $this->hasMany(Zone::class);
}
/**
@ -194,73 +181,8 @@ class System extends Model
return $val ?: Setup::findOrFail(config('app.id'))->msgs_pkt;
}
public function getLastSeenAttribute(): ?Carbon
{
return $this->logs_recent->first()?->created_at;
}
/* METHODS */
public function addresses_common(): Collection
{
$our = our_address()->pluck('zone.domain_id')->unique();
// Return our akas, filter with our_addresses()
return $this->addresses->filter(fn($item)=>$our->contains($item->zone->domain_id));
}
/**
* Return the ACTIVE addresses that are common with our addresses
*
* @return Collection
* @throws \Exception
*/
public function aka_common(): Collection
{
$our = our_address()->pluck('zone.domain_id')->unique();
// Return our akas, filter with our_addresses()
return $this->akas->filter(fn($item)=>$our->contains($item->zone->domain_id));
}
/**
* Return the ACTIVE addresses that we auth with
*
* @return Collection
* @throws \Exception
*/
public function aka_known(): Collection
{
return $this->aka_common()
->filter(fn($item)=>$this->sessions->contains($item->zone_id));
}
/**
* Return the ACTIVE addresses in the same networks as us, but dont auth here
*
* @return Collection
* @throws \Exception
*/
public function aka_unknown(): Collection
{
return $this->aka_common()
->filter(fn($item)=>! $this->sessions->contains($item->zone_id));
}
/**
* Return the AKAs that are in networks not common with us
*
* @return Collection
* @throws \Exception
*/
public function aka_uncommon(): Collection
{
$our = our_address()->pluck('zone.domain_id')->unique();
// Return our akas, filter with our_addresses()
return $this->akas->filter(fn($item)=>! $our->contains($item->zone->domain_id));
}
public function echoareas()
{
return Echoarea::select('echoareas.*')
@ -302,13 +224,6 @@ class System extends Model
}
}
public function inDomain(Domain $o): bool
{
return $this->addresses
->filter(fn($item)=>$item->zone->domain_id === $o->id)
->count() > 0;
}
/**
* Return the system's address in the same zone
* This function can filter based on the address type needed.
@ -329,18 +244,32 @@ class System extends Model
: $akas;
}
/**
* Parse the addresses and return which ones are in my zones
*
* @param \Illuminate\Database\Eloquent\Collection $addresses
* @param int $type
* @return Collection
*/
public function inMyZones(Collection $addresses,int $type=(Address::NODE_HC|Address::NODE_NN|Address::NODE_POINT)): Collection
{
$myzones = $this->addresses->pluck('zone_id')->unique();
return $addresses->filter(function($item) use ($myzones,$type) {
return ($item->role & $type) && ($myzones->search($item->zone_id) !== FALSE);
});
}
/**
* Return the packet that this system uses
*
* @param Address $ao
* @param string|null $password
* @return Packet
* @throws \Exception
*/
public function packet(Address $ao,string $password=NULL): Packet
{
if ($ao->system_id !== $this->id)
throw new \Exception('Packet for [%s] is not for system [%d]',$ao->ftn,$this->id);
// @todo Check that the address is one of the system's addresses
return
(new (collect(Packet::PACKET_TYPES)
@ -353,7 +282,21 @@ class System extends Model
{
return Job::where('queue',AddressPoll::QUEUE)
->get()
->where(fn($item)=>$this->akas->pluck('id')->contains($item->command->address->id))
->where(function($item) {
return $this->akas->pluck('id')->search($item->command->address->id) !== FALSE; })
->last();
}
/**
* Return other addresses that are no collected here, but are on the same network as us.
*
* @return \Illuminate\Database\Eloquent\Collection
* @throws \Exception
*/
public function uncommon(): Collection
{
$our = our_address();
return $this->akas->filter(fn($item)=>($item->parent() && (! $our->contains($item->parent()))));
}
}

View File

@ -2,9 +2,10 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\Casts\UTF8StringOrNull;
use App\Casts\UTF8StringOrNull;
class Tagline extends Model
{

View File

@ -2,9 +2,10 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\Casts\UTF8StringOrNull;
use App\Casts\UTF8StringOrNull;
class Tearline extends Model
{

View File

@ -75,14 +75,19 @@ class User extends Authenticatable implements MustVerifyEmail
/* GENERAL METHODS */
public function addresses(): Collection
public function addresses(Domain $o=NULL): Collection
{
return Address::select('addresses.*')
->join('systems',['systems.id'=>'addresses.system_id'])
->join('system_user',['system_user.system_id'=>'systems.id'])
->when(! is_null($o),function($query) use ($o) {
return $query
->join('zones',['zones.id'=>'addresses.zone_id'])
->where('zones.domain_id',$o->id);
})
->where('system_user.user_id',$this->id)
->ActiveFTN()
->FTN()
->activeFTN()
->with(['zone.domain'])
->get();
}

View File

@ -8,7 +8,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use App\Classes\FTN\Message;
use App\Models\{Echoarea,Echomail,Netmail,Setup};
use App\Models\{Echoarea,Echomail,Setup};
abstract class Echomails extends Notification //implements ShouldQueue
{
@ -53,8 +53,8 @@ abstract class Echomails extends Notification //implements ShouldQueue
$o->echoarea_id = $eo->id;
$o->datetime = Carbon::now()->utc();
$o->tzoffset = Carbon::now()->utcOffset();
$o->datetime = Carbon::now();
$o->tzoffset = $o->datetime->utcOffset();
$o->flags = (Message::FLAG_LOCAL);
@ -62,13 +62,4 @@ abstract class Echomails extends Notification //implements ShouldQueue
return $o;
}
protected function sourceSummary(Echomail|Netmail $o,string $item=NULL): string
{
return sprintf("Your %s was received here on [%s] and it looks like you sent it on [%s].",
$item ?: sprintf('%s with ID [%s] to [%s]',$o instanceof Netmail ? 'Netmail' : 'Echomail',$o->msgid,$o->to),
Carbon::now()->utc()->toDateTimeString(),
$o->date->utc()->toDateTimeString(),
);
}
}

Some files were not shown because too many files have changed in this diff Show More