From 9317f78a3af28bee898c10784aed5a15c1cfba7b Mon Sep 17 00:00:00 2001 From: Deon George Date: Sat, 11 May 2019 11:17:56 +1000 Subject: [PATCH] Work with netmail creation --- app/Classes/FTNMessage.php | 246 ++++++++++++++++++++++---------- app/Classes/FTNPacket.php | 193 +++++++++++++++++++------ app/Console/Commands/FtnPkt.php | 16 ++- app/helpers.php | 54 +++++++ composer.json | 3 + routes/web.php | 6 +- 6 files changed, 389 insertions(+), 129 deletions(-) create mode 100644 app/helpers.php diff --git a/app/Classes/FTNMessage.php b/app/Classes/FTNMessage.php index fad537b..2ae86b8 100644 --- a/app/Classes/FTNMessage.php +++ b/app/Classes/FTNMessage.php @@ -6,8 +6,9 @@ use App\Exceptions\InvalidFidoPacketException; class FTNMessage extends FTN { - private $src = NULL; - private $dst = NULL; + private $_src = NULL; + private $_dst = NULL; + private $flags = NULL; private $cost = 0; @@ -42,7 +43,36 @@ class FTNMessage extends FTN 'tid' => 'TID: ', ]; - public function __construct(string $header) + // Flags for messages + const FLAG_PRIVATE = 0b1; + const FLAG_CRASH = 0b10; + const FLAG_RECD = 0b100; + const FLAG_SENT = 0b1000; + const FLAG_FILEATTACH = 0b10000; + const FLAG_INTRANSIT = 0b100000; + const FLAG_ORPHAN = 0b1000000; + const FLAG_KILLSENT = 0b10000000; + const FLAG_LOCAL = 0b100000000; + const FLAG_HOLD = 0b1000000000; + const FLAG_UNUSED_10 = 0b10000000000; + const FLAG_FREQ = 0b100000000000; + const FLAG_RETRECEIPT = 0b1000000000000; + const FLAG_ISRETRECEIPT = 0b10000000000000; + const FLAG_AUDITREQ = 0b100000000000000; + const FLAG_FILEUPDATEREQ = 0b1000000000000000; + + // FTS-0001.016 Message header 12 bytes + // node, net, flags, cost + private $struct = [ + 'onode'=>[0x00,'v',2], + 'dnode'=>[0x02,'v',2], + 'onet'=>[0x04,'v',2], + 'dnet'=>[0x06,'v',2], + 'flags'=>[0x08,'v',2], + 'cost'=>[0x0a,'v',2], + ]; + + public function __construct(string $header=NULL) { // Initialise vars $this->kludge = collect(); // The message kludge lines @@ -52,39 +82,8 @@ class FTNMessage extends FTN $this->_other = collect(); // Temporarily hold attributes we dont process yet. $this->unknown = collect(); // Temporarily hold attributes we have no logic for. - // FTS-0001.016 Message header 12 bytes - // node, net, flags, cost - $struct = [ - 'onode'=>[0x00,'v',2], - 'dnode'=>[0x02,'v',2], - 'onet'=>[0x04,'v',2], - 'dnet'=>[0x06,'v',2], - 'flags'=>[0x08,'v',2], - 'cost'=>[0x0a,'v',2], - ]; - - $result = unpack($this->unpackheader($struct),$header); - - // For Echomail this is the packet src. - $this->psn = array_get($result,'onet'); - $this->psf = array_get($result,'onode'); - - $this->src = sprintf('%s/%s', - $this->psn, - $this->psf - ); - - // For Echomail this is the packet dst. - $this->pdn = array_get($result,'dnet'); - $this->pdf = array_get($result,'dnode'); - - $this->dst = sprintf('%s/%s', - $this->pdn, - $this->pdf - ); - - $this->flags = array_get($result,'flags'); - $this->cost = array_get($result,'cost'); + if ($header) + $this->parseheader($header); } public function __get($k) @@ -110,44 +109,138 @@ class FTNMessage extends FTN { switch ($k) { - case 'message': - // Remove DOS \n\r - $v = preg_replace("/\n\r/","\r",$v); + case 'fqfa': + case 'fqda': + $this->{$k} = $v; - $this->parsemessage($v); - break; - - case 'origin': - $this->parseorigin($v); - break; + if ($this->fqfa AND $this->fqda) + $this->intl = sprintf('%s %s',$this->fqda,$this->fqfa); default: $this->{$k} = $v; } } - private function znfp(string $data,string $key) + /** + * Export an FTN message, ready for sending. + * + * @return string + */ + public function __toString(): string { - switch ($key) { - case 'z': - return substr($data,0,strpos($data,':')); - case 'n': - $x = strpos($data,':')+1; - return substr($data,$x,strpos($data,'/')-$x); - case 'f': - $x = strpos($data,'/')+1; - return substr($data,$x,strpos($data,'.') ?: strlen($data)-$x); - case 'p': - $x = strpos($data,'.'); - return $x ? substr($data,$x+1) : 0; + $return = ''; + $return .= pack(join('',collect($this->struct)->pluck(1)->toArray()), + $this->ff, + $this->tf, + $this->fn, + $this->tn, + $this->flags, + 0 // @todo cost + ); + + // @todo use pack for this. + $return .= $this->date->format('d M y H:i:s')."\00"; + $return .= $this->to."\00"; + $return .= $this->from."\00"; + $return .= $this->subject."\00"; + $return .= $this->message."\00"; + + return $return; + } + + public function description() + { + switch ($this->type()) + { + case 'echomail': return sprintf('Echomail: '.$this->echoarea); + case 'netmail': return sprintf('Netmail: %s->%s',$this->fqfa,$this->fqda); default: - abort(500,'Unknown key: '.$key); + return 'UNKNOWN'; } } + /** + * Return an array of flag descriptions + * + * @return array + * + * http://ftsc.org/docs/fsc-0001.000 + * AttributeWord bit meaning + --- -------------------- + 0 + Private + 1 + s Crash + 2 Recd + 3 Sent + 4 + FileAttached + 5 InTransit + 6 Orphan + 7 KillSent + 8 Local + 9 s HoldForPickup + 10 + unused + 11 s FileRequest + 12 + s ReturnReceiptRequest + 13 + s IsReturnReceipt + 14 + s AuditRequest + 15 s FileUpdateReq + + s - this bit is supported by SEAdog only + + - this bit is not zeroed before packeting + */ + public function flags(int $flags): array + { + return [ + 'private'=>$this->isFlagSet($flags,self::FLAG_PRIVATE), + 'crash'=>$this->isFlagSet($flags,self::FLAG_CRASH), + 'recd'=>$this->isFlagSet($flags,self::FLAG_RECD), + 'sent'=>$this->isFlagSet($flags,self::FLAG_SENT), + 'killsent'=>$this->isFlagSet($flags,self::FLAG_KILLSENT), + 'local'=>$this->isFlagSet($flags,self::FLAG_LOCAL), + ]; + } + + private function isFlagSet($value,$flag) + { + return (($value & $flag) == $flag); + } + + /** + * Parse the head of an FTN message + * + * @param string $header + */ + public function parseheader(string $header) + { + $result = unpack($this->unpackheader($this->struct),$header); + + // For Echomail this is the packet src. + $this->psn = array_get($result,'onet'); + $this->psf = array_get($result,'onode'); + + $this->_src = sprintf('%s/%s', + $this->psn, + $this->psf + ); + + // For Echomail this is the packet dst. + $this->pdn = array_get($result,'dnet'); + $this->pdf = array_get($result,'dnode'); + + $this->_dst = sprintf('%s/%s', + $this->pdn, + $this->pdf + ); + + $this->flags = array_get($result,'flags'); + $this->cost = array_get($result,'cost'); + } + public function parsemessage(string $message) { + // Remove DOS \n\r + $message = preg_replace("/\n\r/","\r",$message); + // Split out the lines $result = collect(explode("\01",$message))->filter(); @@ -165,7 +258,7 @@ class FTNMessage extends FTN if ($y = strpos($v,"\r * Origin: ")) { $this->message .= substr($v,$x+1,$y-$x-1); - $this->__set('origin',substr($v,$y)); + $this->parseorigin(substr($v,$y)); $matches = []; preg_match('/^.*\((.*)\)$/',$this->origin,$matches); @@ -245,7 +338,7 @@ class FTNMessage extends FTN * * @param string $message */ - public function parseorigin(string $message) + private function parseorigin(string $message) { // Split out each line $result = collect(explode("\r",$message))->filter(); @@ -277,17 +370,6 @@ class FTNMessage extends FTN } } - public function description() - { - switch ($this->type()) - { - case 'echomail': return sprintf('Echomail: '.$this->echoarea); - case 'netmail': return sprintf('Netmail: %s->%s',$this->fqfa,$this->fqda); - default: - return 'UNKNOWN'; - } - } - public function type() { if ($this->echoarea) @@ -298,4 +380,24 @@ class FTNMessage extends FTN return 'UNKNOWN'; } + + private function znfp(string $data,string $key) + { + switch ($key) { + case 'z': + return substr($data,0,strpos($data,':')); + case 'n': + $x = strpos($data,':')+1; + return substr($data,$x,strpos($data,'/')-$x); + case 'f': + $x = strpos($data,'/')+1; + return substr($data,$x,strpos($data,'.') ?: strlen($data)-$x); + case 'p': + $x = strpos($data,'.'); + return $x ? substr($data,$x+1) : 0; + + default: + abort(500,'Unknown key: '.$key); + } + } } \ No newline at end of file diff --git a/app/Classes/FTNPacket.php b/app/Classes/FTNPacket.php index 377ff07..9cc8af6 100644 --- a/app/Classes/FTNPacket.php +++ b/app/Classes/FTNPacket.php @@ -3,6 +3,7 @@ namespace App\Classes; use App\Exceptions\InvalidFidoPacketException; +use Carbon\Carbon; class FTNPacket extends FTN { @@ -11,18 +12,141 @@ class FTNPacket extends FTN private $pktver = NULL; public $date = NULL; private $baud = NULL; + private $software = []; + private $cap = []; private $proddata = NULL; private $password = NULL; + private $sz = NULL; + private $dz = NULL; + private $sn = NULL; + private $dn = NULL; + private $sf = NULL; + private $df = NULL; + private $sp = NULL; + private $dp = NULL; + public $filename = NULL; - public $messages = []; + public $messages = NULL; - public function __construct(string $file) + // First part of header + private $pack1 = [ + 'onode'=>[0x00,'v',2], + 'dnode'=>[0x02,'v',2], + 'y'=>[0x04,'v',2], + 'm'=>[0x06,'v',2], + 'd'=>[0x08,'v',2], + 'H'=>[0x0a,'v',2], + 'M'=>[0x0c,'v',2], + 'S'=>[0x0e,'v',2], + 'baud'=>[0x10,'v',2], + 'pktver'=>[0x12,'v',2], + 'onet'=>[0x14,'v',2], + 'dnet'=>[0x16,'v',2], + 'prodcode-lo'=>[0x18,'C',1], + 'prodrev-maj'=>[0x19,'C',1], + ]; + + // Second part of header + private $pack2 = [ + 'qozone'=>[0x22,'v',2], + 'qdzone'=>[0x24,'v',2], + 'filler'=>[0x26,'v',2], + 'capvalid'=>[0x28,'v',2], + 'prodcode-hi'=>[0x2a,'C',1], + 'prodrev-min'=>[0x2b,'C',1], + 'capword'=>[0x2c,'v',1], + 'ozone'=>[0x2e,'v',2], + 'dzone'=>[0x30,'v',2], + 'opoint'=>[0x32,'v',2], + 'dpoint'=>[0x34,'v',2], + ]; + + public function __construct(string $file=NULL) { - $this->filename = $file; + $this->messages = collect(); + + // @todo - is this appropriate to set here + $this->date = now(); + $this->software['prodcode-lo'] = 0; + $this->software['prodcode-hi'] = 1; + $this->software['rev-maj'] = 2; + $this->software['rev-min'] = 3; + $this->cap['valid'] = 0; // @todo this is wrong + $this->cap['word'] = 0; // @todo this is wrong + $this->pktver = 0x02; + + if ($file) { + $this->filename = $file; - if ($file) return $this->OpenFile($file); + } + } + + public function __toString(): string + { + $return = $this->createHeader(); + + foreach ($this->messages as $o) + $return .= "\02\00".(string)$o; + + $return .= "\00\00"; + + return $return; + } + + /** + * Create our message packet header + */ + private function createHeader(): string + { + try { + $a = pack(join('',collect($this->pack1)->pluck(1)->toArray()), + $this->sf, + $this->df, + $this->date->year, + $this->date->month, + $this->date->day, + $this->date->hour, + $this->date->minute, + $this->date->second, + $this->baud, + $this->pktver, + $this->sn, + $this->dn, + $this->software['prodcode-lo'], // @todo change to this software + $this->software['rev-maj'] // @todo change to this software + ); + + $b = pack(join('',collect($this->pack2)->pluck(1)->toArray()), + $this->sz, + $this->dz, + 0x00, // Baud + $this->cap['valid'], // @todo to check + $this->software['prodcode-hi'], // @todo change to this software + $this->software['rev-min'], // @todo change to this software + $this->cap['word'], // @todo to check + $this->sz, + $this->dz, + $this->sp, + $this->dp + ); + + return $a.pack('a8',$this->password).$b."mbse"; // @todo change to this software + + } catch (\Exception $e) { + return $e->getMessage(); + } + } + + public function addMessage(FTNMessage $o) + { + $this->messages->push($o); + } + + public function dump() + { + return hex_dump((string)$this); } /** @@ -45,10 +169,9 @@ class FTNPacket extends FTN // Not a type 2 packet if (array_get(unpack('vv',substr($header,0x12)),'v') != 2) - throw new InvalidFidoPacketException('Not a type 2 packet:'. $file); + throw new InvalidFidoPacketException('Not a type 2 packet in file: '. $file); - $this->setHeader($header); - $this->messages = collect(); + $this->parseHeader($header); while (! feof($f)) { @@ -69,11 +192,12 @@ class FTNPacket extends FTN break; $message = new FTNMessage(fread($f,0xc)); - $message->date = $this->readnullfield($f); + $message->date = Carbon::createFromFormat('d M y H:i:s',$this->readnullfield($f)); $message->to = $this->readnullfield($f); $message->from = $this->readnullfield($f); $message->subject = $this->readnullfield($f); - $message->message = $this->readnullfield($f); + + $message->parsemessage($this->readnullfield($f)); $this->messages->push($message); } @@ -91,41 +215,12 @@ class FTNPacket extends FTN return $result; } - public function setHeader(string $header) + private function parseHeader(string $header) { - $pack1 = [ - 'onode'=>[0x00,'v',2], - 'dnode'=>[0x02,'v',2], - 'y'=>[0x04,'v',2], - 'm'=>[0x06,'v',2], - 'd'=>[0x08,'v',2], - 'H'=>[0x0a,'v',2], - 'M'=>[0x0c,'v',2], - 'S'=>[0x0e,'v',2], - 'baud'=>[0x10,'v',2], - 'pktver'=>[0x12,'v',2], - 'onet'=>[0x14,'v',2], - 'dnet'=>[0x16,'v',2], - 'prodcode-lo'=>[0x18,'C',1], - 'prodrev-maj'=>[0x19,'C',1], - ]; - - $pack2 = [ - 'qozone'=>[0x22,'v',2], - 'qdzone'=>[0x24,'v',2], - 'filler'=>[0x26,'v',2], - 'capvalid'=>[0x28,'v',2], - 'prodcode-hi'=>[0x2a,'C',1], - 'prodrev-min'=>[0x2b,'C',1], - 'capword'=>[0x2c,'v',1], - 'ozone'=>[0x2e,'v',2], - 'dzone'=>[0x30,'v',2], - 'opoint'=>[0x32,'v',2], - 'dpoint'=>[0x34,'v',2], - ]; - - $result1 = unpack($this->unpackheader($pack1),substr($header,0,0x1a)); - $result2 = unpack($this->unpackheader($pack2),substr($header,0x22,0x14)); + $result1 = unpack($this->unpackheader($this->pack1),substr($header,0,0x1a)); + $this->password = array_get(unpack('a*p',substr($header,0x1a,8)),'p'); + $result2 = unpack($this->unpackheader($this->pack2),substr($header,0x22,0x14)); + $this->proddata = array_get(unpack('A*p',substr($header,0x36,4)),'p'); $this->sz = array_get($result2,'ozone'); $this->sn = array_get($result1,'onet'); @@ -149,7 +244,7 @@ class FTNPacket extends FTN $this->dp ); - $this->date = sprintf ('%d-%d-%d %d:%d:%d', + $this->date = Carbon::create( array_get($result1,'y'), array_get($result1,'m'), array_get($result1,'d'), @@ -160,8 +255,12 @@ class FTNPacket extends FTN $this->baud = array_get($result1,'baud'); $this->pktver = array_get($result1,'pktver'); - - $this->password = array_get(unpack('A*p',substr($header,0x1a,8)),'p'); - $this->proddata = array_get(unpack('A*p',substr($header,0x36,4)),'p'); + $this->software['prodcode-lo'] = array_get($result1,'prodcode-lo'); + $this->software['prodcode-hi'] = array_get($result2,'prodcode-hi'); + $this->software['rev-maj'] = array_get($result1,'prodrev-maj'); + $this->software['rev-min'] = array_get($result2,'prodrev-min'); + $this->cap['valid'] = array_get($result2,'capvalid'); + $this->cap['word'] = array_get($result2,'capword'); + // @todo filler } } \ No newline at end of file diff --git a/app/Console/Commands/FtnPkt.php b/app/Console/Commands/FtnPkt.php index 8835706..2813598 100644 --- a/app/Console/Commands/FtnPkt.php +++ b/app/Console/Commands/FtnPkt.php @@ -13,7 +13,7 @@ class FtnPkt extends Command * * @var string */ - protected $signature = 'ftn:pkt {file : Fidonet Packet File PKT} {--dump : Dump packet}'; + protected $signature = 'ftn:pkt {file : Fidonet Packet File PKT} {--dump : Dump packet} {--detail : Dump Detail}'; /** * The console command description. @@ -51,21 +51,23 @@ class FtnPkt extends Command foreach ($pkt->messages as $o) { - $this->warn(sprintf('-- From: %s(%s)->%s(%s), Type: %s, Size: %d, FQFA: %s', + $this->warn(sprintf('-- From: %s(%s)->%s(%s), Type: %s, Size: %d', $o->from, - $o->src, + $o->fqfa, $o->to, - $o->dst, + $o->fqda, $o->description(), - strlen($o->message), - $o->fqfa + strlen($o->message) )); if ($o->unknown->count()) $this->error(sprintf('?? %s Unknown headers',$o->unknown->count())); } + if ($this->option('detail')) + dd($o); + if ($this->option('dump')) - dump($o); + echo $pkt->dump(); } } \ No newline at end of file diff --git a/app/helpers.php b/app/helpers.php new file mode 100644 index 0000000..4567b3a --- /dev/null +++ b/app/helpers.php @@ -0,0 +1,54 @@ +> 8) ^ ord($data[$i])) & 0xFF; + $x ^= $x >> 4; + $crc = (($crc << 8) ^ ($x << 12) ^ ($x << 5) ^ $x) & 0xFFFF; + } + return $crc; + } +} + +/** + * Dump out data into a hex dump + */ +if (! function_exists('hex_dump')) { + function hex_dump($data,$newline="\n",$width=16) + { + $result = ''; + + $pad = '.'; # padding for non-visible characters + $to = $from = ''; + + for ($i=0; $i<=0xFF; $i++) + { + $from .= chr($i); + $to .= ($i >= 0x20 && $i <= 0x7E) ? chr($i) : $pad; + } + + $hex = str_split(bin2hex($data),$width*2); + $chars = str_split(strtr($data,$from,$to),$width); + + $offset = 0; + foreach ($hex as $i => $line) + { + $result .= sprintf('%08X: %-48s [%s]%s', + $offset, + substr_replace(implode(' ',str_split($line,2)),' ',8*3,0), + $chars[$i], + $newline); + + $offset += $width; + } + + return $result; + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index e2bd0ed..88c6c0e 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,9 @@ "phpunit/phpunit": "^7.0" }, "autoload": { + "files": [ + "app/helpers.php" + ], "classmap": [ "database/seeds", "database/factories" diff --git a/routes/web.php b/routes/web.php index 810aa34..ae64a68 100644 --- a/routes/web.php +++ b/routes/web.php @@ -11,6 +11,6 @@ | */ -Route::get('/', function () { - return view('welcome'); -}); +#Route::get('/', function () { +# return view('welcome'); +#});