Move mailer details into a separate table

This commit is contained in:
Deon George 2023-07-07 23:59:04 +10:00
parent ad4ea699a5
commit ccdce6bb62
12 changed files with 274 additions and 112 deletions

View File

@ -87,12 +87,12 @@ abstract class Protocol
protected const O_YES = 1<<5;
// Session Status
protected const S_OK = 0;
public const S_OK = 0;
protected const S_NODIAL = 1;
protected const S_REDIAL = 2;
protected const S_BUSY = 3;
protected const S_FAILURE = 4;
protected const S_MASK = 7;
public const S_MASK = 7;
protected const S_HOLDR = 8;
protected const S_HOLDX = 16;
protected const S_HOLDA = 32;
@ -324,10 +324,10 @@ abstract class Protocol
* @param int $type
* @param SocketClient $client
* @param Address|null $o
* @return void
* @return int
* @throws \Exception
*/
public function session(int $type,SocketClient $client,Address $o=NULL): void
public function session(int $type,SocketClient $client,Address $o=NULL): int
{
if ($o->exists)
Log::withContext(['ftn'=>$o->ftn]);
@ -370,7 +370,7 @@ abstract class Protocol
if ($rc < 0) {
Log::error(sprintf('%s:! Unable to start EMSI [%d]',self::LOGKEY,$rc));
return;
return self::S_FAILURE;
}
case self::SESSION_EMSI:
@ -389,14 +389,13 @@ abstract class Protocol
Log::debug(sprintf('%s:- Starting ZMODEM',self::LOGKEY));
$this->client->speed = self::TCP_SPEED;
$this->originate = FALSE;
$this->protocol_session();
return;
return $this->protocol_session();
default:
Log::error(sprintf('%s:! Unsupported session type [%d]',self::LOGKEY,$type));
return;
return self::S_FAILURE;
}
// @todo Unlock outbounds
@ -453,6 +452,8 @@ abstract class Protocol
// @todo Optional after session includes mail event
// if ($this->node->start_time && $this->setup->cfg('CFG_AFTERMAIL')) {}
return $rc;
}
/* SE_* flags determine our session processing status, at any point in time */

View File

@ -194,16 +194,16 @@ final class DNS extends BaseProtocol
$ao = Address::findFTN(sprintf('%d:%d/%d.%d@%s',$z,$n,$f,$p,$d));
// Check we have the right record
if ((! $ao) || (! $ao->system->mailer_address) || (($rootdn !== self::TLD) && ((! $ao->zone->domain->dnsdomain) || ($ao->zone->domain->dnsdomain !== $d.'.'.$rootdn)))) {
if ((! $ao) || (! $ao->system->address) || (($rootdn !== self::TLD) && ((! $ao->zone->domain->dnsdomain) || ($ao->zone->domain->dnsdomain !== $d.'.'.$rootdn)))) {
Log::alert(sprintf('%s:= No DNS record for [%d:%d/%d.%d@%s]',self::LOGKEY,$z,$n,$f,$p,$d));
return $this->nameerr();
}
Log::debug(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->mailer_address,$ao->ftn));
Log::debug(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->address,$ao->ftn));
return $this->reply(
self::DNS_NOERROR,
[serialize($this->domain_split($ao->system->mailer_address)) => self::DNS_TYPE_CNAME]);
[serialize($this->domain_split($ao->system->address)) => self::DNS_TYPE_CNAME]);
// Other attributes return NOTIMPL
default:

View File

@ -6,7 +6,7 @@ use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Log;
use App\Models\Address;
use App\Models\{Address,Mailer};
use App\Jobs\AddressPoll as Job;
class CommBinkpSend extends Command
@ -25,6 +25,8 @@ class CommBinkpSend extends Command
*/
protected $description = 'BINKP send';
private const ID = 'BINKP';
/**
* Execute the console command.
*
@ -34,10 +36,12 @@ class CommBinkpSend extends Command
{
Log::info('CBS:- Call BINKP send');
$mo = Mailer::where('name',self::ID)->singleOrFail();
$ao = Address::findFTN($this->argument('ftn'));
if (! $ao)
throw new ModelNotFoundException('Unknown node: '.$this->argument('ftn'));
Job::dispatchSync($ao);
Job::dispatchSync($ao,$mo);
}
}

View File

@ -6,7 +6,7 @@ use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Log;
use App\Models\Address;
use App\Models\{Address,Mailer};
use App\Jobs\AddressPoll as Job;
class CommEMSISend extends Command
@ -25,6 +25,8 @@ class CommEMSISend extends Command
*/
protected $description = 'EMSI send';
private const ID = 'EMSI';
/**
* Execute the console command.
*
@ -32,12 +34,14 @@ class CommEMSISend extends Command
*/
public function handle(): void
{
Log::info('Call EMSI send');
Log::info('CES:- Call EMSI send');
$mo = Mailer::where('name',self::ID)->singleOrFail();
$ao = Address::findFTN($this->argument('ftn'));
if (! $ao)
throw new ModelNotFoundException('Unknown node: '.$this->argument('ftn'));
Job::dispatchSync($ao);
Job::dispatchSync($ao,$mo);
}
}

