1391 lines
39 KiB
PHP
1391 lines
39 KiB
PHP
<?php
|
|
|
|
namespace App\Classes\Protocol;
|
|
|
|
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\Protocol as BaseProtocol;
|
|
use App\Classes\Sock\SocketClient;
|
|
use App\Classes\Sock\SocketException;
|
|
use App\Models\{Address,Setup};
|
|
|
|
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 */
|
|
|
|
/* 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;
|
|
|
|
/* 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 */
|
|
|
|
private const SE_BASE = 1;
|
|
private const SE_INIT = (1<<self::SE_BASE); /* 0000 0001 Are we in initialise mode */
|
|
private const SE_SENTEOB = (1<<self::SE_BASE+1); /* 0000 0010 Have we sent our EOB */
|
|
private const SE_RECVEOB = (1<<self::SE_BASE+2); /* 0000 0100 Have we received EOB */
|
|
private const SE_DELAYEOB = (1<<self::SE_BASE+3); /* 0000 1000 Delay sending M_EOB message until remote's one if remote is binkp/1.0 and pretends to have FREQ on us */
|
|
private const SE_WAITGET = (1<<self::SE_BASE+4); /* 0001 0000 Wait for GET before sending a file */
|
|
private const SE_WAITGOT = (1<<self::SE_BASE+5); /* 0010 0000 We are waiting for a GOT from the remote */
|
|
private const SE_SENDFILE = (1<<self::SE_BASE+6); /* 0100 0000 Are we sending a file */
|
|
private const SE_NOFILES = (1<<self::SE_BASE+7); /* 1000 0000 We have no more files to send */
|
|
|
|
private string $md_challenge; /* The MD5 challenge with the remote system */
|
|
private int $is_msg;
|
|
private int $mib;
|
|
private int $rc;
|
|
private int $error;
|
|
|
|
private int $rx_ptr; // @todo Whats the point of this? It seems its only the size of $rx_buf?
|
|
private int $rx_size;
|
|
private string $rx_buf = '';
|
|
|
|
private ?Collection $mqueue;
|
|
private string $tx_buf;
|
|
private int $tx_ptr;
|
|
private int $tx_left;
|
|
|
|
/* BINK COMMANDS */
|
|
private const M_NUL = 0;
|
|
private const M_ADR = 1;
|
|
private const M_PWD = 2;
|
|
private const M_FILE = 3;
|
|
private const M_OK = 4;
|
|
private const M_EOB = 5;
|
|
private const M_GOTSKIP = 6;
|
|
private const M_ERR = 7;
|
|
private const M_BSY = 8;
|
|
private const M_GET = 9;
|
|
private const M_CHAT = 12;
|
|
|
|
/**
|
|
* Incoming BINKP session
|
|
*
|
|
* @param SocketClient $client
|
|
* @return int|null
|
|
* @throws SocketException
|
|
* @throws Exception
|
|
*/
|
|
public function onConnect(SocketClient $client): ?int
|
|
{
|
|
// If our parent returns a PID, we've forked
|
|
if (! parent::onConnect($client)) {
|
|
Log::withContext(['pid'=>getmypid()]);
|
|
|
|
$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);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* BINKD handshake
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
private function binkp_hs(): void
|
|
{
|
|
if ($this->DEBUG)
|
|
Log::debug(sprintf('%s:+ binkp_hs',self::LOGKEY));
|
|
|
|
if (! $this->originate && ($this->setup->opt_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('SYS %s',$this->setup->system_name));
|
|
$this->msgs(self::BPM_NUL,sprintf('ZYZ %s',$this->setup->sysop));
|
|
$this->msgs(self::BPM_NUL,sprintf('LOC %s',$this->setup->location));
|
|
$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));
|
|
|
|
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' : ''));
|
|
}
|
|
|
|
// If we are originating, we'll show the remote our address in the same network
|
|
if ($this->originate) {
|
|
if ($this->setup->optionGet(Setup::O_HIDEAKA)) {
|
|
$addresses = collect();
|
|
|
|
foreach ($this->node->aka_remote_authed as $ao)
|
|
$addresses = $addresses->merge($this->setup->system->match($ao->zone,Address::NODE_ZC|Address::NODE_RC|Address::NODE_NC|Address::NODE_HC|Address::NODE_ACTIVE|Address::NODE_PVT|Address::NODE_POINT));
|
|
|
|
$addresses = $addresses->unique();
|
|
|
|
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(',')));
|
|
}
|
|
|
|
$this->msgs(self::BPM_ADR,$this->setup->system->addresses->pluck('ftn')->join(' '));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return int
|
|
*/
|
|
private function binkp_hsdone(): int
|
|
{
|
|
if ($this->DEBUG)
|
|
Log::debug(sprintf('%s:+ binkp_hsdone',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 (! $this->setup->phone)
|
|
$this->setup->phone = '-Unpublished-';
|
|
|
|
if (! $this->optionGet(self::O_PWD) || ($this->setup->opt_md != self::O_YES))
|
|
$this->setup->opt_cr = self::O_NO;
|
|
|
|
if (($this->setup->opt_cr&self::O_WE) && ($this->setup->opt_cr&self::O_THEY)) {
|
|
dump('Enable crypting messages');
|
|
|
|
/*
|
|
$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;
|
|
}
|
|
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->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->setup->opt_cht&self::O_WE) && ($this->setup->opt_cht&self::O_WANT))
|
|
$this->setup->opt_cht = 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;
|
|
|
|
if ($this->node->get_versionint() > 100)
|
|
$this->sessionClear(self::SE_DELAYEOB);
|
|
|
|
$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',
|
|
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
|
|
));
|
|
|
|
return 1;
|
|
}
|
|
|
|
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;
|
|
$this->mib = 0;
|
|
$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;
|
|
|
|
for ($x=0;$x<count($this->setup->binkp_options);$x++) {
|
|
switch (strtolower($this->setup->binkp_options[$x])) {
|
|
case 'p': /* Force password digest */
|
|
$this->setup->opt_md |= self::O_NEED;
|
|
|
|
case 'm': /* Want password digest */
|
|
$this->setup->opt_md |= self::O_WANT;
|
|
break;
|
|
|
|
case 'c': /* Can crypt */
|
|
$this->setup->opt_cr |= self::O_WE;
|
|
break;
|
|
|
|
case 'd': /* No dupes mode */
|
|
$this->setup->opt_nd |= self::O_NO; /*mode?O_THEY:O_NO;*/
|
|
|
|
case 'r': /* Non-reliable mode */
|
|
$this->setup->opt_nr |= ($this->originate ? self::O_WANT : self::O_NO);
|
|
break;
|
|
|
|
case 'b': /* Multi-batch mode */
|
|
$this->setup->opt_mb |= self::O_WANT;
|
|
break;
|
|
|
|
case 't': /* Chat - not implemented */
|
|
//$this->setup->opt_cht |= self::O_WANT;
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s: ! binkp_init ERROR - Unknown BINKP option [%s]',self::LOGKEY,$this->setup->binkp_options[$x]));
|
|
}
|
|
}
|
|
|
|
return self::OK;
|
|
}
|
|
|
|
/**
|
|
* Receive data from the remote
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
private function binkp_recv(): int
|
|
{
|
|
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));
|
|
|
|
if ($blksz !== 0) {
|
|
try {
|
|
$this->rx_buf .= $this->client->read(0,$blksz-$this->rx_ptr);
|
|
|
|
} 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));
|
|
|
|
return 1;
|
|
}
|
|
Log::error(sprintf('%s: - binkp_recv Exception [%s].',self::LOGKEY,$e->getCode()));
|
|
|
|
$this->socket_error = $e->getMessage();
|
|
$this->error = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (strlen($this->rx_buf) === 0) {
|
|
// @todo Check that this is correct.
|
|
Log::debug(sprintf('%s: - binkp_recv Was the socket closed by the remote?',self::LOGKEY));
|
|
$this->error = -2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
if ($this->setup->opt_cr == self::O_YES ) {
|
|
//decrypt_buf( (void *) &bp->rx_buf[bp->rx_ptr], (size_t) readsz, bp->keys_in );
|
|
}
|
|
*/
|
|
}
|
|
|
|
$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));
|
|
|
|
/* Received complete block */
|
|
if ($this->rx_ptr === $blksz) {
|
|
/* Header */
|
|
if ($this->rx_size == -1 ) {
|
|
$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 ($this->DEBUG)
|
|
Log::debug(sprintf('%s: - binkp_recv HEADER, is_msg [%d], rx_size [%d]',self::LOGKEY,$this->is_msg,$this->rx_size));
|
|
|
|
if ($this->rx_size == 0)
|
|
goto ZeroLen;
|
|
|
|
$rc = 1;
|
|
|
|
/* Next block */
|
|
} else {
|
|
ZeroLen:
|
|
if ($this->DEBUG)
|
|
Log::debug(sprintf('%s: - binkp_recv NEXT BLOCK, is_msg [%d]',self::LOGKEY,$this->is_msg));
|
|
|
|
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));
|
|
$this->rx_size = -1;
|
|
$this->rx_ptr = 0;
|
|
$this->rx_buf = '';
|
|
|
|
return 1;
|
|
}
|
|
|
|
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));
|
|
|
|
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;
|
|
|
|
} 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:
|
|
$rc = $this->M_adr($data);
|
|
break;
|
|
|
|
case self::M_EOB:
|
|
$rc = $this->M_eob($data);
|
|
break;
|
|
|
|
case self::M_NUL:
|
|
$rc = $this->M_nul($data);
|
|
break;
|
|
|
|
case self::M_PWD:
|
|
$rc = $this->M_pwd($data);
|
|
break;
|
|
|
|
case self::M_ERR:
|
|
$rc = $this->M_err($data);
|
|
break;
|
|
|
|
case self::M_FILE:
|
|
$rc = $this->M_file($data);
|
|
break;
|
|
|
|
case self::M_GET:
|
|
$rc = $this->M_get($data);
|
|
break;
|
|
|
|
case self::M_GOTSKIP:
|
|
$rc = $this->M_gotskip($data);
|
|
break;
|
|
|
|
case self::M_OK:
|
|
$rc = $this->M_ok($data);
|
|
break;
|
|
|
|
case self::M_CHAT:
|
|
$rc = $this->M_chat($data);
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s: ! binkp_recv Command not implemented [%d]',self::LOGKEY,$rc));
|
|
$rc = 1;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
if ($this->recv->fd) {
|
|
try {
|
|
$rc = $this->recv->write($this->rx_buf);
|
|
|
|
} 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));
|
|
|
|
$this->msgs(self::BPM_GOT,$this->recv->name_size_time);
|
|
$this->recv->close();
|
|
$rc = 1;
|
|
}
|
|
|
|
} else {
|
|
Log::critical(sprintf('%s: - binkp_recv Ignoring data block', self::LOGKEY));
|
|
$rc = 1;
|
|
}
|
|
}
|
|
|
|
$this->rx_ptr = 0;
|
|
$this->rx_size = -1;
|
|
}
|
|
|
|
$this->rx_buf = '';
|
|
|
|
} else {
|
|
$rc = 1;
|
|
}
|
|
|
|
if ($this->DEBUG)
|
|
Log::debug(sprintf('%s:= binkp_recv [%d]',self::LOGKEY,$rc));
|
|
|
|
return $rc;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function binkp_send(): int
|
|
{
|
|
if ($this->DEBUG)
|
|
Log::debug(sprintf('%s:+ binkp_send - tx_left [%d]',self::LOGKEY,$this->tx_left));
|
|
|
|
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()));
|
|
|
|
if ($this->mqueue->count()) { /* there are unsent messages */
|
|
while ($msg = $this->mqueue->shift()) {
|
|
if (($msg->len+$this->tx_left) > self::MAX_BLKSIZE) {
|
|
break;
|
|
}
|
|
|
|
$this->tx_buf .= $msg->msg;
|
|
$this->tx_left += $msg->len;
|
|
}
|
|
|
|
} elseif ($this->sessionGet(self::SE_SENDFILE) && $this->send->fd && (! $this->sessionGet(self::SE_WAITGET))) {
|
|
$data = '';
|
|
|
|
try {
|
|
$data = $this->send->read(self::BP_BLKSIZE);
|
|
|
|
} 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()));
|
|
}
|
|
|
|
if ($data) {
|
|
$this->tx_buf .= BinkpMessage::mkheader(strlen($data));
|
|
$this->tx_buf .= $data;
|
|
|
|
/*
|
|
if ($this->setup->opt_cr == self::O_YES) {
|
|
encrypt_buf($this->tx_buf,($data + BinkpMessage::BLK_HDR_SIZE),$this->keys_out);
|
|
}
|
|
*/
|
|
|
|
$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) {
|
|
$this->sessionSet(self::SE_WAITGOT);
|
|
$this->sessionClear(self::SE_SENDFILE);
|
|
}
|
|
}
|
|
|
|
} 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)));
|
|
|
|
$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)
|
|
return 1;
|
|
|
|
$this->socket_error = $e->getMessage();
|
|
Log::error(sprintf('%s:! binkp_send - ERROR [%s]',self::LOGKEY,$e->getMessage()));
|
|
return 0;
|
|
}
|
|
|
|
$this->tx_ptr += $rc;
|
|
$this->tx_left -= $rc;
|
|
|
|
if (! $this->tx_left) {
|
|
$this->tx_buf = '';
|
|
$this->tx_ptr = 0;
|
|
}
|
|
}
|
|
|
|
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 = $this->strsep($str,' ');
|
|
$time = $this->strsep($str,' ');
|
|
$offs = $this->strsep($str,' ');
|
|
|
|
if ($name && $size && $time) {
|
|
return [
|
|
'file'=>['name'=>$name,'size'=>$size,'mtime'=>$time],
|
|
'offs'=>$offs
|
|
];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Add a BINKP control message to the queue
|
|
*
|
|
* @param string $id
|
|
* @param string $msg_body
|
|
* @return void
|
|
*/
|
|
private function msgs(string $id,string $msg_body): void
|
|
{
|
|
if ($this->DEBUG)
|
|
Log::debug(sprintf('%s:+ msgs [%d:%s]',self::LOGKEY,$id,$msg_body));
|
|
|
|
$this->mqueue->push(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);
|
|
}
|
|
*/
|
|
|
|
$this->mib++;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function M_adr(string $buf): int
|
|
{
|
|
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));
|
|
|
|
try {
|
|
if (! ($o = Address::findFTN($rem_aka))) {
|
|
Log::debug(sprintf('%s: ? AKA is UNKNOWN [%s]',self::LOGKEY,$rem_aka));
|
|
|
|
$this->node->ftn_other = $rem_aka;
|
|
continue;
|
|
}
|
|
|
|
} 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;
|
|
}
|
|
|
|
// 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));
|
|
|
|
$this->msgs(self::BPM_ERR,sprintf('Sorry that is my AKA [%s]',$rem_aka));
|
|
$this->rc = self::S_FAILURE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// @todo lock nodes
|
|
$this->node->ftn = $o;
|
|
|
|
$rc = $this->node->aka_num;
|
|
}
|
|
|
|
if ($rc == 0) {
|
|
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);
|
|
|
|
return 0;
|
|
}
|
|
|
|
if ($this->originate) {
|
|
if (! $this->node->originate_check()) {
|
|
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;
|
|
|
|
return 0;
|
|
}
|
|
|
|
if ($this->md_challenge) {
|
|
$this->msgs(self::BPM_PWD,sprintf('CRAM-MD5-%s',$this->node->get_md5chal($this->md_challenge)));
|
|
|
|
} elseif ($this->setup->opt_md == self::O_YES ) {
|
|
$this->msgs(self::BPM_ERR,'Can\'t use plaintext password');
|
|
$this->rc = self::S_FAILURE|self::S_ADDTRY;
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
$this->msgs(self::BPM_PWD,$this->node->password);
|
|
}
|
|
}
|
|
|
|
if (! $this->node->aka_num)
|
|
$this->optionClear(self::O_PWD);
|
|
else
|
|
$this->optionSet(self::O_PWD);
|
|
|
|
// If we are not the originator, we'll show our addresses in common.
|
|
if (! $this->originate)
|
|
$this->msgs(self::BPM_ADR,$this->our_addresses()->pluck('ftn')->join(' '));
|
|
|
|
return 1;
|
|
}
|
|
|
|
private function M_chat(string $buf): int
|
|
{
|
|
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));
|
|
|
|
} else {
|
|
Log::error(sprintf('%s: ! We got a chat message, but chat is disabled',self::LOGKEY));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* We received EOB from the remote.
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
private function M_eob(string $buf): int
|
|
{
|
|
Log::debug(sprintf('%s:+ M_eob [%s]',self::LOGKEY,$buf));
|
|
|
|
if ($this->recv->fd)
|
|
$this->recv->close();
|
|
|
|
$this->sessionSet(self::SE_RECVEOB);
|
|
$this->sessionClear(self::SE_DELAYEOB);
|
|
|
|
if (! $this->send->total_count && $this->sessionGet(self::SE_NOFILES)) {
|
|
// 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));
|
|
$this->send->mail($ao);
|
|
$this->send->files($ao);
|
|
}
|
|
|
|
if ($this->send->total_count)
|
|
$this->sessionClear(self::SE_NOFILES|self::SE_SENTEOB);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function M_err(string $buf): int
|
|
{
|
|
Log::debug(sprintf('%s:+ M_err [%s]',self::LOGKEY,$buf));
|
|
|
|
$this->error_close();
|
|
$this->rc = ($this->originate ? (self::S_REDIAL|self::S_ADDTRY) : self::S_BUSY);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function M_file(string $buf): int
|
|
{
|
|
Log::debug(sprintf('%s:+ M_file [%s]',self::LOGKEY,$buf));
|
|
|
|
if ($this->sessionGet(self::SE_SENTEOB|self::SE_RECVEOB))
|
|
$this->sessionClear(self::SE_SENTEOB);
|
|
|
|
$this->sessionClear(self::SE_RECVEOB);
|
|
|
|
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 ($this->sessionGet(self::SE_SENDFILE))
|
|
$this->send->close(FALSE);
|
|
|
|
$this->rc = ($this->originate ? self::S_REDIAL|self::S_ADDTRY : self::S_BUSY);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// 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);
|
|
|
|
return 1;
|
|
}
|
|
|
|
$this->recv->new($file['file']);
|
|
|
|
try {
|
|
switch ($this->recv->open($this->node->address,$file['offs']<0)) {
|
|
case self::FOP_ERROR:
|
|
Log::error(sprintf('%s: ! File ERROR',self::LOGKEY));
|
|
|
|
case self::FOP_SUSPEND:
|
|
Log::info(sprintf('%s: - File Suspended',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);
|
|
|
|
break;
|
|
|
|
case self::FOP_OK:
|
|
Log::debug(sprintf('%s: - Getting file from [%d]',self::LOGKEY,$file['offs']));
|
|
|
|
if ($file['offs'] != -1) {
|
|
if (! ($this->setup->opt_nr&self::O_THEY)) {
|
|
$this->setup->opt_nr |= 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()));
|
|
|
|
$this->msgs(self::BPM_SKIP,$this->recv->name_size_time);
|
|
|
|
// Close the file, since we had an error opening it.
|
|
if ($this->recv->fd)
|
|
$this->recv->close();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function M_get(string $buf): int
|
|
{
|
|
Log::debug(sprintf('%s:+ M_get [%s]',self::LOGKEY,$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)
|
|
&& $this->send->mtime == Arr::get($file,'file.mtime')
|
|
&& $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']));
|
|
|
|
$this->msgs(self::BPM_ERR,'Can\'t send file from requested offset');
|
|
$this->send->close(FALSE);
|
|
$this->sessionClear(self::SE_SENDFILE);
|
|
|
|
} 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']));
|
|
}
|
|
|
|
} else {
|
|
Log::error(sprintf('%s: ! M_got for unknown file [%s]',self::LOGKEY,$buf));
|
|
}
|
|
|
|
} else {
|
|
Log::error(sprintf('%s: - UNPARSABLE file info [%s]',self::LOGKEY,$buf));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* M_GOT/M_SKIP commands
|
|
*
|
|
* @param string $buf
|
|
* @return int
|
|
* @throws Exception
|
|
*/
|
|
private function M_gotskip(string $buf):int
|
|
{
|
|
Log::debug(sprintf('%s:+ M_gotskip [%s]',self::LOGKEY,$buf));
|
|
|
|
if ($file = $this->file_parse($buf)) {
|
|
if ($this->send->sendas
|
|
&& ! strncasecmp(Arr::get($file,'file.name'),$this->send->sendas,self::MAX_PATH)
|
|
&& $this->send->mtime == Arr::get($file,'file.mtime')
|
|
&& $this->send->size == Arr::get($file,'file.size'))
|
|
{
|
|
// @todo Commit our mail transaction if the remote end confirmed receipt of the file.
|
|
if ($this->sessionGet(self::SE_SENDFILE)) {
|
|
Log::debug(sprintf('%s:Packet/File [%s] sent. (%s)',self::LOGKEY,$this->send->sendas,$this->send->name));
|
|
$this->sessionClear(self::SE_SENDFILE);
|
|
$this->send->close(TRUE);
|
|
|
|
return 1;
|
|
}
|
|
|
|
if ($this->sessionGet(self::SE_WAITGOT)) {
|
|
Log::debug(sprintf('%s:Packet/File [%s] sent. (%s)',self::LOGKEY,$this->send->sendas,$this->send->name));
|
|
$this->sessionClear(self::SE_WAITGOT);
|
|
$this->send->close(TRUE);
|
|
|
|
} else {
|
|
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));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function M_nul(string $buf): int
|
|
{
|
|
Log::debug(sprintf('%s:+ M_nul [%s]',self::LOGKEY,$buf));
|
|
|
|
if (! strncmp($buf,'SYS ',4)) {
|
|
$this->node->system = $this->skip_blanks(substr($buf,4));
|
|
|
|
} elseif (! strncmp($buf, 'ZYZ ',4)) {
|
|
$this->node->sysop = $this->skip_blanks(substr($buf,4));
|
|
|
|
} elseif (! strncmp($buf,'LOC ',4)) {
|
|
$this->node->location = $this->skip_blanks(substr($buf,4));
|
|
|
|
} elseif (! strncmp($buf,'NDL ',4)) {
|
|
$data = $this->skip_blanks(substr($buf,4));
|
|
$comma = strpos($data,',');
|
|
$spd = substr($data,0,$comma);
|
|
|
|
if ($comma)
|
|
$this->node->flags = substr($data,$comma+1);
|
|
|
|
if ($spd >= 300) {
|
|
$this->client->speed = $spd;
|
|
|
|
} else {
|
|
$comma = $this->skip_blanks(substr($buf,4));
|
|
$c = 0;
|
|
while (($x=substr($comma,$c,1)) && is_numeric($x))
|
|
$c++;
|
|
|
|
$comma = substr($comma,0,$c);
|
|
|
|
if (! $comma) {
|
|
$this->client->speed = SocketClient::TCP_SPEED;
|
|
|
|
} elseif (strtolower(substr($comma,$c+1,1)) == 'k') {
|
|
$this->client->speed = $spd * 1024;
|
|
|
|
} elseif (strtolower(substr($comma,$c+1,1)) == 'm') {
|
|
$this->client->speed = $spd * 1024 * 1024;
|
|
|
|
} else {
|
|
$this->client->speed = SocketClient::TCP_SPEED;
|
|
}
|
|
}
|
|
|
|
} elseif (! strncmp($buf,'TIME ',5)) {
|
|
$this->node->node_time = $this->skip_blanks(substr($buf,5));
|
|
|
|
} elseif (! strncmp($buf,'VER ',4)) {
|
|
$data = $this->skip_blanks(substr($buf,4));
|
|
$matches = [];
|
|
preg_match('#^(.+)\s+binkp/([0-9]+)\.([0-9]+)$#',$data,$matches);
|
|
|
|
$this->node->software = $matches[1];
|
|
$this->node->ver_major = $matches[2];
|
|
$this->node->ver_minor = $matches[3];
|
|
|
|
} elseif (! strncmp($buf,'TRF ',4)) {
|
|
$data = $this->skip_blanks(substr($buf,4));
|
|
$matches = [];
|
|
preg_match('/^([0-9]+)\s+([0-9]+)$/',$data,$matches);
|
|
|
|
$this->node->netmail = isset($matches[1]) ?: 0;
|
|
$this->node->files = isset($matches[2]) ?: 0;
|
|
|
|
if ($this->node->netmail + $this->node->files)
|
|
$this->sessionSet(self::SE_DELAYEOB);
|
|
|
|
} elseif (! strncmp($buf,'FREQ',4)) {
|
|
$this->sessionSet(self::SE_DELAYEOB);
|
|
|
|
} elseif (! strncmp($buf,'PHN ',4)) {
|
|
$this->node->phone = $this->skip_blanks(substr($buf,4));
|
|
|
|
} elseif (! strncmp($buf,'OPM ',4)) {
|
|
$this->node->message = $this->skip_blanks(substr($buf,4));
|
|
|
|
} elseif (! strncmp($buf,'OPT ',4)) {
|
|
$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;
|
|
|
|
} elseif (! strcmp($p,'ND')) {
|
|
$this->setup->opt_nd |= self::O_WE;
|
|
|
|
} elseif (! strcmp($p,'NDA')) {
|
|
$this->setup->opt_nd |= self::O_EXT;
|
|
|
|
} elseif (! strcmp($p,'CHAT')) {
|
|
$this->setup->opt_cht |= self::O_WE;
|
|
|
|
} elseif (! strcmp($p,'CRYPT')) {
|
|
$this->setup->opt_cr |= self::O_THEY;
|
|
|
|
} elseif (! strncmp($p,'CRAM-MD5-',9) && $this->originate && $this->setup->opt_md) {
|
|
if (($x=strlen(substr($p,9))) > 64 ) {
|
|
Log::error(sprintf('%s: - M_nul Got TOO LONG [%d] challenge string',self::LOGKEY,$x));
|
|
|
|
} else {
|
|
$this->md_challenge = hex2bin(substr($p,9));
|
|
|
|
if ($this->md_challenge)
|
|
$this->setup->opt_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;
|
|
|
|
} 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: - M_nul Got UNKNOWN NUL [%s]',self::LOGKEY,$buf));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Remote accepted our password
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
private function M_ok(string $buf): int
|
|
{
|
|
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));
|
|
|
|
$this->rc = self::S_FAILURE;
|
|
return 0;
|
|
}
|
|
|
|
$buf = $this->skip_blanks($buf);
|
|
|
|
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));
|
|
|
|
$this->setup->opt_cr = 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
|
|
*/
|
|
private function M_pwd(string $buf): int
|
|
{
|
|
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));
|
|
|
|
$this->rc = self::S_FAILURE;
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
|
|
} elseif ($this->setup->opt_md&self::O_NEED) {
|
|
Log::error(sprintf('%s: ! Remote doesnt support MD5',self::LOGKEY));
|
|
|
|
$this->msgs( self::BPM_ERR,'You must support MD5');
|
|
$this->rc = self::S_FAILURE;
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (! $this->md_challenge || (! $have_CRAM && ! ($this->setup->opt_md&self::O_NEED))) {
|
|
// Loop to match passwords
|
|
$this->node->auth($buf);
|
|
}
|
|
|
|
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));
|
|
|
|
$this->msgs(self::BPM_ERR,'Security violation');
|
|
$this->optionSet(self::O_BAD);
|
|
$this->rc = self::S_FAILURE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
} elseif (! $this->node->aka_authed) {
|
|
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;
|
|
|
|
if (!$have_pwd || $this->setup->opt_md != self::O_YES)
|
|
$this->setup->opt_cr = self::O_NO;
|
|
|
|
$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 (strlen($tmp))
|
|
$this->msgs(self::BPM_NUL,sprintf('OPT%s',$tmp));
|
|
|
|
// 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));
|
|
$this->msgs(self::BPM_OK,sprintf('%ssecure',$have_pwd ? '' : 'non-'));
|
|
|
|
return $this->binkp_hsdone();
|
|
}
|
|
|
|
protected function protocol_init(): int
|
|
{
|
|
// Not Used
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Setup our BINKP session
|
|
*
|
|
* @return int
|
|
* @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;
|
|
|
|
$this->binkp_hs();
|
|
|
|
while (TRUE) {
|
|
if ($this->DEBUG)
|
|
Log::debug(sprintf('%s: - protocol_session LOOP',self::LOGKEY));
|
|
|
|
if (! $this->sessionGet(self::SE_INIT|self::SE_SENDFILE|self::SE_SENTEOB|self::SE_NOFILES) && ! $this->send->fd) {
|
|
// Open our next file to send
|
|
if ($this->send->total_count && ! $this->send->fd)
|
|
$this->send->open();
|
|
|
|
// We have an open file descriptor, set our mode to send
|
|
if ($this->send->fd) {
|
|
$this->sessionSet(self::SE_SENDFILE);
|
|
|
|
// NR mode, we wait for an M_GET before sending
|
|
if ($this->setup->opt_nr&self::O_WE) {
|
|
$this->sessionSet(self::SE_WAITGET);
|
|
|
|
Log::debug(sprintf('%s: - 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));
|
|
|
|
$this->sessionClear(self::SE_SENTEOB);
|
|
|
|
// We dont have anything to send
|
|
} else {
|
|
$this->sessionSet(self::SE_NOFILES);
|
|
}
|
|
}
|
|
|
|
if (! $this->sessionGet(self::SE_INIT|self::SE_WAITGOT|self::SE_SENTEOB|self::SE_DELAYEOB) && $this->sessionGet(self::SE_NOFILES)) {
|
|
$this->msgs(self::BPM_EOB,'');
|
|
$this->sessionSet(self::SE_SENTEOB);
|
|
}
|
|
|
|
$this->rc = self::S_OK;
|
|
|
|
if ($this->sessionGet(self::SE_SENTEOB) && $this->sessionGet(self::SE_RECVEOB)) {
|
|
if ($this->mib < 3 || $this->node->get_versionint() <= 100) {
|
|
break;
|
|
}
|
|
|
|
$this->mib = 0;
|
|
$this->sessionClear(self::SE_RECVEOB|self::SE_SENTEOB);
|
|
}
|
|
|
|
$wd = ($this->mqueue->count() || $this->tx_left || ($this->sessionGet(self::SE_SENDFILE) && $this->send->fd && ! $this->sessionGet(self::SE_WAITGET)));
|
|
$rd = TRUE;
|
|
|
|
try {
|
|
if ($this->DEBUG)
|
|
Log::debug(sprintf('%s: - Checking if there more data (ttySelect), timeout [%d]',self::LOGKEY,self::BP_TIMEOUT));
|
|
|
|
// @todo we need to catch a timeout if there are no reads/writes
|
|
$rc = $this->client->ttySelect($rd,$wd,self::BP_TIMEOUT);
|
|
|
|
if ($this->DEBUG)
|
|
Log::debug(sprintf('%s: - ttySelect returned [%d]',self::LOGKEY,$rc));
|
|
|
|
} catch (Exception) {
|
|
$this->error_close();
|
|
$this->error = -2;
|
|
|
|
break;
|
|
}
|
|
|
|
$this->rc = ($this->originate ? (self::S_REDIAL|self::S_ADDTRY) : self::S_BUSY);
|
|
|
|
if ($rc == 0) {
|
|
$this->error_close();
|
|
$this->error = -1;
|
|
|
|
break;
|
|
}
|
|
|
|
if ($rd && ! $this->binkp_recv())
|
|
break;
|
|
|
|
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->error == -1)
|
|
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));
|
|
|
|
while (! $this->error) {
|
|
try {
|
|
$buf = $this->client->read(0,self::MAX_BLKSIZE);
|
|
|
|
} catch (Exception $e) {
|
|
if ($e->getCode() !== 11) {
|
|
Log::debug(sprintf('%s: ? protocol_session Got Exception [%d] (%s)',self::LOGKEY,$e->getCode(),$e->getMessage()));
|
|
|
|
$this->error = 1;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (strlen($buf) == 0)
|
|
break;
|
|
|
|
Log::warning(sprintf('%s: - Purged (%s) [%d] bytes from input stream',self::LOGKEY,hex_dump($buf),strlen($buf)));
|
|
}
|
|
|
|
while (! $this->error && ($this->mqueue->count() || $this->tx_left) && $this->binkp_send());
|
|
|
|
return $this->rc;
|
|
}
|
|
|
|
/**
|
|
* Strip blanks at the beginning of a string
|
|
*
|
|
* @param string $str
|
|
* @return string
|
|
* @throws Exception
|
|
*/
|
|
private function skip_blanks(string $str): string
|
|
{
|
|
$c = 0;
|
|
|
|
if ($str != NULL)
|
|
while ($this->isSpace(substr($str,$c,1)))
|
|
$c++;
|
|
|
|
return substr($str,$c);
|
|
}
|
|
|
|
/**
|
|
* Return the string delimited by char and shorten the input to the remaining characters
|
|
*
|
|
* @param string $str
|
|
* @param string $char
|
|
* @return string
|
|
*/
|
|
private function strsep(string &$str,string $char): string
|
|
{
|
|
$return = strstr($str,$char,TRUE) ?: $str;
|
|
$str = substr($str,strlen($return)+strlen($char));
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Check if the string is a space
|
|
*
|
|
* @param string $str
|
|
* @return bool
|
|
* @throws Exception
|
|
*/
|
|
private function isSpace(string $str):bool
|
|
{
|
|
if (strlen($str) > 1)
|
|
throw new Exception('String is more than 1 char');
|
|
|
|
return $str && in_array($str,[' ',"\n","\r","\v","\f","\t"]);
|
|
}
|
|
}
|