Files
fpc-binkp/examples/example_outbound.pas
Ken Johnson b5d017fd9b v0.2.0 — full Comet/Argus parity + live ED25519
Bumps BP_VERSION to 0.2.0, BP_MIN_COMPATIBLE_VERSION to
0.2.0, adds the comprehensive 0.2.0 CHANGELOG entry.

Fixes the last asymmetry surfaced by the TCP-loopback
ED25519 test: the originator's ED25519 M_PWD path didn't
populate FSessionPwd, so post-auth CRYPT activation was
asymmetric (answerer set up the stream, originator didn't,
both hung waiting to decrypt each other's garbage).  The
originator now runs the same OnLookupPassword lookup the
answerer's OnFrame_MPWD uses, so CRYPT keys agree or don't
activate on either side.

Examples grew FPC_BINKP_PRIVKEY / FPC_BINKP_PEERKEY env
var hooks so a two-process TCP loopback can exercise the
full ED25519+CRYPT+GZ stack end-to-end.  example_outbound
also prints the session's final AuthMethod + TX block.

Live loopback result (same-machine TCP, both ends
fpc-binkp):

- 64 KB AAAA file, ED25519 auth + CRYPT stream cipher +
  EXTCMD GZ + secure-routing, delivered in 607 wire bytes,
  SHA-verified byte-identical on inbound secure dir.
- Inbound trace confirms full ED25519 path: keypair
  derived, answerer challenge issued, originator's
  signature verified, AuthMethod=bpAuthED25519.

All 98 checks across 7 test programs green, 5 platforms
clean (x86_64-linux, i386-go32v2, i386-os2, i386-win32,
i386-linux, i386-freebsd).  Live Argus regression still
passes (regular + FREQ-client).
2026-04-21 15:08:14 -07:00

143 lines
3.9 KiB
ObjectPascal

{ example_outbound -- connect to a remote BinkP peer and send
one file. Uses the library's reference TCP transport and
filesystem provider.
Build: fpc -Mobjfpc -Fu../src -Fu../../fpc-msgbase/src
-Fu../../fpc-log/src example_outbound.pas
Run: ./example_outbound host[:port] password file-to-send
Example:
./example_outbound 192.0.2.5:24554 SecretPwd /tmp/TEST.PKT
UNIX only (Linux / FreeBSD) — reference transport is UNIX-only
for v0.1.0. Windows / OS2 / DOS consumers provide their own
IBPTransport. }
program example_outbound;
{$mode objfpc}{$H+}
uses
SysUtils,
log.types, log.console,
mb.address,
bp.types, bp.config, bp.session,
bp.transport.tcp, bp.provider.fs;
type
TPwdLookup = class
public
Password: string;
PeerPubHex: string; { from FPC_BINKP_PEERKEY env -- empty ok }
function Lookup(const Addr: TFTNAddress): string;
function LookupPub(const Addr: TFTNAddress): string;
end;
function TPwdLookup.Lookup(const Addr: TFTNAddress): string;
begin
Result := Password;
end;
function TPwdLookup.LookupPub(const Addr: TFTNAddress): string;
begin
Result := PeerPubHex;
end;
procedure Usage;
begin
WriteLn('usage: example_outbound host[:port] password file-to-send');
Halt(2);
end;
var
HostArg, Pwd, FilePath, Host: string;
Port: Word;
ColonPos: Integer;
Cfg: TBPSessionConfig;
Transport: TBPTcpTransport;
Provider: TBPFsProvider;
Session: TBPSession;
Logger: TConsoleLogger;
PwdLookup: TPwdLookup;
begin
if ParamCount < 3 then Usage;
HostArg := ParamStr(1);
Pwd := ParamStr(2);
FilePath := ParamStr(3);
Port := 24554;
ColonPos := Pos(':', HostArg);
if ColonPos > 0 then
begin
Port := StrToIntDef(Copy(HostArg, ColonPos + 1, Length(HostArg)), 24554);
Host := Copy(HostArg, 1, ColonPos - 1);
end
else
Host := HostArg;
Logger := TConsoleLogger.Create(llDebug);
PwdLookup := TPwdLookup.Create;
PwdLookup.Password := Pwd;
PwdLookup.PeerPubHex := GetEnvironmentVariable('FPC_BINKP_PEERKEY');
Provider := TBPFsProvider.Create('', ''); { no inbound needed here }
Provider.Enqueue(FilePath, bpFsKeep);
try
Transport := TBPTcpTransport.CreateClient(Host, Port);
except
on E: Exception do
begin
WriteLn('connect failed: ', E.Message);
Halt(3);
end;
end;
BPConfigDefaults(Cfg);
SetLength(Cfg.LocalAddrs, 1);
Cfg.LocalAddrs[0] := MakeFTNAddress(1, 218, 720, 0);
Cfg.SystemName := 'fpc-binkp example';
Cfg.MailerName := 'example_outbound/0.1';
Cfg.Transport := Transport;
Cfg.Provider := Provider;
Cfg.Log := @Logger.Log;
Cfg.OnLookupPassword := @PwdLookup.Lookup;
Cfg.PrivateKey := GetEnvironmentVariable('FPC_BINKP_PRIVKEY');
Cfg.OnLookupPubKey := @PwdLookup.LookupPub;
Session := TBPSession.Create(bpDirOutbound, Cfg);
try
while Session.NextStep do
Transport.WaitReady(True, False, 50); { yield 50ms when idle }
if Session.Result_.Success then
begin
WriteLn(Format('OK: sent %d files, %d uncompressed bytes (%d on wire)',
[Session.Result_.FilesSent,
Session.Result_.BytesSent,
Session.Result_.WireBytesSent]));
WriteLn(Format(' final TX-block: %d',
[Session.Stats.CurrentTxBlock]));
case Session.Result_.AuthMethod of
bpAuthNone: WriteLn(' auth: NOPWD');
bpAuthPlain: WriteLn(' auth: plain');
bpAuthCRAM: WriteLn(' auth: CRAM-MD5');
bpAuthED25519: WriteLn(' auth: ED25519');
else WriteLn(' auth: other ', Ord(Session.Result_.AuthMethod));
end;
Halt(0);
end
else
begin
WriteLn(Format('FAIL: %s (code %d)',
[Session.Result_.ErrorMessage, Ord(Session.Result_.ErrorCode)]));
Halt(1);
end;
finally
Session.Free;
PwdLookup.Free;
Logger.Free;
{ Transport and Provider are TInterfacedObject and free
via ref-count when Cfg goes out of scope. }
end;
end.