PHP Classes

File: scripts/test_crashmail.php

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   scripts/test_crashmail.php   Download  
File: scripts/test_crashmail.php
Role: Example script
Content type: text/plain
Description: Example script
Class: Binkterm PHP
Bulletin board system based on the Web
Author: By
Last change:
Date: 5 days ago
Size: 9,990 bytes
 

Contents

Class file image Download
#!/usr/bin/env php
<?php
/**
 * Test script for sending crashmail to a remote binkp system
 * Simplified stream-based implementation
 */

require_once __DIR__ . '/../vendor/autoload.php';
require_once
__DIR__ . '/../src/functions.php';

use
BinktermPHP\Binkp\Config\BinkpConfig;
use
BinktermPHP\Database;
use
BinktermPHP\BinkdProcessor;

if (
$argc < 3) {
    echo
"Usage: php test_crashmail.php <remote_ip> <to_address> [--password=PASS]\n";
    exit(
1);
}

$remoteIp = $argv[1];
$toAddress = $argv[2];
$password = '';

// Parse password option
for ($i = 3; $i < $argc; $i++) {
    if (
preg_match('/^--password=(.*)$/', $argv[$i], $matches)) {
       
$password = $matches[1];
    }
}

function
log_msg($msg) {
    echo
"[" . date('H:i:s') . "] $msg\n";
}

function
sendFrame($stream, $isCommand, $opcode, $data, $debug = false) {
   
$dataLen = strlen($data);

   
// Correct binkp format: 2-byte length with command bit, then command byte, then data
   
if ($isCommand) {
       
$lengthWithFlag = 0x8000 | ($dataLen + 1); // +1 for command byte
       
$frame = pack('n', $lengthWithFlag) . chr($opcode) . $data;
    } else {
       
$lengthWithFlag = $dataLen;
       
$frame = pack('n', $lengthWithFlag) . $data;
    }

    if (
$debug) {
       
$opcodeNames = [0 => 'M_NUL', 1 => 'M_ADR', 2 => 'M_PWD', 3 => 'M_FILE', 4 => 'M_OK', 5 => 'M_EOB', 6 => 'M_GOT'];
       
$name = $opcodeNames[$opcode] ?? "CMD_{$opcode}";
       
$hex = bin2hex(substr($frame, 0, min(20, strlen($frame))));
       
$dataPreview = strlen($data) > 30 ? substr($data, 0, 30) . '...' : $data;
       
$lengthWithFlag = $isCommand ? (0x8000 | ($dataLen + 1)) : $dataLen;
        echo
" ? {$name}: length_with_flag=0x" . sprintf('%04X', $lengthWithFlag) . " opcode={$opcode} datalen={$dataLen} hex=[{$hex}...] data=\"{$dataPreview}\"\n";
    }

   
$written = fwrite($stream, $frame);
   
fflush($stream);

    if (
$written != strlen($frame)) {
        throw new
Exception("Failed to write frame");
    }

    return
true;
}

function
readFrame($stream, $timeout = 5) {
   
// Check if there's data available first
   
$read = [$stream];
   
$write = null;
   
$except = null;
   
$result = stream_select($read, $write, $except, $timeout, 0);

    if (
$result === false) {
        throw new
Exception("stream_select failed");
    }

    if (
$result === 0) {
       
// Timeout - no data available
       
return null;
    }

   
// Data is available, read it
   
stream_set_timeout($stream, $timeout);

   
// Read 2-byte header (correct format!)
   
$header = fread($stream, 2);
    if (
strlen($header) < 2) {
        return
null;
    }

   
// Parse length with command bit
   
$lengthWithFlag = unpack('n', $header)[1];
   
$isCommand = ($lengthWithFlag & 0x8000) !== 0;
   
$length = $lengthWithFlag & 0x7FFF;

   
$opcode = 0;
   
$data = '';

    if (
$isCommand && $length > 0) {
       
// Read command byte
       
$cmdByte = fread($stream, 1);
        if (
strlen($cmdByte) < 1) {
            return
null;
        }
       
$opcode = ord($cmdByte);
       
$length--; // Remaining data length

       
if ($length > 0) {
           
$data = fread($stream, $length);
            if (
strlen($data) < $length) {
                return
null;
            }
        }
    } elseif (!
$isCommand && $length > 0) {
       
// Data frame
       
$data = fread($stream, $length);
        if (
strlen($data) < $length) {
            return
null;
        }
    }

    return [
'isCommand' => $isCommand, 'opcode' => $opcode, 'data' => $data];
}