View File

@ -276,11 +276,17 @@ class SystemController extends Controller
$this->authorize('update',$o);
if ($request->post()) {
foreach (['name','location','sysop','hold','phone','address','port','active','method','notes','mailer_type','mailer_address','mailer_port','zt_id','pkt_type'] as $key)
foreach (['name','location','sysop','hold','phone','address','port','active','method','notes','zt_id','pkt_type'] as $key)
$o->{$key} = $request->post($key);
$o->save();
$mailers = collect($request->post('mailer_details'))
->filter(function($item) { return $item['port']; })
->transform(function($item) { $item['active'] = Arr::get($item,'active',FALSE); return $item; });
$o->mailers()->sync($mailers);
return redirect()->action([self::class,'home']);
}
@ -687,7 +693,7 @@ class SystemController extends Controller
if (! $o->exist) {
$o->sysop = Auth::user()->name;
foreach (['name','zt_id','location','mailer_type','mailer_address','mailer_port','phone','method','address','port'] as $item)
foreach (['name','zt_id','location','phone','method','address','port'] as $item)
if ($request->{$item})
$o->{$item} = $request->{$item};

View File

@ -56,9 +56,8 @@ class SystemRegister extends FormRequest
'address' => 'nullable|regex:/^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){1,127}(?![0-9]*$)[a-z0-9-]+\.?)$/i',
'port' => 'nullable|digits_between:2,5',
'method' => 'nullable|numeric',
'mailer_type' => 'nullable|numeric',
'mailer_address' => 'nullable|regex:/^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){1,127}(?![0-9]*$)[a-z0-9-]+\.?)$/i',
'mailer_port' => 'nullable|digits_between:2,5',
'mailer_details.*' => 'nullable|array',
'mailer_details.*.port' => 'nullable|digits_between:2,5',
'zt_id' => 'nullable|size:10|regex:/^([A-Fa-f0-9]){10}$/|unique:systems,zt_id,'.($this->so->exists ? $this->so->id : 0),
'pkt_type' => ['required',Rule::in(array_keys(Packet::PACKET_TYPES))],
] : [],

View File

