FTN Packet inspection

This commit is contained in:
Deon George 2019-03-03 16:29:35 +02:00
parent cad523577e
commit 5753982a8d
6 changed files with 522 additions and 68 deletions

31
app/Classes/FTN.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Classes;
abstract class FTN
{
/**
* Determine if a line is a kludge line.
*
* @param string $kludge
* @param string $string
* @return string
*/
protected function kludge(string $kludge,string $string)
{
return (preg_match("/^{$kludge}/",$string))
? chop(preg_replace("/^{$kludge}/",'',$string),"\r")
: FALSE;
}
/**
* This function creates our unpack header
* @return string
*/
protected function unpackheader(array $pack)
{
return join('/',array_values(collect($pack)
->sortBy(function($k,$v) {return $k[0];})
->transform(function($k,$v) {return $k[1].$v;})->toArray()));
}
}

251
app/Classes/FTNMessage.php Normal file
View File

@ -0,0 +1,251 @@
<?php
namespace App\Classes;
use App\Exceptions\InvalidFidoPacketException;
class FTNMessage extends FTN
{
private $src = NULL;
private $dst = NULL;
private $flags = NULL;
private $cost = 0;
private $from = NULL; // FTS-0001.016 From Name: upto 36 chars null terminated
private $to = NULL; // FTS-0001.016 To Name: upto 36 chars null terminated
private $subject = NULL; // FTS-0001.016 Subject: upto 72 chars null terminated
private $date = NULL; // FTS-0001.016 Date: upto 20 chars null terminated
private $message = NULL; // The actual message content
private $echoarea = NULL; // FTS-0004.001
private $intl = NULL;
private $msgid = NULL;
private $reply = NULL; // Message thread reply source
private $origin = NULL; // FTS-0004.001
private $kludge = []; // Hold kludge items
private $path = []; // FTS-0004.001
private $seenby = []; // FTS-0004.001
private $via = [];
private $_other = [];
private $unknown = [];
private $fqfa = NULL; // Fully qualified fidonet source where packet originated
private $fqfd = NULL; // Fully qualified fidonet destination address (Netmail)
// Single value kludge items
private $_kludge = [
'chrs' => 'CHRS: ',
'charset' => 'CHARSET: ',
'codepage' => 'CODEPAGE: ',
'pid' => 'PID: ',
'tid' => 'TID: ',
];
public function __construct(string $header)
{
// Initialise vars
$this->kludge = collect(); // The message kludge lines
$this->path = collect(); // The message PATH lines
$this->seenby = collect(); // The message SEEN-BY lines
$this->via = collect(); // The path the message has gone using Via lines
$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);
$this->src = sprintf('%s/%s',array_get($result,'onet'),array_get($result,'onode'));
$this->dst = sprintf('%s/%s',array_get($result,'dnet'),array_get($result,'dnode'));
$this->flags = array_get($result,'flags');
$this->cost = array_get($result,'cost');
}
public function __get($k)
{
return $this->{$k};
}
public function __set($k,$v)
{
switch ($k)
{
case 'message':
// Remove DOS \n\r
$v = preg_replace("/\n\r/","\r",$v);
$this->parsemessage($v);
break;
case 'origin':
$this->parseorigin($v);
break;
default:
$this->{$k} = $v;
}
}
public function parsemessage(string $message)
{
// Split out the <SOH> lines
$result = collect(explode("\01",$message))->filter();
foreach ($result as $k => $v)
{
// Search for \r - if that is the end of the line, then its a kludge
$x = strpos($v,"\r");
// If there are more characters, then put the kludge back into the result, so that we process it.
if ($x != strlen($v)-1)
{
/**
* Anything after the origin line is also kludge data.
*/
if ($y = strpos($v,"\r * Origin: "))
{
$this->message .= substr($v,$x+1,$y-$x-1);
$this->__set('origin',substr($v,$y));
$matches = [];
preg_match('/^.*\((.*)\)$/',$this->origin,$matches);
if (($this->type() == 'Netmail' AND array_get($matches,1) != $this->fqfa) OR ! array_get($matches,1))
throw new InvalidFidoPacketException(sprintf('Source address mismatch? [%s,%s]',$this->fqfa,array_get($matches,1)));
$this->fqfa = array_get($matches,1);
}
$v = substr($v,0,$x+1);
}
foreach ($this->_kludge as $a => $b) {
if ($t = $this->kludge($b, $v)) {
$this->kludge->put($a,$t);
break;
}
}
if ($t)
continue;
if ($t = $this->kludge('AREA:',$v))
$this->echoarea = $t;
// From point: <SOH>"FMPT <point number><CR>
elseif ($t = $this->kludge('FMPT ',$v))
$this->_other->push($t);
/*
* The INTL control paragraph shall be used to give information about
* the zone numbers of the original sender and the ultimate addressee
* of a message.
*
* <SOH>"INTL "<destination address>" "<origin address><CR>
*/
elseif ($t = $this->kludge('INTL ',$v))
{
$this->intl = $t;
list($this->fqfd,$this->fqfa) = explode(' ',$t);
}
elseif ($t = $this->kludge('MSGID: ',$v))
$this->msgid = $t;
elseif ($t = $this->kludge('PATH: ',$v))
$this->path->push($t);
elseif ($t = $this->kludge('REPLY: ',$v))
$this->reply = $t;
// To Point: <SOH>TOPT <point number><CR>
elseif ($t = $this->kludge('TOPT ',$v))
$this->_other->push($t);
// Time Zone of the sender.
elseif ($t = $this->kludge('TZUTC: ',$v))
$this->tzutc= $t;
// <SOH>Via <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone] <Program Name> <Version> [Serial Number]<CR>
elseif ($t = $this->kludge('Via ',$v))
$this->via->push($t);
// We got a kludge line we dont know about
else {
$this->unknown->push(chop($v,"\r"));
//dd(['v'=>$v,'t'=>$t]);
}
}
}
/**
* Process the data after the ORIGIN
* There may be kludge lines after the origin - notably SEEN-BY
*
* @param string $message
*/
public function parseorigin(string $message)
{
// Split out each line
$result = collect(explode("\r",$message))->filter();
foreach ($result as $k => $v) {
foreach ($this->_kludge as $a => $b) {
if ($t = $this->kludge($b, $v)) {
$this->kludge->put($a, $t);
break;
}
}
if ($t = $this->kludge('SEEN-BY: ', $v))
$this->seenby->push($t);
elseif ($t = $this->kludge('PATH: ', $v))
$this->path->push($t);
elseif ($t = $this->kludge(' \* Origin: ',$v))
$this->origin = $t;
// We got unknown Kludge lines in the origin
else {
$this->unknown->push($v);
//dd(['v'=>$v,'t'=>$t,'message'=>$message]);
}
}
}
public function description()
{
switch ($this->type())
{
case 'Echomail': return sprintf('Echomail: '.$this->echoarea);
case 'Netmail': return sprintf('Netmail: %s->%s',$this->fqfa,$this->fqfd);
default:
return 'UNKNOWN';
}
}
public function type()
{
if ($this->echoarea)
return 'Echomail';
if ($this->intl)
return 'Netmail';
return 'UNKNOWN';
}
}

