2021-04-01 10:59:15 +00:00
< ? php
namespace App\Classes\Protocol ;
use Carbon\Carbon ;
use Exception ;
use Illuminate\Support\Arr ;
use Illuminate\Support\Collection ;
use Illuminate\Support\Facades\Log ;
2022-11-01 11:24:36 +00:00
use League\Flysystem\UnreadableFileEncountered ;
2021-04-01 10:59:15 +00:00
use App\Classes\Protocol as BaseProtocol ;
use App\Classes\Sock\SocketClient ;
use App\Classes\Sock\SocketException ;
2021-08-15 14:41:43 +00:00
use App\Models\ { Address , Setup };
2021-04-01 10:59:15 +00:00
2021-07-02 13:44:01 +00:00
final class Binkp extends BaseProtocol
2021-04-01 10:59:15 +00:00
{
2021-08-15 14:41:43 +00:00
private const LOGKEY = 'PB-' ;
2021-04-01 10:59:15 +00:00
/* -- */
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 */
2021-08-14 06:14:22 +00:00
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 */
2021-04-01 10:59:15 +00:00
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 ;
2021-08-17 13:49:39 +00:00
private int $rx_ptr ; // @todo Whats the point of this? It seems its only the size of $rx_buf?
2021-04-01 10:59:15 +00:00
private int $rx_size ;
2021-08-17 13:49:39 +00:00
private string $rx_buf = '' ;
2021-04-01 10:59:15 +00:00
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 )) {
2021-08-15 14:41:43 +00:00
Log :: withContext ([ 'pid' => getmypid ()]);
2021-06-24 10:16:37 +00:00
$this -> session ( self :: SESSION_BINKP , $client ,( new Address ));
2021-04-01 10:59:15 +00:00
$this -> client -> close ();
2021-08-15 14:41:43 +00:00
2021-08-17 13:49:39 +00:00
Log :: info ( sprintf ( '%s:= onConnect - Connection closed [%s]' , self :: LOGKEY , $client -> address_remote ));
2021-07-19 14:23:41 +00:00
exit ( 0 );
2021-04-01 10:59:15 +00:00
}
return NULL ;
}
/**
* @ throws Exception
*/
private function binkp_hs () : void
{
2021-08-15 14:41:43 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s:+ binkp_hs' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
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' : '' ,
2021-07-18 12:10:21 +00:00
( $this -> setup -> opt_nr & self :: O_WANT ) ? ' NR' : '' ,
( $this -> setup -> opt_nd & self :: O_THEY ) ? ' ND' : '' ,
( $this -> setup -> opt_mb & self :: O_WANT ) ? ' MB' : '' ,
2021-04-01 10:59:15 +00:00
( $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
2021-08-14 06:14:22 +00:00
if ( $this -> originate ) {
if ( $this -> setup -> optionGet ( Setup :: O_HIDEAKA )) {
$addresses = collect ();
foreach ( $this -> node -> aka_remote_authed as $ao )
2022-01-24 11:56:13 +00:00
$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 ));
2021-08-14 06:14:22 +00:00
2021-08-15 09:25:04 +00:00
$addresses = $addresses -> unique ();
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s: - Presenting limited AKAs [%s]' , self :: LOGKEY , $addresses -> pluck ( 'ftn' ) -> join ( ',' )));
2021-08-14 06:14:22 +00:00
} else {
$addresses = $this -> setup -> system -> addresses ;
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s: - Presenting ALL our AKAs [%s]' , self :: LOGKEY , $addresses -> pluck ( 'ftn' ) -> join ( ',' )));
2021-08-14 06:14:22 +00:00
}
2021-08-14 01:22:45 +00:00
$this -> msgs ( self :: BPM_ADR , $this -> setup -> system -> addresses -> pluck ( 'ftn' ) -> join ( ' ' ));
2021-08-14 06:14:22 +00:00
}
2021-04-01 10:59:15 +00:00
}
/**
* @ return int
*/
private function binkp_hsdone () : int
{
2021-08-15 14:41:43 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s:+ binkp_hsdone' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
2021-08-14 06:14:22 +00:00
if (( $this -> setup -> opt_nd == self :: O_WE ) || ( $this -> setup -> opt_nd == self :: O_THEY ))
2021-04-01 10:59:15 +00:00
$this -> setup -> opt_nd = self :: O_NO ;
if ( ! $this -> setup -> phone )
$this -> setup -> phone = '-Unpublished-' ;
2021-08-14 06:14:22 +00:00
if ( ! $this -> optionGet ( self :: O_PWD ) || ( $this -> setup -> opt_md != self :: O_YES ))
2021-04-01 10:59:15 +00:00
$this -> setup -> opt_cr = self :: O_NO ;
2021-08-14 06:14:22 +00:00
if (( $this -> setup -> opt_cr & self :: O_WE ) && ( $this -> setup -> opt_cr & self :: O_THEY )) {
2021-04-01 10:59:15 +00:00
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 )
2021-08-15 14:41:43 +00:00
Log :: warning ( sprintf ( '%s: ! We cant do chat' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
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 ;
2021-08-14 06:14:22 +00:00
if (( $this -> setup -> opt_cht & self :: O_WE ) && ( $this -> setup -> opt_cht & self :: O_WANT ))
2021-04-01 10:59:15 +00:00
$this -> setup -> opt_cht = self :: O_YES ;
2021-08-14 06:14:22 +00:00
$this -> setup -> opt_mb = (( $this -> node -> get_versionint () > 100 ) || ( $this -> setup -> opt_mb & self :: O_WE )) ? self :: O_YES : self :: O_NO ;
2021-04-01 10:59:15 +00:00
if ( $this -> node -> get_versionint () > 100 )
$this -> sessionClear ( self :: SE_DELAYEOB );
$this -> mib = 0 ;
$this -> sessionClear ( self :: SE_INIT );
2021-08-15 14:41:43 +00:00
Log :: info ( sprintf ( '%s: - Session: BINKP/%d.%d - NR:%d, ND:%d, MD:%d, MB:%d, CR:%d, CHT:%d' ,
self :: LOGKEY ,
2021-04-01 10:59:15 +00:00
$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
{
2021-08-15 14:41:43 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s:+ binkp_init' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
$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 :
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! binkp_init ERROR - Unknown BINKP option [%s]' , self :: LOGKEY , $this -> setup -> binkp_options [ $x ]));
2021-04-01 10:59:15 +00:00
}
}
return self :: OK ;
}
/**
* @ throws Exception
*/
private function binkp_recv () : int
{
2021-08-17 13:49:39 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s:+ binkp_recv' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
$blksz = $this -> rx_size == - 1 ? BinkpMessage :: BLK_HDR_SIZE : $this -> rx_size ;
2022-11-04 13:06:47 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s: - binkp_recv blksize [%d] rx_size [%d].' , self :: LOGKEY , $blksz , $this -> rx_size ));
2021-04-01 10:59:15 +00:00
if ( $blksz !== 0 ) {
try {
2021-08-17 13:49:39 +00:00
$this -> rx_buf .= $this -> client -> read ( 0 , $blksz - $this -> rx_ptr );
2021-04-01 10:59:15 +00:00
} catch ( SocketException $e ) {
if ( $e -> getCode () == 11 ) {
// @todo We maybe should count these and abort if there are too many?
2021-06-16 12:26:08 +00:00
if ( $this -> DEBUG )
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s: - binkp_recv Socket EAGAIN' , self :: LOGKEY ));
2021-06-24 10:16:37 +00:00
2021-04-01 10:59:15 +00:00
return 1 ;
}
2021-08-17 13:49:39 +00:00
Log :: error ( sprintf ( '%s: - binkp_recv Exception [%s].' , self :: LOGKEY , $e -> getCode ()));
2021-04-01 10:59:15 +00:00
$this -> socket_error = $e -> getMessage ();
$this -> error = 1 ;
return 0 ;
}
2021-08-17 13:49:39 +00:00
if ( strlen ( $this -> rx_buf ) == 0 ) {
2021-04-01 10:59:15 +00:00
// @todo Check that this is correct.
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s: - binkp_recv Was the socket closed by the remote?' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
$this -> error = - 2 ;
2021-06-24 10:16:37 +00:00
2021-04-01 10:59:15 +00:00
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 );
}
*/
}
2021-08-17 13:49:39 +00:00
$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 ));
2021-04-01 10:59:15 +00:00
/* Received complete block */
if ( $this -> rx_ptr == $blksz ) {
/* Header */
if ( $this -> rx_size == - 1 ) {
2021-08-17 13:49:39 +00:00
$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 ));
2021-04-01 10:59:15 +00:00
$this -> rx_ptr = 0 ;
2021-08-17 13:49:39 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s: - binkp_recv HEADER, is_msg [%d], rx_size [%d]' , self :: LOGKEY , $this -> is_msg , $this -> rx_size ));
2021-04-01 10:59:15 +00:00
if ( $this -> rx_size == 0 )
goto ZeroLen ;
$rc = 1 ;
/* Next block */
} else {
ZeroLen :
2022-11-04 13:06:47 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s: - binkp_recv NEXT BLOCK, is_msg [%d]' , self :: LOGKEY , $this -> is_msg ));
2021-04-01 10:59:15 +00:00
if ( $this -> is_msg ) {
$this -> mib ++ ;
/* Handle zero length block */
if ( $this -> rx_size == 0 ) {
2021-08-17 13:49:39 +00:00
Log :: debug ( sprintf ( '%s:- binkp_recv Zero length msg - dropped' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
$this -> rx_size = - 1 ;
$this -> rx_ptr = 0 ;
2021-08-17 13:49:39 +00:00
$this -> rx_buf = '' ;
2021-04-01 10:59:15 +00:00
return 1 ;
}
2022-11-04 13:06:47 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s: - binkp_recv BUFFER [%d]' , self :: LOGKEY , strlen ( $this -> rx_buf )));
2021-04-01 10:59:15 +00:00
2021-08-17 13:49:39 +00:00
$rc = ord ( substr ( $this -> rx_buf , 0 , 1 ));
2021-04-01 10:59:15 +00:00
if ( $rc > self :: BPM_MAX ) {
2021-08-17 13:49:39 +00:00
Log :: error ( sprintf ( '%s: ! binkp_recv Unknown Message [%s] (%d)' , self :: LOGKEY , $this -> rx_buf , strlen ( $this -> rx_buf )));
2021-04-01 10:59:15 +00:00
$rc = 1 ;
} else {
//DEBUG(('B',2,"rcvd %s '%s'%s",mess[rc],bp->rx_buf + 1,CRYPT(bps))); //@todo CRYPT
2021-08-17 13:49:39 +00:00
$data = substr ( $this -> rx_buf , 1 );
2021-04-01 10:59:15 +00:00
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 :
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! binkp_recv Command not implemented [%d]' , self :: LOGKEY , $rc ));
2021-04-01 10:59:15 +00:00
$rc = 1 ;
}
}
} else {
if ( $this -> recv -> fd ) {
try {
2021-08-17 13:49:39 +00:00
$rc = $this -> recv -> write ( $this -> rx_buf );
2021-04-01 10:59:15 +00:00
} catch ( Exception $e ) {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! %s' , self :: LOGKEY , $e -> getMessage ()));
2021-04-01 10:59:15 +00:00
$this -> recv -> close ();
2021-08-15 14:41:43 +00:00
$this -> msgs ( self :: BPM_SKIP , $this -> recv -> name_size_time );
2021-04-01 10:59:15 +00:00
$rc = 1 ;
}
if ( $this -> recv -> filepos == $this -> recv -> size ) {
2021-08-15 14:41:43 +00:00
Log :: info ( sprintf ( '%s: - Finished receiving file [%s] with size [%d]' , self :: LOGKEY , $this -> recv -> name , $this -> recv -> size ));
2021-04-01 10:59:15 +00:00
2021-08-15 14:41:43 +00:00
$this -> msgs ( self :: BPM_GOT , $this -> recv -> name_size_time );
2021-08-17 13:49:39 +00:00
$this -> recv -> close ();
2021-04-01 10:59:15 +00:00
$rc = 1 ;
}
} else {
2021-08-15 14:41:43 +00:00
Log :: critical ( sprintf ( '%s: - binkp_recv Ignoring data block' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
$rc = 1 ;
}
}
$this -> rx_ptr = 0 ;
$this -> rx_size = - 1 ;
}
2021-08-17 13:49:39 +00:00
$this -> rx_buf = '' ;
2021-04-01 10:59:15 +00:00
} else {
$rc = 1 ;
}
2022-11-04 13:06:47 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s:= binkp_recv [%d]' , self :: LOGKEY , $rc ));
2021-06-16 12:26:08 +00:00
2021-04-01 10:59:15 +00:00
return $rc ;
}
/**
* @ throws Exception
*/
private function binkp_send () : int
{
2022-11-04 13:06:47 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s:+ binkp_send - tx_left [%d]' , self :: LOGKEY , $this -> tx_left ));
2021-04-01 10:59:15 +00:00
if ( $this -> tx_left == 0 ) { /* tx buffer is empty */
$this -> tx_ptr = $this -> tx_left = 0 ;
2021-06-16 12:26:08 +00:00
if ( $this -> DEBUG )
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s: - binkp_send msgs [%d]' , self :: LOGKEY , $this -> mqueue -> count ()));
2021-04-01 10:59:15 +00:00
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 );
2022-11-01 11:24:36 +00:00
} catch ( UnreadableFileEncountered ) {
2021-04-01 10:59:15 +00:00
$this -> send -> close ( FALSE );
$this -> sessionClear ( self :: SE_SENDFILE );
} catch ( Exception $e ) {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! binkp_send unexpected ERROR [%s]' , self :: LOGKEY , $e -> getMessage ()));
2021-04-01 10:59:15 +00:00
}
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 {
$rc = $this -> client -> send ( substr ( $this -> tx_buf , $this -> tx_ptr , $this -> tx_left ), self :: BP_TIMEOUT );
} catch ( Exception $e ) {
if ( $e -> getCode () == 11 )
return 1 ;
$this -> socket_error = $e -> getMessage ();
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s:! binkp_send - ERROR [%s]' , self :: LOGKEY , $e -> getMessage ()));
2021-04-01 10:59:15 +00:00
return 0 ;
}
$this -> tx_ptr += $rc ;
$this -> tx_left -= $rc ;
if ( ! $this -> tx_left ) {
$this -> tx_buf = '' ;
$this -> tx_ptr = 0 ;
}
}
2022-11-04 13:06:47 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s:= binkp_send [1]' , self :: LOGKEY ));
2021-06-16 12:26:08 +00:00
2021-04-01 10:59:15 +00:00
return 1 ;
}
private function file_parse ( string $str ) : ? array
{
2021-08-15 14:41:43 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s:+ file_parse [%s]' , self :: LOGKEY , $str ));
2021-04-01 10:59:15 +00:00
$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
{
2022-11-04 13:06:47 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s:+ msgs [%d:%s]' , self :: LOGKEY , $id , $msg_body ));
2021-04-01 10:59:15 +00:00
$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
{
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s:+ M_adr [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
$buf = $this -> skip_blanks ( $buf );
$rc = 0 ;
while (( $rem_aka = $this -> strsep ( $buf , ' ' ))) {
2021-08-15 14:41:43 +00:00
Log :: info ( sprintf ( '%s: - Parsing AKA [%s]' , self :: LOGKEY , $rem_aka ));
2021-04-01 10:59:15 +00:00
try {
2021-06-24 10:16:37 +00:00
if ( ! ( $o = Address :: findFTN ( $rem_aka ))) {
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s: ? AKA is UNKNOWN [%s]' , self :: LOGKEY , $rem_aka ));
2021-04-01 10:59:15 +00:00
continue ;
}
2022-11-03 11:05:49 +00:00
} catch ( Exception $e ) {
Log :: error ( sprintf ( '%s: ! AKA is INVALID [%s] (%s)' , self :: LOGKEY , $rem_aka , $e -> getMessage ()));
2021-04-01 10:59:15 +00:00
$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
2021-06-24 10:16:37 +00:00
if ( $this -> setup -> system -> addresses -> pluck ( 'ftn' ) -> search ( $rem_aka ) !== FALSE ) {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! AKA is OURS [%s]' , self :: LOGKEY , $rem_aka ));
2021-04-01 10:59:15 +00:00
2021-06-24 10:16:37 +00:00
$this -> msgs ( self :: BPM_ERR , sprintf ( 'Sorry that is my AKA [%s]' , $rem_aka ));
2021-04-01 10:59:15 +00:00
$this -> rc = self :: S_FAILURE ;
return 0 ;
}
// @todo lock nodes
$this -> node -> ftn = $o ;
$rc = $this -> node -> aka_num ;
}
if ( $rc == 0 ) {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! All AKAs [%d] are busy' , self :: LOGKEY , $this -> node -> aka_num ));
2021-04-01 10:59:15 +00:00
$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 ()) {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! We didnt get who we called?' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
$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.
2021-08-15 14:41:43 +00:00
if ( ! $this -> originate )
$this -> msgs ( self :: BPM_ADR , $this -> our_addresses () -> pluck ( 'ftn' ) -> join ( ' ' ));
2021-04-01 10:59:15 +00:00
return 1 ;
}
private function M_chat ( string $buf ) : int
{
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s:+ M_chat [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
if ( $this -> setup -> opt_cht == self :: O_YES ) {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! We cannot do chat' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
} else {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! We got a chat message, but chat is disabled' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
}
return 1 ;
}
/**
2021-07-23 14:53:35 +00:00
* We received EOB from the remote .
*
2021-04-01 10:59:15 +00:00
* @ throws Exception
*/
private function M_eob ( string $buf ) : int
{
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s:+ M_eob [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
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 )) {
2021-07-18 12:10:21 +00:00
// Add our mail to the queue if we have authenticated
if ( $this -> node -> aka_authed )
2021-08-14 06:14:22 +00:00
foreach ( $this -> node -> aka_remote_authed as $ao ) {
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s: - M_eob Checking for any new mail to [%s]' , self :: LOGKEY , $ao -> ftn ));
2021-07-18 12:10:21 +00:00
$this -> send -> mail ( $ao );
}
2021-04-01 10:59:15 +00:00
if ( $this -> send -> total_count )
2021-07-23 14:53:35 +00:00
$this -> sessionClear ( self :: SE_NOFILES | self :: SE_SENTEOB );
2021-04-01 10:59:15 +00:00
}
return 1 ;
}
/**
* @ throws Exception
*/
private function M_err ( string $buf ) : int
{
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s:+ M_err [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
$this -> error_close ();
2021-08-15 14:41:43 +00:00
$this -> rc = ( $this -> originate ? ( self :: S_REDIAL | self :: S_ADDTRY ) : self :: S_BUSY );
2021-04-01 10:59:15 +00:00
return 0 ;
}
/**
* @ throws Exception
*/
private function M_file ( string $buf ) : int
{
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s:+ M_file [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
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 )) {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: - UNPARSABLE file info [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
$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' ])
{
2021-07-18 12:10:21 +00:00
$this -> recv -> open ( $this -> node -> address , $file [ 'offs' ] < 0 );
2021-04-01 10:59:15 +00:00
return 1 ;
}
$this -> recv -> new ( $file [ 'file' ]);
try {
2021-07-18 12:10:21 +00:00
switch ( $this -> recv -> open ( $this -> node -> address , $file [ 'offs' ] < 0 )) {
2021-04-01 10:59:15 +00:00
case self :: FOP_ERROR :
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! File ERROR' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
case self :: FOP_SUSPEND :
2021-08-15 14:41:43 +00:00
Log :: info ( sprintf ( '%s: - File Suspended' , self :: LOGKEY ));
$this -> msgs ( self :: BPM_SKIP , $this -> recv -> name_size_time );
2021-04-01 10:59:15 +00:00
break ;
case self :: FOP_SKIP :
2021-08-15 14:41:43 +00:00
Log :: info ( sprintf ( '%s: - File Skipped' , self :: LOGKEY ));
$this -> msgs ( self :: BPM_GOT , $this -> recv -> name_size_time );
2021-04-01 10:59:15 +00:00
break ;
case self :: FOP_OK :
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s: - Getting file from [%d]' , self :: LOGKEY , $file [ 'offs' ]));
2021-04-01 10:59:15 +00:00
if ( $file [ 'offs' ] != - 1 ) {
if ( ! ( $this -> setup -> opt_nr & self :: O_THEY )) {
$this -> setup -> opt_nr |= self :: O_THEY ;
}
break ;
}
case self :: FOP_CONT :
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s: - Continuing file [%s] (%ld)' , self :: LOGKEY , $this -> recv -> name , $file [ 'offs' ]));
2021-04-01 10:59:15 +00:00
2021-08-15 14:41:43 +00:00
$this -> msgs ( self :: BPM_GET , sprintf ( '%s %ld' , $this -> recv -> name_size_time ,( $file [ 'offs' ] < 0 ) ? 0 : $file [ 'offs' ]));
2021-04-01 10:59:15 +00:00
break ;
}
} catch ( Exception $e ) {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! File Open ERROR [%s]' , self :: LOGKEY , $e -> getMessage ()));
2021-04-01 10:59:15 +00:00
2021-08-15 14:41:43 +00:00
$this -> msgs ( self :: BPM_SKIP , $this -> recv -> name_size_time );
2022-02-06 09:21:35 +00:00
// Close the file, since we had an error opening it.
if ( $this -> recv -> fd )
$this -> recv -> close ();
2021-04-01 10:59:15 +00:00
}
return 1 ;
}
/**
* @ throws Exception
*/
private function M_get ( string $buf ) : int
{
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s:+ M_get [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
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' ])) {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! Cannot send file from requested offset [%d]' , self :: LOGKEY , $file [ 'offs' ]));
2021-04-01 10:59:15 +00:00
$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 );
2022-01-20 11:47:44 +00:00
Log :: debug ( sprintf ( '%s:Sending packet [%s] as [%s]' , self :: LOGKEY , $this -> send -> name , $this -> send -> sendas ));
2021-04-01 10:59:15 +00:00
$this -> msgs ( self :: BPM_FILE , sprintf ( '%s %lu %ld %lu' , $this -> send -> sendas , $this -> send -> size , $this -> send -> mtime , $file [ 'offs' ]));
}
} else {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! M_got for unknown file [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
}
} else {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: - UNPARSABLE file info [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
}
return 1 ;
}
/**
* M_GOT / M_SKIP commands
*
* @ param string $buf
* @ return int
* @ throws Exception
*/
private function M_gotskip ( string $buf ) : int
{
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s:+ M_gotskip [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
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' ))
{
2022-01-20 11:47:44 +00:00
// @todo Commit our mail transaction if the remote end confirmed receipt of the file.
2021-04-01 10:59:15 +00:00
if ( $this -> sessionGet ( self :: SE_SENDFILE )) {
2022-01-20 11:47:44 +00:00
Log :: debug ( sprintf ( '%s:Packet [%s] sent. (%s)' , self :: LOGKEY , $this -> send -> sendas , $this -> send -> name ));
2021-04-01 10:59:15 +00:00
$this -> sessionClear ( self :: SE_SENDFILE );
2022-01-20 11:47:44 +00:00
$this -> send -> close ( TRUE );
2021-04-01 10:59:15 +00:00
return 1 ;
}
if ( $this -> sessionGet ( self :: SE_WAITGOT )) {
2022-01-20 11:47:44 +00:00
Log :: debug ( sprintf ( '%s:Packet [%s] sent. (%s)' , self :: LOGKEY , $this -> send -> sendas , $this -> send -> name ));
2021-04-01 10:59:15 +00:00
$this -> sessionClear ( self :: SE_WAITGOT );
$this -> send -> close ( TRUE );
} else {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! M_got[skip] for unknown file [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
}
}
} else {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: - UNPARSABLE file info [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
}
return 1 ;
}
/**
* @ throws Exception
*/
private function M_nul ( string $buf ) : int
{
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s:+ M_nul [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
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 ) {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: - M_nul Got TOO LONG [%d] challenge string' , self :: LOGKEY , $x ));
2021-04-01 10:59:15 +00:00
} 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" )) */
2021-08-15 14:41:43 +00:00
Log :: warning ( sprintf ( '%s: - M_nul Got UNSUPPORTED option [%s]' , self :: LOGKEY , $p ));
2021-04-01 10:59:15 +00:00
}
$data = substr ( $data , strpos ( $data , ' ' ));
}
} else {
2021-08-15 14:41:43 +00:00
Log :: warning ( sprintf ( '%s: - M_nul Got UNKNOWN NUL [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
}
return 1 ;
}
/**
2021-07-04 13:24:38 +00:00
* Remote accepted our password
*
2021-04-01 10:59:15 +00:00
* @ throws Exception
*/
private function M_ok ( string $buf ) : int
{
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s:+ M_ok [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
if ( ! $this -> originate ) {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! UNEXPECTED M_OK [%s] from remote on incoming call' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
$this -> rc = self :: S_FAILURE ;
return 0 ;
}
$buf = $this -> skip_blanks ( $buf );
2021-08-14 06:14:22 +00:00
if ( $this -> optionGet ( self :: O_PWD ) && $buf ) {
2021-04-01 10:59:15 +00:00
while (( $t = $this -> strsep ( $buf , " \t " )))
if ( strcmp ( $t , 'non-secure' ) == 0 ) {
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s: - Non Secure' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
$this -> setup -> opt_cr = self :: O_NO ;
$this -> optionClear ( self :: O_PWD );
break ;
}
}
2021-07-23 14:53:35 +00:00
// Add our mail to the queue if we have authenticated
if ( $this -> node -> aka_authed )
2021-08-14 06:14:22 +00:00
foreach ( $this -> node -> aka_remote_authed as $ao ) {
2021-07-23 14:53:35 +00:00
$this -> send -> mail ( $ao );
}
$this -> msgs ( self :: BPM_NUL , sprintf ( 'TRF %lu %lu' , $this -> send -> mail_size , $this -> send -> file_size ));
2021-08-17 13:49:39 +00:00
Log :: debug ( sprintf ( '%s:= M_ok' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
return $this -> binkp_hsdone ();
}
/**
* @ throws Exception
*/
private function M_pwd ( string $buf ) : int
{
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s:+ M_pwd [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
$buf = $this -> skip_blanks ( $buf );
$have_CRAM = ! strncasecmp ( $buf , 'CRAM-MD5-' , 9 );
$have_pwd = $this -> optionGet ( self :: O_PWD );
if ( $this -> originate ) {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! Unexpected password [%s] from remote on OUTGOING call' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
$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 ) {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! Remote doesnt support MD5' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
$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 ) {
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! Bad password [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
$this -> msgs ( self :: BPM_ERR , 'Security violation' );
$this -> optionSet ( self :: O_BAD );
$this -> rc = self :: S_FAILURE ;
return 0 ;
}
} elseif ( ! $this -> node -> aka_authed ) {
2021-08-15 14:41:43 +00:00
Log :: notice ( sprintf ( '%s: - Remote proposed password for us [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
}
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' : '' ,
2021-08-14 06:14:22 +00:00
( ! ( $this -> setup -> opt_nd & self :: O_WE ) != ( ! ( $this -> setup -> opt_nd & self :: O_THEY ))) ? ' NDA' : '' ,
2021-04-01 10:59:15 +00:00
(( $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 ));
2021-07-23 14:53:35 +00:00
// Add our mail to the queue if we have authenticated
if ( $this -> node -> aka_authed )
2021-08-14 06:14:22 +00:00
foreach ( $this -> node -> aka_remote_authed as $ao ) {
2021-07-23 14:53:35 +00:00
$this -> send -> mail ( $ao );
}
2021-04-01 10:59:15 +00:00
$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
2021-08-15 14:41:43 +00:00
return 0 ;
2021-04-01 10:59:15 +00:00
}
/**
* Setup our BINKP session
*
* @ return int
* @ throws Exception
*/
protected function protocol_session () : int
{
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s:+ protocol_session' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
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 -> 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 ();
if ( $this -> send -> fd ) {
$this -> sessionSet ( self :: SE_SENDFILE );
if ( $this -> setup -> opt_nr & self :: O_WE ) {
$this -> sessionSet ( self :: SE_WAITGET );
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s: - Waiting for M_GET' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
}
$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 );
} 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 {
// @todo we need to catch a timeout if there are no reads/writes
$rc = $this -> client -> ttySelect ( $rd , $wd , self :: BP_TIMEOUT );
} 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 ;
2021-08-17 13:49:39 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s: - mqueue [%d]' , self :: LOGKEY , $this -> mqueue -> count ()));
2021-07-23 14:53:35 +00:00
if (( $this -> mqueue -> count () || $wd ) && ! $this -> binkp_send () && ( ! $this -> send -> total_count ))
2021-04-01 10:59:15 +00:00
break ;
}
if ( $this -> error == - 1 )
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! protocol_session TIMEOUT' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
elseif ( $this -> error > 0 )
2021-08-15 14:41:43 +00:00
Log :: error ( sprintf ( '%s: ! protocol_session Got ERROR [%d]' , self :: LOGKEY , $this -> socket_error ));
2021-04-01 10:59:15 +00:00
while ( ! $this -> error ) {
try {
$buf = $this -> client -> read ( 0 , self :: MAX_BLKSIZE );
} catch ( Exception $e ) {
if ( $e -> getCode () !== 11 ) {
2021-08-15 14:41:43 +00:00
Log :: debug ( sprintf ( '%s: ? protocol_session Got Exception [%d] (%s)' , self :: LOGKEY , $e -> getCode (), $e -> getMessage ()));
2021-04-01 10:59:15 +00:00
$this -> error = 1 ;
}
break ;
}
if ( strlen ( $buf ) == 0 )
break ;
2022-02-12 23:24:31 +00:00
Log :: warning ( sprintf ( '%s: - Purged (%s) [%d] bytes from input stream' , self :: LOGKEY , hex_dump ( $buf ), strlen ( $buf )));
2021-04-01 10:59:15 +00:00
}
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 " ]);
}
}