diff --git a/app/Classes/Crypt.php b/app/Classes/Crypt.php new file mode 100644 index 0000000..af2ab8b --- /dev/null +++ b/app/Classes/Crypt.php @@ -0,0 +1,97 @@ +extend_key($password); + } + + /** + * Update crc. + */ + private function crc32_cr(int $oldCrc, int $charAt): int + { + return (($oldCrc >> 8) & 0xFFFFFF) ^ self::crc32_tab[($oldCrc ^ $charAt) & 0xFF]; + } + + public function decrypt(string $content): string + { + $result = ''; + + foreach (unpack('C*', $content) as $byte) { + $byte = ($byte ^ $this->decrypt_byte()) & 0xFF; + $this->update_keys($byte); + $result .= chr($byte); + } + + return $result; + } + + /** + * Decrypt byte. + */ + private function decrypt_byte(): int + { + $temp = $this->keys[2] | 2; + + return (($temp * ($temp ^ 1)) >> 8) & 0xFFFFFF; + } + + public function encrypt(string $content): string + { + $result = ''; + + foreach (unpack('C*', $content) as $val) + $result .= pack('c', $this->encrypt_byte($val)); + + return $result; + } + + private function encrypt_byte(int $byte): int + { + $result = $byte ^ $this->decrypt_byte() & 0xFF; + $this->update_keys($byte); + + return $result; + } + + public function extend_key(string $password): void + { + foreach (unpack('C*', $password) as $byte) + $this->update_keys($byte); + } + + public static function toSignedInt32(int $int): int + { + if (\PHP_INT_SIZE === 8) { + $int &= 0xFFFFFFFF; + + if ($int & 0x80000000) + return $int - 0x100000000; + } + + return $int; + } + + /** + * Update keys. + */ + private function update_keys(int $byte): void + { + $this->keys[0] = $this->crc32_cr($this->keys[0],$byte); + $this->keys[1] += ($this->keys[0] & 0xFF); + $this->keys[1] = $this->toSignedInt32($this->keys[1]*134775813+1); + $this->keys[2] = $this->toSignedInt32($this->crc32_cr($this->keys[2],($this->keys[1] >> 24) & 0xFF)); + } +} \ No newline at end of file diff --git a/app/Classes/File/Item.php b/app/Classes/File/Item.php index df70ab5..0e631bd 100644 --- a/app/Classes/File/Item.php +++ b/app/Classes/File/Item.php @@ -2,7 +2,6 @@ namespace App\Classes\File; -use Exception; use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Support\Facades\Storage; use League\Flysystem\UnreadableFileEncountered; @@ -23,6 +22,10 @@ class Item // For deep debugging protected bool $DEBUG = FALSE; + /** @var int max size of file to use compression */ + // @todo MAX_COMPSIZE hasnt been implemented in RECEIVE OR SEND + protected const MAX_COMPSIZE = 0x1fff; + protected const IS_PKT = (1<<1); protected const IS_ARC = (1<<2); protected const IS_FILE = (1<<3); @@ -30,29 +33,33 @@ class Item protected const IS_REQ = (1<<5); protected const IS_TIC = (1<<6); - protected const I_RECV = (1<<6); - protected const I_SEND = (1<<7); + protected const I_RECV = (1<<0); + protected const I_SEND = (1<<1); protected string $file_name = ''; protected int $file_size = 0; protected int $file_mtime = 0; - protected int $file_type = 0; - protected int $action = 0; + /** Current read/write pointer */ + protected int $file_pos = 0; + /** File descriptor */ + protected mixed $f = NULL; + protected int $type; + protected int $action; protected File $filemodel; public bool $sent = FALSE; public bool $received = FALSE; public bool $incomplete = FALSE; + /** Time we started sending/receiving */ + protected int $start; /** * @throws FileNotFoundException * @throws UnreadableFileEncountered - * @throws Exception + * @throws \Exception */ public function __construct($file,int $action) { - $this->action |= $action; - switch ($action) { case self::I_SEND: if ($file instanceof File) { @@ -63,7 +70,7 @@ class Item } else { if (! is_string($file)) - throw new Exception('Invalid object creation - file should be a string'); + throw new \Exception('Invalid object creation - file should be a string'); if (! file_exists($file)) throw new FileNotFoundException('Item doesnt exist: '.$file); @@ -83,7 +90,7 @@ class Item $keys = ['name','mtime','size']; if (! is_array($file) || array_diff(array_keys($file),$keys)) - throw new Exception('Invalid object creation - file is not a valid array :'.serialize(array_diff(array_keys($file),$keys))); + throw new \Exception('Invalid object creation - file is not a valid array :'.serialize(array_diff(array_keys($file),$keys))); $this->file_name = $file['name']; $this->file_size = $file['size']; @@ -92,14 +99,15 @@ class Item break; default: - throw new Exception('Unknown action: '.$action); + throw new \Exception('Unknown action: '.$action); } - $this->file_type |= $this->whatType(); + $this->action = $action; + $this->type = $this->whatType(); } /** - * @throws Exception + * @throws \Exception */ public function __get($key) { @@ -107,10 +115,10 @@ class Item case 'mtime': case 'name': case 'size': - if ($this->action & self::I_RECV) + if ($this->action & self::I_RECV|self::I_SEND) return $this->{'file_'.$key}; - throw new Exception('Invalid request for key: '.$key); + throw new \Exception('Invalid request for key: '.$key); case 'recvas': return $this->file_name; @@ -119,13 +127,13 @@ class Item return $this->file_name ? basename($this->file_name) : $this->filemodel->name; default: - throw new Exception('Unknown key: '.$key); + throw new \Exception('Unknown key: '.$key); } } protected function isType(int $type): bool { - return $this->file_type & $type; + return $this->type & $type; } private function whatType(): int diff --git a/app/Classes/File/Mail.php b/app/Classes/File/Mail.php index f0f05c9..d3d8b2a 100644 --- a/app/Classes/File/Mail.php +++ b/app/Classes/File/Mail.php @@ -15,8 +15,6 @@ class Mail extends Item */ public function __construct(Packet $mail,int $action) { - $this->action |= $action; - switch ($action) { case self::I_SEND: $this->file = $mail; @@ -29,6 +27,8 @@ class Mail extends Item default: throw new \Exception('Unknown action: '.$action); } + + $this->action = $action; } public function read(int $start,int $length): string diff --git a/app/Classes/File/Receive.php b/app/Classes/File/Receive.php index 4085a9c..a45f506 100644 --- a/app/Classes/File/Receive.php +++ b/app/Classes/File/Receive.php @@ -5,10 +5,12 @@ namespace App\Classes\File; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Storage; use Symfony\Component\HttpFoundation\File\Exception\FileException; -use App\Classes\File; +use App\Classes\{File,Protocol}; use App\Classes\FTN\{InvalidPacketException,Packet}; +use App\Exceptions\FileGrewException; use App\Jobs\{MessageProcess,TicProcess}; use App\Models\Address; @@ -23,22 +25,26 @@ final class Receive extends Item { private const LOGKEY = 'IR-'; + private const compression = [ + 'BZ2', + 'GZ', + ]; + 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 + /** @var ?string The compression used by the incoming file */ + private ?string $comp; + /** @var string|null The compressed data received */ + private ?string $comp_data; public function __construct() { // Initialise our variables $this->list = collect(); $this->receiving = NULL; - $this->file_pos = 0; - $this->f = NULL; } public function __get($key) @@ -71,7 +77,7 @@ final class Receive extends Item case 'total_recv_bytes': return $this->list ->filter(function($item) { return ($item->action & self::I_RECV) && $item->received === TRUE; }) - ->sum(function($item) { return $item->file_size; }); + ->sum(function($item) { return $item->size; }); default: throw new \Exception('Unknown key: '.$key); @@ -85,114 +91,121 @@ final class Receive extends Item */ public function close(): void { - if (! $this->f) + if (! $this->receiving) throw new \Exception('No file to close'); - if ($this->file_pos != $this->receiving->file_size) { - Log::warning(sprintf('%s: - Closing [%s], but missing [%d] bytes',self::LOGKEY,$this->receiving->file_name,$this->receiving->file_size-$this->file_pos)); - $this->receiving->incomplete = TRUE; - } + if ($this->f) { + if ($this->file_pos !== $this->receiving->size) { + Log::warning(sprintf('%s:- Closing [%s], but missing [%d] bytes',self::LOGKEY,$this->receiving->name,$this->receiving->size-$this->file_pos)); + $this->receiving->incomplete = TRUE; + } - $this->receiving->received = TRUE; + $this->receiving->received = TRUE; - $end = time()-$this->start; - Log::debug(sprintf('%s: - Closing [%s], received in [%d]',self::LOGKEY,$this->receiving->file_name,$end)); + $end = time()-$this->start; + Log::debug(sprintf('%s:- Closing [%s], received in [%d]',self::LOGKEY,$this->receiving->name,$end)); - fclose($this->f); - $this->file_pos = 0; - $this->f = NULL; + if ($this->comp) + Log::info(sprintf('%s:= Compressed file using [%s] was [%d] bytes (%d). Compression rate [%3.2f%%]',self::LOGKEY,$this->comp,$x=strlen($this->comp_data),$this->receiving->size,$x/$this->receiving->size*100)); - // If the packet has been received but not the right size, dont process it any more. + fclose($this->f); + // Set our mtime + touch($this->file,$this->mtime); + $this->file_pos = 0; + $this->f = NULL; - // If we received a packet, we'll dispatch a job to process it - if (! $this->receiving->incomplete) - switch ($this->receiving->file_type) { - case self::IS_ARC: - case self::IS_PKT: - Log::info(sprintf('%s: - Processing mail %s [%s]',self::LOGKEY,$this->receiving->file_type === self::IS_PKT ? 'PACKET' : 'ARCHIVE',$this->file)); + // If the packet has been received but not the right size, dont process it any more. - try { - $f = new File($this->file); - $processed = FALSE; + // If we received a packet, we'll dispatch a job to process it + if (! $this->receiving->incomplete) + switch ($this->receiving->type) { + case self::IS_ARC: + case self::IS_PKT: + Log::info(sprintf('%s:- Processing mail %s [%s]',self::LOGKEY,$this->receiving->type === self::IS_PKT ? 'PACKET' : 'ARCHIVE',$this->file)); - foreach ($f as $packet) { - $po = Packet::process($packet,Arr::get(stream_get_meta_data($packet),'uri'),$f->itemSize(),$this->ao->system); + try { + $f = new File($this->file); + $processed = FALSE; - // Check the messages are from the uplink - if ($this->ao->system->addresses->search(function($item) use ($po) { return $item->id === $po->fftn_o->id; }) === FALSE) { - Log::error(sprintf('%s: ! Packet [%s] is not from this link? [%d]',self::LOGKEY,$po->fftn_o->ftn,$this->ao->system_id)); + foreach ($f as $packet) { + $po = Packet::process($packet,Arr::get(stream_get_meta_data($packet),'uri'),$f->itemSize(),$this->ao->system); - break; - } + // Check the messages are from the uplink + if ($this->ao->system->addresses->search(function($item) use ($po) { return $item->id === $po->fftn_o->id; }) === FALSE) { + Log::error(sprintf('%s:! Packet [%s] is not from this link? [%d]',self::LOGKEY,$po->fftn_o->ftn,$this->ao->system_id)); - // Check the packet password - if ($this->ao->session('pktpass') != $po->password) { - Log::error(sprintf('%s: ! Packet from [%s] with password [%s] is invalid.',self::LOGKEY,$this->ao->ftn,$po->password)); - - // @todo Generate message to system advising invalid password - that message should be sent without a packet password! - break; - } - - Log::info(sprintf('%s: - Packet has [%d] messages',self::LOGKEY,$po->count())); - - // Queue messages if there are too many in the packet. - if ($queue = ($po->count() > config('app.queue_msgs'))) - Log::info(sprintf('%s: - Messages will be sent to the queue for processing',self::LOGKEY)); - - $count = 0; - foreach ($po as $msg) { - Log::info(sprintf('%s: - Mail from [%s] to [%s]',self::LOGKEY,$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. - - try { - // Dispatch job. - if ($queue) - MessageProcess::dispatch($msg,$f->pktName()); - else - MessageProcess::dispatchSync($msg,$f->pktName()); - - } catch (\Exception $e) { - Log::error(sprintf('%s:! Got error dispatching message [%s] (%d:%s-%s).',self::LOGKEY,$msg->msgid,$e->getLine(),$e->getFile(),$e->getMessage())); + break; } - $count++; + // Check the packet password + if ($this->ao->session('pktpass') != $po->password) { + Log::error(sprintf('%s:! Packet from [%s] with password [%s] is invalid.',self::LOGKEY,$this->ao->ftn,$po->password)); + + // @todo Generate message to system advising invalid password - that message should be sent without a packet password! + break; + } + + Log::info(sprintf('%s:- Packet has [%d] messages',self::LOGKEY,$po->count())); + + // Queue messages if there are too many in the packet. + if ($queue = ($po->count() > config('app.queue_msgs'))) + Log::info(sprintf('%s:- Messages will be sent to the queue for processing',self::LOGKEY)); + + $count = 0; + foreach ($po as $msg) { + Log::info(sprintf('%s:- Mail from [%s] to [%s]',self::LOGKEY,$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. + + try { + // Dispatch job. + if ($queue) + MessageProcess::dispatch($msg,$f->pktName()); + else + MessageProcess::dispatchSync($msg,$f->pktName()); + + } catch (\Exception $e) { + Log::error(sprintf('%s:! Got error dispatching message [%s] (%d:%s-%s).',self::LOGKEY,$msg->msgid,$e->getLine(),$e->getFile(),$e->getMessage())); + } + + $count++; + } + + if ($count === $po->count()) + $processed = TRUE; } - if ($count === $po->count()) - $processed = TRUE; + if (! $processed) { + Log::alert(sprintf('%s:- Not deleting packet [%s], it doesnt seem to be processed?',self::LOGKEY,$this->file)); + + // If we want to keep the packet, we could do that logic here + } elseif (! config('app.packet_keep')) { + Log::debug(sprintf('%s:- Deleting processed packet [%s]',self::LOGKEY,$this->file)); + unlink($this->file); + } + + } catch (InvalidPacketException $e) { + Log::error(sprintf('%s:- Not deleting packet [%s], as it generated an InvalidPacketException',self::LOGKEY,$this->file),['e'=>$e->getMessage()]); + + } catch (\Exception $e) { + Log::error(sprintf('%s:- Not deleting packet [%s], as it generated an uncaught exception',self::LOGKEY,$this->file),['e'=>$e->getMessage()]); } - if (! $processed) { - Log::alert(sprintf('%s: - Not deleting packet [%s], it doesnt seem to be processed?',self::LOGKEY,$this->file)); + break; - // If we want to keep the packet, we could do that logic here - } elseif (! config('app.packet_keep')) { - Log::debug(sprintf('%s: - Deleting processed packet [%s]',self::LOGKEY,$this->file)); - unlink($this->file); - } + case self::IS_TIC: + Log::info(sprintf('%s:- Processing TIC file [%s]',self::LOGKEY,$this->file)); - } catch (InvalidPacketException $e) { - Log::error(sprintf('%s: - Not deleting packet [%s], as it generated an InvalidPacketException',self::LOGKEY,$this->file),['e'=>$e->getMessage()]); + // Queue the tic to be processed later, in case the referenced file hasnt been received yet + TicProcess::dispatch($this->file); - } catch (\Exception $e) { - Log::error(sprintf('%s: - Not deleting packet [%s], as it generated an uncaught exception',self::LOGKEY,$this->file),['e'=>$e->getMessage()]); - } + break; - break; - - case self::IS_TIC: - Log::info(sprintf('%s: - Processing TIC file [%s]',self::LOGKEY,$this->file)); - - // Queue the tic to be processed later, in case the referenced file hasnt been received yet - TicProcess::dispatch($this->file); - - break; - - default: - Log::debug(sprintf('%s: - Leaving file [%s] in the inbound dir',self::LOGKEY,$this->file)); - } + default: + Log::debug(sprintf('%s:- Leaving file [%s] in the inbound dir',self::LOGKEY,$this->file)); + } + } $this->receiving = NULL; } @@ -202,38 +215,66 @@ final class Receive extends Item * * @param Address $ao * @param bool $check - * @return bool + * @param string|null $comp If the incoming file will be compressed + * @return int * @throws \Exception + * @todo $comp should be parsed, in case it contains other items */ - public function open(Address $ao,bool $check=FALSE): bool + public function open(Address $ao,bool $check=FALSE,string $comp=NULL): int { - Log::debug(sprintf('%s:+ open [%d]',self::LOGKEY,$check)); - - // Check we can open this file - // @todo - // @todo implement return 2 - SKIP file // @todo implement return 4 - SUSPEND(?) file - if ($check) { - return 0; - } - + // @todo Change to use Storage::class() if (! $this->receiving) throw new \Exception('No files currently receiving'); + $this->comp_data = ''; + $this->comp = $comp; + + /* + if ($this->receiving->size <= self::MAX_COMPSIZE) { + $this->comp = $comp; + + } else { + Log::alert(sprintf('%s:- Compression [%s] disabled for file [%s], its size is too big [%d]',self::LOGKEY,$comp,$this->receiving->name,$this->receiving->size)); + } + */ + + if ($this->comp && (! in_array($this->comp,self::compression))) + throw new \Exception('Unsupported compression:'.$this->comp); + elseif ($this->comp) + Log::debug(sprintf('%s:- Receiving file with [%s] compression',self::LOGKEY,$this->comp)); + $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); + $this->file = sprintf('storage/app/%s',$this->local_path($ao)); - Log::debug(sprintf('%s: - Opening [%s]',self::LOGKEY,$this->file)); - $this->f = fopen($this->file,'wb'); - if (! $this->f) { - Log::error(sprintf('%s:! Unable to open file [%s] for writing',self::LOGKEY,$this->receiving->file_name)); - return 3; // @todo change to const + if (file_exists($this->file) + && (Storage::disk('local')->lastModified($this->local_path($ao)) === $this->mtime) + && (Storage::disk('local')->size($this->local_path($ao)) === $this->size)) { + Log::alert(sprintf('%s:- File already exists - skipping [%s]', self::LOGKEY, $this->file)); + return Protocol::FOP_SKIP; + + } elseif (file_exists($this->file) && (Storage::disk('local')->size($this->local_path($ao)) > 0)) { + Log::alert(sprintf('%s:- File exists with different details - skipping [%s]',self::LOGKEY,$this->file)); + return Protocol::FOP_SUSPEND; + + } else { + Log::debug(sprintf('%s:- Opening [%s]',self::LOGKEY,$this->file)); } - Log::info(sprintf('%s:= open - File [%s] opened for writing',self::LOGKEY,$this->receiving->file_name)); - return 0; // @todo change to const + // If we are only checking, we'll return (NR mode) + if ($check) + return Protocol::FOP_OK; + + $this->f = fopen($this->file,'wb'); + if (! $this->f) { + Log::error(sprintf('%s:! Unable to open file [%s] for writing',self::LOGKEY,$this->receiving->name)); + return Protocol::FOP_ERROR; + } + + Log::info(sprintf('%s:= open - File [%s] opened for writing',self::LOGKEY,$this->receiving->name)); + return Protocol::FOP_OK; } /** @@ -255,29 +296,76 @@ final class Receive extends Item $this->receiving = $o; } + private function local_path(Address $ao): string + { + return sprintf('%s/%04X-%s',config('app.fido'),$ao->id,$this->receiving->recvas); + } + /** * Write data to the file we are receiving * * @param string $buf - * @return int + * @return bool * @throws \Exception */ - public function write(string $buf): int + public function write(string $buf): bool { if (! $this->f) - throw new \Exception('No file open for read'); + throw new \Exception('No file open for write'); - if ($this->file_pos+strlen($buf) > $this->receiving->file_size) - throw new \Exception(sprintf('Too many bytes received [%d] (%d)?',$this->file_pos+strlen($buf),$this->receiving->file_size)); + $data = ''; - $rc = fwrite($this->f,$buf); + // If we are using compression mode, then we need to buffer the right until we have everything + if ($this->comp) { + $this->comp_data .= $buf; + + // See if we can uncompress the data yet + switch ($this->comp) { + case 'BZ2': + if (($data=bzdecompress($this->comp_data,TRUE)) === FALSE) + throw new FileException('BZ2 decompression failed?'); + elseif (is_numeric($data)) + throw new FileException(sprintf('BZ2 decompression failed with (:%d)?',$data)); + + break; + + case 'GZ': + if (($data=gzdeflate($this->comp_data)) === FALSE) + throw new FileException('BZ2 decompression failed?'); + + break; + } + + // Compressed file grew + if (! strlen($data)) { + if (strlen($this->comp_data) > $this->receiving->size) { + fclose($this->f); + $this->f = NULL; + + throw new FileGrewException(sprintf('Error compressed file grew, rejecting [%d] -> [%d]', strlen($this->comp_data), $this->receiving->size)); + } + + return TRUE; + } + + } else { + $data = $buf; + } + + if ($this->file_pos+strlen($data) > $this->receiving->size) + throw new \Exception(sprintf('Too many bytes received [%d] (%d)?',$this->file_pos+strlen($buf),$this->receiving->size)); + + $rc = fwrite($this->f,$data); if ($rc === FALSE) throw new FileException('Error while writing to file'); $this->file_pos += $rc; - Log::debug(sprintf('%s:- Write [%d] bytes, file pos now [%d] of [%d] (%d)',self::LOGKEY,$rc,$this->file_pos,$this->receiving->file_size,strlen($buf))); + Log::debug(sprintf('%s:- Write [%d] bytes, file pos now [%d] of [%d] (%d)',self::LOGKEY,$rc,$this->file_pos,$this->receiving->size,strlen($buf))); - return $rc; + if (strlen($this->comp_data) > $this->receiving->size) + Log::alert(sprintf('%s:- Compression grew the file during transfer (%d->%d)',self::LOGKEY,$this->receiving->size,strlen($this->comp_data))); + + return TRUE; } } \ No newline at end of file diff --git a/app/Classes/File/Send.php b/app/Classes/File/Send.php index 3353ce6..b0b438f 100644 --- a/app/Classes/File/Send.php +++ b/app/Classes/File/Send.php @@ -15,9 +15,6 @@ use App\Models\Address; * Object representing the files we are sending * * @property-read resource fd - * @property-read int file_mtime - * @property-read int file_size - * @property-read string file_name * @property-read int mail_size * @property-read int total_count * @property-read int total_sent @@ -31,9 +28,7 @@ final class Send extends Item private ?Item $sending; private Collection $packets; - private mixed $f; // File descriptor - private int $start; // Time we started sending - private int $file_pos; // Current read pointer + private string $comp_data; public function __construct() { @@ -41,8 +36,6 @@ final class Send extends Item $this->list = collect(); $this->packets = collect(); $this->sending = NULL; - $this->file_pos = 0; - $this->f = NULL; } public function __get($key) @@ -51,15 +44,15 @@ final class Send extends Item case 'fd': return is_resource($this->f) ?: $this->f; - case 'file_count': + case 'files_count': return $this->list ->filter(function($item) { return $item->isType(self::IS_FILE|self::IS_TIC); }) ->count(); - case 'file_size': + case 'files_size': return $this->list ->filter(function($item) { return $item->isType(self::IS_FILE|self::IS_TIC); }) - ->sum(function($item) { return $item->file_size; }); + ->sum(function($item) { return $item->size; }); case 'filepos': return $this->file_pos; @@ -73,8 +66,8 @@ final class Send extends Item case 'mail_size': return $this->list ->filter(function($item) { return $item->isType(self::IS_ARC|self::IS_PKT); }) - ->sum(function($item) { return $item->file_size; }) - + $this->packets->sum(function($item) { return $item->file_size; }); + ->sum(function($item) { return $item->size; }) + + $this->packets->sum(function($item) { return $item->size; }); case 'sendas': return $this->sending ? $this->sending->{$key} : NULL; @@ -95,10 +88,10 @@ final class Send extends Item case 'total_sent_bytes': return $this->list ->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; }) - ->sum(function($item) { return $item->file_size; }) + ->sum(function($item) { return $item->size; }) + $this->packets ->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; }) - ->sum(function($item) { return $item->file_size; }); + ->sum(function($item) { return $item->size; }); case 'total_count': return $this->list @@ -110,8 +103,8 @@ final class Send extends Item case 'total_size': return $this->list - ->sum(function($item) { return $item->file_size; }) - + $this->packets->sum(function($item) { return $item->file_size; }); + ->sum(function($item) { return $item->size; }) + + $this->packets->sum(function($item) { return $item->size; }); default: throw new Exception('Unknown key: '.$key); @@ -146,6 +139,21 @@ final class Send extends Item } } + /* + private function compress(string $comp_mode): void + { + switch ($comp_mode) { + case 'BZ2': + $this->comp_data = bzcompress($buf); + break; + + case 'GZ': + $this->comp_data = gzcompress($buf); + break; + } + } + */ + /** * Close the file descriptor of the file we are sending * @@ -160,7 +168,7 @@ final class Send extends Item if ($successful) { $this->sending->sent = TRUE; $end = time()-$this->start; - Log::debug(sprintf('%s: - Closing [%s], sent in [%d]',self::LOGKEY,$this->sending->file_name,$end)); + Log::debug(sprintf('%s: - Closing [%s], sent in [%d]',self::LOGKEY,$this->sending->name,$end)); } // @todo This should be done better isType == file? @@ -208,9 +216,9 @@ final class Send extends Item Log::debug(sprintf('%s:- [%d] Files(s) added for sending to [%s]',self::LOGKEY,$x->count(),$ao->ftn)); // Add Files - foreach ($x as $xx) { - $this->list->push(new Item($xx,self::I_SEND)); - $this->list->push(new Tic($ao,$xx,self::I_SEND)); + foreach ($x as $fo) { + $this->list->push(new Item($fo,self::I_SEND)); + $this->list->push(new Tic($ao,$fo,self::I_SEND)); } $file = TRUE; @@ -222,10 +230,11 @@ final class Send extends Item /** * Open a file for sending * + * @param string $compress * @return bool * @throws Exception */ - public function open(): bool + public function open(string $compress=''): bool { Log::debug(sprintf('%s:+ open',self::LOGKEY)); @@ -238,6 +247,11 @@ final class Send extends Item $this->start = time(); $this->f = TRUE; + /* + if ($compress) + $this->comp_data = $this->compdata($compress); + */ + return TRUE; } @@ -258,18 +272,19 @@ final class Send extends Item } // If sending file is a File::class, then our file is s3 - if (! $this->sending->file_name && $this->sending->filemodel) { + if (! $this->sending->name && $this->sending->filemodel) { $this->f = Storage::readStream($this->sending->filemodel->full_storage_path); return TRUE; } else { - $this->f = fopen($this->sending->file_name,'rb'); + $this->f = fopen($this->sending->name,'rb'); + if (! $this->f) { - Log::error(sprintf('%s:! Unable to open file [%s] for reading',self::LOGKEY,$this->sending->file_name)); + Log::error(sprintf('%s:! Unable to open file [%s] for reading',self::LOGKEY,$this->sending->name)); return FALSE; } - Log::info(sprintf('%s:= open - File [%s] opened with size [%d]',self::LOGKEY,$this->sending->file_name,$this->sending->file_size)); + Log::info(sprintf('%s:= open - File [%s] opened with size [%d]',self::LOGKEY,$this->sending->name,$this->sending->size)); return TRUE; } } @@ -343,7 +358,7 @@ final class Send extends Item Log::debug(sprintf('%s: - Read [%d] bytes, file pos now [%d]',self::LOGKEY,strlen($data),$this->file_pos)); if ($data === FALSE) - throw new UnreadableFileEncountered('Error reading file: '.$this->sending->file_name); + throw new UnreadableFileEncountered('Error reading file: '.$this->sending->name); return $data; } diff --git a/app/Classes/File/Tic.php b/app/Classes/File/Tic.php index b8a5137..3cdff24 100644 --- a/app/Classes/File/Tic.php +++ b/app/Classes/File/Tic.php @@ -7,19 +7,17 @@ use App\Models\{Address,File}; class Tic extends Item { + private string $file; + /** * @throws \Exception */ public function __construct(Address $ao,File $fo,int $action) { - $this->action |= $action; - - $tic = new FTNTic; - switch ($action) { case self::I_SEND: + $tic = new FTNTic; $this->file = $tic->generate($ao,$fo); - $this->file_type = self::IS_TIC; $this->file_name = sprintf('%s.tic',sprintf('%08x',$fo->id)); $this->file_size = strlen($this->file); $this->file_mtime = $fo->created_at->timestamp; @@ -29,6 +27,9 @@ class Tic extends Item default: throw new \Exception('Unknown action: '.$action); } + + $this->action = $action; + $this->type = self::IS_TIC; } public function read(int $start,int $length): string diff --git a/app/Classes/Protocol.php b/app/Classes/Protocol.php index 1d0ad60..2d61b97 100644 --- a/app/Classes/Protocol.php +++ b/app/Classes/Protocol.php @@ -17,12 +17,12 @@ abstract class Protocol private const LOGKEY = 'P--'; + /* CONSTS */ + // Return constants protected const OK = 0; - protected const EOF = -1; protected const TIMEOUT = -2; protected const RCDO = -3; - protected const GCOUNT = -4; protected const ERROR = -5; // Our sessions Types @@ -33,24 +33,57 @@ abstract class Protocol protected const MAX_PATH = 1024; - /* 9 most right bits are zeros */ - private const O_BASE = 9; /* First 9 bits are protocol */ - protected const O_NRQ = (1<system->addresses->count()) - throw new \Exception('We dont have any FTN addresses assigned'); + if ($o && ! $o->system->akas->count()) + throw new \Exception('We dont have any ACTIVE FTN addresses assigned'); $this->setup = $o; } @@ -105,7 +149,6 @@ abstract class Protocol switch ($key) { case 'ls_SkipGuard': /* double-skip protection on/off */ case 'rxOptions': /* Options from ZRINIT header */ - case 'socket_error': return $this->comms[$key] ?? 0; case 'ls_rxAttnStr': @@ -125,7 +168,6 @@ abstract class Protocol case 'ls_rxAttnStr': case 'ls_SkipGuard': case 'rxOptions': - case 'socket_error': $this->comms[$key] = $value; break; @@ -134,6 +176,55 @@ abstract class Protocol } } + /* Capabilities are what we negotitate with the remote and are valid for the session */ + + /** + * Clear a capability bit + * + * @param int $cap (F_*) + * @param int $val (O_*) + * @return void + */ + public function capClear(int $cap,int $val): void + { + if (! array_key_exists($cap,$this->capability)) + $this->capability[$cap] = 0; + + $this->capability[$cap] &= ~$val; + } + + /** + * Get a session bit (SE_*) + * + * @param int $cap (F_*) + * @param int $val (O_*) + * @return bool + */ + protected function capGet(int $cap,int $val): bool + { + if (! array_key_exists($cap,$this->capability)) + $this->capability[$cap] = 0; + + if ($val === self::O_WE) + return $this->capGet($cap,self::O_WANT) && $this->capGet($cap,self::O_THEY); + + return $this->capability[$cap] & $val; + } + + /** + * Set a session bit (SE_*) + * + * @param int $cap (F_*) + * @param int $val (O_*) + */ + protected function capSet(int $cap,int $val): void + { + if (! array_key_exists($cap,$this->capability) || $val === 0) + $this->capability[$cap] = 0; + + $this->capability[$cap] |= $val; + } + /** * We got an error, close anything we are have open * @@ -162,22 +253,40 @@ abstract class Protocol if ($pid === -1) throw new SocketException(SocketException::CANT_ACCEPT,'Could not fork process'); - Log::debug(sprintf('%s:= End [%d]',self::LOGKEY,$pid)); - // Parent return ready for next connection return $pid; } + /* O_* determine what features processing is availabile */ + + /** + * Clear an option bit (O_*) + * + * @param int $key + * @return void + */ protected function optionClear(int $key): void { $this->options &= ~$key; } + /** + * Get an option bit (O_*) + * + * @param int $key + * @return int + */ protected function optionGet(int $key): int { return ($this->options & $key); } + /** + * Set an option bit (O_*) + * + * @param int $key + * @return void + */ protected function optionSet(int $key): void { $this->options |= $key; @@ -197,12 +306,12 @@ abstract class Protocol $addresses = $addresses->unique(); - Log::debug(sprintf('%s: - Presenting limited AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(','))); + Log::debug(sprintf('%s:- Presenting limited AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(','))); } else { $addresses = $this->setup->system->addresses; - Log::debug(sprintf('%s: - Presenting ALL our AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(','))); + Log::debug(sprintf('%s:- Presenting ALL our AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(','))); } return $addresses; @@ -214,19 +323,18 @@ abstract class Protocol * @param int $type * @param SocketClient $client * @param Address|null $o - * @return int + * @return void * @throws \Exception */ - public function session(int $type,SocketClient $client,Address $o=NULL): int + public function session(int $type,SocketClient $client,Address $o=NULL): void { if ($o->exists) Log::withContext(['ftn'=>$o->ftn]); - Log::debug(sprintf('%s:+ Start [%d]',self::LOGKEY,$type)); - // This sessions options $this->options = 0; $this->session = 0; + $this->capability = []; // Our files that we are sending/receive $this->send = new Send; @@ -240,7 +348,7 @@ abstract class Protocol // If we are connecting to a node if ($o->exists) { - Log::debug(sprintf('%s: + Originating a connection to [%s]',self::LOGKEY,$o->ftn)); + Log::debug(sprintf('%s:+ Originating a connection to [%s]',self::LOGKEY,$o->ftn)); $this->node->originate($o); } else { @@ -255,38 +363,39 @@ abstract class Protocol switch ($type) { /** @noinspection PhpMissingBreakStatementInspection */ case self::SESSION_AUTO: - Log::debug(sprintf('%s: - Trying EMSI',self::LOGKEY)); + Log::debug(sprintf('%s:- Trying EMSI',self::LOGKEY)); $rc = $this->protocol_init(); if ($rc < 0) { Log::error(sprintf('%s:! Unable to start EMSI [%d]',self::LOGKEY,$rc)); - return self::S_REDIAL | self::S_ADDTRY; + return; } case self::SESSION_EMSI: - Log::debug(sprintf('%s: - Starting EMSI',self::LOGKEY)); + Log::debug(sprintf('%s:- Starting EMSI',self::LOGKEY)); $rc = $this->protocol_session(); break; case self::SESSION_BINKP: - Log::debug(sprintf('%s: - Starting BINKP',self::LOGKEY)); + Log::debug(sprintf('%s:- Starting BINKP',self::LOGKEY)); $rc = $this->protocol_session(); break; case self::SESSION_ZMODEM: - Log::debug(sprintf('%s: - Starting ZMODEM',self::LOGKEY)); - $this->client->speed = SocketClient::TCP_SPEED; + Log::debug(sprintf('%s:- Starting ZMODEM',self::LOGKEY)); + $this->client->speed = self::TCP_SPEED; $this->originate = FALSE; + $this->protocol_session(); - return $this->protocol_session(); + return; default: - Log::error(sprintf('%s: ! Unsupported session type [%d]',self::LOGKEY,$type)); + Log::error(sprintf('%s:! Unsupported session type [%d]',self::LOGKEY,$type)); - return self::S_REDIAL | self::S_ADDTRY; + return; } // @todo Unlock outbounds @@ -302,7 +411,7 @@ abstract class Protocol if ($this->optionGet(self::O_HAT)) $rc |= self::S_HOLDA; - Log::info(sprintf('%s: Total: %s - %d:%02d:%02d online, (%d) %lu%s sent, (%d) %lu%s received - %s', + Log::info(sprintf('%s:= Total: %s - %d:%02d:%02d online, (%d) %lu%s sent, (%d) %lu%s received - %s', self::LOGKEY, $this->node->address ? $this->node->address->ftn : 'Unknown', $this->node->session_time/3600, @@ -343,12 +452,12 @@ abstract class Protocol // @todo Optional after session includes mail event // if ($this->node->start_time && $this->setup->cfg('CFG_AFTERMAIL')) {} - - return ($rc & ~self::S_ADDTRY); } + /* SE_* flags determine our session processing status, at any point in time */ + /** - * Clear a session bit + * Clear a session bit (SE_*) * * @param int $key */ @@ -358,7 +467,8 @@ abstract class Protocol } /** - * Get a session bit + * Get a session bit (SE_*) + * * @param int $key * @return bool */ @@ -368,7 +478,7 @@ abstract class Protocol } /** - * Set a session bit (with SE_*) + * Set a session bit (SE_*) * * @param int $key */ @@ -381,6 +491,7 @@ abstract class Protocol * Set our client that we are communicating with * * @param SocketClient $client + * @deprecated use __get()/__set() */ protected function setClient(SocketClient $client): void { diff --git a/app/Classes/Protocol/Binkp.php b/app/Classes/Protocol/Binkp.php index dc79d0c..95d661a 100644 --- a/app/Classes/Protocol/Binkp.php +++ b/app/Classes/Protocol/Binkp.php @@ -2,13 +2,14 @@ namespace App\Classes\Protocol; +use App\Exceptions\FileGrewException; use Carbon\Carbon; -use Exception; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; use League\Flysystem\UnreadableFileEncountered; +use App\Classes\Crypt; use App\Classes\Protocol as BaseProtocol; use App\Classes\Sock\SocketClient; use App\Classes\Sock\SocketException; @@ -18,58 +19,109 @@ final class Binkp extends BaseProtocol { private const LOGKEY = 'PB-'; - /* -- */ - private const BP_PROT = 'binkp'; /* protocol text */ - private const BP_VERSION = '1.1'; /* version implemented */ - private const BP_BLKSIZE = 4096; /* block size */ - private const BP_TIMEOUT = 300; /* session timeout */ - private const MAX_BLKSIZE = 0x7fff; /* max block size */ + /* CONSTS */ - /* options */ - private const O_NO = 0; /* I/They dont a capability? */ - private const O_WANT = 1; /* I want a capability, but can be persuaded */ - private const O_WE = 2; /* We agree on a capability */ - private const O_THEY = 4; /* They want a capability */ - private const O_NEED = 8; /* I want a capability, and wont compromise */ - private const O_EXT = 16; - private const O_YES = 32; + public const PORT = 24554; + /** protocol text **/ + private const PROT = 'binkp'; + /** version implemented */ + private const VERSION = '1.1'; + /** block size - compressed files can only use a blocksize of 0x3fff */ + private const BLOCKSIZE = 0x1000; + /** session timeout secs */ + private const TIMEOUT_TIME = 300; + /** max block size */ + private const MAX_BLKSIZE = 0x7fff; - /* messages */ - private const BPM_NONE = 99; /* No available data */ - private const BPM_DATA = 98; /* Binary data */ - private const BPM_NUL = 0; /* Site information */ - private const BPM_ADR = 1; /* List of addresses */ - private const BPM_PWD = 2; /* Session password */ - private const BPM_FILE = 3; /* File information */ - private const BPM_OK = 4; /* Password was acknowlged (data ignored) */ - private const BPM_EOB = 5; /* End Of Batch (data ignored) */ - private const BPM_GOT = 6; /* File received */ - private const BPM_ERR = 7; /* Misc errors */ - private const BPM_BSY = 8; /* All AKAs are busy */ - private const BPM_GET = 9; /* Get a file from offset */ - private const BPM_SKIP = 10; /* Skip a file (RECEIVE LATER) */ - private const BPM_RESERVED = 11; /* Reserved for later */ - private const BPM_CHAT = 12; /* For chat */ - private const BPM_MIN = self::BPM_NUL; /* Minimal message type value */ - private const BPM_MAX = self::BPM_CHAT; /* Maximal message type value */ + /* FEATURES */ - private const SE_BASE = 1; - private const SE_INIT = (1<getmypid()]); + // @todo If we can use SESSION_EMSI set an object class value that in BINKP of SESSION_BINKP, and move this method to the parent class $this->session(self::SESSION_BINKP,$client,(new Address)); $this->client->close(); - - Log::info(sprintf('%s:= onConnect - Connection closed [%s]',self::LOGKEY,$client->address_remote)); exit(0); } @@ -118,17 +167,16 @@ final class Binkp extends BaseProtocol /** * BINKD handshake * - * @throws Exception + * @throws \Exception */ private function binkp_hs(): void { - if ($this->DEBUG) - Log::debug(sprintf('%s:+ binkp_hs',self::LOGKEY)); + Log::debug(sprintf('%s:+ Starting BINKP handshake',self::LOGKEY)); - if (! $this->originate && ($this->setup->opt_md&self::O_WANT)) { + if (! $this->originate && $this->capGet(self::F_MD,self::O_WANT)) { $random_key = random_bytes(8); $this->md_challenge = md5($random_key,TRUE); - $this->msgs(self::BPM_NUL,sprintf('OPT CRAM-MD5-%s',md5($random_key,FALSE))); + $this->msgs(self::BPM_NUL,sprintf('OPT CRAM-MD5-%s',md5($random_key))); } $this->msgs(self::BPM_NUL,sprintf('SYS %s',$this->setup->system_name)); @@ -137,17 +185,20 @@ 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',config('app.name'),$this->setup->version,self::BP_PROT,self::BP_VERSION)); + sprintf('VER %s-%s %s/%s',config('app.name'),$this->setup->version,self::PROT,self::VERSION)); if ($this->originate) { - $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_cr&self::O_WE) ? ' CRYPT' : '', - ($this->setup->opt_cht&self::O_WANT) ? ' CHAT' : '')); + $opt = $this->capGet(self::F_NOREL,self::O_WANT) ? ' NR' : ''; + $opt .= $this->capGet(self::F_NODUPE,self::O_WANT) ? ' ND' : ''; + $opt .= $this->capGet(self::F_NODUPEA,self::O_WANT) ? ' NDA': ''; + $opt .= $this->capGet(self::F_MULTIBATCH,self::O_WANT) ? ' MB' : ''; + $opt .= $this->capGet(self::F_CHAT,self::O_WANT) ? ' CHAT' : ''; + $opt .= $this->capGet(self::F_COMP,self::O_WANT) ? ' EXTCMD GZ' : ''; + $opt .= $this->capGet(self::F_COMP,self::O_WANT) && $this->capGet(self::F_COMP,self::O_EXT) ? ' BZ2' : ''; + $opt .= $this->capGet(self::F_CRYPT,self::O_WANT) ? ' CRYPT' : ''; + + if (strlen($opt)) + $this->msgs(self::BPM_NUL,sprintf('OPT%s',$opt)); } // If we are originating, we'll show the remote our address in the same network @@ -160,12 +211,12 @@ final class Binkp extends BaseProtocol $addresses = $addresses->unique(); - Log::debug(sprintf('%s: - Presenting limited AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(','))); + Log::debug(sprintf('%s:- Presenting limited AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(','))); } else { $addresses = $this->setup->system->addresses; - Log::debug(sprintf('%s: - Presenting ALL our AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(','))); + Log::debug(sprintf('%s:- Presenting ALL our AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(','))); } $this->msgs(self::BPM_ADR,$this->setup->system->addresses->pluck('ftn')->join(' ')); @@ -175,53 +226,45 @@ final class Binkp extends BaseProtocol /** * @return int */ - private function binkp_hsdone(): int + private function binkp_hsdone(): bool { - if ($this->DEBUG) - Log::debug(sprintf('%s:+ binkp_hsdone',self::LOGKEY)); + Log::debug(sprintf('%s:+ BINKP handshake complete',self::LOGKEY)); - if (($this->setup->opt_nd === self::O_WE) || ($this->setup->opt_nd === self::O_THEY)) - $this->setup->opt_nd = self::O_NO; + // If the remote doesnt provide a password, or in MD5 mode, then we cant use CRYPT + if (! $this->optionGet(self::O_PWD) || (! $this->capGet(self::F_MD,self::O_WE))) + $this->capSet(self::F_CRYPT,self::O_NO); - if (! $this->setup->phone) - $this->setup->phone = '-Unpublished-'; + if ($this->capGet(self::F_CRYPT,self::O_WE)) { + $this->capSet(self::F_CRYPT,self::O_YES); - if (! $this->optionGet(self::O_PWD) || ($this->setup->opt_md != self::O_YES)) - $this->setup->opt_cr = self::O_NO; + Log::info(sprintf('%s:- CRYPT mode initialised',self::LOGKEY)); - if (($this->setup->opt_cr&self::O_WE) && ($this->setup->opt_cr&self::O_THEY)) { - dump('Enable crypting messages'); + if ($this->originate) { + $this->crypt_out = new Crypt($this->node->password); + $this->crypt_in = new Crypt('-'.$this->node->password); - /* - $this->setup->opt_cr = O_YES; - if ( bp->to ) { - init_keys( bp->keys_out, $this->node->password ); - init_keys( bp->keys_in, "-" ); - keys = bp->keys_in; } else { - init_keys( bp->keys_in, $this->node->password ); - init_keys( bp->keys_out, "-" ); - keys = bp->keys_out; + $this->crypt_in = new Crypt($this->node->password); + $this->crypt_out = new Crypt('-'.$this->node->password); } - for( p = $this->node->password; *p; p++ ) { - update_keys( keys, (int) *p ); - } - */ } // @todo Implement max incoming sessions and max incoming session for the same node // We have no mechanism to support chat - if ($this->setup->opt_cht&self::O_WANT) - Log::warning(sprintf('%s: ! We cant do chat',self::LOGKEY)); + if ($this->capGet(self::F_CHAT,self::O_THEY)) + Log::warning(sprintf('%s:/ The remote wants to chat, but we cant do chat',self::LOGKEY)); - if ($this->setup->opt_nd&self::O_WE || ($this->originate && ($this->setup->opt_nr&self::O_WANT) && $this->node->get_versionint() > 100)) - $this->setup->opt_nr |= self::O_WE; + /* + if ($this->capGet(self::F_CHAT,self::O_WE)) + $this->capSet(self::F_CHAT,self::O_YES); + */ - if (($this->setup->opt_cht&self::O_WE) && ($this->setup->opt_cht&self::O_WANT)) - $this->setup->opt_cht = self::O_YES; + // No dupes mode is preferred on BINKP 1.1 + if ($this->capGet(self::F_NODUPE,self::O_WE) || ($this->originate && $this->capGet(self::F_NOREL,self::O_WANT) && $this->node->get_versionint() > 100)) + $this->capSet(self::F_NOREL,self::O_YES); - $this->setup->opt_mb = (($this->node->get_versionint() > 100) || ($this->setup->opt_mb&self::O_WE)) ? self::O_YES : self::O_NO; + $this->capSet(self::F_MULTIBATCH,(($this->node->get_versionint() > 100) || $this->capGet(self::F_MULTIBATCH,self::O_WE)) ? self::O_YES : self::O_NO); if ($this->node->get_versionint() > 100) $this->sessionClear(self::SE_DELAYEOB); @@ -229,26 +272,25 @@ final class Binkp extends BaseProtocol $this->mib = 0; $this->sessionClear(self::SE_INIT); - Log::info(sprintf('%s: - Session: BINKP/%d.%d - NR:%d, ND:%d, MD:%d, MB:%d, CR:%d, CHT:%d', + Log::info(sprintf('%s:= Session: BINKP/%d.%d - NR:%d, ND:%d, NDA:%d, MD:%d, MB:%d, CR:%d, CO:%d, CH:%d', self::LOGKEY, $this->node->ver_major, $this->node->ver_minor, - $this->setup->opt_nr, - $this->setup->opt_nd, - $this->setup->opt_md, - $this->setup->opt_mb, - $this->setup->opt_cr, - $this->setup->opt_cht + $this->capGet(self::F_NOREL,0xFF), + $this->capGet(self::F_NODUPE,0xFF), + $this->capGet(self::F_NODUPEA,0xFF), + $this->capGet(self::F_MD,0xFF), + $this->capGet(self::F_MULTIBATCH,0xFF), + $this->capGet(self::F_CRYPT,0xFF), + $this->capGet(self::F_COMP,0xFF), + $this->capGet(self::F_CHAT,0xFF), )); - return 1; + return TRUE; } private function binkp_init(): int { - if ($this->DEBUG) - Log::debug(sprintf('%s:+ binkp_init',self::LOGKEY)); - $this->sessionSet(self::SE_INIT); $this->is_msg = -1; @@ -256,43 +298,54 @@ final class Binkp extends BaseProtocol $this->error = 0; $this->mqueue = collect(); - $this->rx_ptr = 0; $this->rx_size = -1; $this->tx_buf = ''; - $this->tx_left = 0; - $this->tx_ptr = 0; + $this->tx_left = 0; // @todo can we replace this with strlen($tx_buf)? + $this->tx_ptr = 0; // @todo is this required? - for ($x=0;$xsetup->binkp_options);$x++) { - switch (strtolower($this->setup->binkp_options[$x])) { - case 'p': /* Force password digest */ - $this->setup->opt_md |= self::O_NEED; + // Setup our default capabilities + $this->md_challenge = ''; - case 'm': /* Want password digest */ - $this->setup->opt_md |= self::O_WANT; - break; + // We cant do chat + $this->capSet(self::F_CHAT,self::O_NO); - case 'c': /* Can crypt */ - $this->setup->opt_cr |= self::O_WE; - break; + // Compression + if ($this->setup->optionGet(self::F_COMP,'binkp_options')) + $this->capSet(self::F_COMP,self::O_WANT|self::O_EXT); - case 'd': /* No dupes mode */ - $this->setup->opt_nd |= self::O_NO; /*mode?O_THEY:O_NO;*/ + // CRAM-MD5 session + if ($this->setup->optionGet(self::F_MD,'binkp_options')) { + dump(['we want MD5']); + $this->capSet(self::F_MD,self::O_WANT); - case 'r': /* Non-reliable mode */ - $this->setup->opt_nr |= ($this->originate ? self::O_WANT : self::O_NO); - break; + if ($this->setup->optionGet(self::F_MDFORCE,'binkp_options')) { + dump(['no passwords']); + $this->capSet(self::F_MD,self::O_NEED); + } + } - case 'b': /* Multi-batch mode */ - $this->setup->opt_mb |= self::O_WANT; - break; + // Crypt Mode + if ($this->setup->optionGet(self::F_CRYPT,'binkp_options')) { + dump(['we want CRYPT']); + $this->capSet(self::F_CRYPT,self::O_WANT); + } - case 't': /* Chat - not implemented */ - //$this->setup->opt_cht |= self::O_WANT; - break; + // Multibatch + if ($this->setup->optionGet(self::F_MULTIBATCH,'binkp_options')) + $this->capSet(self::F_MULTIBATCH,self::O_WANT); - default: - Log::error(sprintf('%s: ! binkp_init ERROR - Unknown BINKP option [%s]',self::LOGKEY,$this->setup->binkp_options[$x])); + // Non reliable mode + if ($this->setup->optionGet(self::F_NOREL,'binkp_options')) { + $this->capSet(self::F_NOREL,self::O_WANT); + + // No dupes + if ($this->setup->optionGet(self::F_NODUPE,'binkp_options')) { + $this->capSet(self::F_NODUPE,self::O_WANT); + + // No dupes asymmetric + if ($this->setup->optionGet(self::F_NODUPEA,'binkp_options')) + $this->capSet(self::F_NODUPEA,self::O_WANT); } } @@ -302,187 +355,204 @@ final class Binkp extends BaseProtocol /** * Receive data from the remote * - * @throws Exception + * @throws \Exception */ - private function binkp_recv(): int + private function binkp_recv(): bool { - if ($this->DEBUG) - Log::debug(sprintf('%s:+ binkp_recv',self::LOGKEY)); - $blksz = $this->rx_size === -1 ? BinkpMessage::BLK_HDR_SIZE : $this->rx_size; - - if ($this->DEBUG) - Log::debug(sprintf('%s: - binkp_recv blksize [%d] rx_size [%d] rx_ptr (%d).',self::LOGKEY,$blksz,$this->rx_size,$this->rx_ptr)); + Log::debug(sprintf('%s:+ BINKP receive, reading [%d] chars',self::LOGKEY,$blksz)); if ($blksz !== 0) { try { - $this->rx_buf .= $this->client->read(0,$blksz-$this->rx_ptr); + Log::debug(sprintf('%s:- Reading [%d] chars, buffer currently has [%d] chars',self::LOGKEY,$blksz,strlen($this->rx_buf))); + $rx_buf = $this->client->read(0,$blksz); + Log::debug(sprintf('%s:- Read buffer now has [%d] chars',self::LOGKEY,strlen($rx_buf))); } catch (SocketException $e) { if ($e->getCode() === 11) { // @todo We maybe should count these and abort if there are too many? if ($this->DEBUG) - Log::debug(sprintf('%s: - binkp_recv Socket EAGAIN',self::LOGKEY)); + Log::debug(sprintf('%s:- Got a socket EAGAIN',self::LOGKEY)); - return 1; + return TRUE; } - Log::error(sprintf('%s: - binkp_recv Exception [%s].',self::LOGKEY,$e->getCode())); - $this->socket_error = $e->getMessage(); $this->error = 1; - return 0; + Log::error(sprintf('%s:! Reading we got an EXCEPTION [%d-%s]',self::LOGKEY,$e->getCode(),$e->getMessage())); + return FALSE; } - if (strlen($this->rx_buf) === 0) { + if (strlen($rx_buf) === 0) { // @todo Check that this is correct. - Log::debug(sprintf('%s: - binkp_recv Was the socket closed by the remote?',self::LOGKEY)); + Log::debug(sprintf('%s:- Was the socket closed by the remote?',self::LOGKEY)); $this->error = -2; - return 0; + return FALSE; } - /* - if ($this->setup->opt_cr === self::O_YES ) { - //decrypt_buf( (void *) &bp->rx_buf[bp->rx_ptr], (size_t) readsz, bp->keys_in ); - } - */ + dump(['rx_buf'=>hex_dump($rx_buf)]); + $this->rx_buf .= ($this->capGet(self::F_CRYPT,self::O_YES)) ? $this->crypt_in->decrypt($rx_buf) : $rx_buf; + dump(['rx_buf'=>hex_dump($this->rx_buf)]); } - $this->rx_ptr = strlen($this->rx_buf); - if ($this->DEBUG) - Log::debug(sprintf('%s: - binkp_recv rx_ptr [%d] blksz [%d].',self::LOGKEY,$this->rx_ptr,$blksz)); + Log::debug(sprintf('%s: - binkp_recv rx_buf [%d] blksz [%d].',self::LOGKEY,strlen($this->rx_buf),$blksz)); /* Received complete block */ - if ($this->rx_ptr === $blksz) { + if (strlen($this->rx_buf) === $blksz) { + dump(['A'=>$this->is_msg,'rx_size'=>$this->rx_size,'strlen'=>strlen($this->rx_buf)]); /* Header */ if ($this->rx_size === -1 ) { + dump(['B'=>$this->is_msg]); $this->is_msg = ord(substr($this->rx_buf,0,1)) >> 7; - $this->rx_size = ((ord(substr($this->rx_buf,0,1))&0x7f) << 8 )+ord(substr($this->rx_buf,1,1)); - $this->rx_ptr = 0; + // If compression is used, then this needs to be &0x3f, since the 2nd high bit is the compression flag + // @todo Need to see what happens, if we are receiving a block higher than 0x3fff. Possible? + $this->rx_size = ((ord(substr($this->rx_buf,0,1))&0x7f) << 8)+ord(substr($this->rx_buf,1,1)); - if ($this->DEBUG) - Log::debug(sprintf('%s: - binkp_recv HEADER, is_msg [%d], rx_size [%d]',self::LOGKEY,$this->is_msg,$this->rx_size)); + dump([ + 'is_msg'=>$this->is_msg, + 'alt-is_msg'=>Arr::get(unpack('C',substr($this->rx_buf,0,1)),1,0) >> 7, + 'rx_size'=>$this->rx_size, + 'alt-rx_size'=>Arr::get(unpack('n',substr($this->rx_buf,0,2)),1,0) & 0x7fff, + ]); + Log::debug(sprintf('%s:- BINKP receive HEADER, is_msg [%d], rx_size [%d]',self::LOGKEY,$this->is_msg,$this->rx_size)); if ($this->rx_size === 0) goto ZeroLen; - $rc = 1; + $rc = TRUE; /* Next block */ } else { - ZeroLen: - if ($this->DEBUG) - Log::debug(sprintf('%s: - binkp_recv NEXT BLOCK, is_msg [%d]',self::LOGKEY,$this->is_msg)); - + ZeroLen: if ($this->is_msg) { $this->mib++; /* Handle zero length block */ if ($this->rx_size === 0 ) { - Log::debug(sprintf('%s:- binkp_recv Zero length msg - dropped',self::LOGKEY)); + Log::debug(sprintf('%s:- Received a ZERO length msg - dropped',self::LOGKEY)); $this->rx_size = -1; - $this->rx_ptr = 0; $this->rx_buf = ''; - return 1; + return TRUE; } if ($this->DEBUG) Log::debug(sprintf('%s: - binkp_recv BUFFER [%d]',self::LOGKEY,strlen($this->rx_buf))); - $rc = ord(substr($this->rx_buf,0,1)); + $msg = ord(substr($this->rx_buf,0,1)); - if ($rc > self::BPM_MAX) { - Log::error(sprintf('%s: ! binkp_recv Unknown Message [%s] (%d)',self::LOGKEY,$this->rx_buf,strlen($this->rx_buf))); - $rc = 1; + if ($msg > self::BPM_MAX) { + Log::error(sprintf('%s:! Unknown message received [%d] (%d-%s)',self::LOGKEY,$rc,strlen($this->rx_buf),$this->rx_buf)); + $rc = TRUE; } else { - //DEBUG(('B',2,"rcvd %s '%s'%s",mess[rc],bp->rx_buf + 1,CRYPT(bps))); //@todo CRYPT $data = substr($this->rx_buf,1); - switch ($rc) { - case self::M_ADR: + + switch ($msg) { + case self::BPM_ADR: + Log::debug(sprintf('%s:- ADR:Address [%s]',self::LOGKEY,$data)); $rc = $this->M_adr($data); break; - case self::M_EOB: + case self::BPM_EOB: + Log::debug(sprintf('%s:- EOB:We got an EOB message with [%d] chars in the buffer',self::LOGKEY,strlen($data))); $rc = $this->M_eob($data); break; - case self::M_NUL: + case self::BPM_NUL: + Log::debug(sprintf('%s:- NUL:Message [%s]',self::LOGKEY,$data)); $rc = $this->M_nul($data); break; - case self::M_PWD: + case self::BPM_PWD: + Log::debug(sprintf('%s:- PWD:We got a password [%s]',self::LOGKEY,$data)); $rc = $this->M_pwd($data); break; - case self::M_ERR: + case self::BPM_ERR: + Log::debug(sprintf('%s:- ERR:We got an error [%s]',self::LOGKEY,$data)); $rc = $this->M_err($data); break; - case self::M_FILE: + case self::BPM_FILE: + Log::debug(sprintf('%s:- FIL:We are receiving a file [%s]',self::LOGKEY,$data)); $rc = $this->M_file($data); break; - case self::M_GET: + case self::BPM_GET: + Log::debug(sprintf('%s:- GET:We are sending a file [%s]',self::LOGKEY,$data)); $rc = $this->M_get($data); break; - case self::M_GOTSKIP: + 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); break; - case self::M_OK: + case self::BPM_OK: + Log::debug(sprintf('%s:- OK:Got an OK [%s]',self::LOGKEY,$data)); $rc = $this->M_ok($data); break; - case self::M_CHAT: + case self::BPM_CHAT: + Log::debug(sprintf('%s:- CHT:Remote sent a message [%s]',self::LOGKEY,$data)); $rc = $this->M_chat($data); break; default: - Log::error(sprintf('%s: ! binkp_recv Command not implemented [%d]',self::LOGKEY,$rc)); - $rc = 1; + Log::error(sprintf('%s:! BINKP command not implemented [%d]',self::LOGKEY,$rc)); + $rc = TRUE; } } } else { + dump('we need to write'); if ($this->recv->fd) { try { - $rc = $this->recv->write($this->rx_buf); + dump('write...'); + $this->recv->write($this->rx_buf); - } catch (Exception $e) { - Log::error(sprintf('%s: ! %s',self::LOGKEY,$e->getMessage())); + } catch (FileGrewException $e) { + // Retry the file without compression + Log::error(sprintf('%s:! %s',self::LOGKEY,$e->getMessage())); + $this->msgs(self::BPM_GET,sprintf('%s %ld NZ',$this->recv->name_size_time,$this->recv->filepos)); + + } catch (\Exception $e) { + Log::error(sprintf('%s:! %s',self::LOGKEY,$e->getMessage())); $this->recv->close(); $this->msgs(self::BPM_SKIP,$this->recv->name_size_time); - $rc = 1; } - if ($this->recv->filepos === $this->recv->size) { - Log::info(sprintf('%s: - Finished receiving file [%s] with size [%d]',self::LOGKEY,$this->recv->name,$this->recv->size)); + $rc = TRUE; - $this->msgs(self::BPM_GOT,$this->recv->name_size_time); + dump('write done...'); + + if ($this->recv->filepos === $this->recv->size) { + dump('all got...'); + Log::info(sprintf('%s:- Finished receiving file [%s] with size [%d]',self::LOGKEY,$this->recv->name,$this->recv->size)); + + $this->msgs(self::BPM_GOTSKIP,$this->recv->name_size_time); $this->recv->close(); - $rc = 1; } } else { - Log::critical(sprintf('%s: - binkp_recv Ignoring data block', self::LOGKEY)); - $rc = 1; + Log::critical(sprintf('%s:- Ignoring data block, we dont have a received FD open?', self::LOGKEY)); + $rc = TRUE; } } - $this->rx_ptr = 0; + dump('next...'); $this->rx_size = -1; } + dump(['buf empty...'=> $rc]); $this->rx_buf = ''; } else { - $rc = 1; + $rc = TRUE; } if ($this->DEBUG) @@ -492,58 +562,65 @@ final class Binkp extends BaseProtocol } /** - * @throws Exception + * @throws \Exception */ private function binkp_send(): int { - if ($this->DEBUG) - Log::debug(sprintf('%s:+ binkp_send - tx_left [%d]',self::LOGKEY,$this->tx_left)); + Log::debug(sprintf('%s:+ BINKP send, TX buffer has [%d] chars (%d), and [%d] messages queued',self::LOGKEY,strlen($this->tx_buf),$this->tx_left,$this->mqueue->count())); if ($this->tx_left === 0 ) { /* tx buffer is empty */ - $this->tx_ptr = $this->tx_left = 0; - - if ($this->DEBUG) - Log::debug(sprintf('%s: - binkp_send msgs [%d]',self::LOGKEY,$this->mqueue->count())); + $this->tx_ptr = 0; if ($this->mqueue->count()) { /* there are unsent messages */ - while ($msg = $this->mqueue->shift()) { - if (($msg->len+$this->tx_left) > self::MAX_BLKSIZE) { - break; - } + while ($msg=$this->mqueue->shift()) { + if ($msg instanceof BinkpMessage) { + if (($msg->len+$this->tx_left) > self::MAX_BLKSIZE) { + Log::alert(sprintf('%s:! MSG [%d] would overflow our buffer [%d]',self::LOGKEY,$msg->len,$this->tx_left)); + break; + } - $this->tx_buf .= $msg->msg; - $this->tx_left += $msg->len; + Log::debug(sprintf('%s:- TX buffer empty, adding [%d] chars from the queue',self::LOGKEY,$msg->len)); + $this->tx_buf .= $msg->msg; + $this->tx_left += $msg->len; + + } else { + $this->tx_buf .= $msg; + $this->tx_left += strlen($msg); + } } } elseif ($this->sessionGet(self::SE_SENDFILE) && $this->send->fd && (! $this->sessionGet(self::SE_WAITGET))) { $data = ''; try { - $data = $this->send->read(self::BP_BLKSIZE); + $buf = $this->send->read(self::BLOCKSIZE); } catch (UnreadableFileEncountered) { $this->send->close(FALSE); $this->sessionClear(self::SE_SENDFILE); - } catch (Exception $e) { - Log::error(sprintf('%s: ! binkp_send unexpected ERROR [%s]',self::LOGKEY,$e->getMessage())); + } catch (\Exception $e) { + Log::error(sprintf('%s:! BINKP send unexpected ERROR [%s]',self::LOGKEY,$e->getMessage())); } - if ($data) { - $this->tx_buf .= BinkpMessage::mkheader(strlen($data)); - $this->tx_buf .= $data; + if ($buf) { + $data = BinkpMessage::mkheader(strlen($buf)); + $data .= $buf; - /* - if ($this->setup->opt_cr === self::O_YES) { - encrypt_buf($this->tx_buf,($data + BinkpMessage::BLK_HDR_SIZE),$this->keys_out); + if ($this->capGet(self::F_CRYPT,self::O_YES)) { + $enc = $this->crypt_out->encrypt($data); + + $this->tx_buf .= $enc; + $this->tx_left = strlen($enc); + + } else { + $this->tx_buf .= $data; + $this->tx_left = strlen($buf)+BinkpMessage::BLK_HDR_SIZE; } - */ - - $this->tx_left = strlen($data)+BinkpMessage::BLK_HDR_SIZE; } - // @todo should this be less than BP_BLKSIZE? Since a read could return a blocksize and it could be the end of the file? - if (($data < self::BP_BLKSIZE) && ($this->send->filepos === $this->send->size)) { + // @todo should this be less than BLOCKSIZE? Since a read could return a blocksize and it could be the end of the file? + if ($this->send->filepos === $this->send->size) { $this->sessionSet(self::SE_WAITGOT); $this->sessionClear(self::SE_SENDFILE); } @@ -551,20 +628,17 @@ final class Binkp extends BaseProtocol } else { try { - if ($this->DEBUG) - Log::debug(sprintf('%s:+ sending - tx_ptr [%d] tx_buf [%d]',self::LOGKEY,$this->tx_ptr,strlen($this->tx_buf))); + Log::debug(sprintf('%s:- Sending [%d] chars to remote: tx_buf [%d], tx_ptr [%d]',self::LOGKEY,$this->tx_left,strlen($this->tx_buf),$this->tx_ptr)); + $rc = $this->client->send(substr($this->tx_buf,$this->tx_ptr,$this->tx_left),self::TIMEOUT_TIME); + Log::info(sprintf('%s:- Sent [%d] chars to remote',self::LOGKEY,$rc)); - $rc = $this->client->send(substr($this->tx_buf,$this->tx_ptr,$this->tx_left),self::BP_TIMEOUT); - - if ($this->DEBUG) - Log::debug(sprintf('%s:+ sent [%d]',self::LOGKEY,$rc)); - - } catch (Exception $e) { - if ($e->getCode() === 11) + } catch (\Exception $e) { + if ($e->getCode() === 11) { + Log::error(sprintf('%s:! Got a socket EAGAIN',self::LOGKEY)); return 1; + } - $this->socket_error = $e->getMessage(); - Log::error(sprintf('%s:! binkp_send - ERROR [%s]',self::LOGKEY,$e->getMessage())); + Log::error(sprintf('%s:! Sending we got an EXCEPTION [%d-%s]',self::LOGKEY,$e->getCode(),$e->getMessage())); return 0; } @@ -577,26 +651,22 @@ final class Binkp extends BaseProtocol } } - if ($this->DEBUG) - Log::debug(sprintf('%s:= binkp_send [1]',self::LOGKEY)); - return 1; } private function file_parse(string $str): ?array { - if ($this->DEBUG) - Log::debug(sprintf('%s:+ file_parse [%s]',self::LOGKEY,$str)); - $name = $this->strsep($str,' '); $size = (int)$this->strsep($str,' '); $time = (int)$this->strsep($str,' '); $offs = (int)$this->strsep($str,' '); + $flags = $this->strsep($str,' '); if ($name && $size && $time) { return [ 'file'=>['name'=>$name,'size'=>$size,'mtime'=>$time], - 'offs'=>$offs + 'offs'=>$offs, + 'flags'=>$flags, ]; } @@ -612,58 +682,64 @@ final class Binkp extends BaseProtocol */ private function msgs(string $id,string $msg_body): void { - if ($this->DEBUG) - Log::debug(sprintf('%s:+ msgs [%d:%s]',self::LOGKEY,$id,$msg_body)); + Log::debug(sprintf('%s:+ Queueing message to remote [%d:%s]',self::LOGKEY,$id,$msg_body)); - $this->mqueue->push(new BinkpMessage($id,$msg_body)); + $msg = new BinkpMessage($id,$msg_body); - /* - if ($this->setup->opt_cr === self::O_YES) { - //$this->encrypt_buf($this->bps->mqueue[$this->nmsgs]->msg,$this->bps->mqueue[$this->nmsgs]->len,$this->bps->keys_out); + // If encryption is enabled, we need to queue the encrypted version of the message + // @todo rework this so queue only has data, not objects + if ($this->capGet(self::F_CRYPT,self::O_YES)) { + $enc = $this->crypt_out->encrypt($msg->msg); + $this->mqueue->push($enc); + + } else { + $this->mqueue->push($msg); } - */ $this->mib++; } /** - * @throws Exception + * @throws \Exception */ - private function M_adr(string $buf): int + private function M_adr(string $buf): bool { - Log::debug(sprintf('%s:+ M_adr [%s]',self::LOGKEY,$buf)); - $buf = $this->skip_blanks($buf); $rc = 0; - while(($rem_aka = $this->strsep($buf,' '))) { - Log::info(sprintf('%s: - Parsing AKA [%s]',self::LOGKEY,$rem_aka)); - + while ($rem_aka=$this->strsep($buf,' ')) { try { - if (! ($o = Address::findFTN($rem_aka))) { - Log::debug(sprintf('%s: ? AKA is UNKNOWN [%s]',self::LOGKEY,$rem_aka)); + if (! ($o=Address::findFTN($rem_aka,FALSE,NULL,TRUE))) { + Log::alert(sprintf('%s:? AKA is UNKNOWN [%s]',self::LOGKEY,$rem_aka)); $this->node->ftn_other = $rem_aka; continue; + + } else if (! $o->active) { + Log::alert(sprintf('%s:/ AKA is not active [%s], ignoring',self::LOGKEY,$rem_aka)); + continue; + + } else { + Log::info(sprintf('%s:- Got AKA [%s]',self::LOGKEY,$rem_aka)); } - } catch (Exception $e) { - Log::error(sprintf('%s: ! AKA is INVALID [%s] (%s)',self::LOGKEY,$rem_aka,$e->getMessage())); + } catch (\Exception $e) { + Log::error(sprintf('%s:! AKA is INVALID [%s] (%s)',self::LOGKEY,$rem_aka,$e->getMessage())); $this->msgs(self::BPM_ERR,sprintf('Bad address %s',$rem_aka)); $this->rc = self::S_FAILURE; - return 0; + return FALSE; } // Check if the remote has our AKA if ($this->setup->system->addresses->pluck('ftn')->search($rem_aka) !== FALSE) { - Log::error(sprintf('%s: ! AKA is OURS [%s]',self::LOGKEY,$rem_aka)); + Log::error(sprintf('%s:! Remote\'s AKA is mine [%s]?',self::LOGKEY,$rem_aka)); - $this->msgs(self::BPM_ERR,sprintf('Sorry that is my AKA [%s]',$rem_aka)); + $this->msgs(self::BPM_ERR,sprintf('Sorry that is my AKA [%s], who are you?',$rem_aka)); $this->rc = self::S_FAILURE; - return 0; + return FALSE; } // @todo lock nodes @@ -673,35 +749,48 @@ final class Binkp extends BaseProtocol } if ($rc === 0) { - Log::error(sprintf('%s: ! All AKAs [%d] are busy',self::LOGKEY,$this->node->aka_num)); + Log::error(sprintf('%s:! All AKAs [%d] are busy',self::LOGKEY,$this->node->aka_num)); - $this->msgs( self::BPM_BSY,'All AKAs are busy'); - $this->rc = ($this->originate ? self::S_REDIAL|self::S_ADDTRY : self::S_BUSY); + $this->msgs( self::BPM_BSY,'All AKAs are busy, nothing to do :('); + $this->rc = self::S_BUSY; - return 0; + return FALSE; } if ($this->originate) { if (! $this->node->originate_check()) { - Log::error(sprintf('%s: ! We didnt get who we called?',self::LOGKEY)); + Log::error(sprintf('%s:! We didnt get who we called?',self::LOGKEY)); $this->msgs( self::BPM_ERR,'Sorry, you are not who I expected'); - $this->rc = self::S_FAILURE|self::S_ADDTRY; + $this->rc = self::S_FAILURE; return 0; } + // Add our mail to the queue if we have authenticated + if ($this->node->aka_authed) + foreach ($this->node->aka_remote_authed as $ao) { + $this->send->mail($ao); + $this->send->files($ao); + } + + $this->msgs(self::BPM_NUL,sprintf('TRF %lu %lu',$this->send->mail_size,$this->send->files_size)); + if ($this->md_challenge) { + Log::info(sprintf('%s:! Sending MD5 challenge',self::LOGKEY)); $this->msgs(self::BPM_PWD,sprintf('CRAM-MD5-%s',$this->node->get_md5chal($this->md_challenge))); - } elseif ($this->setup->opt_md === self::O_YES) { + } elseif ($this->capGet(self::F_MD,self::O_NEED)) { + Log::error(sprintf('%s:! Node wants plaintext, but we insist on MD5 challenges',self::LOGKEY)); + $this->msgs(self::BPM_ERR,'Can\'t use plaintext password'); - $this->rc = self::S_FAILURE|self::S_ADDTRY; + $this->rc = self::S_FAILURE; return 0; } else { - $this->msgs(self::BPM_PWD,$this->node->password); + Log::info(sprintf('%s:! Sending plain text password',self::LOGKEY)); + $this->msgs(self::BPM_PWD,$this->node->password ?: ''); } } @@ -714,34 +803,33 @@ final class Binkp extends BaseProtocol if (! $this->originate) $this->msgs(self::BPM_ADR,$this->our_addresses()->pluck('ftn')->join(' ')); - return 1; + return TRUE; } - private function M_chat(string $buf): int + private function M_chat(string $buf): bool { - Log::debug(sprintf('%s:+ M_chat [%s]',self::LOGKEY,$buf)); - - if ($this->setup->opt_cht === self::O_YES) { - Log::error(sprintf('%s: ! We cannot do chat',self::LOGKEY)); + if ($this->capGet(self::F_CHAT,self::O_YES)) { + Log::error(sprintf('%s:! We cannot do chat',self::LOGKEY)); } else { - Log::error(sprintf('%s: ! We got a chat message, but chat is disabled',self::LOGKEY)); + Log::error(sprintf('%s:! We got a chat message, but chat is disabled (%s)',self::LOGKEY,strlen($buf)),['buf'=>$buf]); } - return 1; + return TRUE; } /** * We received EOB from the remote. * - * @throws Exception + * @throws \Exception */ - private function M_eob(string $buf): int + private function M_eob(string $buf): bool { - Log::debug(sprintf('%s:+ M_eob [%s]',self::LOGKEY,$buf)); + if ($this->recv->fd) { + Log::info(sprintf('%s:= Closing receiving file.',self::LOGKEY)); - if ($this->recv->fd) $this->recv->close(); + } $this->sessionSet(self::SE_RECVEOB); $this->sessionClear(self::SE_DELAYEOB); @@ -750,37 +838,38 @@ final class Binkp extends BaseProtocol // Add our mail to the queue if we have authenticated if ($this->node->aka_authed) foreach ($this->node->aka_remote_authed as $ao) { - Log::debug(sprintf('%s: - M_eob Checking for any new mail to [%s]',self::LOGKEY,$ao->ftn)); + Log::debug(sprintf('%s:- Checking for any new mail and files to [%s]',self::LOGKEY,$ao->ftn)); $this->send->mail($ao); $this->send->files($ao); } + Log::debug(sprintf('%s:- We have [%d] items to send to [%s]',self::LOGKEY,$this->send->total_count,$ao->ftn)); if ($this->send->total_count) $this->sessionClear(self::SE_NOFILES|self::SE_SENTEOB); } - return 1; + return TRUE; } /** - * @throws Exception + * @throws \Exception */ - private function M_err(string $buf): int + private function M_err(string $buf): bool { - Log::debug(sprintf('%s:+ M_err [%s]',self::LOGKEY,$buf)); + Log::error(sprintf('%s:! We got an error, there are [%d] chars in the buffer (%s)',self::LOGKEY,strlen($buf),$buf)); $this->error_close(); - $this->rc = ($this->originate ? (self::S_REDIAL|self::S_ADDTRY) : self::S_BUSY); + $this->rc = self::S_FAILURE; - return 0; + return TRUE; } /** - * @throws Exception + * @throws \Exception */ - private function M_file(string $buf): int + private function M_file(string $buf): bool { - Log::debug(sprintf('%s:+ M_file [%s]',self::LOGKEY,$buf)); + Log::info(sprintf('%s:+ About to receive a file [%s]',self::LOGKEY,$buf)); if ($this->sessionGet(self::SE_SENTEOB|self::SE_RECVEOB)) $this->sessionClear(self::SE_SENTEOB); @@ -790,69 +879,83 @@ final class Binkp extends BaseProtocol if ($this->recv->fd) $this->recv->close(); - 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"',$buf)); + 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)); if ($this->sessionGet(self::SE_SENDFILE)) $this->send->close(FALSE); - $this->rc = ($this->originate ? self::S_REDIAL|self::S_ADDTRY : self::S_BUSY); + $this->rc = self::S_FAILURE; - return 0; + return FALSE; } + dump([ + 'name'=>$this->recv->name, + 'file.name'=>Arr::get($file,'file.name'), + 'mtime'=>$this->recv->mtime, + 'file.mtime'=>Arr::get($file,'file.mtime'), + 'size'=>$this->recv->size, + 'file.size'=>Arr::get($file,'file.size'), + 'filepos'=>$this->recv->filepos, + 'offs'=>$file['offs'], + ]); // In NR mode, when we got -1 for the file offsite, the reply to our get will confirm our requested offset. if ($this->recv->name && ! strncasecmp(Arr::get($file,'file.name'),$this->recv->name,self::MAX_PATH) && $this->recv->mtime === Arr::get($file,'file.mtime') && $this->recv->size === Arr::get($file,'file.size') && $this->recv->filepos === $file['offs']) { - $this->recv->open($this->node->address,$file['offs']<0); + $this->recv->open($this->node->address,$file['offs']<0,$file['flags']); + dump(['file opened']); - return 1; + return TRUE; } $this->recv->new($file['file']); try { - switch ($this->recv->open($this->node->address,$file['offs']<0)) { + switch ($this->recv->open($this->node->address,$file['offs']<0,$file['flags'])) { case self::FOP_ERROR: - Log::error(sprintf('%s: ! File ERROR',self::LOGKEY)); + Log::error(sprintf('%s:! File ERROR',self::LOGKEY)); case self::FOP_SUSPEND: - Log::info(sprintf('%s: - File Suspended',self::LOGKEY)); + case self::FOP_SKIP: + Log::info(sprintf('%s:- File Skipped',self::LOGKEY)); $this->msgs(self::BPM_SKIP,$this->recv->name_size_time); - break; - - case self::FOP_SKIP: - Log::info(sprintf('%s: - File Skipped',self::LOGKEY)); - $this->msgs(self::BPM_GOT,$this->recv->name_size_time); + // Close the file, since we are skipping it. + $this->recv->close(); break; + case self::FOP_GOT: + Log::info(sprintf('%s:- File skipped, we already have it',self::LOGKEY)); + $this->msgs(self::BPM_GOTSKIP,$this->recv->name_size_time); + + // Close the file, since we already have it. + $this->recv->close(); + + break; + + case self::FOP_CONT: + Log::debug(sprintf('%s:- Continuing file [%s] from (%ld)',self::LOGKEY,$this->recv->name,$file['offs'])); + case self::FOP_OK: - Log::debug(sprintf('%s: - Getting file from [%d]',self::LOGKEY,$file['offs'])); + Log::debug(sprintf('%s:- Getting file from offset [%ld]',self::LOGKEY,$file['offs'])); + $this->msgs(self::BPM_GET,sprintf('%s %ld',$this->recv->name_size_time,($file['offs'] < 0) ? 0 : $file['offs'])); - if ($file['offs'] != -1) { - if (! ($this->setup->opt_nr&self::O_THEY)) { - $this->setup->opt_nr |= self::O_THEY; - } + if ((int)$file['offs'] !== -1) { + if (! $this->capGet(self::F_NOREL,self::O_THEY)) + $this->capSet(self::F_NOREL,self::O_THEY); break; } - - case self::FOP_CONT: - Log::debug(sprintf('%s: - Continuing file [%s] (%ld)',self::LOGKEY,$this->recv->name,$file['offs'])); - - $this->msgs(self::BPM_GET,sprintf('%s %ld',$this->recv->name_size_time,($file['offs'] < 0) ? 0 : $file['offs'])); - - break; } - } catch (Exception $e) { - Log::error(sprintf('%s: ! File Open ERROR [%s]',self::LOGKEY,$e->getMessage())); + } catch (\Exception $e) { + Log::error(sprintf('%s:! File Open ERROR [%s]',self::LOGKEY,$e->getMessage())); $this->msgs(self::BPM_SKIP,$this->recv->name_size_time); @@ -861,17 +964,17 @@ final class Binkp extends BaseProtocol $this->recv->close(); } - return 1; + return TRUE; } /** - * @throws Exception + * @throws \Exception */ - private function M_get(string $buf): int + private function M_get(string $buf): bool { - Log::debug(sprintf('%s:+ M_get [%s]',self::LOGKEY,$buf)); + Log::debug(sprintf('%s:+ Sending file [%s]',self::LOGKEY,$buf)); - if ($file = $this->file_parse($buf)) { + if ($file=$this->file_parse($buf)) { if ($this->sessionGet(self::SE_SENDFILE) && $this->send->sendas && ! strncasecmp(Arr::get($file,'file.name'),$this->send->sendas,self::MAX_PATH) @@ -879,7 +982,7 @@ final class Binkp extends BaseProtocol && $this->send->size === Arr::get($file,'file.size')) { if (! $this->send->seek($file['offs'])) { - Log::error(sprintf('%s: ! Cannot send file from requested offset [%d]',self::LOGKEY,$file['offs'])); + Log::error(sprintf('%s:! Cannot send file from requested offset [%d]',self::LOGKEY,$file['offs'])); $this->msgs(self::BPM_ERR,'Can\'t send file from requested offset'); $this->send->close(FALSE); @@ -887,29 +990,29 @@ final class Binkp extends BaseProtocol } else { $this->sessionClear(self::SE_WAITGET); - Log::debug(sprintf('%s:Sending packet [%s] as [%s]',self::LOGKEY,$this->send->name,$this->send->sendas)); - $this->msgs(self::BPM_FILE,sprintf('%s %lu %ld %lu',$this->send->sendas,$this->send->size,$this->send->mtime,$file['offs'])); + Log::debug(sprintf('%s:Sending file [%s] as [%s]',self::LOGKEY,$this->send->name,$this->send->sendas)); + $this->msgs(self::BPM_FILE,sprintf('%s %lu %ld %lu %s',$this->send->sendas,$this->send->size,$this->send->mtime,$file['offs'],$file['flags'])); } } else { - Log::error(sprintf('%s: ! M_got for unknown file [%s]',self::LOGKEY,$buf)); + Log::error(sprintf('%s:! Remote requested an unknown file [%s]',self::LOGKEY,$buf)); } } else { - Log::error(sprintf('%s: - UNPARSABLE file info [%s]',self::LOGKEY,$buf)); + Log::error(sprintf('%s:! UNPARSABLE file info [%s]',self::LOGKEY,$buf)); } - return 1; + return TRUE; } /** * M_GOT/M_SKIP commands * * @param string $buf - * @return int - * @throws Exception + * @return bool + * @throws \Exception */ - private function M_gotskip(string $buf):int + private function M_gotskip(string $buf): bool { Log::debug(sprintf('%s:+ M_gotskip [%s]',self::LOGKEY,$buf)); @@ -925,7 +1028,7 @@ final class Binkp extends BaseProtocol $this->sessionClear(self::SE_SENDFILE); $this->send->close(TRUE); - return 1; + return TRUE; } if ($this->sessionGet(self::SE_WAITGOT)) { @@ -934,23 +1037,23 @@ final class Binkp extends BaseProtocol $this->send->close(TRUE); } else { - Log::error(sprintf('%s: ! M_got[skip] for unknown file [%s]',self::LOGKEY,$buf)); + Log::error(sprintf('%s:! M_got[skip] for unknown file [%s]',self::LOGKEY,$buf)); } } } else { - Log::error(sprintf('%s: - UNPARSABLE file info [%s]',self::LOGKEY,$buf)); + Log::error(sprintf('%s:! UNPARSABLE file info [%s]',self::LOGKEY,$buf)); } - return 1; + return TRUE; } /** - * @throws Exception + * @throws \Exception */ - private function M_nul(string $buf): int + private function M_nul(string $buf): bool { - Log::debug(sprintf('%s:+ M_nul [%s]',self::LOGKEY,$buf)); + Log::info(sprintf('%s:+ M_NUL [%s]',self::LOGKEY,$buf)); if (! strncmp($buf,'SYS ',4)) { $this->node->system = $this->skip_blanks(substr($buf,4)); @@ -981,7 +1084,7 @@ final class Binkp extends BaseProtocol $comma = substr($comma,0,$c); if (! $comma) { - $this->client->speed = SocketClient::TCP_SPEED; + $this->client->speed = self::TCP_SPEED; } elseif (strtolower(substr($comma,$c+1,1)) === 'k') { $this->client->speed = $spd * 1024; @@ -990,7 +1093,7 @@ final class Binkp extends BaseProtocol $this->client->speed = $spd * 1024 * 1024; } else { - $this->client->speed = SocketClient::TCP_SPEED; + $this->client->speed = self::TCP_SPEED; } } @@ -1030,64 +1133,82 @@ final class Binkp extends BaseProtocol $data = $this->skip_blanks(substr($buf,4)); while ($data && ($p = $this->strsep($data,' '))) { - if (! strcmp($p,'NR')) { - $this->setup->opt_nr |= self::O_WE; - - } elseif (! strcmp($p,'MB')) { - $this->setup->opt_mb |= self::O_WE; + if (! strcmp($p,'MB')) { + Log::info(sprintf('%s:- Remote wants MULTIBATCH mode',self::LOGKEY)); + $this->capSet(self::F_MULTIBATCH,self::O_THEY); } elseif (! strcmp($p,'ND')) { - $this->setup->opt_nd |= self::O_WE; + Log::info(sprintf('%s:- Remote wants NO DUPES mode',self::LOGKEY)); + $this->capSet(self::F_NOREL,self::O_THEY); + $this->capSet(self::F_NODUPE,self::O_THEY); } elseif (! strcmp($p,'NDA')) { - $this->setup->opt_nd |= self::O_EXT; + Log::info(sprintf('%s:- Remote wants NO DUPES ASYMMETRIC mode',self::LOGKEY)); + $this->capSet(self::F_NOREL, self::O_THEY); + $this->capSet(self::F_NODUPE, self::O_THEY); + $this->capSet(self::F_NODUPEA, self::O_THEY); + + } elseif (! strcmp($p,'NR')) { + Log::info(sprintf('%s:- Remote wants NON RELIABLE MODE mode',self::LOGKEY)); + $this->capSet(self::F_NOREL,self::O_THEY); } elseif (! strcmp($p,'CHAT')) { - $this->setup->opt_cht |= self::O_WE; + Log::info(sprintf('%s:- Remote wants CHAT mode',self::LOGKEY)); + $this->capSet(self::F_CHAT,self::O_THEY); } elseif (! strcmp($p,'CRYPT')) { - $this->setup->opt_cr |= self::O_THEY; + Log::info(sprintf('%s:- Remote wants CRYPT mode',self::LOGKEY)); + $this->capSet(self::F_CRYPT,self::O_THEY); - } elseif (! strncmp($p,'CRAM-MD5-',9) && $this->originate && $this->setup->opt_md) { + } elseif (! strcmp($p,'GZ')) { + Log::info(sprintf('%s:- Remote wants GZ compression',self::LOGKEY)); + $this->capSet(self::F_COMP,self::O_THEY); + + } elseif (! strcmp($p,'BZ2')) { + Log::info(sprintf('%s:- Remote wants BZ2 compression',self::LOGKEY)); + $this->capSet(self::F_COMP,self::O_THEY|self::O_EXT); + + } elseif (! strncmp($p,'CRAM-MD5-',9) && $this->originate && $this->capGet(self::F_MD,self::O_WANT)) { if (($x=strlen(substr($p,9))) > 64 ) { - Log::error(sprintf('%s: - M_nul Got TOO LONG [%d] challenge string',self::LOGKEY,$x)); + Log::error(sprintf('%s:! The challenge string is TOO LONG [%d] (%s)',self::LOGKEY,$x,$p)); } else { + Log::info(sprintf('%s:- Remote wants MD5 auth',self::LOGKEY)); $this->md_challenge = hex2bin(substr($p,9)); if ($this->md_challenge) - $this->setup->opt_md |= self::O_THEY; + $this->capSet(self::F_MD,self::O_THEY); } - if (($this->setup->opt_md&(self::O_THEY|self::O_WANT)) === (self::O_THEY|self::O_WANT)) - $this->setup->opt_md = self::O_YES; + if ($this->capGet(self::F_MD,self::O_WE)) + $this->capSet(self::F_MD,self::O_YES); - } else { /* if ( strcmp( p, "GZ" ) || strcmp( p, "BZ2" ) || strcmp( p, "EXTCMD" )) */ - Log::warning(sprintf('%s: - M_nul Got UNSUPPORTED option [%s]',self::LOGKEY,$p)); + } else { + Log::warning(sprintf('%s:/ Ignoring UNSUPPORTED option [%s]',self::LOGKEY,$p)); } } } else { - Log::warning(sprintf('%s: - M_nul Got UNKNOWN NUL [%s]',self::LOGKEY,$buf)); + Log::warning(sprintf('%s:/ M_nul Got UNKNOWN NUL [%s]',self::LOGKEY,$buf)); } - return 1; + return TRUE; } /** * Remote accepted our password * - * @throws Exception + * @throws \Exception */ - private function M_ok(string $buf): int + private function M_ok(string $buf): bool { Log::debug(sprintf('%s:+ M_ok [%s]',self::LOGKEY,$buf)); if (! $this->originate) { - Log::error(sprintf('%s: ! UNEXPECTED M_OK [%s] from remote on incoming call',self::LOGKEY,$buf)); + Log::error(sprintf('%s:! UNEXPECTED M_OK [%s] from remote on incoming call',self::LOGKEY,$buf)); $this->rc = self::S_FAILURE; - return 0; + return FALSE; } $buf = $this->skip_blanks($buf); @@ -1095,63 +1216,51 @@ final class Binkp extends BaseProtocol if ($this->optionGet(self::O_PWD) && $buf) { while (($t = $this->strsep($buf," \t"))) if (strcmp($t,'non-secure') === 0) { - Log::debug(sprintf('%s: - Non Secure',self::LOGKEY)); + Log::debug(sprintf('%s:- NOT secure',self::LOGKEY)); - $this->setup->opt_cr = self::O_NO; + $this->capSet(self::F_CRYPT,self::O_NO); $this->optionClear(self::O_PWD); break; } } - // Add our mail to the queue if we have authenticated - if ($this->node->aka_authed) - foreach ($this->node->aka_remote_authed as $ao) { - $this->send->mail($ao); - $this->send->files($ao); - } - - $this->msgs(self::BPM_NUL,sprintf('TRF %lu %lu',$this->send->mail_size,$this->send->file_size)); - - Log::debug(sprintf('%s:= M_ok',self::LOGKEY)); return $this->binkp_hsdone(); } /** - * @throws Exception + * @throws \Exception */ - private function M_pwd(string $buf): int + private function M_pwd(string $buf): bool { - Log::debug(sprintf('%s:+ M_pwd [%s]',self::LOGKEY,$buf)); - $buf = $this->skip_blanks($buf); $have_CRAM = !strncasecmp($buf,'CRAM-MD5-',9); $have_pwd = $this->optionGet(self::O_PWD); if ($this->originate) { - Log::error(sprintf('%s: ! Unexpected password [%s] from remote on OUTGOING call',self::LOGKEY,$buf)); + Log::error(sprintf('%s:! Unexpected password [%s] from remote on OUTGOING call',self::LOGKEY,$buf)); $this->rc = self::S_FAILURE; - return 0; + return FALSE; } if ($this->md_challenge) { if ($have_CRAM) { // Loop to match passwords $this->node->auth(substr($buf,9),$this->md_challenge); - $this->setup->opt_md |= self::O_THEY; + $this->capSet(self::F_MD,self::O_THEY); - } elseif ($this->setup->opt_md&self::O_NEED) { - Log::error(sprintf('%s: ! Remote doesnt support MD5',self::LOGKEY)); + } elseif ($this->capGet(self::F_MD,self::O_NEED)) { + Log::error(sprintf('%s:! Remote doesnt support MD5, but we want it',self::LOGKEY)); - $this->msgs( self::BPM_ERR,'You must support MD5'); + $this->msgs( self::BPM_ERR,'You must support MD5 auth to talk to me'); $this->rc = self::S_FAILURE; - return 0; + return FALSE; } } - if (! $this->md_challenge || (! $have_CRAM && ! ($this->setup->opt_md&self::O_NEED))) { + if (! $this->md_challenge || (! $have_CRAM && (! $this->capGet(self::F_MD,self::O_NEED)))) { // Loop to match passwords $this->node->auth($buf); } @@ -1159,35 +1268,49 @@ final class Binkp extends BaseProtocol if ($have_pwd) { // If no passwords matched (ie: aka_authed is 0) if (! $this->node->aka_authed) { - Log::error(sprintf('%s: ! Bad password [%s]',self::LOGKEY,$buf)); + Log::error(sprintf('%s:! Bad password [%s]',self::LOGKEY,$buf)); $this->msgs(self::BPM_ERR,'Security violation'); $this->optionSet(self::O_BAD); $this->rc = self::S_FAILURE; - return 0; + return FALSE; } } elseif (! $this->node->aka_authed) { - Log::notice(sprintf('%s: - Remote proposed password for us [%s]',self::LOGKEY,$buf)); + Log::notice(sprintf('%s:= Remote proposed password for us [%s]',self::LOGKEY,$buf)); } - if (($this->setup->opt_md&(self::O_THEY|self::O_WANT )) === (self::O_THEY|self::O_WANT)) - $this->setup->opt_md = self::O_YES; + // We dont use crypt if we dont have an MD5 sessions + if (! $have_pwd || (! $this->capGet(self::F_MD,self::O_YES))) { + Log::notice(sprintf('%s:= CRYPT disabled, since we have no password or not MD5',self::LOGKEY)); + $this->capSet(self::F_CRYPT,self::O_NO); + } - if (!$have_pwd || $this->setup->opt_md != self::O_YES) - $this->setup->opt_cr = self::O_NO; + $opt = ''; - $tmp = sprintf('%s%s%s%s%s%s', - ($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_cht&self::O_WANT) ? ' CHAT' : '', - (! ($this->setup->opt_nd&self::O_WE) != (! ($this->setup->opt_nd&self::O_THEY))) ? ' NDA': '', - (($this->setup->opt_cr&self::O_WE) && ($this->setup->opt_cr&self::O_THEY )) ? ' CRYPT' : ''); + if ($this->capGet(self::F_NOREL,self::O_WE) && $this->capGet(self::F_NODUPE,self::O_WE) && $this->capGet(self::F_NODUPEA,self::O_YES)) + $opt .= ' NDA'; + elseif ($this->capGet(self::F_NOREL,self::O_WE) && $this->capGet(self::F_NODUPE,self::O_WE)) + $opt .= ' ND'; + elseif ($this->capGet(self::F_NOREL,self::O_WE)) + $opt .= ' NR'; - if (strlen($tmp)) - $this->msgs(self::BPM_NUL,sprintf('OPT%s',$tmp)); + $opt .= $this->capGet(self::F_MULTIBATCH,self::O_WE) ? ' MB' : ''; + $opt .= $this->capGet(self::F_CHAT,self::O_WE) ? ' CHAT' : ''; + + if ($this->capGet(self::F_COMP,self::O_WE) && $this->capGet(self::F_COMP,self::O_EXT)) { + $this->comp_mode = 'BZ2'; + $opt .= ' EXTCMD BZ2'; + } elseif ($this->capGet(self::F_COMP,self::O_WE)) { + $this->comp_mode = 'GZ'; + $opt .= ' EXTCMD GZ'; + } + + $opt .= $this->capGet(self::F_CRYPT,self::O_WE) ? ' CRYPT' : ''; + + if (strlen($opt)) + $this->msgs(self::BPM_NUL,sprintf('OPT%s',$opt)); // Add our mail to the queue if we have authenticated if ($this->node->aka_authed) @@ -1196,7 +1319,7 @@ final class Binkp extends BaseProtocol $this->send->files($ao); } - $this->msgs(self::BPM_NUL,sprintf('TRF %lu %lu',$this->send->mail_size,$this->send->file_size)); + $this->msgs(self::BPM_NUL,sprintf('TRF %lu %lu',$this->send->mail_size,$this->send->size)); $this->msgs(self::BPM_OK,sprintf('%ssecure',$have_pwd ? '' : 'non-')); return $this->binkp_hsdone(); @@ -1209,17 +1332,15 @@ final class Binkp extends BaseProtocol } /** - * Setup our BINKP session + * Set up our BINKP session * * @return int - * @throws Exception + * @throws \Exception */ protected function protocol_session(): int { - Log::debug(sprintf('%s:+ protocol_session',self::LOGKEY)); - - if ($this->binkp_init() != self::OK) - return $this->originate ? (self::S_REDIAL|self::S_ADDTRY) : self::S_FAILURE; + if ($this->binkp_init() !== self::OK) + return self::S_FAILURE; $this->binkp_hs(); @@ -1237,14 +1358,19 @@ final class Binkp extends BaseProtocol $this->sessionSet(self::SE_SENDFILE); // NR mode, we wait for an M_GET before sending - if ($this->setup->opt_nr&self::O_WE) { + if ($this->capGet(self::F_NOREL,self::O_WE)) { $this->sessionSet(self::SE_WAITGET); - Log::debug(sprintf('%s: - Waiting for M_GET',self::LOGKEY)); + Log::debug(sprintf('%s:- NR mode, waiting for M_GET',self::LOGKEY)); } $this->msgs(self::BPM_FILE, - sprintf('%s %lu %lu %ld',$this->send->sendas,$this->send->size,$this->send->mtime,$this->sessionGet(self::SE_WAITGET) ? -1 : 0)); + sprintf('%s %lu %lu %ld %s', + $this->send->sendas, + $this->send->size, + $this->send->mtime, + $this->sessionGet(self::SE_WAITGET) ? -1 : 0, + /*$this->send->comp ?:*/ '')); $this->sessionClear(self::SE_SENTEOB); @@ -1275,15 +1401,15 @@ final class Binkp extends BaseProtocol try { if ($this->DEBUG) - Log::debug(sprintf('%s: - Checking if there more data (ttySelect), timeout [%d]',self::LOGKEY,self::BP_TIMEOUT)); + Log::debug(sprintf('%s: - Checking if there more data (ttySelect), timeout [%d]',self::LOGKEY,self::TIMEOUT_TIME)); // @todo we need to catch a timeout if there are no reads/writes - $rc = $this->client->ttySelect($rd,$wd,self::BP_TIMEOUT); + $rc = $this->client->ttySelect($rd,$wd,self::TIMEOUT_TIME); if ($this->DEBUG) Log::debug(sprintf('%s: - ttySelect returned [%d]',self::LOGKEY,$rc)); - } catch (Exception) { + } catch (\Exception) { $this->error_close(); $this->error = -2; @@ -1299,29 +1425,33 @@ final class Binkp extends BaseProtocol break; } - if ($rd && ! $this->binkp_recv()) - break; + if ($rd && ! $this->binkp_recv()) { + Log::info(sprintf('%s:- BINKP finished reading',self::LOGKEY)); - if ($this->DEBUG) - Log::debug(sprintf('%s: - mqueue [%d]',self::LOGKEY,$this->mqueue->count())); - - if (($this->mqueue->count() || $wd) && ! $this->binkp_send() && (! $this->send->total_count)) break; + } + + if (($this->mqueue->count() || $wd) && ! $this->binkp_send() && (! $this->send->total_count)) { + Log::info(sprintf('%s:- BINKP finished sending',self::LOGKEY)); + + break; + } } if ($this->error === -1) - Log::error(sprintf('%s: ! protocol_session TIMEOUT',self::LOGKEY)); - + Log::error(sprintf('%s:! protocol_session TIMEOUT',self::LOGKEY)); elseif ($this->error > 0) - Log::error(sprintf('%s: ! protocol_session Got ERROR [%d]',self::LOGKEY,$this->socket_error)); + Log::error(sprintf('%s:! During our protocol session we got ERROR [%d]',self::LOGKEY,$this->error)); while (! $this->error) { try { + Log::info(sprintf('%s:- BINKP reading [%d]',self::LOGKEY,self::MAX_BLKSIZE)); $buf = $this->client->read(0,self::MAX_BLKSIZE); + Log::info(sprintf('%s:- BINKP got [%d] chars',self::LOGKEY,strlen($buf))); - } catch (Exception $e) { + } catch (\Exception $e) { if ($e->getCode() !== 11) { - Log::debug(sprintf('%s: ? protocol_session Got Exception [%d] (%s)',self::LOGKEY,$e->getCode(),$e->getMessage())); + Log::error(sprintf('%s:! Got an exception [%d] while reading (%s)',self::LOGKEY,$e->getCode(),$e->getMessage())); $this->error = 1; } @@ -1332,10 +1462,11 @@ final class Binkp extends BaseProtocol if (strlen($buf) === 0) break; - Log::warning(sprintf('%s: - Purged (%s) [%d] bytes from input stream',self::LOGKEY,hex_dump($buf),strlen($buf))); + Log::warning(sprintf('%s:- Purged [%d] bytes from input stream (%s) ',self::LOGKEY,strlen($buf),hex_dump($buf))); } - while (! $this->error && ($this->mqueue->count() || $this->tx_left) && $this->binkp_send()); + Log::info(sprintf('%s:- We have [%d] messages and [%d] data left to send',self::LOGKEY,$this->mqueue->count(),strlen($this->tx_left))); + while (! $this->error && ($this->mqueue->count() || $this->tx_left) && $this->binkp_send()) {} return $this->rc; } @@ -1345,7 +1476,8 @@ final class Binkp extends BaseProtocol * * @param string $str * @return string - * @throws Exception + * @throws \Exception + * @todo Can this be replaced with ltrim? */ private function skip_blanks(string $str): string { @@ -1367,8 +1499,14 @@ final class Binkp extends BaseProtocol */ private function strsep(string &$str,string $char): string { - $return = strstr($str,$char,TRUE) ?: $str; - $str = substr($str,strlen($return)+strlen($char)); + if ($x=strpos($str,$char)) { + $return = substr($str,0,$x); + $str = substr($str,$x+1); + + } else { + $return = $str; + $str = ''; + } return $return; } @@ -1378,13 +1516,13 @@ final class Binkp extends BaseProtocol * * @param string $str * @return bool - * @throws Exception + * @throws \Exception */ private function isSpace(string $str):bool { if (strlen($str) > 1) - throw new Exception('String is more than 1 char'); + throw new \Exception('String is more than 1 char'); return $str && in_array($str,[' ',"\n","\r","\v","\f","\t"]); } -} +} \ No newline at end of file diff --git a/app/Classes/Protocol/DNS.php b/app/Classes/Protocol/DNS.php index d3eddc0..38b86f1 100644 --- a/app/Classes/Protocol/DNS.php +++ b/app/Classes/Protocol/DNS.php @@ -32,6 +32,10 @@ final class DNS extends BaseProtocol { private const LOGKEY = 'PD-'; + /* CONSTS */ + + public const PORT = 53; + private const DEFAULT_TTL = 86400; private const TLD = 'ftn'; @@ -249,6 +253,7 @@ final class DNS extends BaseProtocol * @param int $code * @param array $answer * @param array $authority + * @param array $additional * @return bool * @throws \Exception */ diff --git a/app/Classes/Protocol/EMSI.php b/app/Classes/Protocol/EMSI.php index 8d0aff6..72193ab 100644 --- a/app/Classes/Protocol/EMSI.php +++ b/app/Classes/Protocol/EMSI.php @@ -23,24 +23,27 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface use CRCTrait; - private const EMSI_BEG = '**EMSI_'; - private const EMSI_ARGUS1 = '-PZT8AF6-'; - private const EMSI_DAT = self::EMSI_BEG.'DAT'; - private const EMSI_REQ = self::EMSI_BEG.'REQA77E'; - private const EMSI_INQ = self::EMSI_BEG.'INQC816'; - private const EMSI_ACK = self::EMSI_BEG.'ACKA490'; - private const EMSI_NAK = self::EMSI_BEG.'NAKEEC3'; - private const EMSI_HBT = self::EMSI_BEG.'HBTEAEE'; + /* CONSTS */ - private const CR = "\r"; - private const NL = "\n"; - private const DEL = "\x08"; + public const PORT = 60179; + private const EMSI_BEG = '**EMSI_'; + private const EMSI_ARGUS1 = '-PZT8AF6-'; + private const EMSI_DAT = self::EMSI_BEG.'DAT'; + private const EMSI_REQ = self::EMSI_BEG.'REQA77E'; + private const EMSI_INQ = self::EMSI_BEG.'INQC816'; + private const EMSI_ACK = self::EMSI_BEG.'ACKA490'; + private const EMSI_NAK = self::EMSI_BEG.'NAKEEC3'; + private const EMSI_HBT = self::EMSI_BEG.'HBTEAEE'; - private const EMSI_BUF = 8192; - private const TMP_LEN = 1024; + private const CR = "\r"; + private const NL = "\n"; + private const DEL = "\x08"; - private const SM_INBOUND = 0; - private const SM_OUTBOUND = 1; + private const EMSI_BUF = 8192; + private const TMP_LEN = 1024; + + private const SM_INBOUND = 0; + private const SM_OUTBOUND = 1; private const EMSI_HSTIMEOUT = 60; /* Handshake timeout */ private const EMSI_SEQ_LEN = 14; @@ -54,6 +57,8 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface private const EMSI_RESEND_TO = 5; + protected const MO_CHAT = 4; + // Our session status private int $session; @@ -84,10 +89,9 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface if (! parent::onConnect($client)) { Log::withContext(['pid'=>getmypid()]); + // @todo Can this be SESSION_EMSI? if so, set an object class value that in EMSI of SESSION_EMSI, and move this method to the parent class $this->session(self::SESSION_AUTO,$client,(new Address)); $this->client->close(); - - Log::info(sprintf('%s:= onConnect - Connection closed [%s]',self::LOGKEY,$client->address_remote)); exit(0); } @@ -101,7 +105,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface */ private function emsi_banner(): void { - Log::debug(sprintf('%s:+ emsi_banner',self::LOGKEY)); + Log::debug(sprintf('%s:+ Showing EMSI banner',self::LOGKEY)); $banner = 'This is a mail only system - unless you are a mailer, you should disconnect :)'; $this->client->buffer_add(self::EMSI_REQ.str_repeat(self::DEL,strlen(self::EMSI_REQ)).$banner.self::CR); @@ -212,7 +216,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface ); // TRAF - netmail/echomail traffic size (bytes) - $makedata .= sprintf('{TRAF}{%lX %lX}',$this->send->mail_size,$this->send->file_size); + $makedata .= sprintf('{TRAF}{%lX %lX}',$this->send->mail_size,$this->send->size); // MOH# - Mail On Hold - bytes waiting $makedata .= sprintf('{MOH#}{[%lX]}',$this->send->mail_size); @@ -955,6 +959,8 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface */ protected function protocol_session(): int { + // @todo introduce emsi_init() to perform the job of protocol_init. Only needs to be done when we originate a session + Log::debug(sprintf('%s:+ Starting EMSI Protocol Session',self::LOGKEY)); $was_req = 0; @@ -1015,7 +1021,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface // @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->file_size,'b')); + Log::info(sprintf('%s: - We have %lu%s mail, %lu%s files',self::LOGKEY,$this->send->mail_size,'b',$this->send->files_size,'b')); $proto = $this->originate ? $this->node->optionGet(self::P_MASK) : $this->optionGet(self::P_MASK); diff --git a/app/Classes/Protocol/Zmodem.php b/app/Classes/Protocol/Zmodem.php index a31f3dc..0b59664 100644 --- a/app/Classes/Protocol/Zmodem.php +++ b/app/Classes/Protocol/Zmodem.php @@ -70,11 +70,8 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface private const ZRUB1 = 0x6d; //m /* Translate to rubout 0377 */ /* Return codes -- pseudo-characters */ - private const LSZ_OK = self::OK; /* 0 */ - private const LSZ_TIMEOUT = self::TIMEOUT; /* -2 */ - private const LSZ_RCDO = self::RCDO; /* -3 */ - private const LSZ_CAN = self::GCOUNT; /* -4 */ - private const LSZ_ERROR = self::ERROR; /* -5 */ + + private const LSZ_CAN = -4; private const LSZ_NOHEADER = -6; private const LSZ_BADCRC = -7; private const LSZ_XONXOFF = -8; @@ -312,7 +309,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface Log::debug(sprintf('%s:= zmodem_receive ZFIN after INIT, empty batch',self::LOGKEY)); $this->ls_zdonereceiver(); - return self::LSZ_OK; + return self::OK; case self::ZFILE: Log::debug(sprintf('%s: = zmodem_receive ZFILE after INIT',self::LOGKEY)); @@ -324,7 +321,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface $this->ls_zabort(); $this->ls_zdonereceiver(); - return self::LSZ_ERROR; + return self::ERROR; } while (TRUE) { @@ -333,7 +330,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface Log::debug(sprintf('%s:= zmodem_receive ZFIN',self::LOGKEY)); $this->ls_zdonereceiver(); - return self::LSZ_OK; + return self::OK; case self::ZFILE: if (! $this->recv->to_get) { @@ -375,7 +372,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface break; - case self::LSZ_OK: + case self::OK: Log::debug(sprintf('%s: = zmodem_receive OK',self::LOGKEY)); $this->recv->close(); @@ -385,7 +382,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface Log::error(sprintf('%s:! zmodem_receive OTHER [%d]',self::LOGKEY,$rc)); $this->recv->close(); - return self::LSZ_ERROR; + return self::ERROR; } break; @@ -400,7 +397,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface $this->ls_zabort(); $this->ls_zdonereceiver(); - return self::LSZ_ERROR; + return self::ERROR; default: Log::error(sprintf('%s:? zmodem_receive Something strange [%d]',self::LOGKEY,$rc)); @@ -408,13 +405,13 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface $this->ls_zabort(); $this->ls_zdonereceiver(); - return self::LSZ_ERROR; + return self::ERROR; } $rc = $this->ls_zrecvfinfo($frame,1); } - return self::LSZ_OK; + return self::OK; } /** @@ -444,10 +441,10 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface $this->client->buffer_add('OO'); $this->client->buffer_flush(5); - return self::LSZ_OK; + return self::OK; case self::ZNAK: - case self::LSZ_TIMEOUT: + case self::TIMEOUT: $retransmit = 1; break; @@ -512,7 +509,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface $rc = $this->ls_zsendfile($send,$this->ls_SerialNum++,$send->total_count,$send->total_size); switch ($rc) { - case self::LSZ_OK: + case self::OK: $send->close(TRUE); break; @@ -582,7 +579,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface } if (ord($c) === 0) - return self::LSZ_ERROR; + return self::ERROR; return $c&0x7f; } @@ -865,7 +862,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface /* Ok, GOOD */ case ord('O'): $rc = $this->client->read_ch(0); - return self::LSZ_OK; + return self::OK; case self::XON: case self::XOFF: @@ -883,7 +880,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface return $rc; if (self::ZFIN != $rc) - return self::LSZ_OK; + return self::OK; $retransmit = 1; @@ -1014,7 +1011,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface return $this->ls_zsendsinit($attstr); else - return self::LSZ_OK; + return self::OK; /* Return number to peer, he is paranoid */ case self::ZCHALLENGE: @@ -1027,7 +1024,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface /* Send ZRQINIT again */ case self::ZNAK: - case self::LSZ_TIMEOUT: + case self::TIMEOUT: $retransmit = 1; break; @@ -1037,7 +1034,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface Log::debug(sprintf('%s: - ls_zinitsender ZFIN [%d]',self::LOGKEY,$zfins)); if (++$zfins === self::LSZ_TRUSTZFINS) - return self::LSZ_ERROR; + return self::ERROR; break; @@ -1053,7 +1050,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface /* Abort this session -- we trust in ABORT! */ case self::ZABORT: Log::debug(sprintf('%s:- ls_zinitsender ZABORT',self::LOGKEY)); - return self::LSZ_ERROR; + return self::ERROR; default: Log::error(sprintf('%s: ? ls_zinitsender Something strange [%d]',self::LOGKEY,$rc)); @@ -1084,7 +1081,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface $got = 0; /* Bytes total got */ $crc = 0; /* Received CRC */ - $frametype = self::LSZ_ERROR; /* Type of frame - ZCRC(G|W|Q|E) */ + $frametype = self::ERROR; /* Type of frame - ZCRC(G|W|Q|E) */ $rcvdata = 1; /* Data is being received NOW (not CRC) */ while ($rcvdata && (($c = $this->ls_readzdle($timeout)) >= 0)) { @@ -1163,7 +1160,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface $got = 0; /* Bytes total got */ $crc = 0; /* Received CRC */ - $frametype = self::LSZ_ERROR; /* Type of frame - ZCRC(G|W|Q|E) */ + $frametype = self::ERROR; /* Type of frame - ZCRC(G|W|Q|E) */ $rcvdata = 1; /* Data is being received NOW (not CRC) */ while ($rcvdata && (($c=$this->ls_readzdle($timeout)) >= 0)) { @@ -1289,7 +1286,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface break; case self::LSZ_BADCRC: - case self::LSZ_TIMEOUT: + case self::TIMEOUT: if ($this->ls_rxAttnStr) { $this->client->buffer_add($this->ls_rxAttnStr); $this->client->buffer_flush(5); @@ -1344,7 +1341,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface if (($rc=$this->ls_zsendhhdr(self::ZRINIT,$this->ls_storelong(0))) < 0) return $rc; - return self::LSZ_OK; + return self::OK; } Log::debug(sprintf('%s: - ls_zrecvfile ZDATA',self::LOGKEY)); @@ -1357,7 +1354,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface } while (TRUE); - return self::LSZ_OK; + return self::OK; } /** @@ -1422,8 +1419,8 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface case self::ZNAK: Log::debug(sprintf('%s: - ls_zrecvfinfo ZNAK',self::LOGKEY)); - case self::LSZ_TIMEOUT: - Log::debug(sprintf('%s: - ls_zrecvfinfo LSZ_TIMEOUT',self::LOGKEY)); + case self::TIMEOUT: + Log::debug(sprintf('%s: - ls_zrecvfinfo TIMEOUT',self::LOGKEY)); $retransmit = 1; break; @@ -1527,7 +1524,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface } while ($trys < 10); Log::error(sprintf('%s:? ls_zrecvfinfo Something strange or timeout',self::LOGKEY)); - return self::LSZ_TIMEOUT; + return self::TIMEOUT; } private function ls_zrecvhdr(array &$hdr,int $timeout): int @@ -1538,7 +1535,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface $state = self::rhInit; $readmode = self::rm7BIT; - static $frametype = self::LSZ_ERROR; /* Frame type */ + static $frametype = self::ERROR; /* Frame type */ static $crcl = 2; /* Length of CRC (CRC16 is default) */ static $crcgot = 0; /* Number of CRC bytes already got */ static $incrc = 0; /* Calculated CRC */ @@ -1552,7 +1549,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface if ($this->DEBUG) Log::debug(sprintf('%s: - ls_zrecvhdr Init State',self::LOGKEY)); - $frametype = self::LSZ_ERROR; + $frametype = self::ERROR; $crc = 0; $crcl = 2; $crcgot = 0; @@ -1858,7 +1855,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface switch (($rc=$this->ls_zrecvdata($buf,$len,$this->ls_DataTimeout,$this->ls_Protocol&self::LSZ_OPTCRC32))) { /* Ok, here it is */ case self::ZCRCW: - return self::LSZ_OK; + return self::OK; case self::LSZ_BADCRC: Log::debug(sprintf('%s: - ls_zrecvcrcw got BADCRC',self::LOGKEY)); @@ -1866,7 +1863,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface return 1; - case self::LSZ_TIMEOUT: + case self::TIMEOUT: Log::debug(sprintf('%s: - ls_zrecvcrcw got TIMEOUT',self::LOGKEY)); break; @@ -1913,7 +1910,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface break; - case self::LSZ_TIMEOUT: + case self::TIMEOUT: break; case self::LSZ_BADCRC: @@ -1933,7 +1930,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface } while (++$trys < 10); Log::error(sprintf('%s:? ls_zrecvnewpos Something strange or timeout [%d]',self::LOGKEY,$rc)); - return self::LSZ_TIMEOUT; + return self::TIMEOUT; } /** @@ -1952,7 +1949,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface if (++$this->ls_txReposCount > 10) { Log::error(sprintf('%s:! ZRPOS to [%ld] limit reached',self::LOGKEY,$newpos)); - return self::LSZ_ERROR; + return self::ERROR; } } else { @@ -1966,14 +1963,14 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface if (! $send->seek($newpos)) { Log::error(sprintf('%s:! ZRPOS to [%ld] seek error',self::LOGKEY,$send->filepos)); - return self::LSZ_ERROR; + return self::ERROR; } if ($this->ls_txCurBlockSize > 32) $this->ls_txCurBlockSize >>= 1; $this->ls_txGoodBlocks = 0; - return self::LSZ_OK; + return self::OK; } /** @@ -2069,7 +2066,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface [($this->ls_Protocol&self::LSZ_OPTVHDR)==self::LSZ_OPTVHDR] [($this->ls_Protocol&self::LSZ_OPTRLE)==self::LSZ_OPTRLE]) < 0) { - return self::LSZ_ERROR; + return self::ERROR; } /* Send * and packet type */ @@ -2145,7 +2142,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface Log::debug(sprintf('%s: - ls_zsendfile ZABORT/ZFIN',self::LOGKEY)); $this->ls_zsendhhdr(self::ZFIN,$this->ls_storelong(0)); - return self::LSZ_ERROR; + return self::ERROR; default: if ($rc < 0) @@ -2153,7 +2150,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface Log::debug(sprintf('%s:- ls_zsendfile Strange answer on ZFILE [%d]',self::LOGKEY,$rc)); - return self::LSZ_ERROR; + return self::ERROR; } /* Send file data */ @@ -2181,7 +2178,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface } catch (\Exception $e) { Log::error(sprintf('%s:! ls_zsendfile Read error',self::LOGKEY)); - return self::LSZ_ERROR; + return self::ERROR; } /* Select sub-frame type */ @@ -2266,11 +2263,11 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface $this->ls_zsendhhdr(self::ZFIN,$this->ls_storelong(0)); /* Fall through */ - case self::LSZ_RCDO: - case self::LSZ_ERROR: - return self::LSZ_ERROR; + case self::RCDO: + case self::ERROR: + return self::ERROR; - case self::LSZ_TIMEOUT: /* Ok! */ + case self::TIMEOUT: /* Ok! */ break; default: @@ -2289,7 +2286,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface && ++$trys < 10); if ($trys >= 10) - return self::LSZ_ERROR; + return self::ERROR; /* Ok, increase block, if here is MANY good blocks was sent */ if (++$this->ls_txGoodBlocks > 32) { @@ -2329,7 +2326,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface /* OK! */ case self::ZRINIT: - return self::LSZ_OK; + return self::OK; /* ACK for data -- it lost! */ case self::ZACK: @@ -2344,10 +2341,10 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface case self::ZFIN: /* Abort too */ case self::ZCAN: - return self::LSZ_ERROR; + return self::ERROR; /* Ok, here is no header */ - case self::LSZ_TIMEOUT: + case self::TIMEOUT: $trys++; break; @@ -2366,7 +2363,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface if ($send->feof()) { Log::error(sprintf('%s:! ls_zsendfile To many tries waiting for ZEOF ACK',self::LOGKEY)); - return self::LSZ_ERROR; + return self::ERROR; } } } @@ -2477,7 +2474,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface break; case self::ZNAK: - case self::LSZ_TIMEOUT: + case self::TIMEOUT: $retransmit = 1; break; @@ -2587,7 +2584,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface /* Ok */ case self::ZACK: - return self::LSZ_OK; + return self::OK; /* Return number to peer, he is paranoid */ case self::ZCHALLENGE: @@ -2605,7 +2602,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface /* Retransmit */ case ZNAK: - case LSZ_TIMEOUT: + case TIMEOUT: $retransmit = 1; break; diff --git a/app/Console/Commands/CommBinkpReceive.php b/app/Console/Commands/CommBinkpReceive.php index 297a997..fadab09 100644 --- a/app/Console/Commands/CommBinkpReceive.php +++ b/app/Console/Commands/CommBinkpReceive.php @@ -30,13 +30,15 @@ class CommBinkpReceive extends Command * Execute the console command. * * @return mixed + * @throws SocketException */ public function handle() { Log::info('Listening for BINKP connections...'); + $o = Setup::findOrFail(config('app.id')); - $server = new SocketServer(Setup::BINKP_PORT,Setup::BINKP_BIND); - $server->setConnectionHandler([new Binkp(Setup::findOrFail(config('app.id'))),'onConnect']); + $server = new SocketServer($o->binkp_port,$o->binkp_bind); + $server->handler = [new Binkp($o),'onConnect']; try { $server->listen(); diff --git a/app/Console/Commands/CommBinkpSend.php b/app/Console/Commands/CommBinkpSend.php index 532d3b3..2acd16e 100644 --- a/app/Console/Commands/CommBinkpSend.php +++ b/app/Console/Commands/CommBinkpSend.php @@ -32,7 +32,7 @@ class CommBinkpSend extends Command */ public function handle(): void { - Log::info('Call BINKP send'); + Log::info('CBS:- Call BINKP send'); $ao = Address::findFTN($this->argument('ftn')); if (! $ao) diff --git a/app/Console/Commands/CommEMSIReceive.php b/app/Console/Commands/CommEMSIReceive.php index 9bed256..e86a4fe 100644 --- a/app/Console/Commands/CommEMSIReceive.php +++ b/app/Console/Commands/CommEMSIReceive.php @@ -30,13 +30,15 @@ class CommEMSIReceive extends Command * Execute the console command. * * @return mixed + * @throws \Exception */ public function handle() { Log::info('Listening for EMSI connections...'); + $o = Setup::findOrFail(config('app.id')); $server = new SocketServer(Setup::EMSI_PORT,Setup::EMSI_BIND); - $server->setConnectionHandler([new EMSI(Setup::findOrFail(config('app.id'))),'onConnect']); + $server->handler = [new EMSI($o),'onConnect']; try { $server->listen(); diff --git a/app/Console/Commands/CommZmodemReceive.php b/app/Console/Commands/CommZmodemReceive.php index c0c7769..83885ce 100644 --- a/app/Console/Commands/CommZmodemReceive.php +++ b/app/Console/Commands/CommZmodemReceive.php @@ -29,13 +29,14 @@ class CommZmodemReceive extends Command * Execute the console command. * * @return mixed + * @throws SocketException */ public function handle() { Log::info('Listening for ZMODEM connections...'); $server = new SocketServer(60177,'0.0.0.0'); - $server->setConnectionHandler([new ZmodemClass,'onConnect']); + $server->handler = [new ZmodemClass,'onConnect']; try { $server->listen(); diff --git a/app/Console/Commands/ServerStart.php b/app/Console/Commands/ServerStart.php index 4b17eff..62b95f8 100644 --- a/app/Console/Commands/ServerStart.php +++ b/app/Console/Commands/ServerStart.php @@ -43,8 +43,8 @@ class ServerStart extends Command if ($o->optionGet(Setup::O_BINKP)) $start->put('binkp',[ - 'address'=>Setup::BINKP_BIND, - 'port'=>Setup::BINKP_PORT, + 'address'=>$o->binkp_bind, + 'port'=>$o->binkp_port, 'proto'=>SOCK_STREAM, 'class'=>new Binkp($o), ]); @@ -90,7 +90,7 @@ class ServerStart extends Command Log::info(sprintf('%s: - Started [%s]',self::LOGKEY,$item)); $server = new SocketServer($config['port'],$config['address'],$config['proto']); - $server->setConnectionHandler([$config['class'],'onConnect']); + $server->handler = [$config['class'],'onConnect']; try { $server->listen(); diff --git a/app/Exceptions/FileGrewException.php b/app/Exceptions/FileGrewException.php new file mode 100644 index 0000000..dabfbf3 --- /dev/null +++ b/app/Exceptions/FileGrewException.php @@ -0,0 +1,9 @@ +post()) { - $request->validate([ - 'system_id' => 'required|exists:systems,id', - 'binkp' => 'nullable|array', - 'binkp.*' => 'nullable|numeric', - 'options' => 'nullable|array', - 'options.*' => 'nullable|numeric', - ]); - if (! $o->exists) { $o->id = config('app.id'); $o->zmodem = 0; @@ -172,9 +165,17 @@ class HomeController extends Controller $o->permissions = 0; } - $o->binkp = collect($request->post('binkp'))->sum(); + $servers = collect(); + + $binkp = collect(); + $binkp->put('options',collect($request->post('binkp'))->sum()); + $binkp->put('port',$request->post('binkp_port')); + $binkp->put('bind',$request->post('binkp_bind')); + $servers->put('binkp',$binkp); + $o->options = collect($request->post('options'))->sum(); $o->system_id = $request->post('system_id'); + $o->servers = $servers; $o->save(); } diff --git a/app/Http/Requests/SetupRequest.php b/app/Http/Requests/SetupRequest.php new file mode 100644 index 0000000..70f43b2 --- /dev/null +++ b/app/Http/Requests/SetupRequest.php @@ -0,0 +1,35 @@ +isMethod('post')) + return []; + + return [ + 'system_id' => 'required|exists:systems,id', + 'binkp' => 'nullable|array', + 'binkp.*' => 'nullable|numeric', + //'dns' => 'required|array', + 'dns.*' => 'nullable|numeric', + //'emsi' => 'required|array', + 'emsi.*' => 'nullable|numeric', + '*_bind' => 'required|ip', + '*_port' => 'required|numeric', + 'options' => 'nullable|array', + 'options.*' => 'nullable|numeric', + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/SystemRegister.php b/app/Http/Requests/SystemRegister.php index 3733ddf..c86c7ca 100644 --- a/app/Http/Requests/SystemRegister.php +++ b/app/Http/Requests/SystemRegister.php @@ -10,6 +10,7 @@ use Illuminate\Validation\Rule; use App\Classes\FTN\Packet; use App\Models\System; +// @todo rename to SystemRegisterRequest class SystemRegister extends FormRequest { private System $so; diff --git a/app/Models/Address.php b/app/Models/Address.php index 71c5448..6927a04 100644 --- a/app/Models/Address.php +++ b/app/Models/Address.php @@ -72,6 +72,16 @@ class Address extends Model ->FTNorder(); } + public function scopeTrashed($query) + { + return $query->select($this->getTable().'.*') + ->join('zones',['zones.id'=>'addresses.zone_id']) + ->join('domains',['domains.id'=>'zones.domain_id']) + ->orderBy('domains.name') + ->withTrashed() + ->FTNorder(); + } + public function scopeFTNOrder($query) { return $query @@ -371,13 +381,18 @@ class Address extends Model * @return Address|null * @throws \Exception */ - public static function findFTN(string $address,bool $create=FALSE,System $so=NULL): ?self + public static function findFTN(string $address,bool $create=FALSE,System $so=NULL,bool $trashed=FALSE): ?self { $ftn = self::parseFTN($address); // Are we looking for a region address if (($ftn['f'] === 0) && $ftn['p'] === 0) { - $o = (new self)->active() + $o = (new self) + ->when($trashed,function($query) { + $query->trashed(); + },function($query) { + $query->active(); + }) ->where('zones.zone_id',$ftn['z']) ->where(function($q) use ($ftn) { return $q @@ -400,7 +415,12 @@ class Address extends Model return $o; } - $o = (new self)->active() + $o = (new self) + ->when($trashed,function($query) { + $query->trashed(); + },function($query) { + $query->active(); + }) ->where('zones.zone_id',$ftn['z']) ->where(function($q) use ($ftn) { return $q->where(function($qq) use ($ftn) { diff --git a/app/Models/Setup.php b/app/Models/Setup.php index 1d66a0d..0eecd5f 100644 --- a/app/Models/Setup.php +++ b/app/Models/Setup.php @@ -2,11 +2,13 @@ namespace App\Models; -use Exception; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Arr; use Illuminate\Support\Facades\File; +use App\Classes\Protocol\{Binkp,DNS,EMSI}; + /** * This class represents our configuration. * @@ -14,26 +16,17 @@ use Illuminate\Support\Facades\File; * * @package App\Models * @property Collection nodes - * @property array binkp_options */ class Setup extends Model { - public const S_DOMAIN = 1<<1; // Users can create Domains - public const S_SYSTEM = 1<<2; // Users can create Systems + public const PRODUCT_NAME = 'Clearing Houz'; + public const PRODUCT_ID = 0xAB8D; + public const PRODUCT_VERSION_MAJ = 0; + public const PRODUCT_VERSION_MIN = 0; - public const BINKP_OPT_CHT = 1<<1; /* CHAT mode - not implemented */ - public const BINKP_OPT_CR = 1<<2; /* Crypt mode - not implemented */ - public const BINKP_OPT_MB = 1<<3; /* Multi-Batch mode */ - public const BINKP_OPT_MD = 1<<4; /* CRAM-MD5 mode */ - public const BINKP_OPT_ND = 1<<5; /* http://ftsc.org/docs/fsp-1027.001: No-dupes mode */ - public const BINKP_OPT_NDA = 1<<6; /* http://ftsc.org/docs/fsp-1027.001: Asymmetric ND mode */ - public const BINKP_OPT_NR = 1<<7; /* http://ftsc.org/docs/fsp-1027.001: Non-Reliable mode */ - public const BINKP_OPT_MPWD = 1<<8; /* Multi-Password mode - not implemented */ - - public const BINKP_PORT = 24554; - public const BINKP_BIND = '::'; + public const BIND = '::'; public const EMSI_PORT = 60179; - public const EMSI_BIND = self::BINKP_BIND; + public const EMSI_BIND = self::BIND; public const DNS_PORT = 53; public const DNS_BIND = '::'; @@ -42,25 +35,19 @@ class Setup extends Model public const O_DNS = 1<<3; /* List for DNS */ public const O_HIDEAKA = 1<<4; /* Hide AKAs to different Zones */ - public const PRODUCT_NAME = 'Clearing Houz'; - public const PRODUCT_ID = 0xAB8D; - public const PRODUCT_VERSION_MAJ = 0; - public const PRODUCT_VERSION_MIN = 0; - public const MAX_MSGS_PKT = 50; - public const hexdigitslower = '0123456789abcdef'; - // Our non model attributes and values private array $internal = []; + protected $casts = [ + 'servers' => 'array', + ]; + public function __construct(array $attributes = []) { parent::__construct($attributes); - // @todo These option should be in setup? - $this->binkp_options = ['m','d','r','b']; - /* EMSI SETTINGS */ $this->do_prevent = 1; /* EMSI - send an immediate EMSI_INQ on connect */ $this->ignore_nrq = 0; @@ -68,21 +55,30 @@ class Setup extends Model } /** - * @throws Exception + * @throws \Exception */ public function __get($key) { switch ($key) { + case 'binkp_bind': + case 'dns_bind': + case 'emsi_bind': + return Arr::get($this->servers,str_replace('_','.',$key),self::BIND); + + case 'binkp_port': + return Arr::get($this->servers,str_replace('_','.',$key),Binkp::PORT); + case 'dns_port': + return Arr::get($this->servers,str_replace('_','.',$key),EMSI::PORT); + case 'emsi_port': + return Arr::get($this->servers,str_replace('_','.',$key),DNS::PORT); + case 'binkp_options': + case 'dns_options': + case 'emsi_options': + return Arr::get($this->servers,'binkp.options'); + + case 'binkp_settings': case 'ignore_nrq': - case 'opt_nr': // @todo - this keys are now in #binkp as bits - case 'opt_nd': - case 'opt_nda': - case 'opt_md': - case 'opt_cr': - case 'opt_mb': - case 'opt_cht': - case 'opt_mpwd': case 'do_prevent': return $this->internal[$key] ?? FALSE; @@ -98,12 +94,23 @@ class Setup extends Model } /** - * @throws Exception + * @throws \Exception */ public function __set($key,$value) { switch ($key) { + case 'binkp_bind': + case 'binkp_port': case 'binkp_options': + case 'dns_bind': + case 'dns_port': + case 'dns_options': + case 'emsi_bind': + case 'emsi_port': + case 'emsi_options': + return Arr::set($this->servers,str_replace('_','.',$key),$value); + + case 'binkp_settings': case 'ignore_nrq': case 'opt_nr': case 'opt_nd': @@ -127,7 +134,7 @@ class Setup extends Model * * @param int $c * @return string - * @throws Exception + * @throws \Exception */ public static function product_id(int $c=self::PRODUCT_ID): string { @@ -165,37 +172,18 @@ class Setup extends Model /* METHODS */ - /* BINKP OPTIONS: BINKP_OPT_* */ - - public function binkpOptionClear(int $key): void + public function optionClear(int $key,$index='options'): void { - $this->binkp &= ~$key; + $this->{$index} &= ~$key; } - public function binkpOptionGet(int $key): int + public function optionGet(int $key,$index='options'): int { - return ($this->binkp & $key); + return ($this->{$index} & $key); } - public function binkpOptionSet(int $key): void + public function optionSet(int $key,$index='options'): void { - $this->binkp |= $key; - } - - /* GENERAL OPTIONS: O_* */ - - public function optionClear(int $key): void - { - $this->options &= ~$key; - } - - public function optionGet(int $key): int - { - return ($this->options & $key); - } - - public function optionSet(int $key): void - { - $this->options |= $key; + $this->{$index} |= $key; } } \ No newline at end of file diff --git a/app/Models/System.php b/app/Models/System.php index 4f96ad3..9184b4e 100644 --- a/app/Models/System.php +++ b/app/Models/System.php @@ -127,6 +127,11 @@ class System extends Model } } + public function getPhoneAttribute(string $val): string + { + return $val ?: '-Unpublished-'; + } + /* METHODS */ public function echoareas() diff --git a/app/Traits/CRC.php b/app/Traits/CRC.php index 1cb3104..be12a2b 100644 --- a/app/Traits/CRC.php +++ b/app/Traits/CRC.php @@ -9,6 +9,11 @@ trait CRC return (self::crc16usd_tab[(($crc >> 8) ^ $b) & 0xff] ^ (($crc & 0x00ff) << 8)) & 0xffff; } + private function CRC32_CR(int $c,int $b) + { + return (self::crc32_tab[((int)($c) ^ ($b)) & 0xff] ^ (($c) >> 8)); + } + private function CRC32_FINISH(int $crc) { return ~$crc & 0xffffffff; diff --git a/composer.json b/composer.json index 48d67ec..fe6aa28 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,7 @@ "php": "^8.1|8.2", "ext-pcntl": "*", "ext-sockets": "*", + "ext-bz2": "*", "ext-zip": "*", "ext-zlib": "*", "ext-zstd": "*", diff --git a/database/migrations/2023_06_29_071835_setup_server_options.php b/database/migrations/2023_06_29_071835_setup_server_options.php new file mode 100644 index 0000000..8bff901 --- /dev/null +++ b/database/migrations/2023_06_29_071835_setup_server_options.php @@ -0,0 +1,28 @@ +jsonb('servers')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('setups',function (Blueprint $table) { + $table->dropColumn(['servers']); + }); + } +}; diff --git a/resources/views/setup.blade.php b/resources/views/setup.blade.php index 54bbcbd..770e24a 100644 --- a/resources/views/setup.blade.php +++ b/resources/views/setup.blade.php @@ -1,6 +1,7 @@ @php use App\Models\Setup; +use App\Classes\Protocol\Binkp; @endphp @extends('layouts.app') @@ -100,62 +101,94 @@ use App\Models\Setup;
-