159
app/Classes/FTNPacket.php Normal file
View File

@ -0,0 +1,159 @@
<?php
namespace App\Classes;
use App\Exceptions\InvalidFidoPacketException;
class FTNPacket extends FTN
{
public $pktsrc = NULL;
public $pktdst = NULL;
private $pktver = NULL;
public $date = NULL;
private $baud = NULL;
private $proddata = NULL;
private $password = NULL;
public $filename = NULL;
public $messages = [];
public function __construct(string $file)
{
$this->filename = $file;
if ($file)
return $this->OpenFile($file);
}
/**
* Open a packet file
*
* @param string $file
* @throws InvalidFidoPacketException
*/
private function OpenFile(string $file)
{
$f = fopen($file,'r');
// $fstat = fstat($f);
// PKT Header
$header = fread($f,0x3a);
// Could not read header
if (strlen($header) != 0x3a)
throw new InvalidFidoPacketException('Length of Header too short: '.$file);
// Not a type 2 packet
if (array_get(unpack('vv',substr($header,0x12)),'v') != 2)
throw new InvalidFidoPacketException('Not a type 2 packet:'. $file);
$this->setHeader($header);
$this->messages = collect();
while (! feof($f))
{
$x = fread($f,2);
// End of Packet?
if (strlen($x) == 2 and $x == "\00\00")
{
break;
}
// Messages start with 02H 00H
if (strlen($x) == 2 AND $x != "\02\00")
throw new InvalidFidoPacketException('Not a valid packet: '.$x);
// No message attached
else if (! strlen($x))
break;
$message = new FTNMessage(fread($f,0xc));
$message->date = $this->readnullfield($f);
$message->to = $this->readnullfield($f);
$message->from = $this->readnullfield($f);
$message->subject = $this->readnullfield($f);
$message->message = $this->readnullfield($f);
$this->messages->push($message);
}
}
private function readnullfield($f)
{
$result = '';
while (($x = fgetc($f) OR strlen($x)) AND $x !== "\00")
{
$result .= $x;
}
return $result;
}
public function setHeader(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));
$this->pktsrc = sprintf('%s:%s/%s.%s',
array_get($result2,'ozone'),
array_get($result1,'onet'),
array_get($result1,'onode'),
array_get($result2,'dpoint')
);
$this->pktdst = sprintf('%s:%s/%s.%s',
array_get($result2,'dzone'),
array_get($result1,'dnet'),
array_get($result1,'dnode'),
array_get($result2,'dpoint')
);
$this->date = sprintf ('%d-%d-%d %d:%d:%d',
array_get($result1,'y'),
array_get($result1,'m'),
array_get($result1,'d'),
array_get($result1,'H'),
array_get($result1,'M'),
array_get($result1,'S')
);
$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');
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Classes\FTNPacket;
class FtnPkt extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ftn:pkt {file : Fidonet Packet File PKT} {--dump : Dump packet}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Import Packet into Database';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$pkt = new FTNPacket($this->argument('file'));
$this->info(sprintf('Packet: %s has %s messages. Addr: %s->%s (Date: %s)',
$pkt->filename,
$pkt->messages->count(),
$pkt->pktsrc,
$pkt->pktdst,
$pkt->date
));
foreach ($pkt->messages as $o)
{
$this->warn(sprintf('-- From: %s(%s)->%s(%s), Type: %s, Size: %d, FQFA: %s',
$o->from,
$o->src,
$o->to,
$o->dst,
$o->description(),
strlen($o->message),
$o->fqfa
));
if ($o->unknown->count())
$this->error(sprintf('?? %s Unknown headers',$o->unknown->count()));
}
if ($this->option('dump'))
dump($o);
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class InvalidFidoPacketException extends Exception
{
//
}

