diff --git a/.env.example b/.env.example index 614affe..ade2502 100644 --- a/.env.example +++ b/.env.example @@ -43,3 +43,5 @@ PUSHER_APP_CLUSTER=mt1 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" + +FIDO_DIR=fido diff --git a/app/Classes/FTN/Message.php b/app/Classes/FTN/Message.php index 317c09d..e0cf2e7 100644 --- a/app/Classes/FTN/Message.php +++ b/app/Classes/FTN/Message.php @@ -256,6 +256,8 @@ class Message extends FTNBase return Arr::get($this->header,$key); case 'message': + case 'tearline': + case 'origin': case 'subject': case 'user_to': case 'user_from': @@ -280,6 +282,8 @@ class Message extends FTNBase case 'header': case 'intl': case 'message': + case 'tearline': + case 'origin': case 'msgid': case 'subject': case 'user_from': @@ -307,6 +311,10 @@ class Message extends FTNBase $class = get_class($this); foreach ($properties as $property) { + // Dont serialize the validation error + if ($property->name == 'errors') + continue; + if ($property->isStatic()) { continue; } @@ -672,7 +680,7 @@ class Message extends FTNBase ],[ 'user_from' => 'required|min:1|max:'.self::USER_FROM_LEN, 'user_to' => 'required|min:1|max:'.self::USER_TO_LEN, - 'subject' => 'required|max:'.self::SUBJECT_LEN, + 'subject' => 'present|max:'.self::SUBJECT_LEN, 'onode' => ['required',new TwoByteInteger], 'dnode' => ['required',new TwoByteInteger], 'onet' => ['required',new TwoByteInteger], diff --git a/app/Classes/FTN/Packet.php b/app/Classes/FTN/Packet.php index 3ff8809..90496a8 100644 --- a/app/Classes/FTN/Packet.php +++ b/app/Classes/FTN/Packet.php @@ -55,11 +55,13 @@ class Packet extends FTNBase public File $file; // Packet filename public Collection $messages; // Messages in the Packet + private string $name; // Packet name public function __construct(Address $o=NULL) { $this->messages = collect(); $this->domain = NULL; + $this->name = sprintf('%08x',Carbon::now()->timestamp); // If we are creating an outbound packet, we need to set our header if ($o) @@ -95,6 +97,7 @@ class Packet extends FTNBase throw new InvalidPacketException('Not a type 2 packet: '.$version); $o = new self; + $o->name = (string)$file; $o->header = unpack(self::unpackheader(self::v2header),$header); $x = fread($f,2); @@ -217,6 +220,10 @@ class Packet extends FTNBase else return '2e'; + // Packet name: + case 'name': + return $this->{$key}; + default: throw new \Exception('Unknown key: '.$key); } diff --git a/app/Classes/FTN/Process/Ping.php b/app/Classes/FTN/Process/Ping.php index 5656ec5..4e82b6e 100644 --- a/app/Classes/FTN/Process/Ping.php +++ b/app/Classes/FTN/Process/Ping.php @@ -4,6 +4,7 @@ namespace App\Classes\FTN\Process; use Carbon\Carbon; use Carbon\CarbonInterface; +use Illuminate\Support\Facades\Log; use App\Classes\FTN\{Message,Process}; use App\Models\{Netmail,Setup}; @@ -26,6 +27,8 @@ final class Ping extends Process if (strtolower($msg->user_to) !== 'ping') return FALSE; + Log::info(sprintf('Processing PING message from (%s) [%s]',$msg->user_from,$msg->fftn)); + $reply = sprintf("Your ping was received here on %s and it took %s to get here.\r", Carbon::now()->toDateTimeString(), $msg->date->diffForHumans(['parts'=>3,'syntax'=>CarbonInterface::DIFF_ABSOLUTE]) @@ -46,7 +49,7 @@ final class Ping extends Process $o->msg = static::format_msg($reply); $o->reply = $msg->msgid; - $o->tagline = '... My ping pong opponent was not happy with my serve. He kept returning it.'; + $o->tagline = 'My ping pong opponent was not happy with my serve. He kept returning it.'; $o->tearline = sprintf('--- %s (%s)',Setup::PRODUCT_NAME,(new Setup)->version); $o->save(); diff --git a/app/Classes/File/Item.php b/app/Classes/File/Item.php index 8f16469..d57af77 100644 --- a/app/Classes/File/Item.php +++ b/app/Classes/File/Item.php @@ -94,7 +94,7 @@ class Item throw new Exception('Invalid request for key: '.$key); case 'recvas': - return '/tmp/'.$this->file_name; // @todo this should be inbound temp + return $this->file_name; case 'sendas': return basename($this->file_name); @@ -114,6 +114,7 @@ class Item static $ext = ['su','mo','tu','we','th','fr','sa','req']; $x = strrchr($this->file_name,'.'); + if (! $x || (strlen(substr($x,1)) != 3)) return self::IS_FILE; diff --git a/app/Classes/File/Mail.php b/app/Classes/File/Mail.php index cab4492..519b52d 100644 --- a/app/Classes/File/Mail.php +++ b/app/Classes/File/Mail.php @@ -20,7 +20,7 @@ class Mail extends Item switch ($action) { case self::I_SEND: $this->file = $mail; - $this->file_name = sprintf('%08X.PKT',Carbon::now()->timestamp); + $this->file_name = sprintf('%08x.pkt',Carbon::now()->timestamp); $this->file_size = strlen($mail); $this->file_mtime = Carbon::now()->timestamp; // @todo This timestamp should be consistent incase of retries diff --git a/app/Classes/File/Receive.php b/app/Classes/File/Receive.php index 0992d39..0bb0f24 100644 --- a/app/Classes/File/Receive.php +++ b/app/Classes/File/Receive.php @@ -5,8 +5,13 @@ namespace App\Classes\File; use Exception; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; +use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\HttpFoundation\File\Exception\FileException; +use App\Classes\FTN\Packet; +use App\Jobs\ProcessPacket; +use App\Models\Address; + /** * Object representing the files we are receiving * @@ -16,12 +21,14 @@ use Symfony\Component\HttpFoundation\File\Exception\FileException; */ final class Receive extends Item { + private Address $ao; private Collection $list; private ?Item $receiving; private mixed $f; // File descriptor private int $start; // Time we started receiving private int $file_pos; // Current write pointer + private string $file; // Local filename for file received public function __construct() { @@ -86,8 +93,29 @@ final class Receive extends Item fclose($this->f); $this->file_pos = 0; - $this->receiving = NULL; $this->f = NULL; + + // If we received a packet, we'll dispatch a job to process it + switch ($this->receiving->file_type) { + case self::IS_PKT: + Log::info(sprintf('%s: - Processing mail packet [%s]',__METHOD__,$this->file)); + + foreach (Packet::open(new File($this->file),$this->ao->zone->domain)->messages as $msg) { + Log::info(sprintf('%s: - Mail from [%s] to [%s]',__METHOD__,$msg->fftn,$msg->tftn)); + + // @todo Quick check that the packet should be processed by us. + // @todo validate that the packet's zone is in the domain. + + // Dispatch job. + ProcessPacket::dispatchSync($msg); + } + break; + + default: + Log::debug(sprintf('%s: - Leaving file [%s] in the inbound dir',__METHOD__,$this->file)); + } + + $this->receiving = NULL; } /** @@ -97,7 +125,7 @@ final class Receive extends Item * @return bool * @throws Exception */ - public function open(bool $check=FALSE): bool + public function open(Address $ao,bool $check=FALSE): bool { Log::debug(sprintf('%s: + Start [%d]',__METHOD__,$check)); @@ -112,11 +140,13 @@ final class Receive extends Item if (! $this->receiving) throw new Exception('No files currently receiving'); + $this->ao = $ao; $this->file_pos = 0; $this->start = time(); + $this->file = sprintf('storage/app/%s/%04X-%s',config('app.fido'),$this->ao->id,$this->receiving->recvas); - Log::debug(sprintf('%s: - Opening [%s]',__METHOD__,$this->receiving->recvas)); - $this->f = fopen($this->receiving->recvas,'wb'); + Log::debug(sprintf('%s: - Opening [%s]',__METHOD__,$this->file)); + $this->f = fopen($this->file,'wb'); if (! $this->f) { Log::error(sprintf('%s: ! Unable to open file [%s] for writing',__METHOD__,$this->receiving->file_name)); return 3; // @todo change to const diff --git a/app/Classes/File/Send.php b/app/Classes/File/Send.php index ee79fec..3eada53 100644 --- a/app/Classes/File/Send.php +++ b/app/Classes/File/Send.php @@ -46,7 +46,7 @@ final class Send extends Item { switch ($key) { case 'fd': - return is_resource($this->f); + return is_resource($this->f) ?: $this->f; case 'file_count': return $this->list @@ -228,8 +228,11 @@ final class Send extends Item public function mail(Address $ao): void { // Netmail - if ($x=$ao->getNetmail()) + if ($x=$ao->getNetmail()) { + Log::debug(sprintf('%s: - Netmail(s) added for sending to [%s]',__METHOD__,$ao->ftn)); + $this->packets->push(new Mail($x,self::I_SEND)); + } } /** diff --git a/app/Classes/Node.php b/app/Classes/Node.php index 6c67f8e..30fee38 100644 --- a/app/Classes/Node.php +++ b/app/Classes/Node.php @@ -52,6 +52,9 @@ class Node public function __get($key) { switch ($key) { + case 'address': + return ($x=$this->ftns_authed)->count() ? $x->first() : $this->ftns->first(); + // Number of AKAs the remote has case 'aka_num': return $this->ftns->count(); @@ -117,6 +120,7 @@ class Node } $this->ftns->push($value); + break; case 'system': diff --git a/app/Classes/Protocol/Binkp.php b/app/Classes/Protocol/Binkp.php index c6c7984..d528649 100644 --- a/app/Classes/Protocol/Binkp.php +++ b/app/Classes/Protocol/Binkp.php @@ -102,7 +102,7 @@ final class Binkp extends BaseProtocol if (! parent::onConnect($client)) { $this->session(self::SESSION_BINKP,$client,(new Address)); $this->client->close(); - Log::info(sprintf('%s: = End - Connection closed [%s]',__METHOD__,$client->getAddress())); + Log::info(sprintf('%s: = End - Connection closed [%s]',__METHOD__,$client->address_remote)); } return NULL; @@ -133,9 +133,9 @@ final class Binkp extends BaseProtocol $this->msgs(self::BPM_NUL, sprintf('OPT%s%s%s%s%s%s', ($this->setup->opt_nda) ? ' NDA' : '', - ($this->setup->opt_nr&self::O_WANT) ? ' NR' : '', - ($this->setup->opt_nd&self::O_THEY) ? ' ND' : '', - ($this->setup->opt_mb&self::O_WANT) ? ' MB' : '', + ($this->setup->opt_nr&self::O_WANT) ? ' NR' : '', + ($this->setup->opt_nd&self::O_THEY) ? ' ND' : '', + ($this->setup->opt_mb&self::O_WANT) ? ' MB' : '', ($this->setup->opt_cr&self::O_WE) ? ' CRYPT' : '', ($this->setup->opt_cht&self::O_WANT) ? ' CHAT' : '')); } @@ -622,8 +622,11 @@ final class Binkp extends BaseProtocol // @todo lock nodes $this->node->ftn = $o; - // @todo Find files for node - $this->send->add('/tmp/aa'); + // Add our mail to the queue if we have authenticated + if ($this->node->aka_authed) + foreach ($this->node->aka_remote as $ao) { + $this->send->mail($ao); + } Log::info(sprintf('%s: = Node has [%lu] mail and [%lu] files - [%lu] items',__METHOD__,$this->send->mail_size,$this->send->file_size,$this->send->total_count)); @@ -706,7 +709,13 @@ final class Binkp extends BaseProtocol $this->sessionClear(self::SE_DELAYEOB); if (! $this->send->total_count && $this->sessionGet(self::SE_NOFILES)) { - // @todo See if we need to send anything else, based on what we just recevied + // Add our mail to the queue if we have authenticated + if ($this->node->aka_authed) + foreach ($this->node->aka_remote as $ao) { + Log::debug(sprintf('%s: - Checking for any new mail to [%s]',__METHOD__,$ao->ftn)); + $this->send->mail($ao); + } + if ($this->send->total_count) $this->sessionClear(self::SE_NOFILES); } @@ -760,7 +769,7 @@ final class Binkp extends BaseProtocol && $this->recv->size == Arr::get($file,'file.size') && $this->recv->filepos == $file['offs']) { - $this->recv->open($file['offs']<0); + $this->recv->open($this->node->address,$file['offs']<0); return 1; } @@ -768,7 +777,7 @@ final class Binkp extends BaseProtocol $this->recv->new($file['file']); try { - switch ($this->recv->open($file['offs']<0)) { + switch ($this->recv->open($this->node->address,$file['offs']<0)) { case self::FOP_ERROR: Log::error(sprintf('%s: ! File Error',__METHOD__)); diff --git a/app/Classes/Protocol/EMSI.php b/app/Classes/Protocol/EMSI.php index ee9dd37..8cb8d94 100644 --- a/app/Classes/Protocol/EMSI.php +++ b/app/Classes/Protocol/EMSI.php @@ -1154,7 +1154,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface { Log::debug(sprintf('%s: + Start',__METHOD__)); - $rc = (new Zmodem)->zmodem_receive($this->client,$zap,$this->recv,$this->setup->inbound); + $rc = (new Zmodem)->zmodem_receive($this->client,$zap,$this->recv); return ($rc == self::RCDO || $rc == self::ERROR); } diff --git a/app/Classes/Protocol/Zmodem.php b/app/Classes/Protocol/Zmodem.php index f6abf5b..904d060 100644 --- a/app/Classes/Protocol/Zmodem.php +++ b/app/Classes/Protocol/Zmodem.php @@ -274,7 +274,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface $rc = $this->zmodem_senddone(); } else { - $rc = $this->zmodem_receive($this->client,$proto,$this->recv,'.'); + $rc = $this->zmodem_receive($this->client,$proto,$this->recv); } return $rc; @@ -285,13 +285,12 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface * * @param SocketClient $client * @param Receive $recv - * @param string $dir * @param int $canzap * @return int */ - public function zmodem_receive(SocketClient $client,int $canzap,Receive $recv,string $dir): int + public function zmodem_receive(SocketClient $client,int $canzap,Receive $recv,Address $ao): int { - Log::debug(sprintf('%s: + Start [%d] into dir [%s]',__METHOD__,$canzap,$dir)); + Log::debug(sprintf('%s: + Start [%d]',__METHOD__,$canzap)); $opts = $this->init($client,$canzap); @@ -336,7 +335,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface $frame = self::ZSKIP; } else { - switch ($this->recv->open()) { + switch ($this->recv->open($ao)) { case self::FOP_SKIP: Log::info(sprintf('%s: = Skip this file [%s]',__METHOD__,$this->recv->name)); $frame = self::ZSKIP; diff --git a/app/Jobs/ProcessPacket.php b/app/Jobs/ProcessPacket.php index 3ae8351..755bf6c 100644 --- a/app/Jobs/ProcessPacket.php +++ b/app/Jobs/ProcessPacket.php @@ -36,7 +36,7 @@ class ProcessPacket implements ShouldQueue // @todo Enable checks to reject old messages // Determine if the message is to this system, or in transit - if ($ftns->search(function($item) { dump($item->ftn);return $this->msg->tftn == $item->ftn; }) !== FALSE) { + if ($ftns->search(function($item) { return $this->msg->tftn == $item->ftn; }) !== FALSE) { // @todo Check if it is a duplicate message // @todo Check if the message is from a system we know about diff --git a/app/Models/Address.php b/app/Models/Address.php index 7f52520..2f215b2 100644 --- a/app/Models/Address.php +++ b/app/Models/Address.php @@ -2,6 +2,7 @@ namespace App\Models; +use Carbon\Carbon; use Exception; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; @@ -157,13 +158,22 @@ class Address extends Model */ public function getNetmail(): ?Packet { - if (($x=Netmail::whereIn('tftn_id',$this->children->pluck('id')->push($this->id)))->count()) { + if (($x=Netmail::whereIn('tftn_id',$this->children->pluck('id')->push($this->id)) + ->where(function($q) { + return $q->whereNull('sent') + ->orWhere('sent',FALSE); + })) + ->count()) + { $o = new Packet($this); foreach ($x->get() as $oo) { $o->addNetmail($oo->packet()); - // @todo We need to mark the netmail as sent + $oo->packet = $o->name; + $oo->sent = TRUE; + $oo->sent_at = Carbon::now(); + $oo->save(); } return $o; diff --git a/app/Models/Netmail.php b/app/Models/Netmail.php index b4fff3f..1f9f6ba 100644 --- a/app/Models/Netmail.php +++ b/app/Models/Netmail.php @@ -12,8 +12,20 @@ class Netmail extends Model { use SoftDeletes; - protected $connection = 'mongodb'; - protected $dates = ['datetime']; + //protected $connection = 'mongodb'; + protected $dates = ['datetime','sent_at']; + + /** + * Resolve a connection instance. + * We need to do this, because our relations are in pgsql and somehow loading a relation changes this models + * connection information. Using protected $connection is not enough. + * + * @param string|null $connection + */ + public static function resolveConnection($connection = null) + { + return static::$resolver->connection('mongodb'); + } /* RELATIONS */ @@ -69,6 +81,10 @@ class Netmail extends Model $o->user_from = $this->from; $o->subject = $this->subject; $o->message = $this->msg; + if ($this->tagline) + $o->message .= "\r... ".$this->tagline."\r"; + + $o->tearline .= $this->tearline; $o->msgid = sprintf('%s %08x',$this->fftn->ftn3d,crc32($this->id)); diff --git a/app/Models/Setup.php b/app/Models/Setup.php index 0b34249..cc5727e 100644 --- a/app/Models/Setup.php +++ b/app/Models/Setup.php @@ -93,9 +93,6 @@ class Setup extends Model $this->do_prevent = 1; /* EMSI - send an immediate EMSI_INQ on connect */ $this->ignore_nrq = 0; $this->options = 0; /* EMSI - our capabilities */ - - /* EMSI - the order of protocols we are able to accept */ - $this->inbound = '/tmp'; } /** @@ -106,7 +103,6 @@ class Setup extends Model switch ($key) { case 'binkp_options': case 'ignore_nrq': - case 'inbound': case 'opt_nr': // @todo - this keys are now in #binkp as bits case 'opt_nd': case 'opt_nda': @@ -134,7 +130,6 @@ class Setup extends Model switch ($key) { case 'binkp_options': case 'ignore_nrq': - case 'inbound': case 'opt_nr': case 'opt_nd': case 'opt_nda': diff --git a/config/app.php b/config/app.php index c8a4d9e..2dc7668 100644 --- a/config/app.php +++ b/config/app.php @@ -15,6 +15,7 @@ return [ 'name' => env('APP_NAME', 'Laravel'), 'id' => env('APP_SETUP_ID', 1), + 'fido' => env('FIDO_DIR', 'fido'), /* |-------------------------------------------------------------------------- @@ -68,7 +69,7 @@ return [ | */ - 'timezone' => 'UTC', + 'timezone' => env('APP_TIMEZONE', 'UTC'), /* |-------------------------------------------------------------------------- diff --git a/init-php.sh b/init-php.sh index 27a4176..8bc8490 100755 --- a/init-php.sh +++ b/init-php.sh @@ -1,23 +1,22 @@ #!/bin/bash -# Run our web init startup -rm -f bootstrap/cache/*.php -/sbin/init php-fpm & +set -e -# Wait for DB to start -# If DB_HOST not set, source the env file -[ -z "${DB_HOST}" -a -r .env ] && . .env +BASE_DIR=storage/app -while ! wait-for-it -h ${DB_HOST} -p ${DB_PORT} -t 15 -q; do - echo "? Waiting for database at ${DB_HOST}:${DB_PORT}" -done -echo "- DB is active on ${DB_HOST}:${DB_PORT}" +# If FIDO_DIR not set, source the env file +[ -z "${FIDO_DIR}" -a -r .env ] && . .env + +FIDO_DIR=${FIDO_DIR:-fido} + +if [ ! -d "${BASE_DIR}/${FIDO_DIR}" ]; then + if ! mkdir ${BASE_DIR}/${FIDO_DIR}; then + echo "! ERROR creating FIDO_DIR [${BASE_DIR}/${FIDO_DIR}]" + exit 1 + fi +fi -# Wait for our config to have been rebuild -while [ ! -e bootstrap/cache/config.php -o -e .migrate ]; do - echo "? Waiting for configuration to be built by init" - sleep 15; -done echo "* Ready to start server" +echo " - INBOUND [${BASE_DIR}/${FIDO_DIR}]" exec su www-data -s /bin/sh -c "./artisan server:start" diff --git a/resources/views/setup.blade.php b/resources/views/setup.blade.php index 5684fb2..119c3ed 100644 --- a/resources/views/setup.blade.php +++ b/resources/views/setup.blade.php @@ -23,6 +23,7 @@ use App\Models\Setup; @csrf
+
@@ -44,6 +45,7 @@ use App\Models\Setup;
+
@if ($o->exists) @@ -64,17 +66,17 @@ use App\Models\Setup;
+

Site Permissions

- +
binkpOptionGet(Setup::O_HIDEAKA))) checked @endif disabled>
+

ZeroTier API

BINKP Settings

Bink has been configured to listen on {{ Setup::BINKP_BIND }}:{{ Setup::BINKP_PORT }}

@@ -147,6 +150,7 @@ use App\Models\Setup;
+

EMSI Settings

Bink has been configured to listen on {{ Setup::EMSI_BIND }}:{{ Setup::EMSI_PORT }}