BINKP Settings

-

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

+
+
+

BINKP Settings

-
- optionGet(Setup::O_BINKP))) checked @endif> - +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ optionGet(Setup::O_BINKP))) checked @endif> + +
+ +
+ optionGet(Binkp::F_CHAT,'binkp_options'))) checked @endif disabled> + +
+ +
+ optionGet(Binkp::F_COMP,'binkp_options'))) checked @endif> + +
+ +
+ optionGet(Binkp::F_MD,'binkp_options'))) checked @endif> + +
+ + +
+ optionGet(Binkp::F_MDFORCE,'binkp_options'))) checked @endif> + +
+ +
+ optionGet(Binkp::F_CRYPT,'binkp_options'))) checked @endif> + +
+ +
+ optionGet(Binkp::F_MULTIBATCH,'binkp_options'))) checked @endif> + +
+ +
+ optionGet(Binkp::F_MULTIPASS,'binkp_options'))) checked @endif disabled> + +
+ +
+ optionGet(Binkp::F_NODUPE,'binkp_options'))) checked @endif> + +
+ +
+ optionGet(Binkp::F_NODUPEA,'binkp_options'))) checked @endif> + +
+ +
+ optionGet(Binkp::F_NOREL,'binkp_options'))) checked @endif> + +
+ +