@ -9,10 +9,11 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use App\Classes\Protocol;
use App\Classes\Protocol\{Binkp,EMSI};
use App\Classes\Sock\SocketClient;
use App\Classes\Sock\SocketException;
use App\Models\{Address,Setup};
use App\Models\{Address,Mailer,Setup};
class AddressPoll implements ShouldQueue
{
@ -21,11 +22,12 @@ class AddressPoll implements ShouldQueue
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private Address $ao;
private ?Mailer $mo;
public function __construct(Address $ao)
public function __construct(Address $ao,Mailer $mo=NULL)
{
// Some checks
$this->ao = $ao;
$this->mo = $mo;
}
/**
@ -33,34 +35,49 @@ class AddressPoll implements ShouldQueue
*/
public function handle()
{
if ((! $this->ao->system->mailer_address) || (! $this->ao->system->mailer_port))
if (! $this->ao->system->mailer_preferred->count() || ($this->mo && (! $this->ao->system->mailer_preferred->find($this->mo))))
throw new \Exception(sprintf('Unable to poll [%s] missing mailer details',$this->ao->ftn));
try {
$client = SocketClient::create($this->ao->system->mailer_address,$this->ao->system->mailer_port);
} catch (SocketException $e) {
Log::error(sprintf('%s:! Unable to connect to [%s]: %s',self::LOGKEY,$this->ao->ftn,$e->getMessage()));
abort(500);
}
foreach ($this->ao->system->mailer_preferred as $o) {
// If we chose a protocol, skip to find the mailer details for it
if ($this->mo && ($o->id !== $this->mo->id))
continue;
switch ($this->ao->system->mailer_type) {
case Setup::O_BINKP:
$o = new Binkp(Setup::findOrFail(config('app.id')));
$o->session(Binkp::SESSION_BINKP,$client,$this->ao);
Log::info(sprintf('%s:- Starting a [%s] session to [%s:%d]',self::LOGKEY,$o->name,$this->ao->system->address,$o->pivot->port));
try {
$client = SocketClient::create($this->ao->system->address,$o->pivot->port);
} catch (SocketException $e) {
Log::error(sprintf('%s:! Unable to connect to [%s]: %s',self::LOGKEY,$this->ao->ftn,$e->getMessage()));
abort(500);
}
switch ($o->name) {
case 'BINKP':
$s = new Binkp(Setup::findOrFail(config('app.id')));
$session = Binkp::SESSION_BINKP;
break;
case 'EMSI':
$s = new EMSI(Setup::findOrFail(config('app.id')));
$session = EMSI::SESSION_AUTO;
break;
default:
throw new \Exception(sprintf('Node [%s] has a mailer type that is unhandled',$this->ao->ftn));
}
if (($s->session($session,$client,$this->ao) & Protocol::S_MASK) === Protocol::S_OK) {
Log::info(sprintf('%s:= Connection ended successfully with [%s]',self::LOGKEY,$client->address_remote));
break;
case Setup::O_EMSI:
$o = new EMSI(Setup::findOrFail(config('app.id')));
$o->session(EMSI::SESSION_AUTO,$client,$this->ao);
break;
default:
throw new \Exception(sprintf('Node [%s] has a mailer type that is unhandled',$this->ao->ftn));
} else {
Log::alert(sprintf('%s:! Connection failed to [%s]',self::LOGKEY,$client->address_remote));
}
}
Log::info(sprintf('%s:Connection ended: %s',self::LOGKEY,$client->address_remote));
}
}

31
app/Models/Mailer.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Mailer extends Model
{
public $timestamps = FALSE;
private const SORTORDER = [
'BINKP'=>1,
'EMSI'=>2,
];
/* RELATIONS */
public function system()
{
return $this->belongsTo(System::class);
}
/* SCOPES */
public function scopePreferred($query)
{
return $query
->orderBy('priority','ASC')
->where('mailer_system.active',TRUE);
}
}

View File

@ -48,6 +48,18 @@ class System extends Model
->active();
}
public function mailers()
{
return $this->belongsToMany(Mailer::class)
->withPivot(['last_poll','port','attempts','active']);
}
public function mailer_preferred()
{
return $this->mailers()
->preferred();
}
public function logs()
{
return $this->hasMany(SystemLog::class);
@ -119,11 +131,11 @@ class System extends Model
public function getAccessMailerAttribute(): string
{
switch ($this->mailer_type) {
case Setup::O_BINKP: return sprintf('binkp://%s:%s',$this->mailer_address,$this->mailer_port);
case Setup::O_EMSI: return sprintf('emsi://%s:%s',$this->mailer_address,$this->mailer_port);
switch (($x=$this->mailer_preferred()->first())?->name) {
case 'BINKP': return sprintf('binkp://%s:%s',$this->address,$x->pivot->port);
case 'EMSI': return sprintf('emsi://%s:%s',$this->address,$x->pivot->port);
default:
return $this->mailer_type ? sprintf('%s:%s',$this->address,$this->port) : 'No mailer available.';
return $x?->name ? sprintf('%s:%s',$this->address,$x->pivot->port) : 'No mailer available.';
}
}

View File

@ -0,0 +1,89 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('mailers',function (Blueprint $table) {
$table->id();
$table->string('name')->unqiue();
$table->integer('priority')->unsigned()->unique();
});
Schema::create('mailer_system',function (Blueprint $table) {
$table->integer('port')->unsigned();
$table->dateTime('last_poll')->nullable();
$table->boolean('active');
$table->integer('attempts')->unsigned()->nullable();
$table->bigInteger('system_id');
$table->foreign('system_id')->references('id')->on('systems');
$table->bigInteger('mailer_id');
$table->foreign('mailer_id')->references('id')->on('mailers');
$table->unique(['system_id','mailer_id']);
$table->unique(['system_id','port']);
});
Schema::table('systems',function (Blueprint $table) {
$table->dropUnique(['mailer_type','mailer_address','mailer_port']);
});
$binkp = new \App\Models\Mailer;
$binkp->name = 'BINKP';
$binkp->priority = 1;
$binkp->save();
$emsi = new \App\Models\Mailer;
$emsi->name = 'EMSI';
$emsi->priority = 2;
$emsi->save();
foreach (\App\Models\System::whereNotNull('mailer_address')->cursor() as $o) {
if ($o->mailer_type & \App\Models\Setup::O_BINKP)
$type = $binkp;
elseif ($o->mailer_type & \App\Models\Setup::O_EMSI)
$type = $emsi;
else
$type = NULL;
if (! $type)
abort(500,sprintf('No mailer type [%d] :',$o->mailer_type));
$o->mailers()->sync([$type->id=>[
'port'=>$o->mailer_port,
'active'=>TRUE,
]]);
$o->address = $o->mailer_address;
$o->mailer_port = null;
$o->mailer_type = null;
$o->mailer_address = null;
$o->save();
}
Schema::table('systems',function (Blueprint $table) {
$table->dropColumn(['mailer_type','mailer_address','mailer_port']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
abort(500,'Cant go back');
Schema::dropIfExists('mailer_system');
Schema::dropIfExists('mailers');
Schema::table('systems',function (Blueprint $table) {
$table->unique(['mailer_type','mailer_address','mailer_port']);
});
}
};

View File

@ -16,7 +16,7 @@ System Test Messsage
| System Name | Address | FTNs |
| ------------- |:-------------:| --------:|
@foreach ($user->systems as $so)
| {{ $so->name }} | {{ $so->mailer_address }} | {{ $so->addresses->pluck('ftn3d')->join(', ') }} |
| {{ $so->name }} | {{ $so->address }} | {{ $so->addresses->pluck('ftn3d')->join(', ') }} |
@endforeach
@endcomponent
@endif

View File

@ -73,7 +73,7 @@
<div class="col-4">
<label for="sysop" class="form-label">Sysop</label>
<div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-globe"></i></span>
<span class="input-group-text"><i class="bi bi-person-fill"></i></span>
<input type="text" class="form-control @error('sysop') is-invalid @enderror" id="sysop" placeholder="Sysop" name="sysop" value="{{ old('sysop',$o->sysop) }}" required @cannot('admin',$o)readonly @endcannot autocomplete="name">
<span class="invalid-feedback" role="alert">
@error('sysop')
@ -102,60 +102,68 @@
</div>
</div>
<div class="row">
<div class="col-4">
<label for="phone" class="form-label">Phone</label>
<div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-telephone-fill"></i></span>
<input type="text" class="form-control @error('phone') is-invalid @enderror" id="phone" placeholder="Phone" name="phone" value="{{ old('phone',$o->phone) }}" @cannot($action,$o)readonly @endcannot>
<span class="invalid-feedback" role="alert">
@error('phone')
{{ $message }}
@enderror
</span>
</div>
</div>
<!-- Address -->
<div class="col-8">
<label for="address" class="form-label">Internet Address</label>
<div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-globe"></i></span>
<input type="text" class="w-75 form-control @error('address') is-invalid @enderror" id="address" placeholder="FQDN" name="address" value="{{ old('address',$o->address) }}" @cannot($action,$o)readonly @endcannot>
<span class="invalid-feedback" role="alert">
@error('address')
{{ $message }}
@enderror
</span>
</div>
</div>
</div>
<div class="row">
<!-- Mailer Details -->
<div class="col-12">
<h4>Mailer Details</h4>
<h4 class="pt-4 mb-0 pb-2">Mailer Details</h4>
<div class="pt-0 row">
<div class="col-4">
<label for="method" class="form-label">Connection Method</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-wifi"></i></span>
<select class="form-select @error('method') is-invalid @enderror" id="mailer_type" name="mailer_type" @cannot($action,$o)readonly @endcannot>
<option></option>
<option value="{{ Setup::O_BINKP }}" @if(old('mailer_type',$o->mailer_type) == Setup::O_BINKP)selected @endif>BINKP</option>
<option value="{{ Setup::O_EMSI }}" @if(old('mailer_type',$o->mailer_type) == Setup::O_EMSI)selected @endif>EMSI</option>
</select>
</div>
</div>
<div class="col-8">
<label for="address" class="form-label">Address</label>
<div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-globe"></i></span>
<input type="text" class="w-75 form-control @error('mailer_address') is-invalid @enderror" id="mailer_address" placeholder="FQDN" name="mailer_address" value="{{ old('mailer_address',$o->mailer_address) }}" @cannot($action,$o)readonly @endcannot>
<input type="text" class="form-control @error('mailer_port') is-invalid @enderror" id="mailer_port" placeholder="Port" name="mailer_port" value="{{ old('mailer_port',$o->mailer_port) }}" @cannot($action,$o)readonly @endcannot>
<span class="invalid-feedback" role="alert">
@error('mailer_address')
{{ $message }}
@enderror
@error('mailer_port')
{{ $message }}
@enderror
</span>
</div>
</div>
</div>
<div class="pt-0 row">
<div class="col-4">
<label for="phone" class="form-label">Phone</label>
<div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-telephone-fill"></i></span>
<input type="text" class="form-control @error('phone') is-invalid @enderror" id="phone" placeholder="Phone" name="phone" value="{{ old('phone',$o->phone) }}" @cannot($action,$o)readonly @endcannot>
<span class="invalid-feedback" role="alert">
@error('phone')
{{ $message }}
@enderror
</span>
</div>
<div class="col-3">
@foreach (\App\Models\Mailer::all() as $mo)
@php($x=$o->mailers->find($mo))
<div class="pt-0 row">
<div class="col-12">
<label for="mailer_port_{{ $mo->id }}" class="form-label w-100">{{ $mo->name }} <span class="float-end text-warning">{{ $x?->pivot->last_poll }}</span></label>
<div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-modem-fill"></i></span>
<input type="text" class="form-control text-end @error('mailer_details.'.$mo->id.'.port') is-invalid @enderror" id="mailer_port_{{ $mo->id }}" placeholder="Port" name="mailer_details[{{ $mo->id }}][port]" value="{{ old('mailer_details.'.$mo->id.'.port',$x?->pivot->port) }}" @cannot($action,$o)readonly @endcannot>
<div class="input-group-text">
<input type="checkbox" class="form-control-input" name="mailer_details[{{ $mo->id }}][active]" value="1" title="Active" @if(old('mailer_details.'.$mo->id.'.active',$x?->pivot->active))checked @endif>
</div>
<span class="invalid-feedback" role="alert">
@error('mailer_details.'.$mo->id.'.port')
{{ $message }}
@enderror
</span>
</div>
</div>
</div>
@endforeach
</div>
<div class="col-2">
<label for="pkt_type" class="form-label">Mail Packet</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-wifi"></i></span>
<span class="input-group-text"><i class="bi bi-ui-radios"></i></span>
<select class="form-select @error('pkt_type') is-invalid @enderror" id="pkt_type" name="pkt_type" @cannot($action,$o)readonly @endcannot>
@foreach (\App\Classes\FTN\Packet::PACKET_TYPES as $type => $class)
<option value="{{ $type }}" @if(old('pkt_type',$o->pkt_type ?: config('app.default_pkt')) === $type)selected @endif>{{ $type }}</option>
@ -176,10 +184,10 @@
<div class="row">
<!-- BBS Details -->
<div class="col-12">
<h4>BBS Details</h4>
<h4 class="pt-4 mb-0 pb-2">BBS Details</h4>
<div class="pt-0 row">
<div class="col-4">
<div class="col-2">
<label for="method" class="form-label">Connection Method</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-wifi"></i></span>
@ -191,23 +199,14 @@
</select>
</div>
</div>
<div class="col-8">
<label for="address" class="form-label">Address</label>
<div class="input-group has-validation">
<span class="input-group-text"><i class="bi bi-globe"></i></span>
<input type="text" class="w-75 form-control @error('address') is-invalid @enderror" id="address" placeholder="FQDN" name="address" value="{{ old('address',$o->address) }}" @cannot($action,$o)readonly @endcannot>
<input type="text" class="form-control @error('port') is-invalid @enderror" id="port" placeholder="Port" name="port" value="{{ old('port',$o->port) }}" @cannot($action,$o)readonly @endcannot>
<span class="invalid-feedback" role="alert">
@error('address')
{{ $message }}
@enderror
@error('port')
{{ $message }}
@enderror
</span>
<div class="col-2">
<label for="method" class="form-label">Port</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-wifi"></i></span>
<input type="text" class="form-control text-end @error('port') is-invalid @enderror" id="port" placeholder="Port" name="port" value="{{ old('port',$o->port) }}" @cannot($action,$o)readonly @endcannot>
</div>
</div>
</div>
</div>
</div>