2021-04-01 10:59:15 +00:00
< ? php
namespace App\Classes\Protocol ;
use Carbon\Carbon ;
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
2023-07-02 13:40:08 +00:00
use App\Classes\Crypt ;
2021-04-01 10:59:15 +00:00
use App\Classes\Protocol as BaseProtocol ;
use App\Classes\Sock\SocketClient ;
use App\Classes\Sock\SocketException ;
2023-07-11 07:22:31 +00:00
use App\Exceptions\FileGrewException ;
2023-07-09 12:19:11 +00:00
use App\Models\Address ;
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-' ;
2023-07-02 13:40:08 +00:00
/* CONSTS */
public const PORT = 24554 ;
/** protocol text **/
private const PROT = 'binkp' ;
/** version implemented */
private const VERSION = '1.1' ;
/** block size - compressed files can only use a blocksize of 0x3fff */
private const BLOCKSIZE = 0x1000 ;
/** session timeout secs */
private const TIMEOUT_TIME = 300 ;
/** max block size */
private const MAX_BLKSIZE = 0x7fff ;
/* FEATURES */
/** COMPRESS mode */
public const F_COMP = 1 << 0 ;
/** CHAT mode - not implemented */
public const F_CHAT = 1 << 1 ;
/** Crypt mode */
public const F_CRYPT = 1 << 2 ;
/** Multi-Batch mode */
public const F_MULTIBATCH = 1 << 3 ;
/** CRAM-MD5 mode */
public const F_MD = 1 << 4 ;
/** Force MD5 passwords */
public const F_MDFORCE = 1 << 5 ;
/** http://ftsc.org/docs/fsp-1027.001: No-dupes mode - negotiated, implies NR mode */
public const F_NODUPE = 1 << 6 ;
/** http://ftsc.org/docs/fsp-1027.001: Asymmetric ND mode */
public const F_NODUPEA = 1 << 7 ;
/** http://ftsc.org/docs/fsp-1028.001: Non-Reliable mode */
public const F_NOREL = 1 << 8 ;
/** Multi-Password mode - not implemented */
public const F_MULTIPASS = 1 << 9 ;
/* BINKP MESSAGES */
/** No available data */
private const BPM_NONE = 99 ;
/** Binary data */
private const BPM_DATA = 98 ;
/** Site information */
private const BPM_NUL = 0 ;
/** List of addresses */
private const BPM_ADR = 1 ;
/** Session password */
private const BPM_PWD = 2 ;
/** File information */
private const BPM_FILE = 3 ;
/** Password was acknowledged (data ignored) */
private const BPM_OK = 4 ;
/** End Of Batch (data ignored) */
private const BPM_EOB = 5 ;
/** File received */
private const BPM_GOTSKIP = 6 ;
/** Misc errors */
private const BPM_ERR = 7 ;
/** All AKAs are busy */
private const BPM_BSY = 8 ;
/** Get a file from offset */
private const BPM_GET = 9 ;
/** Skip a file (RECEIVE LATER) */
private const BPM_SKIP = 10 ;
/** Reserved for later */
private const BPM_RESERVED = 11 ;
/** For chat */
private const BPM_CHAT = 12 ;
/** Minimal message type value */
private const BPM_MIN = self :: BPM_NUL ;
/** Maximal message type value */
private const BPM_MAX = self :: BPM_CHAT ;
/* SESSION STATE */
/** 0000 0001 - Are we in initialise mode */
private const SE_INIT = 1 << 0 ;
/** 0000 0010 - Have we sent our EOB */
private const SE_SENTEOB = 1 << 1 ;
/** 0000 0100 - Have we received EOB */
private const SE_RECVEOB = 1 << 2 ;
/** 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_DELAYEOB = 1 << 3 ;
/** 0001 0000 - Wait for GET before sending a file */
private const SE_WAITGET = 1 << 4 ;
/** 0010 0000 - We are waiting for a GOT from the remote */
private const SE_WAITGOT = 1 << 5 ;
/** 0100 0000 - Are we sending a file */
private const SE_SENDFILE = 1 << 6 ;
/** 1000 0000 - We have no more files to send */
private const SE_NOFILES = 1 << 7 ;
/* CLASS VARS */
/** The MD5 challenge with the remote system */
private string $md_challenge ;
2021-04-01 10:59:15 +00:00
private int $is_msg ;
2023-07-02 13:40:08 +00:00
/** Messages In Batch (MIB 3 :) */
2021-04-01 10:59:15 +00:00
private int $mib ;
private int $rc ;
private int $error ;
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 ;
2023-07-02 13:40:08 +00:00
private string $comp_mode = '' ;
/**
* @ var Crypt Inbound encryption
*/
private Crypt $crypt_in ;
/**
* @ var Crypt Outbound encryption
*/
private Crypt $crypt_out ;
2021-04-01 10:59:15 +00:00
/**
* Incoming BINKP session
*
* @ param SocketClient $client
* @ return int | null
* @ throws SocketException
2023-07-02 13:40:08 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
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 ()]);
2023-07-02 13:40:08 +00:00
// @todo If we can use SESSION_EMSI set an object class value that in BINKP of SESSION_BINKP, and move this method to the parent class
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-07-19 14:23:41 +00:00
exit ( 0 );
2021-04-01 10:59:15 +00:00
}
return NULL ;
}
/**
2023-01-11 03:36:31 +00:00
* BINKD handshake
*
2023-07-02 13:40:08 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
private function binkp_hs () : void
{
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:+ Starting BINKP handshake' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
if ( ! $this -> originate && $this -> capGet ( self :: F_MD , self :: O_WANT )) {
2021-04-01 10:59:15 +00:00
$random_key = random_bytes ( 8 );
$this -> md_challenge = md5 ( $random_key , TRUE );
2023-07-02 13:40:08 +00:00
$this -> msgs ( self :: BPM_NUL , sprintf ( 'OPT CRAM-MD5-%s' , md5 ( $random_key )));
2021-04-01 10:59:15 +00:00
}
2023-07-05 12:42:59 +00:00
$this -> msgs ( self :: BPM_NUL , sprintf ( 'SYS %s' , $this -> setup -> system -> name ));
$this -> msgs ( self :: BPM_NUL , sprintf ( 'ZYZ %s' , $this -> setup -> system -> sysop ));
$this -> msgs ( self :: BPM_NUL , sprintf ( 'LOC %s' , $this -> setup -> system -> location ));
2021-04-01 10:59:15 +00:00
$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 ,
2023-07-02 13:40:08 +00:00
sprintf ( 'VER %s-%s %s/%s' , config ( 'app.name' ), $this -> setup -> version , self :: PROT , self :: VERSION ));
2021-04-01 10:59:15 +00:00
if ( $this -> originate ) {
2023-07-02 13:40:08 +00:00
$opt = $this -> capGet ( self :: F_NOREL , self :: O_WANT ) ? ' NR' : '' ;
$opt .= $this -> capGet ( self :: F_NODUPE , self :: O_WANT ) ? ' ND' : '' ;
$opt .= $this -> capGet ( self :: F_NODUPEA , self :: O_WANT ) ? ' NDA' : '' ;
$opt .= $this -> capGet ( self :: F_MULTIBATCH , self :: O_WANT ) ? ' MB' : '' ;
$opt .= $this -> capGet ( self :: F_CHAT , self :: O_WANT ) ? ' CHAT' : '' ;
$opt .= $this -> capGet ( self :: F_COMP , self :: O_WANT ) ? ' EXTCMD GZ' : '' ;
$opt .= $this -> capGet ( self :: F_COMP , self :: O_WANT ) && $this -> capGet ( self :: F_COMP , self :: O_EXT ) ? ' BZ2' : '' ;
$opt .= $this -> capGet ( self :: F_CRYPT , self :: O_WANT ) ? ' CRYPT' : '' ;
if ( strlen ( $opt ))
$this -> msgs ( self :: BPM_NUL , sprintf ( 'OPT%s' , $opt ));
2021-04-01 10:59:15 +00:00
}
// 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 ) {
2023-07-08 08:00:23 +00:00
$addresses = $this -> our_addresses ();
2021-08-14 06:14:22 +00:00
2023-07-08 08:00:23 +00:00
$this -> msgs ( self :: BPM_ADR , $addresses -> pluck ( 'ftn' ) -> join ( ' ' ));
2021-08-14 06:14:22 +00:00
}
2021-04-01 10:59:15 +00:00
}
/**
* @ return int
*/
2023-07-02 13:40:08 +00:00
private function binkp_hsdone () : bool
2021-04-01 10:59:15 +00:00
{
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:+ BINKP handshake complete' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
// If the remote doesnt provide a password, or in MD5 mode, then we cant use CRYPT
if ( ! $this -> optionGet ( self :: O_PWD ) || ( ! $this -> capGet ( self :: F_MD , self :: O_WE )))
$this -> capSet ( self :: F_CRYPT , self :: O_NO );
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
if ( $this -> capGet ( self :: F_CRYPT , self :: O_WE )) {
$this -> capSet ( self :: F_CRYPT , self :: O_YES );
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
Log :: info ( sprintf ( '%s:- CRYPT mode initialised' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
if ( $this -> originate ) {
$this -> crypt_out = new Crypt ( $this -> node -> password );
$this -> crypt_in = new Crypt ( '-' . $this -> node -> password );
2021-04-01 10:59:15 +00:00
} else {
2023-07-02 13:40:08 +00:00
$this -> crypt_in = new Crypt ( $this -> node -> password );
$this -> crypt_out = new Crypt ( '-' . $this -> node -> password );
2021-04-01 10:59:15 +00:00
}
}
// @todo Implement max incoming sessions and max incoming session for the same node
// We have no mechanism to support chat
2023-07-02 13:40:08 +00:00
if ( $this -> capGet ( self :: F_CHAT , self :: O_THEY ))
Log :: warning ( sprintf ( '%s:/ The remote wants to chat, but we cant do chat' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
/*
if ( $this -> capGet ( self :: F_CHAT , self :: O_WE ))
$this -> capSet ( self :: F_CHAT , self :: O_YES );
*/
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
// No dupes mode is preferred on BINKP 1.1
2023-07-11 07:22:31 +00:00
if ( $this -> capGet ( self :: F_NODUPE , self :: O_WE ) || ( $this -> originate && $this -> capGet ( self :: F_NOREL , self :: O_WANT ) && $this -> node -> get_versionint () > 101 )) {
2023-07-09 01:18:57 +00:00
Log :: debug ( sprintf ( '%s:/ NR mode enabled, because we are in NDA mode, or I want NDA and the remote is version [%d]' , self :: LOGKEY , $this -> node -> get_versionint ()));
2023-07-02 13:40:08 +00:00
$this -> capSet ( self :: F_NOREL , self :: O_YES );
2023-07-09 01:18:57 +00:00
}
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
$this -> capSet ( self :: F_MULTIBATCH ,(( $this -> node -> get_versionint () > 100 ) || $this -> capGet ( self :: F_MULTIBATCH , 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 );
2023-07-02 13:40:08 +00:00
Log :: info ( sprintf ( '%s:= Session: BINKP/%d.%d - NR:%d, ND:%d, NDA:%d, MD:%d, MB:%d, CR:%d, CO:%d, CH:%d' ,
2021-08-15 14:41:43 +00:00
self :: LOGKEY ,
2021-04-01 10:59:15 +00:00
$this -> node -> ver_major ,
$this -> node -> ver_minor ,
2023-07-11 07:22:31 +00:00
$this -> capGet ( self :: F_NOREL , self :: O_WE ),
$this -> capGet ( self :: F_NODUPE , self :: O_WE ),
$this -> capGet ( self :: F_NODUPEA , self :: O_WE ),
$this -> capGet ( self :: F_MD , self :: O_WE ),
$this -> capGet ( self :: F_MULTIBATCH , self :: O_WE ),
$this -> capGet ( self :: F_CRYPT , self :: O_WE ),
$this -> capGet ( self :: F_COMP , self :: O_WE ),
$this -> capGet ( self :: F_CHAT , self :: O_WE ),
2021-04-01 10:59:15 +00:00
));
2023-07-02 13:40:08 +00:00
return TRUE ;
2021-04-01 10:59:15 +00:00
}
private function binkp_init () : int
{
$this -> sessionSet ( self :: SE_INIT );
$this -> is_msg = - 1 ;
$this -> mib = 0 ;
$this -> error = 0 ;
$this -> mqueue = collect ();
$this -> rx_size = - 1 ;
$this -> tx_buf = '' ;
2023-07-02 13:40:08 +00:00
$this -> tx_left = 0 ; // @todo can we replace this with strlen($tx_buf)?
$this -> tx_ptr = 0 ; // @todo is this required?
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
// Setup our default capabilities
$this -> md_challenge = '' ;
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
// We cant do chat
$this -> capSet ( self :: F_CHAT , self :: O_NO );
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
// Compression
if ( $this -> setup -> optionGet ( self :: F_COMP , 'binkp_options' ))
$this -> capSet ( self :: F_COMP , self :: O_WANT | self :: O_EXT );
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
// CRAM-MD5 session
if ( $this -> setup -> optionGet ( self :: F_MD , 'binkp_options' )) {
$this -> capSet ( self :: F_MD , self :: O_WANT );
2021-04-01 10:59:15 +00:00
2023-07-07 12:42:02 +00:00
if ( $this -> setup -> optionGet ( self :: F_MDFORCE , 'binkp_options' ))
2023-07-02 13:40:08 +00:00
$this -> capSet ( self :: F_MD , self :: O_NEED );
}
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
// Crypt Mode
2023-07-07 12:42:02 +00:00
if ( $this -> setup -> optionGet ( self :: F_CRYPT , 'binkp_options' ))
2023-07-02 13:40:08 +00:00
$this -> capSet ( self :: F_CRYPT , self :: O_WANT );
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
// Multibatch
if ( $this -> setup -> optionGet ( self :: F_MULTIBATCH , 'binkp_options' ))
$this -> capSet ( self :: F_MULTIBATCH , self :: O_WANT );
// Non reliable mode
if ( $this -> setup -> optionGet ( self :: F_NOREL , 'binkp_options' )) {
$this -> capSet ( self :: F_NOREL , self :: O_WANT );
// No dupes
if ( $this -> setup -> optionGet ( self :: F_NODUPE , 'binkp_options' )) {
$this -> capSet ( self :: F_NODUPE , self :: O_WANT );
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
// No dupes asymmetric
if ( $this -> setup -> optionGet ( self :: F_NODUPEA , 'binkp_options' ))
$this -> capSet ( self :: F_NODUPEA , self :: O_WANT );
2021-04-01 10:59:15 +00:00
}
}
return self :: OK ;
}
/**
2023-01-11 03:36:31 +00:00
* Receive data from the remote
*
2023-07-02 13:40:08 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
2023-07-02 13:40:08 +00:00
private function binkp_recv () : bool
2021-04-01 10:59:15 +00:00
{
2023-06-16 13:18:35 +00:00
$blksz = $this -> rx_size === - 1 ? BinkpMessage :: BLK_HDR_SIZE : $this -> rx_size ;
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:+ BINKP receive, reading [%d] chars' , self :: LOGKEY , $blksz ));
2021-04-01 10:59:15 +00:00
if ( $blksz !== 0 ) {
try {
2023-07-07 13:13:43 +00:00
Log :: debug ( sprintf ( '%s:- We need [%d] more chars, buffer currently has [%d] chars' , self :: LOGKEY , $blksz , strlen ( $this -> rx_buf )));
$rx_buf = $this -> client -> read ( 0 , $blksz - strlen ( $this -> rx_buf ));
Log :: debug ( sprintf ( '%s:- Got [%d] more chars for the read buffer' , self :: LOGKEY , strlen ( $rx_buf )));
2021-04-01 10:59:15 +00:00
} catch ( SocketException $e ) {
2023-06-27 07:39:11 +00:00
if ( $e -> getCode () === 11 ) {
2021-04-01 10:59:15 +00:00
// @todo We maybe should count these and abort if there are too many?
2021-06-16 12:26:08 +00:00
if ( $this -> DEBUG )
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:- Got a socket EAGAIN' , self :: LOGKEY ));
2021-06-24 10:16:37 +00:00
2023-07-02 13:40:08 +00:00
return TRUE ;
2021-04-01 10:59:15 +00:00
}
$this -> error = 1 ;
2023-07-02 13:40:08 +00:00
Log :: error ( sprintf ( '%s:! Reading we got an EXCEPTION [%d-%s]' , self :: LOGKEY , $e -> getCode (), $e -> getMessage ()));
return FALSE ;
2021-04-01 10:59:15 +00:00
}
2023-07-02 13:40:08 +00:00
if ( strlen ( $rx_buf ) === 0 ) {
2021-04-01 10:59:15 +00:00
// @todo Check that this is correct.
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:- 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
2023-07-02 13:40:08 +00:00
return FALSE ;
2021-04-01 10:59:15 +00:00
}
2023-07-02 13:40:08 +00:00
$this -> rx_buf .= ( $this -> capGet ( self :: F_CRYPT , self :: O_YES )) ? $this -> crypt_in -> decrypt ( $rx_buf ) : $rx_buf ;
2021-04-01 10:59:15 +00:00
}
2023-07-07 13:13:43 +00:00
Log :: debug ( sprintf ( '%s:- Read buffer has [%d] chars to process.' , self :: LOGKEY , strlen ( $this -> rx_buf )));
2021-04-01 10:59:15 +00:00
/* Received complete block */
2023-07-02 13:40:08 +00:00
if ( strlen ( $this -> rx_buf ) === $blksz ) {
2021-04-01 10:59:15 +00:00
/* Header */
2023-06-27 07:39:11 +00:00
if ( $this -> rx_size === - 1 ) {
2021-08-17 13:49:39 +00:00
$this -> is_msg = ord ( substr ( $this -> rx_buf , 0 , 1 )) >> 7 ;
2023-07-02 13:40:08 +00:00
// If compression is used, then this needs to be &0x3f, since the 2nd high bit is the compression flag
// @todo Need to see what happens, if we are receiving a block higher than 0x3fff. Possible?
$this -> rx_size = (( ord ( substr ( $this -> rx_buf , 0 , 1 )) & 0x7f ) << 8 ) + ord ( substr ( $this -> rx_buf , 1 , 1 ));
Log :: debug ( sprintf ( '%s:- BINKP receive HEADER, is_msg [%d], rx_size [%d]' , self :: LOGKEY , $this -> is_msg , $this -> rx_size ));
2021-04-01 10:59:15 +00:00
2023-06-27 07:39:11 +00:00
if ( $this -> rx_size === 0 )
2021-04-01 10:59:15 +00:00
goto ZeroLen ;
2023-07-02 13:40:08 +00:00
$rc = TRUE ;
2021-04-01 10:59:15 +00:00
/* Next block */
} else {
2023-07-02 13:40:08 +00:00
ZeroLen :
2021-04-01 10:59:15 +00:00
if ( $this -> is_msg ) {
$this -> mib ++ ;
/* Handle zero length block */
2023-06-27 07:39:11 +00:00
if ( $this -> rx_size === 0 ) {
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:- Received a ZERO length msg - dropped' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
$this -> rx_size = - 1 ;
2021-08-17 13:49:39 +00:00
$this -> rx_buf = '' ;
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
return TRUE ;
2021-04-01 10:59:15 +00:00
}
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
2023-07-02 13:40:08 +00:00
$msg = ord ( substr ( $this -> rx_buf , 0 , 1 ));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
if ( $msg > self :: BPM_MAX ) {
Log :: error ( sprintf ( '%s:! Unknown message received [%d] (%d-%s)' , self :: LOGKEY , $rc , strlen ( $this -> rx_buf ), $this -> rx_buf ));
$rc = TRUE ;
2021-04-01 10:59:15 +00:00
} else {
2021-08-17 13:49:39 +00:00
$data = substr ( $this -> rx_buf , 1 );
2023-07-02 13:40:08 +00:00
switch ( $msg ) {
case self :: BPM_ADR :
Log :: debug ( sprintf ( '%s:- ADR:Address [%s]' , self :: LOGKEY , $data ));
2021-04-01 10:59:15 +00:00
$rc = $this -> M_adr ( $data );
break ;
2023-07-02 13:40:08 +00:00
case self :: BPM_EOB :
Log :: debug ( sprintf ( '%s:- EOB:We got an EOB message with [%d] chars in the buffer' , self :: LOGKEY , strlen ( $data )));
2021-04-01 10:59:15 +00:00
$rc = $this -> M_eob ( $data );
break ;
2023-07-02 13:40:08 +00:00
case self :: BPM_NUL :
Log :: debug ( sprintf ( '%s:- NUL:Message [%s]' , self :: LOGKEY , $data ));
2021-04-01 10:59:15 +00:00
$rc = $this -> M_nul ( $data );
break ;
2023-07-02 13:40:08 +00:00
case self :: BPM_PWD :
Log :: debug ( sprintf ( '%s:- PWD:We got a password [%s]' , self :: LOGKEY , $data ));
2021-04-01 10:59:15 +00:00
$rc = $this -> M_pwd ( $data );
break ;
2023-07-02 13:40:08 +00:00
case self :: BPM_ERR :
Log :: debug ( sprintf ( '%s:- ERR:We got an error [%s]' , self :: LOGKEY , $data ));
2021-04-01 10:59:15 +00:00
$rc = $this -> M_err ( $data );
break ;
2023-07-02 13:40:08 +00:00
case self :: BPM_FILE :
Log :: debug ( sprintf ( '%s:- FIL:We are receiving a file [%s]' , self :: LOGKEY , $data ));
2021-04-01 10:59:15 +00:00
$rc = $this -> M_file ( $data );
break ;
2023-07-02 13:40:08 +00:00
case self :: BPM_GET :
Log :: debug ( sprintf ( '%s:- GET:We are sending a file [%s]' , self :: LOGKEY , $data ));
2021-04-01 10:59:15 +00:00
$rc = $this -> M_get ( $data );
break ;
2023-07-02 13:40:08 +00:00
case self :: BPM_GOTSKIP :
Log :: debug ( sprintf ( '%s:- GOT:Remote received, or already has a file [%s]' , self :: LOGKEY , $data ));
2021-04-01 10:59:15 +00:00
$rc = $this -> M_gotskip ( $data );
break ;
2023-07-02 13:40:08 +00:00
case self :: BPM_OK :
Log :: debug ( sprintf ( '%s:- OK:Got an OK [%s]' , self :: LOGKEY , $data ));
2021-04-01 10:59:15 +00:00
$rc = $this -> M_ok ( $data );
break ;
2023-07-02 13:40:08 +00:00
case self :: BPM_CHAT :
Log :: debug ( sprintf ( '%s:- CHT:Remote sent a message [%s]' , self :: LOGKEY , $data ));
2021-04-01 10:59:15 +00:00
$rc = $this -> M_chat ( $data );
break ;
default :
2023-07-02 13:40:08 +00:00
Log :: error ( sprintf ( '%s:! BINKP command not implemented [%d]' , self :: LOGKEY , $rc ));
$rc = TRUE ;
2021-04-01 10:59:15 +00:00
}
}
} else {
if ( $this -> recv -> fd ) {
try {
2023-07-02 13:40:08 +00:00
$this -> recv -> write ( $this -> rx_buf );
} catch ( FileGrewException $e ) {
// Retry the file without compression
Log :: error ( sprintf ( '%s:! %s' , self :: LOGKEY , $e -> getMessage ()));
$this -> msgs ( self :: BPM_GET , sprintf ( '%s %ld NZ' , $this -> recv -> name_size_time , $this -> recv -> filepos ));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
} catch ( \Exception $e ) {
Log :: error ( sprintf ( '%s:! %s' , self :: LOGKEY , $e -> getMessage ()));
2021-08-15 14:41:43 +00:00
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
}
2023-07-02 13:40:08 +00:00
$rc = TRUE ;
2023-06-27 07:39:11 +00:00
if ( $this -> recv -> filepos === $this -> recv -> size ) {
2023-07-02 13:40:08 +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
2023-07-02 13:40:08 +00:00
$this -> msgs ( self :: BPM_GOTSKIP , $this -> recv -> name_size_time );
2021-08-17 13:49:39 +00:00
$this -> recv -> close ();
2021-04-01 10:59:15 +00:00
}
} else {
2023-07-02 13:40:08 +00:00
Log :: critical ( sprintf ( '%s:- Ignoring data block, we dont have a received FD open?' , self :: LOGKEY ));
$rc = TRUE ;
2021-04-01 10:59:15 +00:00
}
}
$this -> rx_size = - 1 ;
}
2021-08-17 13:49:39 +00:00
$this -> rx_buf = '' ;
2021-04-01 10:59:15 +00:00
} else {
2023-07-02 13:40:08 +00:00
$rc = TRUE ;
2021-04-01 10:59:15 +00:00
}
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 ;
}
/**
2023-07-02 13:40:08 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
private function binkp_send () : int
{
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:+ BINKP send, TX buffer has [%d] chars (%d), and [%d] messages queued' , self :: LOGKEY , strlen ( $this -> tx_buf ), $this -> tx_left , $this -> mqueue -> count ()));
2021-04-01 10:59:15 +00:00
2023-06-27 07:39:11 +00:00
if ( $this -> tx_left === 0 ) { /* tx buffer is empty */
2023-07-02 13:40:08 +00:00
$this -> tx_ptr = 0 ;
2021-04-01 10:59:15 +00:00
2023-06-27 07:39:11 +00:00
if ( $this -> mqueue -> count ()) { /* there are unsent messages */
2023-07-02 13:40:08 +00:00
while ( $msg = $this -> mqueue -> shift ()) {
if ( $msg instanceof BinkpMessage ) {
if (( $msg -> len + $this -> tx_left ) > self :: MAX_BLKSIZE ) {
Log :: alert ( sprintf ( '%s:! MSG [%d] would overflow our buffer [%d]' , self :: LOGKEY , $msg -> len , $this -> tx_left ));
break ;
}
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:- TX buffer empty, adding [%d] chars from the queue' , self :: LOGKEY , $msg -> len ));
$this -> tx_buf .= $msg -> msg ;
$this -> tx_left += $msg -> len ;
} else {
$this -> tx_buf .= $msg ;
$this -> tx_left += strlen ( $msg );
}
2021-04-01 10:59:15 +00:00
}
} elseif ( $this -> sessionGet ( self :: SE_SENDFILE ) && $this -> send -> fd && ( ! $this -> sessionGet ( self :: SE_WAITGET ))) {
try {
2023-07-02 13:40:08 +00:00
$buf = $this -> send -> read ( self :: BLOCKSIZE );
2021-04-01 10:59:15 +00:00
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 );
2023-07-02 13:40:08 +00:00
} catch ( \Exception $e ) {
Log :: error ( sprintf ( '%s:! BINKP send unexpected ERROR [%s]' , self :: LOGKEY , $e -> getMessage ()));
2021-04-01 10:59:15 +00:00
}
2023-07-02 13:40:08 +00:00
if ( $buf ) {
$data = BinkpMessage :: mkheader ( strlen ( $buf ));
$data .= $buf ;
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
if ( $this -> capGet ( self :: F_CRYPT , self :: O_YES )) {
$enc = $this -> crypt_out -> encrypt ( $data );
$this -> tx_buf .= $enc ;
$this -> tx_left = strlen ( $enc );
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
} else {
$this -> tx_buf .= $data ;
$this -> tx_left = strlen ( $buf ) + BinkpMessage :: BLK_HDR_SIZE ;
}
2021-04-01 10:59:15 +00:00
}
2023-07-02 13:40:08 +00:00
// @todo should this be less than BLOCKSIZE? Since a read could return a blocksize and it could be the end of the file?
if ( $this -> send -> filepos === $this -> send -> size ) {
2021-04-01 10:59:15 +00:00
$this -> sessionSet ( self :: SE_WAITGOT );
$this -> sessionClear ( self :: SE_SENDFILE );
}
2023-06-27 07:39:11 +00:00
}
2021-04-01 10:59:15 +00:00
} else {
try {
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:- Sending [%d] chars to remote: tx_buf [%d], tx_ptr [%d]' , self :: LOGKEY , $this -> tx_left , strlen ( $this -> tx_buf ), $this -> tx_ptr ));
$rc = $this -> client -> send ( substr ( $this -> tx_buf , $this -> tx_ptr , $this -> tx_left ), self :: TIMEOUT_TIME );
Log :: info ( sprintf ( '%s:- Sent [%d] chars to remote' , self :: LOGKEY , $rc ));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
} catch ( \Exception $e ) {
if ( $e -> getCode () === 11 ) {
Log :: error ( sprintf ( '%s:! Got a socket EAGAIN' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
return 1 ;
2023-07-02 13:40:08 +00:00
}
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
Log :: error ( sprintf ( '%s:! Sending we got an EXCEPTION [%d-%s]' , self :: LOGKEY , $e -> getCode (), $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 ;
}
}
2023-06-27 07:39:11 +00:00
return 1 ;
2021-04-01 10:59:15 +00:00
}
private function file_parse ( string $str ) : ? array
{
$name = $this -> strsep ( $str , ' ' );
2023-06-27 07:39:11 +00:00
$size = ( int ) $this -> strsep ( $str , ' ' );
$time = ( int ) $this -> strsep ( $str , ' ' );
$offs = ( int ) $this -> strsep ( $str , ' ' );
2023-07-02 13:40:08 +00:00
$flags = $this -> strsep ( $str , ' ' );
2021-04-01 10:59:15 +00:00
if ( $name && $size && $time ) {
return [
'file' => [ 'name' => $name , 'size' => $size , 'mtime' => $time ],
2023-07-02 13:40:08 +00:00
'offs' => $offs ,
'flags' => $flags ,
2021-04-01 10:59:15 +00:00
];
}
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
{
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:+ Queueing message to remote [%d:%s]' , self :: LOGKEY , $id , $msg_body ));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
$msg = new BinkpMessage ( $id , $msg_body );
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
// If encryption is enabled, we need to queue the encrypted version of the message
// @todo rework this so queue only has data, not objects
if ( $this -> capGet ( self :: F_CRYPT , self :: O_YES )) {
$enc = $this -> crypt_out -> encrypt ( $msg -> msg );
$this -> mqueue -> push ( $enc );
} else {
$this -> mqueue -> push ( $msg );
2021-04-01 10:59:15 +00:00
}
$this -> mib ++ ;
}
/**
2023-07-02 13:40:08 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
2023-07-02 13:40:08 +00:00
private function M_adr ( string $buf ) : bool
2021-04-01 10:59:15 +00:00
{
$buf = $this -> skip_blanks ( $buf );
2023-06-27 07:39:11 +00:00
$rc = 0 ;
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
while ( $rem_aka = $this -> strsep ( $buf , ' ' )) {
2021-04-01 10:59:15 +00:00
try {
2023-07-02 13:40:08 +00:00
if ( ! ( $o = Address :: findFTN ( $rem_aka , FALSE , NULL , TRUE ))) {
Log :: alert ( sprintf ( '%s:? AKA is UNKNOWN [%s]' , self :: LOGKEY , $rem_aka ));
2021-04-01 10:59:15 +00:00
2022-12-01 12:51:43 +00:00
$this -> node -> ftn_other = $rem_aka ;
2021-04-01 10:59:15 +00:00
continue ;
2023-07-02 13:40:08 +00:00
} else if ( ! $o -> active ) {
Log :: alert ( sprintf ( '%s:/ AKA is not active [%s], ignoring' , self :: LOGKEY , $rem_aka ));
continue ;
} else {
Log :: info ( sprintf ( '%s:- Got AKA [%s]' , self :: LOGKEY , $rem_aka ));
2021-04-01 10:59:15 +00:00
}
2023-07-02 13:40:08 +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 ;
2023-07-02 13:40:08 +00:00
return FALSE ;
2021-04-01 10:59:15 +00:00
}
// 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 ) {
2023-07-02 13:40:08 +00:00
Log :: error ( sprintf ( '%s:! Remote\'s AKA is mine [%s]?' , self :: LOGKEY , $rem_aka ));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
$this -> msgs ( self :: BPM_ERR , sprintf ( 'Sorry that is my AKA [%s], who are you?' , $rem_aka ));
2021-04-01 10:59:15 +00:00
$this -> rc = self :: S_FAILURE ;
2023-07-02 13:40:08 +00:00
return FALSE ;
2021-04-01 10:59:15 +00:00
}
// @todo lock nodes
$this -> node -> ftn = $o ;
$rc = $this -> node -> aka_num ;
}
2023-06-27 07:39:11 +00:00
if ( $rc === 0 ) {
2023-07-02 13:40:08 +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
2023-07-02 13:40:08 +00:00
$this -> msgs ( self :: BPM_BSY , 'All AKAs are busy, nothing to do :(' );
$this -> rc = self :: S_BUSY ;
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
return FALSE ;
2021-04-01 10:59:15 +00:00
}
if ( $this -> originate ) {
if ( ! $this -> node -> originate_check ()) {
2023-07-02 13:40:08 +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' );
2023-07-02 13:40:08 +00:00
$this -> rc = self :: S_FAILURE ;
2021-04-01 10:59:15 +00:00
return 0 ;
}
2023-07-02 13:40:08 +00:00
// Add our mail to the queue if we have authenticated
if ( $this -> node -> aka_authed )
foreach ( $this -> node -> aka_remote_authed as $ao ) {
$this -> send -> mail ( $ao );
$this -> send -> files ( $ao );
}
$this -> msgs ( self :: BPM_NUL , sprintf ( 'TRF %lu %lu' , $this -> send -> mail_size , $this -> send -> files_size ));
2021-04-01 10:59:15 +00:00
if ( $this -> md_challenge ) {
2023-07-02 13:40:08 +00:00
Log :: info ( sprintf ( '%s:! Sending MD5 challenge' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
$this -> msgs ( self :: BPM_PWD , sprintf ( 'CRAM-MD5-%s' , $this -> node -> get_md5chal ( $this -> md_challenge )));
2023-07-02 13:40:08 +00:00
} elseif ( $this -> capGet ( self :: F_MD , self :: O_NEED )) {
Log :: error ( sprintf ( '%s:! Node wants plaintext, but we insist on MD5 challenges' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
$this -> msgs ( self :: BPM_ERR , 'Can\'t use plaintext password' );
2023-07-02 13:40:08 +00:00
$this -> rc = self :: S_FAILURE ;
2021-04-01 10:59:15 +00:00
return 0 ;
} else {
2023-07-02 13:40:08 +00:00
Log :: info ( sprintf ( '%s:! Sending plain text password' , self :: LOGKEY ));
$this -> msgs ( self :: BPM_PWD , $this -> node -> password ? : '' );
2021-04-01 10:59:15 +00:00
}
}
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
2023-07-02 13:40:08 +00:00
return TRUE ;
2021-04-01 10:59:15 +00:00
}
2023-07-02 13:40:08 +00:00
private function M_chat ( string $buf ) : bool
2021-04-01 10:59:15 +00:00
{
2023-07-02 13:40:08 +00:00
if ( $this -> capGet ( self :: F_CHAT , self :: O_YES )) {
Log :: error ( sprintf ( '%s:! We cannot do chat' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
} else {
2023-07-02 13:40:08 +00:00
Log :: error ( sprintf ( '%s:! We got a chat message, but chat is disabled (%s)' , self :: LOGKEY , strlen ( $buf )),[ 'buf' => $buf ]);
2021-04-01 10:59:15 +00:00
}
2023-07-02 13:40:08 +00:00
return TRUE ;
2021-04-01 10:59:15 +00:00
}
/**
2021-07-23 14:53:35 +00:00
* We received EOB from the remote .
*
2023-07-02 13:40:08 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
2023-07-02 13:40:08 +00:00
private function M_eob ( string $buf ) : bool
2021-04-01 10:59:15 +00:00
{
2023-07-02 13:40:08 +00:00
if ( $this -> recv -> fd ) {
Log :: info ( sprintf ( '%s:= Closing receiving file.' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
$this -> recv -> close ();
2023-07-02 13:40:08 +00:00
}
2021-04-01 10:59:15 +00:00
$this -> sessionSet ( self :: SE_RECVEOB );
$this -> sessionClear ( self :: SE_DELAYEOB );
2023-07-11 07:22:31 +00:00
// @todo Is this multibatch mode, if so we should check that MB hasn been agreed.
2021-04-01 10:59:15 +00:00
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 ) {
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:- Checking for any new mail and files to [%s]' , self :: LOGKEY , $ao -> ftn ));
2021-07-18 12:10:21 +00:00
$this -> send -> mail ( $ao );
2023-06-22 07:36:22 +00:00
$this -> send -> files ( $ao );
2021-07-18 12:10:21 +00:00
}
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:- We have [%d] items to send to [%s]' , self :: LOGKEY , $this -> send -> total_count , $ao -> ftn ));
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
}
2023-07-02 13:40:08 +00:00
return TRUE ;
2021-04-01 10:59:15 +00:00
}
/**
2023-07-02 13:40:08 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
2023-07-02 13:40:08 +00:00
private function M_err ( string $buf ) : bool
2021-04-01 10:59:15 +00:00
{
2023-07-02 13:40:08 +00:00
Log :: error ( sprintf ( '%s:! We got an error, there are [%d] chars in the buffer (%s)' , self :: LOGKEY , strlen ( $buf ), $buf ));
2021-04-01 10:59:15 +00:00
$this -> error_close ();
2023-07-02 13:40:08 +00:00
$this -> rc = self :: S_FAILURE ;
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
return TRUE ;
2021-04-01 10:59:15 +00:00
}
/**
2023-07-02 13:40:08 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
2023-07-02 13:40:08 +00:00
private function M_file ( string $buf ) : bool
2021-04-01 10:59:15 +00:00
{
2023-07-02 13:40:08 +00:00
Log :: info ( sprintf ( '%s:+ About to receive a file [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
2023-07-11 07:22:31 +00:00
if ( $this -> sessionGet ( self :: SE_SENTEOB ) && $this -> sessionGet ( self :: SE_RECVEOB ))
2021-04-01 10:59:15 +00:00
$this -> sessionClear ( self :: SE_SENTEOB );
$this -> sessionClear ( self :: SE_RECVEOB );
if ( $this -> recv -> fd )
$this -> recv -> close ();
2023-07-02 13:40:08 +00:00
if ( ! $file = $this -> file_parse ( $buf )) {
Log :: error ( sprintf ( '%s:! UNPARSABLE file info [%s]' , self :: LOGKEY , $buf ));
$this -> msgs ( self :: BPM_ERR , sprintf ( 'M_FILE: unparsable file info: "%s", what are you on?' , $buf ));
2021-04-01 10:59:15 +00:00
if ( $this -> sessionGet ( self :: SE_SENDFILE ))
$this -> send -> close ( FALSE );
2023-07-02 13:40:08 +00:00
$this -> rc = self :: S_FAILURE ;
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
return FALSE ;
2021-04-01 10:59:15 +00:00
}
// 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 )
2023-06-27 07:39:11 +00:00
&& $this -> recv -> mtime === Arr :: get ( $file , 'file.mtime' )
&& $this -> recv -> size === Arr :: get ( $file , 'file.size' )
&& $this -> recv -> filepos === $file [ 'offs' ])
2021-04-01 10:59:15 +00:00
{
2023-07-02 13:40:08 +00:00
$this -> recv -> open ( $this -> node -> address , $file [ 'offs' ] < 0 , $file [ 'flags' ]);
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
return TRUE ;
2021-04-01 10:59:15 +00:00
}
$this -> recv -> new ( $file [ 'file' ]);
try {
2023-07-02 13:40:08 +00:00
switch ( $this -> recv -> open ( $this -> node -> address , $file [ 'offs' ] < 0 , $file [ 'flags' ])) {
2021-04-01 10:59:15 +00:00
case self :: FOP_ERROR :
2023-07-02 13:40:08 +00:00
Log :: error ( sprintf ( '%s:! File ERROR' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
case self :: FOP_SUSPEND :
2023-07-02 13:40:08 +00:00
case self :: FOP_SKIP :
Log :: info ( sprintf ( '%s:- File Skipped' , self :: LOGKEY ));
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
2023-07-02 13:40:08 +00:00
// Close the file, since we are skipping it.
$this -> recv -> close ();
2021-04-01 10:59:15 +00:00
break ;
2023-07-02 13:40:08 +00:00
case self :: FOP_GOT :
Log :: info ( sprintf ( '%s:- File skipped, we already have it' , self :: LOGKEY ));
$this -> msgs ( self :: BPM_GOTSKIP , $this -> recv -> name_size_time );
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
// Close the file, since we already have it.
$this -> recv -> close ();
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
break ;
2021-04-01 10:59:15 +00:00
case self :: FOP_CONT :
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:- Continuing file [%s] from (%ld)' , self :: LOGKEY , $this -> recv -> name , $file [ 'offs' ]));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
case self :: FOP_OK :
Log :: debug ( sprintf ( '%s:- Getting file from offset [%ld]' , self :: LOGKEY , $file [ 'offs' ]));
2023-07-09 12:19:11 +00:00
if ((( int ) $file [ 'offs' ] === - 1 ) && ( ! $this -> capGet ( self :: F_NOREL , self :: O_THEY ))) {
2023-07-09 01:18:57 +00:00
Log :: debug ( sprintf ( '%s:- Assuming the remote wants NR mode, since offset is [%d] and they didnt specify an OPT with it' , self :: LOGKEY , $file [ 'offs' ]));
$this -> capSet ( self :: F_NOREL , self :: O_THEY );
2023-07-02 13:40:08 +00:00
}
2023-07-09 01:18:57 +00:00
2023-07-11 07:22:31 +00:00
if ( $this -> capGet ( self :: F_NOREL , self :: O_YES ))
$this -> msgs ( self :: BPM_GET , sprintf ( '%s %ld' , $this -> recv -> name_size_time ,( $file [ 'offs' ] < 0 ) ? 0 : $file [ 'offs' ]));
2023-07-09 01:18:57 +00:00
break ;
2021-04-01 10:59:15 +00:00
}
2023-07-02 13:40:08 +00:00
} catch ( \Exception $e ) {
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
}
2023-07-02 13:40:08 +00:00
return TRUE ;
2021-04-01 10:59:15 +00:00
}
/**
2023-07-02 13:40:08 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
2023-07-02 13:40:08 +00:00
private function M_get ( string $buf ) : bool
2021-04-01 10:59:15 +00:00
{
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:+ Sending file [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
if ( $file = $this -> file_parse ( $buf )) {
2021-04-01 10:59:15 +00:00
if ( $this -> sessionGet ( self :: SE_SENDFILE )
&& $this -> send -> sendas
&& ! strncasecmp ( Arr :: get ( $file , 'file.name' ), $this -> send -> sendas , self :: MAX_PATH )
2023-06-27 07:39:11 +00:00
&& $this -> send -> mtime === Arr :: get ( $file , 'file.mtime' )
&& $this -> send -> size === Arr :: get ( $file , 'file.size' ))
2021-04-01 10:59:15 +00:00
{
if ( ! $this -> send -> seek ( $file [ 'offs' ])) {
2023-07-02 13:40:08 +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 );
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:Sending file [%s] as [%s]' , self :: LOGKEY , $this -> send -> name , $this -> send -> sendas ));
$this -> msgs ( self :: BPM_FILE , sprintf ( '%s %lu %ld %lu %s' , $this -> send -> sendas , $this -> send -> size , $this -> send -> mtime , $file [ 'offs' ], $file [ 'flags' ]));
2021-04-01 10:59:15 +00:00
}
} else {
2023-07-02 13:40:08 +00:00
Log :: error ( sprintf ( '%s:! Remote requested an unknown file [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
}
} else {
2023-07-02 13:40:08 +00:00
Log :: error ( sprintf ( '%s:! UNPARSABLE file info [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
}
2023-07-02 13:40:08 +00:00
return TRUE ;
2021-04-01 10:59:15 +00:00
}
/**
* M_GOT / M_SKIP commands
*
* @ param string $buf
2023-07-02 13:40:08 +00:00
* @ return bool
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
2023-07-02 13:40:08 +00:00
private function M_gotskip ( string $buf ) : bool
2021-04-01 10:59:15 +00:00
{
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 )
2023-06-27 07:39:11 +00:00
&& $this -> send -> mtime === Arr :: get ( $file , 'file.mtime' )
&& $this -> send -> size === Arr :: get ( $file , 'file.size' ))
2021-04-01 10:59:15 +00:00
{
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 )) {
2023-07-09 01:18:57 +00:00
Log :: info ( sprintf ( '%s:= Packet/File [%s] sent.' , self :: LOGKEY , $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
2023-07-02 13:40:08 +00:00
return TRUE ;
2021-04-01 10:59:15 +00:00
}
if ( $this -> sessionGet ( self :: SE_WAITGOT )) {
2023-07-09 01:18:57 +00:00
Log :: info ( sprintf ( '%s:= Packet/File [%s] sent.' , self :: LOGKEY , $this -> send -> name ));
2021-04-01 10:59:15 +00:00
$this -> sessionClear ( self :: SE_WAITGOT );
$this -> send -> close ( TRUE );
} else {
2023-07-02 13:40:08 +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 {
2023-07-02 13:40:08 +00:00
Log :: error ( sprintf ( '%s:! UNPARSABLE file info [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
}
2023-07-02 13:40:08 +00:00
return TRUE ;
2021-04-01 10:59:15 +00:00
}
/**
2023-07-02 13:40:08 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
2023-07-02 13:40:08 +00:00
private function M_nul ( string $buf ) : bool
2021-04-01 10:59:15 +00:00
{
2023-07-02 13:40:08 +00:00
Log :: info ( 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 ) {
2023-07-02 13:40:08 +00:00
$this -> client -> speed = self :: TCP_SPEED ;
2021-04-01 10:59:15 +00:00
2023-06-27 07:39:11 +00:00
} elseif ( strtolower ( substr ( $comma , $c + 1 , 1 )) === 'k' ) {
2021-04-01 10:59:15 +00:00
$this -> client -> speed = $spd * 1024 ;
2023-06-27 07:39:11 +00:00
} elseif ( strtolower ( substr ( $comma , $c + 1 , 1 )) === 'm' ) {
2021-04-01 10:59:15 +00:00
$this -> client -> speed = $spd * 1024 * 1024 ;
} else {
2023-07-02 13:40:08 +00:00
$this -> client -> speed = self :: TCP_SPEED ;
2021-04-01 10:59:15 +00:00
}
}
} 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 , ' ' ))) {
2023-07-02 13:40:08 +00:00
if ( ! strcmp ( $p , 'MB' )) {
Log :: info ( sprintf ( '%s:- Remote wants MULTIBATCH mode' , self :: LOGKEY ));
$this -> capSet ( self :: F_MULTIBATCH , self :: O_THEY );
2021-04-01 10:59:15 +00:00
} elseif ( ! strcmp ( $p , 'ND' )) {
2023-07-02 13:40:08 +00:00
Log :: info ( sprintf ( '%s:- Remote wants NO DUPES mode' , self :: LOGKEY ));
$this -> capSet ( self :: F_NOREL , self :: O_THEY );
$this -> capSet ( self :: F_NODUPE , self :: O_THEY );
2021-04-01 10:59:15 +00:00
} elseif ( ! strcmp ( $p , 'NDA' )) {
2023-07-02 13:40:08 +00:00
Log :: info ( sprintf ( '%s:- Remote wants NO DUPES ASYMMETRIC mode' , self :: LOGKEY ));
$this -> capSet ( self :: F_NOREL , self :: O_THEY );
$this -> capSet ( self :: F_NODUPE , self :: O_THEY );
$this -> capSet ( self :: F_NODUPEA , self :: O_THEY );
} elseif ( ! strcmp ( $p , 'NR' )) {
Log :: info ( sprintf ( '%s:- Remote wants NON RELIABLE MODE mode' , self :: LOGKEY ));
$this -> capSet ( self :: F_NOREL , self :: O_THEY );
2021-04-01 10:59:15 +00:00
} elseif ( ! strcmp ( $p , 'CHAT' )) {
2023-07-02 13:40:08 +00:00
Log :: info ( sprintf ( '%s:- Remote wants CHAT mode' , self :: LOGKEY ));
$this -> capSet ( self :: F_CHAT , self :: O_THEY );
2021-04-01 10:59:15 +00:00
} elseif ( ! strcmp ( $p , 'CRYPT' )) {
2023-07-02 13:40:08 +00:00
Log :: info ( sprintf ( '%s:- Remote wants CRYPT mode' , self :: LOGKEY ));
$this -> capSet ( self :: F_CRYPT , self :: O_THEY );
} elseif ( ! strcmp ( $p , 'GZ' )) {
Log :: info ( sprintf ( '%s:- Remote wants GZ compression' , self :: LOGKEY ));
$this -> capSet ( self :: F_COMP , self :: O_THEY );
} elseif ( ! strcmp ( $p , 'BZ2' )) {
Log :: info ( sprintf ( '%s:- Remote wants BZ2 compression' , self :: LOGKEY ));
$this -> capSet ( self :: F_COMP , self :: O_THEY | self :: O_EXT );
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
} elseif ( ! strncmp ( $p , 'CRAM-MD5-' , 9 ) && $this -> originate && $this -> capGet ( self :: F_MD , self :: O_WANT )) {
2021-04-01 10:59:15 +00:00
if (( $x = strlen ( substr ( $p , 9 ))) > 64 ) {
2023-07-02 13:40:08 +00:00
Log :: error ( sprintf ( '%s:! The challenge string is TOO LONG [%d] (%s)' , self :: LOGKEY , $x , $p ));
2021-04-01 10:59:15 +00:00
} else {
2023-07-02 13:40:08 +00:00
Log :: info ( sprintf ( '%s:- Remote wants MD5 auth' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
$this -> md_challenge = hex2bin ( substr ( $p , 9 ));
if ( $this -> md_challenge )
2023-07-02 13:40:08 +00:00
$this -> capSet ( self :: F_MD , self :: O_THEY );
2021-04-01 10:59:15 +00:00
}
2023-07-02 13:40:08 +00:00
if ( $this -> capGet ( self :: F_MD , self :: O_WE ))
$this -> capSet ( self :: F_MD , self :: O_YES );
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
} else {
Log :: warning ( sprintf ( '%s:/ Ignoring UNSUPPORTED option [%s]' , self :: LOGKEY , $p ));
2021-04-01 10:59:15 +00:00
}
}
} else {
2023-07-02 13:40:08 +00:00
Log :: warning ( sprintf ( '%s:/ M_nul Got UNKNOWN NUL [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
}
2023-07-02 13:40:08 +00:00
return TRUE ;
2021-04-01 10:59:15 +00:00
}
/**
2021-07-04 13:24:38 +00:00
* Remote accepted our password
*
2023-07-02 13:40:08 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
2023-07-02 13:40:08 +00:00
private function M_ok ( string $buf ) : bool
2021-04-01 10:59:15 +00:00
{
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 ) {
2023-07-02 13:40:08 +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 ;
2023-07-02 13:40:08 +00:00
return FALSE ;
2021-04-01 10:59:15 +00:00
}
$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 " )))
2023-06-27 07:39:11 +00:00
if ( strcmp ( $t , 'non-secure' ) === 0 ) {
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:- NOT secure' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
$this -> capSet ( self :: F_CRYPT , self :: O_NO );
2021-04-01 10:59:15 +00:00
$this -> optionClear ( self :: O_PWD );
break ;
}
}
return $this -> binkp_hsdone ();
}
/**
2023-07-02 13:40:08 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
2023-07-02 13:40:08 +00:00
private function M_pwd ( string $buf ) : bool
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 ) {
2023-07-02 13:40:08 +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 ;
2023-07-02 13:40:08 +00:00
return FALSE ;
2021-04-01 10:59:15 +00:00
}
if ( $this -> md_challenge ) {
if ( $have_CRAM ) {
// Loop to match passwords
$this -> node -> auth ( substr ( $buf , 9 ), $this -> md_challenge );
2023-07-02 13:40:08 +00:00
$this -> capSet ( self :: F_MD , self :: O_THEY );
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
} elseif ( $this -> capGet ( self :: F_MD , self :: O_NEED )) {
Log :: error ( sprintf ( '%s:! Remote doesnt support MD5, but we want it' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
$this -> msgs ( self :: BPM_ERR , 'You must support MD5 auth to talk to me' );
2021-04-01 10:59:15 +00:00
$this -> rc = self :: S_FAILURE ;
2023-07-02 13:40:08 +00:00
return FALSE ;
2021-04-01 10:59:15 +00:00
}
}
2023-07-02 13:40:08 +00:00
if ( ! $this -> md_challenge || ( ! $have_CRAM && ( ! $this -> capGet ( self :: F_MD , self :: O_NEED )))) {
2021-04-01 10:59:15 +00:00
// 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 ) {
2023-07-02 13:40:08 +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 ;
2023-07-02 13:40:08 +00:00
return FALSE ;
2021-04-01 10:59:15 +00:00
}
} elseif ( ! $this -> node -> aka_authed ) {
2023-07-02 13:40:08 +00:00
Log :: notice ( sprintf ( '%s:= Remote proposed password for us [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
}
2023-07-02 13:40:08 +00:00
// We dont use crypt if we dont have an MD5 sessions
2023-07-09 01:18:57 +00:00
if ( ! $have_pwd && ( ! $this -> capGet ( self :: F_MD , self :: O_YES ))) {
2023-07-02 13:40:08 +00:00
Log :: notice ( sprintf ( '%s:= CRYPT disabled, since we have no password or not MD5' , self :: LOGKEY ));
$this -> capSet ( self :: F_CRYPT , self :: O_NO );
}
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
$opt = '' ;
if ( $this -> capGet ( self :: F_NOREL , self :: O_WE ) && $this -> capGet ( self :: F_NODUPE , self :: O_WE ) && $this -> capGet ( self :: F_NODUPEA , self :: O_YES ))
$opt .= ' NDA' ;
elseif ( $this -> capGet ( self :: F_NOREL , self :: O_WE ) && $this -> capGet ( self :: F_NODUPE , self :: O_WE ))
$opt .= ' ND' ;
elseif ( $this -> capGet ( self :: F_NOREL , self :: O_WE ))
$opt .= ' NR' ;
$opt .= $this -> capGet ( self :: F_MULTIBATCH , self :: O_WE ) ? ' MB' : '' ;
$opt .= $this -> capGet ( self :: F_CHAT , self :: O_WE ) ? ' CHAT' : '' ;
if ( $this -> capGet ( self :: F_COMP , self :: O_WE ) && $this -> capGet ( self :: F_COMP , self :: O_EXT )) {
$this -> comp_mode = 'BZ2' ;
$opt .= ' EXTCMD BZ2' ;
} elseif ( $this -> capGet ( self :: F_COMP , self :: O_WE )) {
$this -> comp_mode = 'GZ' ;
$opt .= ' EXTCMD GZ' ;
}
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
$opt .= $this -> capGet ( self :: F_CRYPT , self :: O_WE ) ? ' CRYPT' : '' ;
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
if ( strlen ( $opt ))
$this -> msgs ( self :: BPM_NUL , sprintf ( 'OPT%s' , $opt ));
2021-04-01 10:59:15 +00:00
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 );
2023-06-22 07:36:22 +00:00
$this -> send -> files ( $ao );
2021-07-23 14:53:35 +00:00
}
2023-07-02 13:40:08 +00:00
$this -> msgs ( self :: BPM_NUL , sprintf ( 'TRF %lu %lu' , $this -> send -> mail_size , $this -> send -> size ));
2021-04-01 10:59:15 +00:00
$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
}
/**
2023-07-02 13:40:08 +00:00
* Set up our BINKP session
2021-04-01 10:59:15 +00:00
*
* @ return int
2023-07-02 13:40:08 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
protected function protocol_session () : int
{
2023-07-02 13:40:08 +00:00
if ( $this -> binkp_init () !== self :: OK )
return self :: S_FAILURE ;
2021-04-01 10:59:15 +00:00
$this -> binkp_hs ();
while ( TRUE ) {
2023-06-16 13:18:35 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s: - protocol_session LOOP' , self :: LOGKEY ));
2023-07-11 07:22:31 +00:00
if (( ! $this -> sessionGet ( self :: SE_INIT ))
&& ( ! $this -> sessionGet ( self :: SE_SENDFILE ))
&& ( ! $this -> sessionGet ( self :: SE_SENTEOB ))
&& ( ! $this -> sessionGet ( self :: SE_NOFILES ))
&& ( ! $this -> send -> fd ))
{
2021-04-01 10:59:15 +00:00
// Open our next file to send
if ( $this -> send -> total_count && ! $this -> send -> fd )
$this -> send -> open ();
2023-01-11 03:36:31 +00:00
// We have an open file descriptor, set our mode to send
2021-04-01 10:59:15 +00:00
if ( $this -> send -> fd ) {
$this -> sessionSet ( self :: SE_SENDFILE );
2023-01-11 03:36:31 +00:00
// NR mode, we wait for an M_GET before sending
2023-07-11 07:22:31 +00:00
if ( $this -> capGet ( self :: F_NOREL , self :: O_YES )) {
2021-04-01 10:59:15 +00:00
$this -> sessionSet ( self :: SE_WAITGET );
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s:- NR mode, waiting for M_GET' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
}
$this -> msgs ( self :: BPM_FILE ,
2023-07-02 13:40:08 +00:00
sprintf ( '%s %lu %lu %ld %s' ,
$this -> send -> sendas ,
$this -> send -> size ,
$this -> send -> mtime ,
$this -> sessionGet ( self :: SE_WAITGET ) ? - 1 : 0 ,
/*$this->send->comp ?:*/ '' ));
2021-04-01 10:59:15 +00:00
$this -> sessionClear ( self :: SE_SENTEOB );
2023-01-11 03:36:31 +00:00
// We dont have anything to send
2021-04-01 10:59:15 +00:00
} else {
$this -> sessionSet ( self :: SE_NOFILES );
}
}
2023-07-11 07:22:31 +00:00
if (( ! $this -> sessionGet ( self :: SE_INIT ))
&& ( ! $this -> sessionGet ( self :: SE_WAITGOT ))
&& ( ! $this -> sessionGet ( self :: SE_SENTEOB ))
&& ( ! $this -> sessionGet ( self :: SE_DELAYEOB ))
&& $this -> sessionGet ( self :: SE_NOFILES ))
{
2021-04-01 10:59:15 +00:00
$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 {
2023-06-16 13:18:35 +00:00
if ( $this -> DEBUG )
2023-07-02 13:40:08 +00:00
Log :: debug ( sprintf ( '%s: - Checking if there more data (ttySelect), timeout [%d]' , self :: LOGKEY , self :: TIMEOUT_TIME ));
2023-06-16 13:18:35 +00:00
2021-04-01 10:59:15 +00:00
// @todo we need to catch a timeout if there are no reads/writes
2023-07-02 13:40:08 +00:00
$rc = $this -> client -> ttySelect ( $rd , $wd , self :: TIMEOUT_TIME );
2021-04-01 10:59:15 +00:00
2023-06-16 13:18:35 +00:00
if ( $this -> DEBUG )
Log :: debug ( sprintf ( '%s: - ttySelect returned [%d]' , self :: LOGKEY , $rc ));
2023-07-02 13:40:08 +00:00
} catch ( \Exception ) {
2021-04-01 10:59:15 +00:00
$this -> error_close ();
$this -> error = - 2 ;
break ;
}
$this -> rc = ( $this -> originate ? ( self :: S_REDIAL | self :: S_ADDTRY ) : self :: S_BUSY );
2023-06-27 07:39:11 +00:00
if ( $rc === 0 ) {
2021-04-01 10:59:15 +00:00
$this -> error_close ();
$this -> error = - 1 ;
break ;
}
2023-07-02 13:40:08 +00:00
if ( $rd && ! $this -> binkp_recv ()) {
Log :: info ( sprintf ( '%s:- BINKP finished reading' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
break ;
2023-07-02 13:40:08 +00:00
}
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
if (( $this -> mqueue -> count () || $wd ) && ! $this -> binkp_send () && ( ! $this -> send -> total_count )) {
Log :: info ( sprintf ( '%s:- BINKP finished sending' , self :: LOGKEY ));
2021-08-17 13:49:39 +00:00
2021-04-01 10:59:15 +00:00
break ;
2023-07-02 13:40:08 +00:00
}
2021-04-01 10:59:15 +00:00
}
2023-06-27 07:39:11 +00:00
if ( $this -> error === - 1 )
2023-07-02 13:40:08 +00:00
Log :: error ( sprintf ( '%s:! protocol_session TIMEOUT' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
elseif ( $this -> error > 0 )
2023-07-02 13:40:08 +00:00
Log :: error ( sprintf ( '%s:! During our protocol session we got ERROR [%d]' , self :: LOGKEY , $this -> error ));
2021-04-01 10:59:15 +00:00
while ( ! $this -> error ) {
try {
2023-07-02 13:40:08 +00:00
Log :: info ( sprintf ( '%s:- BINKP reading [%d]' , self :: LOGKEY , self :: MAX_BLKSIZE ));
2021-04-01 10:59:15 +00:00
$buf = $this -> client -> read ( 0 , self :: MAX_BLKSIZE );
2023-07-02 13:40:08 +00:00
Log :: info ( sprintf ( '%s:- BINKP got [%d] chars' , self :: LOGKEY , strlen ( $buf )));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
} catch ( \Exception $e ) {
2021-04-01 10:59:15 +00:00
if ( $e -> getCode () !== 11 ) {
2023-07-02 13:40:08 +00:00
Log :: error ( sprintf ( '%s:! Got an exception [%d] while reading (%s)' , self :: LOGKEY , $e -> getCode (), $e -> getMessage ()));
2021-04-01 10:59:15 +00:00
$this -> error = 1 ;
}
break ;
}
2023-06-27 07:39:11 +00:00
if ( strlen ( $buf ) === 0 )
2021-04-01 10:59:15 +00:00
break ;
2023-07-02 13:40:08 +00:00
Log :: warning ( sprintf ( '%s:- Purged [%d] bytes from input stream (%s) ' , self :: LOGKEY , strlen ( $buf ), hex_dump ( $buf )));
2021-04-01 10:59:15 +00:00
}
2023-07-02 13:40:08 +00:00
Log :: info ( sprintf ( '%s:- We have [%d] messages and [%d] data left to send' , self :: LOGKEY , $this -> mqueue -> count (), strlen ( $this -> tx_left )));
while ( ! $this -> error && ( $this -> mqueue -> count () || $this -> tx_left ) && $this -> binkp_send ()) {}
2021-04-01 10:59:15 +00:00
return $this -> rc ;
}
/**
* Strip blanks at the beginning of a string
*
* @ param string $str
* @ return string
2023-07-02 13:40:08 +00:00
* @ throws \Exception
* @ todo Can this be replaced with ltrim ?
2021-04-01 10:59:15 +00:00
*/
private function skip_blanks ( string $str ) : string
{
$c = 0 ;
if ( $str != NULL )
while ( $this -> isSpace ( substr ( $str , $c , 1 )))
$c ++ ;
2023-06-27 07:39:11 +00:00
return substr ( $str , $c );
2021-04-01 10:59:15 +00:00
}
/**
* 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
{
2023-07-02 13:40:08 +00:00
if ( $x = strpos ( $str , $char )) {
$return = substr ( $str , 0 , $x );
$str = substr ( $str , $x + 1 );
} else {
$return = $str ;
$str = '' ;
}
2021-04-01 10:59:15 +00:00
return $return ;
}
/**
* Check if the string is a space
*
* @ param string $str
* @ return bool
2023-07-02 13:40:08 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
private function isSpace ( string $str ) : bool
{
if ( strlen ( $str ) > 1 )
2023-07-02 13:40:08 +00:00
throw new \Exception ( 'String is more than 1 char' );
2021-04-01 10:59:15 +00:00
return $str && in_array ( $str ,[ ' ' , " \n " , " \r " , " \ v " , " \ f " , " \t " ]);
}
2023-07-02 13:40:08 +00:00
}