* Recommended Defaults

+ + + + {{-- + $this->binkp_options = ['m','d','r','b']; + --}} +
+
- -
- binkpOptionGet(Setup::BINKP_OPT_CHT))) checked @endif disabled> - -
- -
- binkpOptionGet(Setup::BINKP_OPT_MD))) checked @endif> - -
- -
- binkpOptionGet(Setup::BINKP_OPT_CR))) checked @endif disabled> - -
- -
- binkpOptionGet(Setup::BINKP_OPT_MB))) checked @endif> - -
- -
- binkpOptionGet(Setup::BINKP_OPT_MPWD))) checked @endif disabled> - -
- -
- binkpOptionGet(Setup::BINKP_OPT_ND))) checked @endif> - -
- -
- binkpOptionGet(Setup::BINKP_OPT_NDA))) checked @endif> - -
- -
- binkpOptionGet(Setup::BINKP_OPT_NR))) checked @endif> - -
- -

* Recommended Defaults

- - - - {{-- - $this->binkp_options = ['m','d','r','b']; - --}} -
@@ -163,7 +196,24 @@ use App\Models\Setup;

EMSI Settings

-

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

+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
optionGet(Setup::O_EMSI))) checked @endif> @@ -175,7 +225,24 @@ use App\Models\Setup;