function
computeCramDigest($challenge, $password) {
    return
hash_hmac('md5', hex2bin($challenge), $password);
}

echo
"=== BinktermPHP Crashmail Test ===\n";
echo
"Remote: {$remoteIp}:24554\n";
echo
"To: {$toAddress}\n";
echo
"Password: " . (empty($password) ? '(insecure)' : '(provided)') . "\n\n";

try {
   
$config = BinkpConfig::getInstance();
   
$systemName = $config->getSystemName();
   
$sysopName = $config->getSystemSysop();
   
$primaryAddress = $config->getSystemAddress();
   
$myAddresses = $config->getMyAddresses();
   
$myAddresses[] = $primaryAddress;

   
// Create packet
   
log_msg("Creating packet...");
   
$tempDir = sys_get_temp_dir();
   
$packetFile = $tempDir . '/test_' . substr(uniqid(), -8) . '.pkt';

   
$message = [
       
'from_address' => $primaryAddress,
       
'to_address' => $toAddress,
       
'from_name' => $sysopName,
       
'to_name' => 'Sysop',
       
'subject' => 'Test crashmail',
       
'message_text' => "Test message from BinktermPHP\n\n--- BinktermPHP Test",
       
'date_written' => date('Y-m-d H:i:s'),
       
'attributes' => 0x0001 | 0x0100,
       
'message_id' => null,
       
'kludge_lines' => null,
    ];

   
$processor = new BinkdProcessor();
   
$processor->createOutboundPacket([$message], $toAddress, $packetFile);

    if (!
file_exists($packetFile)) {
        throw new
Exception("Failed to create packet");
    }

   
$packetSize = filesize($packetFile);
   
$packetName = basename($packetFile);
   
log_msg("? Packet: {$packetName} ({$packetSize} bytes)");

   
// Connect
   
log_msg("Connecting...");
   
$stream = @stream_socket_client(
       
"tcp://{$remoteIp}:24554",
       
$errno,
       
$errstr,
       
30,
       
STREAM_CLIENT_CONNECT
   
);

    if (!
$stream) {
        throw new
Exception("Connection failed: {$errstr}");
    }

   
stream_set_blocking($stream, true);
   
log_msg("? Connected\n");

   
// Small delay to ensure connection is fully established
   
usleep(100000); // 100ms

    // Send handshake with delays
   
log_msg("Sending handshake...");
   
sendFrame($stream, true, 0, "SYS {$systemName}", true); // M_NUL
   
usleep(50000); // 50ms delay
   
sendFrame($stream, true, 0, "ZYZ {$sysopName}", true);
   
usleep(50000);
   
sendFrame($stream, true, 0, "LOC Unknown", true);
   
usleep(50000);
   
sendFrame($stream, true, 0, "VER BinktermPHP/Test binkp/1.0", true);
   
usleep(50000);
   
sendFrame($stream, true, 0, "TIME " . gmdate('D, d M Y H:i:s') . " UTC", true);
   
usleep(50000);
   
sendFrame($stream, true, 1, implode(' ', $myAddresses), true); // M_ADR
   
usleep(50000);

   
// Receive handshake
   
log_msg("Receiving handshake...");
   
$cramChallenge = null;
   
$gotOk = false;
   
$sentPwd = false;

   
$startTime = time();
   
$frameCount = 0;
    while (!
$gotOk && (time() - $startTime) < 30) {
       
$frame = readFrame($stream, 2); // Shorter timeout

       
if (!$frame) {
           
// Show we're waiting every 10 attempts
           
if ($frameCount % 10 == 0) {
                echo
" [Still waiting... " . (time() - $startTime) . "s elapsed]\n";
            }
           
$frameCount++;
           
usleep(100000); // 100ms
           
continue;
        }

       
$frameCount = 0; // Reset counter when we get data
       
echo " Received frame: command=" . ($frame['isCommand'] ? 'yes' : 'no') . " opcode=" . $frame['opcode'] . " len=" . strlen($frame['data']) . "\n";

        if (!
$frame['isCommand']) {
            continue;
        }

        switch (
$frame['opcode']) {
            case
0: // M_NUL
               
if (preg_match('/CRAM-MD5-([0-9a-fA-F]+)/', $frame['data'], $m)) {
                   
$cramChallenge = $m[1];
                   
log_msg(" Got challenge");
                }
                break;

            case
1: // M_ADR
               
log_msg(" Got address");

               
// Send M_PWD after receiving M_ADR
               
if (!$sentPwd) {
                   
$digest = null;
                    if (
$cramChallenge) {
                       
$digest = computeCramDigest($cramChallenge, $password);
                       
$pwdData = "CRAM-MD5-{$digest}";
                       
log_msg(" Sending M_PWD (challenge={$cramChallenge}, digest={$digest})...");
                    } else {
                       
$pwdData = $password;
                       
log_msg(" Sending M_PWD (plain text, password_len=" . strlen($password) . ")...");
                    }

                   
sendFrame($stream, true, 2, $pwdData, true); // M_PWD with debug
                   
$sentPwd = true;
                }
                break;

            case
4: // M_OK
               
log_msg("? Got M_OK\n");
               
$gotOk = true;
                break;

            case
7: // M_ERR
               
throw new Exception("Remote error: {$frame['data']}");
        }
    }

    if (!
$gotOk) {
        throw new
Exception("Handshake timeout");
    }

   
// Send file
   
log_msg("Sending file...");
   
$timestamp = filemtime($packetFile);
   
sendFrame($stream, true, 3, "{$packetName} {$packetSize} {$timestamp} 0"); // M_FILE

   
$handle = fopen($packetFile, 'rb');
    while (!
feof($handle)) {
       
$chunk = fread($handle, 4096);
        if (
strlen($chunk) > 0) {
           
sendFrame($stream, false, 0, $chunk); // M_DATA
       
}
    }
   
fclose($handle);

   
sendFrame($stream, true, 5, ''); // M_EOB
   
log_msg("? File sent");

   
// Wait for confirmation
   
log_msg("Waiting for M_GOT...");
   
$gotConfirm = false;
   
$startTime = time();

    while (!
$gotConfirm && (time() - $startTime) < 30) {
       
$frame = readFrame($stream, 5);

        if (!
$frame) {
            continue;
        }

        if (
$frame['isCommand']) {
            if (
$frame['opcode'] == 6) { // M_GOT
               
log_msg("? Got M_GOT\n");
               
$gotConfirm = true;
            } elseif (
$frame['opcode'] == 5) { // M_EOB
               
log_msg("? Got M_EOB");
            }
        }
    }

    if (!
$gotConfirm) {
        throw new
Exception("No M_GOT received");
    }

   
fclose($stream);
   
unlink($packetFile);

    echo
"\n=== SUCCESS ===\n";
    echo
"Crashmail sent successfully!\n";
    exit(
0);

} catch (
Exception $e) {
    echo
"\n? ERROR: " . $e->getMessage() . "\n";
    if (isset(
$stream)) {
        @
fclose($stream);
    }
    if (isset(
$packetFile) && file_exists($packetFile)) {
        @
unlink($packetFile);
    }
    exit(
1);
}