View File

@ -1,68 +0,0 @@
<p align="center"><img src="https://laravel.com/assets/img/components/logo-laravel.svg"></p>
<p align="center">
<a href="https://travis-ci.org/laravel/framework"><img src="https://travis-ci.org/laravel/framework.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://poser.pugx.org/laravel/framework/d/total.svg" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://poser.pugx.org/laravel/framework/v/stable.svg" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://poser.pugx.org/laravel/framework/license.svg" alt="License"></a>
</p>
## About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel attempts to take the pain out of development by easing common tasks used in the majority of web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, yet powerful, providing tools needed for large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of any modern web application framework, making it a breeze to get started learning the framework.
If you're not in the mood to read, [Laracasts](https://laracasts.com) contains over 1100 video tutorials on a range of topics including Laravel, modern PHP, unit testing, JavaScript, and more. Boost the skill level of yourself and your entire team by digging into our comprehensive video library.
## Laravel Sponsors
We would like to extend our thanks to the following sponsors for helping fund on-going Laravel development. If you are interested in becoming a sponsor, please visit the Laravel [Patreon page](https://patreon.com/taylorotwell):
- **[Vehikl](https://vehikl.com/)**
- **[Tighten Co.](https://tighten.co)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Cubet Techno Labs](https://cubettech.com)**
- **[British Software Development](https://www.britishsoftware.co)**
- **[Webdock, Fast VPS Hosting](https://www.webdock.io/en)**
- [UserInsights](https://userinsights.com)
- [Fragrantica](https://www.fragrantica.com)
- [SOFTonSOFA](https://softonsofa.com/)
- [User10](https://user10.com)
- [Soumettre.fr](https://soumettre.fr/)
- [CodeBrisk](https://codebrisk.com)
- [1Forge](https://1forge.com)
- [TECPRESSO](https://tecpresso.co.jp/)
- [Runtime Converter](http://runtimeconverter.com/)
- [WebL'Agence](https://weblagence.com/)
- [Invoice Ninja](https://www.invoiceninja.com)
- [iMi digital](https://www.imi-digital.de/)
- [Earthlink](https://www.earthlink.ro/)
- [Steadfast Collective](https://steadfastcollective.com/)
- [We Are The Robots Inc.](https://watr.mx/)
- [Understand.io](https://www.understand.io/)
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).