DNS Settings

-

DNS has been configured to listen on {{ Setup::DNS_BIND }}:{{ Setup::DNS_PORT }}

+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
optionGet(Setup::O_DNS))) checked @endif> @@ -210,8 +277,13 @@ use App\Models\Setup; @if($errors->count()) - There were errors with the submission. - @dump($errors) +

There were errors with the submission.

+ +
    + @foreach ($errors->all() as $message) +
  • {{ $message }}
  • + @endforeach +
@endif
@@ -230,6 +302,15 @@ use App\Models\Setup; @section('page-css') @css('select2') + + @append @section('page-scripts') @js('select2') diff --git a/resources/views/system/addedit.blade.php b/resources/views/system/addedit.blade.php index 0027eee..d2f4652 100644 --- a/resources/views/system/addedit.blade.php +++ b/resources/views/system/addedit.blade.php @@ -388,6 +388,71 @@
+ + +
+ + +
+
+
+
+ The last Netmails sent: + + + + + + + + + + + @dump($o->addresses->sortBy('zone.zone_id')->pluck('ftn')) + @foreach (\App\Models\Netmail::where('tftn_id',$o->addresses->pluck('id'))->whereNotNull('sent_at')->orderBy('sent_at')->limit(10) as $no) + + + + + + @endforeach + +
FTNPacketSent
{{ $no->tftn->ftn }}
+
+ +
+ The last Echomails sent: + + + + + + + + + + + @foreach (\App\Models\Echomail::join('echomail_seenby',['echomail_seenby.echomail_id'=>'echomails.id']) + ->whereNotNull('echomail_seenby.sent_at') + ->whereIn('address_id',$o->addresses->pluck('id')) + ->orderBy('sent_at','DESC') + ->orderBy('packet') + ->limit(10) + ->get() + ->groupBy('packet') as $oo) + + + + + + @endforeach + +
FTNPacketSent
{{ \App\Models\Address::where('id',$oo->first()->address_id)->single()->ftn }}{{ $oo->first()->packet }}{{ $oo->first()->sent_at }}
+
+
+
+
+
@endif @else @include('system.form-system')