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 ;
2023-12-01 07:14:07 +00:00
use App\Classes\Node ;
2021-04-01 10:59:15 +00:00
use App\Classes\Protocol as BaseProtocol ;
2024-06-01 02:55:27 +00:00
use App\Classes\Sock\Exception\SocketException ;
2021-04-01 10:59:15 +00:00
use App\Classes\Sock\SocketClient ;
2023-09-18 11:22:21 +00:00
use App\Exceptions\ { FileGrewException , InvalidFTNException };
2023-12-10 08:07:53 +00:00
use App\Models\ { Address , Mailer };
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
*/
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-12-10 08:07:53 +00:00
$this -> session ( Mailer :: where ( 'name' , 'BINKP' ) -> singleOrFail (), $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
*/
2024-06-07 08:07:34 +00:00
private function binkp_hs () : bool
2021-04-01 10:59:15 +00:00
{
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
2024-06-07 08:07:34 +00:00
if ( ! $this -> originate && $this -> down ) {
Log :: info ( sprintf ( '%s:! System down for maintenance' , self :: LOGKEY ));
$this -> msgs ( self :: BPM_BSY , 'RETRY 0600: Down for maintenance, back soon...' );
2024-06-26 02:18:06 +00:00
// @note Sometimes the remote drops the connection when we send the busy
while (( $this -> tx_left || $this -> mqueue -> count ()) && $this -> binkp_send ()) {}
2024-06-07 08:07:34 +00:00
return FALSE ;
}
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
}
2024-06-07 08:07:34 +00:00
return TRUE ;
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
2023-07-12 13:34:01 +00:00
if ( ! $this -> optionGet ( self :: O_PWD ) && ( ! $this -> capGet ( self :: F_MD , self :: O_WE ))) {
Log :: notice ( sprintf ( '%s:= CRYPT disabled, since we have no password or not MD5' , self :: LOGKEY ));
2023-07-02 13:40:08 +00:00
$this -> capSet ( self :: F_CRYPT , self :: O_NO );
2023-07-12 13:34:01 +00:00
}
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-12 13:34:01 +00:00
if ((( $this -> node -> get_versionint () > 100 ) && $this -> capGet ( self :: F_MULTIBATCH , self :: O_WANT )) || $this -> capGet ( self :: F_MULTIBATCH , self :: O_WE )) {
Log :: debug ( sprintf ( '%s:/ MB mode enabled, because we agree to MB mode, or I want MB and the remote is version [%d]' , self :: LOGKEY , $this -> node -> get_versionint ()));
$this -> capSet ( self :: F_MULTIBATCH , self :: O_YES );
}
2021-04-01 10:59:15 +00:00
2023-07-12 13:34:01 +00:00
if (( $this -> node -> get_versionint () > 100 ) && ( ! $this -> capGet ( self :: F_MULTIBATCH , self :: O_YES )))
2021-04-01 10:59:15 +00:00
$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-12 13:34:01 +00:00
$this -> capGet ( self :: F_NOREL , self :: O_YES ),
2023-07-11 07:22:31 +00:00
$this -> capGet ( self :: F_NODUPE , self :: O_WE ),
$this -> capGet ( self :: F_NODUPEA , self :: O_WE ),
$this -> capGet ( self :: F_MD , self :: O_WE ),
2023-07-12 13:34:01 +00:00
$this -> capGet ( self :: F_MULTIBATCH , self :: O_YES ),
$this -> capGet ( self :: F_CRYPT , self :: O_YES ),
2023-07-11 07:22:31 +00:00
$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?
2023-07-19 00:27:47 +00:00
if ( static :: 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-12 13:34:01 +00:00
if ( $this -> capGet ( self :: F_CRYPT , self :: O_YES )) {
Log :: debug ( sprintf ( '%s:%% Decrypting data from remote.' , self :: LOGKEY ));
2024-06-26 22:57:00 +00:00
$this -> rx_buf .= ( $x = $this -> crypt_in -> decrypt ( $rx_buf ));
2023-07-12 13:34:01 +00:00
} else {
2024-06-26 22:57:00 +00:00
$this -> rx_buf .= ( $x = $rx_buf );
2023-07-12 13:34:01 +00:00
}
2024-06-26 22:57:00 +00:00
Log :: debug ( sprintf ( '%s:- We read [%d] chars from remote' , self :: LOGKEY , strlen ( $x )),[ 'rx_buf' => hex_dump ( $x )]);
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
}
2023-07-19 00:27:47 +00:00
if ( static :: DEBUG )
2024-06-27 10:23:14 +00:00
Log :: debug ( sprintf ( '%s:- rx_buf size [%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 ) {
2023-07-17 06:36:53 +00:00
Log :: error ( sprintf ( '%s:! Unknown message received [%d] (%d-%s)' , self :: LOGKEY , $msg , strlen ( $this -> rx_buf ), $this -> rx_buf ));
2023-07-02 13:40:08 +00:00
$rc = TRUE ;
2021-04-01 10:59:15 +00:00
} else {
2023-11-24 02:13:38 +00:00
// http://ftsc.org/docs/fts-1026.001 - frames may be NULL terminated
$data = rtrim ( substr ( $this -> rx_buf , 1 ), " \x00 " );
2023-07-02 13:40:08 +00:00
switch ( $msg ) {
case self :: BPM_ADR :
Log :: debug ( sprintf ( '%s:- ADR:Address [%s]' , self :: LOGKEY , $data ));
2023-11-23 05:02:28 +00:00
// @note It seems taurus may pad data with nulls at the end (esp BPM_ADR), so we should trim that.
$rc = $this -> M_adr ( trim ( $data ));
2021-04-01 10:59:15 +00:00
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 )));
2023-11-23 05:02:28 +00:00
if ( strlen ( $data ))
Log :: critical ( sprintf ( '%s:! EOB but we have data?' , self :: LOGKEY ),[ 'data' => $data ]);
$rc = $this -> M_eob ();
2021-04-01 10:59:15 +00:00
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 ));
2023-11-23 05:02:28 +00:00
$rc = $this -> M_pwd ( ltrim ( $data ));
2021-04-01 10:59:15 +00:00
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 ;
2024-09-09 04:02:51 +00:00
case self :: BPM_SKIP :
Log :: debug ( sprintf ( '%s:- SKIP:Remote requested to skip file [%s]' , self :: LOGKEY , $data ));
$rc = $this -> M_skip ( $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 ));
2023-11-23 05:02:28 +00:00
$rc = $this -> M_ok ( ltrim ( $data ));
2021-04-01 10:59:15 +00:00
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-17 06:36:53 +00:00
Log :: error ( sprintf ( '%s:! BINKP command not implemented [%d]' , self :: LOGKEY , $msg ));
2023-07-02 13:40:08 +00:00
$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 ()));
2023-07-17 06:36:53 +00:00
$this -> msgs ( self :: BPM_GET , sprintf ( '%s %ld NZ' , $this -> recv -> name_size_time , $this -> recv -> pos ));
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-07-19 06:24:35 +00:00
if ( $this -> recv -> pos === $this -> recv -> recvsize ) {
Log :: info ( sprintf ( '%s:- Finished receiving file [%s] with size [%d]' , self :: LOGKEY , $this -> recv -> nameas , $this -> recv -> recvsize ));
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
}
2023-07-19 00:27:47 +00:00
if ( static :: DEBUG )
2022-11-04 13:06:47 +00:00
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 ) {
2023-07-19 02:32:41 +00:00
$this -> send -> close ( FALSE , $this -> node );
2021-04-01 10:59:15 +00:00
$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 ()));
2023-07-17 06:36:53 +00:00
throw new \Exception ( $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?
2023-07-17 06:36:53 +00:00
if ( $this -> send -> pos === $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 );
2023-09-06 02:11:18 +00:00
Log :: debug ( 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
{
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-09-15 12:57:32 +00:00
if ( ! ( $o = Address :: findFTN ( $rem_aka , TRUE ))) {
2023-09-05 21:32:04 +00:00
// @todo when we have multiple inactive records, this returns more than 1, so pluck the active record if there is one
2023-07-02 13:40:08 +00:00
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
2024-05-18 02:26:00 +00:00
// If we only present limited AKAs dont validate password against akas outside of the domains we present
} elseif ( is_null ( our_address ( $o ))) {
2024-06-26 06:02:26 +00:00
Log :: debug ( sprintf ( '%s:/ AKA domain [%s] is not in our domain(s) [%s] - ignoring' , self :: LOGKEY , $o -> zone -> domain -> name , our_address () -> pluck ( 'zone.domain.name' ) -> unique () -> join ( ',' )));
2024-05-22 12:12:38 +00:00
$this -> node -> ftn_other = $rem_aka ;
2024-05-18 02:26:00 +00:00
continue ;
2023-09-04 00:10:51 +00:00
} elseif ( ! $o -> active ) {
2024-05-18 02:26:00 +00:00
Log :: alert ( sprintf ( '%s:/ AKA is not active [%s] - ignoring' , self :: LOGKEY , $rem_aka ));
2024-05-22 12:12:38 +00:00
$this -> node -> ftn_other = $rem_aka ;
2023-07-02 13:40:08 +00:00
continue ;
} else {
Log :: info ( sprintf ( '%s:- Got AKA [%s]' , self :: LOGKEY , $rem_aka ));
2024-05-25 12:25:57 +00:00
// We'll update this address status
2024-09-15 12:00:40 +00:00
// @todo this shouldnt be here, since we havent authenticated the node
2024-05-25 12:25:57 +00:00
$o -> validated = TRUE ;
$o -> role &= ~ ( Address :: NODE_HOLD | Address :: NODE_DOWN );
$o -> save ();
2021-04-01 10:59:15 +00:00
}
2023-09-18 11:22:21 +00:00
} catch ( InvalidFTNException $e ) {
2024-05-18 02:26:00 +00:00
Log :: error ( sprintf ( '%s:! AKA is INVALID [%s] (%s) - ignoring' , self :: LOGKEY , $rem_aka , $e -> getMessage ()));
2023-09-18 11:22:21 +00:00
continue ;
2023-07-02 13:40:08 +00:00
} catch ( \Exception $e ) {
2023-09-04 00:10:51 +00:00
Log :: error ( sprintf ( '%s:! AKA is INVALID [%s] (%d:%s-%s)' , self :: LOGKEY , $rem_aka , $e -> getLine (), $e -> getFile (), $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-12-01 07:14:07 +00:00
/**
* http :// ftsc . org / docs / fts - 1026.001
* M_NUL " TRF netmail_bytes arcmail_bytes "
* traffic prognosis ( in bytes ) for the netmail
* ( netmail_bytes ) and arcmail + files ( arcmail_bytes ),
* both are decimal ASCII strings
*/
// @todo This is affectively redundant, because we are not determining our mail until later
2023-07-02 13:40:08 +00:00
$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-11-23 05:02:28 +00:00
private function M_eob () : 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-17 06:36:53 +00:00
if ( ! $this -> send -> togo_count && $this -> sessionGet ( self :: SE_NOFILES ) && $this -> capGet ( self :: F_MULTIBATCH , self :: O_YES )) {
2023-12-01 07:14:07 +00:00
$this -> getFiles ( $this -> node );
2023-09-05 21:32:04 +00:00
2023-07-17 06:36:53 +00:00
if ( $this -> send -> togo_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 );
2023-07-17 06:36:53 +00:00
//if ($this->recv->fd)
// $this->recv->close();
2021-04-01 10:59:15 +00:00
2023-07-17 06:36:53 +00:00
if ( ! ( $file = $this -> file_parse ( $buf ))) {
2023-07-02 13:40:08 +00:00
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 ))
2023-07-19 02:32:41 +00:00
$this -> send -> close ( FALSE , $this -> node );
2021-04-01 10:59:15 +00:00
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.
2023-07-17 06:36:53 +00:00
if ( $this -> recv -> ready
&& $this -> recv -> nameas
&& ( ! strncasecmp ( Arr :: get ( $file , 'file.name' ), $this -> recv -> nameas , self :: MAX_PATH ))
2023-07-19 06:34:09 +00:00
&& $this -> recv -> recvmtime === Arr :: get ( $file , 'file.mtime' )
&& $this -> recv -> recvsize === Arr :: get ( $file , 'file.size' )
2023-07-17 06:36:53 +00:00
&& $this -> recv -> pos === $file [ 'offs' ])
2021-04-01 10:59:15 +00:00
{
2023-07-17 06:36:53 +00:00
$this -> recv -> open ( $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
}
2023-11-22 10:04:58 +00:00
$this -> recv -> new ( $file [ 'file' ], $this -> node -> address , $this -> force_queue );
2021-04-01 10:59:15 +00:00
try {
2023-07-17 06:36:53 +00:00
switch ( $this -> recv -> open ( $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-12 13:34:01 +00:00
if ((( int ) $file [ 'offs' ] === - 1 ) && $this -> capGet ( self :: F_NOREL , self :: O_WANT )) {
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' ]));
2023-07-12 13:34:01 +00:00
$this -> capSet ( self :: F_NOREL , self :: O_YES );
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-12 13:34:01 +00:00
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 )
2023-07-17 06:36:53 +00:00
&& $this -> send -> nameas
&& ! strncasecmp ( Arr :: get ( $file , 'file.name' ), $this -> send -> nameas , 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' );
2023-07-19 02:32:41 +00:00
$this -> send -> close ( FALSE , $this -> node );
2021-04-01 10:59:15 +00:00
$this -> sessionClear ( self :: SE_SENDFILE );
} else {
$this -> sessionClear ( self :: SE_WAITGET );
2023-07-17 06:36:53 +00:00
Log :: debug ( sprintf ( '%s:Sending file [%s] as [%s]' , self :: LOGKEY , $this -> send -> name , $this -> send -> nameas ));
$this -> msgs ( self :: BPM_FILE , sprintf ( '%s %lu %ld %lu %s' , $this -> send -> nameas , $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
}
/**
2024-09-09 04:02:51 +00:00
* M_SKIP commands
*
* @ param string $buf
* @ return bool
* @ throws \Exception
2024-09-09 04:20:55 +00:00
* @ todo We need to not add more files this session if a node skips a file
* @ todo This incorrectly records the mail / file as sent and thus it is not sent again
2024-09-09 04:02:51 +00:00
*/
private function M_skip ( string $buf ) : bool
{
2024-09-09 04:20:55 +00:00
Log :: debug ( sprintf ( '%s:+ Remote request to skip the file for now [%s]' , self :: LOGKEY , $buf ));
throw new \Exception ( 'We got a skip, but we dont handle it properly' );
2024-09-09 04:02:51 +00:00
if ( $file = $this -> file_parse ( $buf )) {
if ( $this -> send -> nameas
&& ! strncasecmp ( Arr :: get ( $file , 'file.name' ), $this -> send -> nameas , self :: MAX_PATH )
&& $this -> send -> mtime === Arr :: get ( $file , 'file.mtime' )
&& $this -> send -> size === Arr :: get ( $file , 'file.size' ))
{
if (( ! $this -> sessionGet ( self :: SE_SENDFILE )) && ( ! $this -> sessionGet ( self :: SE_WAITGOT ))) {
Log :: error ( sprintf ( '%s:! M_skip for unknown file [%s]' , self :: LOGKEY , $buf ));
} else {
Log :: info ( sprintf ( '%s:= Packet/File [%s], type [%d] skipped.' , self :: LOGKEY , $this -> send -> nameas , $this -> send -> type ));
$this -> sessionClear ( self :: SE_WAITGOT | self :: SE_SENDFILE );
$this -> send -> close ( FALSE , $this -> node );
}
} else {
Log :: error ( sprintf ( '%s:! M_skip not for our file? [%s]' , self :: LOGKEY , $buf ));
}
} else {
Log :: error ( sprintf ( '%s:! UNPARSABLE file info [%s]' , self :: LOGKEY , $buf ));
}
return TRUE ;
}
/**
* M_GOTSKIP command
2021-04-01 10:59:15 +00:00
*
* @ 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
{
2023-07-12 13:34:01 +00:00
Log :: debug ( sprintf ( '%s:+ Remote confirms receipt for file [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
if ( $file = $this -> file_parse ( $buf )) {
2023-07-17 06:36:53 +00:00
if ( $this -> send -> nameas
&& ! strncasecmp ( Arr :: get ( $file , 'file.name' ), $this -> send -> nameas , 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
{
2023-07-12 13:34:01 +00:00
if (( ! $this -> sessionGet ( self :: SE_SENDFILE )) && ( ! $this -> sessionGet ( self :: SE_WAITGOT ))) {
Log :: error ( sprintf ( '%s:! M_got[skip] for unknown file [%s]' , self :: LOGKEY , $buf ));
2021-04-01 10:59:15 +00:00
2023-07-12 13:34:01 +00:00
} else {
2023-07-19 02:32:41 +00:00
Log :: info ( sprintf ( '%s:= Packet/File [%s], type [%d] sent.' , self :: LOGKEY , $this -> send -> nameas , $this -> send -> type ));
2023-07-12 13:34:01 +00:00
$this -> sessionClear ( self :: SE_WAITGOT | self :: SE_SENDFILE );
2023-07-15 12:10:05 +00:00
2023-07-19 02:32:41 +00:00
$this -> send -> close ( TRUE , $this -> node );
2021-04-01 10:59:15 +00:00
}
2024-05-19 13:28:45 +00:00
} else {
Log :: error ( sprintf ( '%s:! M_got[skip] not for our 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 )) {
2023-11-23 05:02:28 +00:00
$this -> node -> system = ltrim ( substr ( $buf , 4 ));
2021-04-01 10:59:15 +00:00
} elseif ( ! strncmp ( $buf , 'ZYZ ' , 4 )) {
2023-11-23 05:02:28 +00:00
$this -> node -> sysop = ltrim ( substr ( $buf , 4 ));
2021-04-01 10:59:15 +00:00
} elseif ( ! strncmp ( $buf , 'LOC ' , 4 )) {
2023-11-23 05:02:28 +00:00
$this -> node -> location = ltrim ( substr ( $buf , 4 ));
2021-04-01 10:59:15 +00:00
} elseif ( ! strncmp ( $buf , 'NDL ' , 4 )) {
2023-11-23 05:02:28 +00:00
$data = ltrim ( substr ( $buf , 4 ));
2021-04-01 10:59:15 +00:00
$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 {
2023-11-23 05:02:28 +00:00
$comma = ltrim ( substr ( $buf , 4 ));
2021-04-01 10:59:15 +00:00
$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 )) {
2023-11-23 05:02:28 +00:00
$this -> node -> node_time = ltrim ( substr ( $buf , 5 ));
2021-04-01 10:59:15 +00:00
} elseif ( ! strncmp ( $buf , 'VER ' , 4 )) {
2023-11-23 05:02:28 +00:00
$data = ltrim ( substr ( $buf , 4 ));
2021-04-01 10:59:15 +00:00
$matches = [];
2023-09-18 06:44:14 +00:00
preg_match ( '#^(.+)\s+\(?binkp/([0-9]+)\.([0-9]+)\)?$#' , $data , $matches );
2021-04-01 10:59:15 +00:00
2023-09-18 06:44:14 +00:00
if ( count ( $matches ) === 4 ) {
$this -> node -> software = $matches [ 1 ];
$this -> node -> ver_major = $matches [ 2 ];
$this -> node -> ver_minor = $matches [ 3 ];
} else {
$this -> node -> software = 'Unknown' ;
$this -> node -> ver_major = 0 ;
$this -> node -> ver_minor = 0 ;
}
2021-04-01 10:59:15 +00:00
} elseif ( ! strncmp ( $buf , 'TRF ' , 4 )) {
2023-11-23 05:02:28 +00:00
$data = ltrim ( substr ( $buf , 4 ));
2021-04-01 10:59:15 +00:00
$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 )) {
2023-11-23 05:02:28 +00:00
$this -> node -> phone = ltrim ( substr ( $buf , 4 ));
2021-04-01 10:59:15 +00:00
} elseif ( ! strncmp ( $buf , 'OPM ' , 4 )) {
2023-11-23 05:02:28 +00:00
$this -> node -> message = ltrim ( substr ( $buf , 4 ));
2021-04-01 10:59:15 +00:00
} elseif ( ! strncmp ( $buf , 'OPT ' , 4 )) {
2023-11-23 05:02:28 +00:00
$data = ltrim ( substr ( $buf , 4 ));
2021-04-01 10:59:15 +00:00
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 )) {
2023-10-05 09:49:58 +00:00
if ( strlen ( $hex = substr ( $p , 9 )) > 64 ) {
Log :: error ( sprintf ( '%s:! The challenge string is TOO LONG [%d] (%s)' , self :: LOGKEY , strlen ( $hex ), $p ));
} elseif ( strlen ( $hex ) % 2 ) {
Log :: error ( sprintf ( '%s:! The challenge string is an odd size [%d] (%s)' , self :: LOGKEY , strlen ( $hex ), $hex ));
2021-04-01 10:59:15 +00:00
} else {
2023-10-05 09:49:58 +00:00
Log :: info ( sprintf ( '%s:- Remote wants MD5 auth with [%s]' , self :: LOGKEY , $hex ));
$this -> md_challenge = hex2bin ( $hex );
2021-04-01 10:59:15 +00:00
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
}
2021-08-14 06:14:22 +00:00
if ( $this -> optionGet ( self :: O_PWD ) && $buf ) {
2023-09-05 21:32:04 +00:00
while (( $t = $this -> strsep ( $buf , " \t " )))
2023-06-27 07:39:11 +00:00
if ( strcmp ( $t , 'non-secure' ) === 0 ) {
2023-09-05 21:32:04 +00:00
Log :: info ( 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 ;
2023-09-05 21:32:04 +00:00
} else {
Log :: debug ( sprintf ( '%s:? Got unknown string from M_ok [%s]' , self :: LOGKEY , $t ));
2021-04-01 10:59:15 +00:00
}
}
2024-09-15 12:00:40 +00:00
if ( $this -> optionGet ( self :: O_PWD )) {
2023-09-05 21:32:04 +00:00
Log :: info ( sprintf ( '%s:- SECURE' , self :: LOGKEY ));
2024-09-15 12:00:40 +00:00
// @todo Since we have connected, if the node was marked down/hold reset that
// Notification::route('netmail',$ao->system->uncommon()->first()->withoutRelations())->notify(new NodeMarkedDownNetmail($ao->withoutRelations()));
}
2021-04-01 10:59:15 +00:00
return $this -> binkp_hsdone ();
}
/**
2023-11-24 02:13:38 +00:00
* @ todo It appears when we poll a node , we dont ask for passwords , but we still send echomail and files .
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
{
$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
2023-09-05 21:32:04 +00:00
$x = $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-09-05 21:32:04 +00:00
Log :: info ( sprintf ( '%s:- We authed [%d] akas' , self :: LOGKEY , $x ));
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
2023-09-05 21:32:04 +00:00
$x = $this -> node -> auth ( $buf );
Log :: info ( sprintf ( '%s:- We authed [%d] akas' , self :: LOGKEY , $x ));
2021-04-01 10:59:15 +00:00
}
if ( $have_pwd ) {
2023-09-05 21:32:04 +00:00
// If no passwords matched (ie: aka_authed is 0), but we know this system
if (( ! $this -> node -> aka_authed ) && ( $this -> node -> aka_remote -> count ())) {
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 -> 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
2023-12-01 07:14:07 +00:00
// @todo This is effectively redundant, because we are not getting files until later
$this -> msgs ( self :: BPM_NUL , sprintf ( 'TRF %lu %lu' , $this -> send -> mail_size , $this -> send -> files_size ));
2021-07-23 14:53:35 +00:00
2023-12-01 07:14:07 +00:00
if ( $this -> node -> aka_authed ) {
2023-09-05 21:32:04 +00:00
$this -> msgs ( self :: BPM_OK , sprintf ( '%ssecure' , $have_pwd ? '' : 'non-' ));
2024-09-15 12:00:40 +00:00
// @todo Since we have connected, if the node was marked down/hold reset that
// Notification::route('netmail',$ao->system->uncommon()->first()->withoutRelations())->notify(new NodeMarkedDownNetmail($ao->withoutRelations()));
2023-09-05 21:32:04 +00:00
} else {
$this -> msgs ( self :: OK , 'non-secure' );
}
2021-04-01 10:59:15 +00:00
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
*
2023-11-22 10:04:58 +00:00
* @ param bool $force_queue
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
*/
2023-11-22 10:04:58 +00:00
protected function protocol_session ( bool $force_queue = FALSE ) : int
2021-04-01 10:59:15 +00:00
{
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
2023-11-22 10:04:58 +00:00
$this -> force_queue = $force_queue ;
2024-06-07 08:07:34 +00:00
if ( ! $this -> binkp_hs ())
return self :: S_FAILURE ;
2021-04-01 10:59:15 +00:00
while ( TRUE ) {
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 ))
{
2024-04-12 11:22:27 +00:00
if ( ! $this -> send -> togo_count )
$this -> getFiles ( $this -> node );
2023-12-01 07:14:07 +00:00
2021-04-01 10:59:15 +00:00
// Open our next file to send
2023-07-17 06:36:53 +00:00
if ( $this -> send -> togo_count && ! $this -> send -> fd ) {
2023-12-18 11:44:55 +00:00
Log :: info ( sprintf ( '%s:- Opening next file to send - we have [%d] left' , self :: LOGKEY , $this -> send -> togo_count ));
2021-04-01 10:59:15 +00:00
$this -> send -> open ();
2023-07-12 13:34:01 +00:00
}
2021-04-01 10:59:15 +00:00
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' ,
2023-07-17 06:36:53 +00:00
$this -> send -> nameas ,
2023-07-02 13:40:08 +00:00
$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 {
2023-07-17 06:36:53 +00:00
Log :: info ( sprintf ( '%s:- Nothing left to send in this batch' , self :: LOGKEY ));
2023-10-05 09:49:58 +00:00
// @todo We should look for more mail/files before thinking about sending an EOB
// IE: When we are set to only send X messages, but we have > X to send, get the next batch.
2021-04-01 10:59:15 +00:00
$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 ))
{
2023-07-12 13:34:01 +00:00
Log :: info ( sprintf ( '%s:- Sending EOB' , self :: LOGKEY ));
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 )) {
2023-07-12 13:34:01 +00:00
Log :: info ( sprintf ( '%s:- EOBs sent and received' , self :: LOGKEY ),[ 'm' => $this -> mib , 'remote_version' => $this -> node -> get_versionint ()]);
2021-04-01 10:59:15 +00:00
if ( $this -> mib < 3 || $this -> node -> get_versionint () <= 100 ) {
break ;
}
2023-07-12 13:34:01 +00:00
Log :: info ( sprintf ( '%s:- EOBs sent and received CLEARED' , self :: LOGKEY ));
2021-04-01 10:59:15 +00:00
$this -> mib = 0 ;
$this -> sessionClear ( self :: SE_RECVEOB | self :: SE_SENTEOB );
2023-07-17 06:36:53 +00:00
$this -> sessionSet ( self :: SE_DELAYEOB );
2021-04-01 10:59:15 +00:00
}
$wd = ( $this -> mqueue -> count () || $this -> tx_left || ( $this -> sessionGet ( self :: SE_SENDFILE ) && $this -> send -> fd && ! $this -> sessionGet ( self :: SE_WAITGET )));
$rd = TRUE ;
try {
2023-07-12 13:34:01 +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-07-12 13:34:01 +00:00
Log :: debug ( sprintf ( '%s:- ttySelect returned [%d]' , self :: LOGKEY , $rc ));
2023-06-16 13:18:35 +00:00
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-17 06:36:53 +00:00
if (( $this -> mqueue -> count () || $wd ) && ! $this -> binkp_send () && ( ! $this -> send -> togo_count )) {
2023-07-02 13:40:08 +00:00
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-09-06 02:11:18 +00:00
Log :: debug ( 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-09-06 02:11:18 +00:00
Log :: debug ( 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-09-06 02:11:18 +00:00
Log :: debug ( sprintf ( '%s:- We have [%d] messages and [%d] data left to send' , self :: LOGKEY , $this -> mqueue -> count (), strlen ( $this -> tx_left )));
2023-07-02 13:40:08 +00:00
while ( ! $this -> error && ( $this -> mqueue -> count () || $this -> tx_left ) && $this -> binkp_send ()) {}
2021-04-01 10:59:15 +00:00
return $this -> rc ;
}
2023-12-01 07:14:07 +00:00
public function getFiles ( Node $node ) : void
{
// Add our mail to the queue if we have authenticated
if ( $node -> aka_authed ) {
2023-12-18 11:44:55 +00:00
Log :: info ( sprintf ( '%s:- We have authed these AKAs [%s]' , self :: LOGKEY , $node -> aka_remote_authed -> pluck ( 'ftn' ) -> join ( ',' )));
2023-12-01 07:14:07 +00:00
foreach ( $node -> aka_remote_authed as $ao ) {
2024-06-26 13:42:25 +00:00
Log :: info ( sprintf ( '%s:- Checking for any new mail and files to [%s]' , self :: LOGKEY , $ao -> ftn ));
2023-12-01 07:14:07 +00:00
$this -> send -> mail ( $ao );
$this -> send -> files ( $ao );
2023-12-03 07:18:05 +00:00
$this -> send -> dynamic ( $ao );
2023-12-01 07:14:07 +00:00
/*
* Add " dynamic files " , eg : nodelist , nodelist segment , status reports .
* Dynamic files are built on the fly
* * query " dynamic " for items for the address
* * column 'method' identifies the method that will be called , with the $ao as the argument
* * a 'new Item' is added to the queue
* * when it its ready to be sent , the __tostring () is called that renders it
* * when sent , the dynamic table is updated with the sent_at
*/
}
2024-06-26 13:11:08 +00:00
Log :: info ( sprintf ( '%s:- We have [%d] items to send to [%s]' , self :: LOGKEY , $this -> send -> togo_count , $ao -> ftn ));
2023-12-01 07:14:07 +00:00
} else {
// @todo We should only send netmail if unauthenticated - netmail that is direct to this node (no routing)
2024-06-26 23:09:47 +00:00
Log :: alert ( sprintf ( '%s:- Not AUTHed so not looking for mail, but we know these akas [%s]' , self :: LOGKEY , $node -> aka_remote -> pluck ( 'ftn' ) -> join ( ',' )));
2023-12-01 07:14:07 +00:00
}
}
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 ;
}
2023-07-02 13:40:08 +00:00
}