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).
226 lines
6.8 KiB
ObjectPascal
226 lines
6.8 KiB
ObjectPascal
{ example_inbound -- accept inbound BinkP connections on a TCP
|
|
port and receive files into secure or unsecure directories
|
|
based on authentication state.
|
|
|
|
Demonstrates:
|
|
- IBPTransport backed by an accepted TCP socket.
|
|
- TBPFsProvider configured with a default inbound dir (unsecure),
|
|
then overridden per session via OnPostAuth.
|
|
- OnPostAuth hook inspecting Direction + AuthMethod to pick
|
|
secure vs unsecure routing.
|
|
|
|
Build: fpc -Mobjfpc -Fu../src -Fu../../fpc-msgbase/src
|
|
-Fu../../fpc-log/src example_inbound.pas
|
|
|
|
Run: ./example_inbound port password secure-dir unsecure-dir [temp-dir]
|
|
|
|
Example:
|
|
./example_inbound 24554 SecretPwd \
|
|
/var/spool/inbound/secure \
|
|
/var/spool/inbound/unsecure \
|
|
/tmp/bp
|
|
|
|
A remote that authenticates via CRAM-MD5 lands files in
|
|
secure-dir. A remote that presented NOPWD ("-") lands in
|
|
unsecure-dir. A remote that failed authentication never gets
|
|
here — the session aborts before bppAuthComplete.
|
|
|
|
Serial accept loop — one session at a time. Real daemons
|
|
would spawn a thread per session. UNIX only (Linux/FreeBSD). }
|
|
|
|
program example_inbound;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
uses
|
|
SysUtils,
|
|
Sockets, BaseUnix,
|
|
log.types, log.console,
|
|
mb.address,
|
|
bp.types, bp.config, bp.session,
|
|
bp.transport.tcp, bp.provider.fs;
|
|
|
|
type
|
|
{ Per-session policy object — owns the password the session
|
|
authenticates against and the secure/unsecure directories
|
|
the OnPostAuth hook routes between. }
|
|
TSessionPolicy = class
|
|
public
|
|
Password: string;
|
|
SecureDir: string;
|
|
UnsecureDir: string;
|
|
PeerPubHex: string; { from FPC_BINKP_PEERKEY env -- empty ok }
|
|
function Lookup(const Addr: TFTNAddress): string;
|
|
function LookupPub(const Addr: TFTNAddress): string;
|
|
procedure PostAuth(Direction: TBPDirection;
|
|
Authenticated: Boolean;
|
|
AuthMethod: TBPAuthMethod;
|
|
const RemoteAddrs: TFTNAddressArray;
|
|
var InboundDir: string;
|
|
var Reject: Boolean;
|
|
var RejectReason: string);
|
|
end;
|
|
|
|
function TSessionPolicy.Lookup(const Addr: TFTNAddress): string;
|
|
begin
|
|
Result := Password;
|
|
end;
|
|
|
|
function TSessionPolicy.LookupPub(const Addr: TFTNAddress): string;
|
|
begin
|
|
Result := PeerPubHex;
|
|
end;
|
|
|
|
procedure TSessionPolicy.PostAuth(Direction: TBPDirection;
|
|
Authenticated: Boolean; AuthMethod: TBPAuthMethod;
|
|
const RemoteAddrs: TFTNAddressArray;
|
|
var InboundDir: string; var Reject: Boolean;
|
|
var RejectReason: string);
|
|
var
|
|
AddrStr: string;
|
|
begin
|
|
{ Route to secure only when:
|
|
- this is an inbound session (we accepted, peer dialed us)
|
|
- the peer authenticated AND used a real credential
|
|
(CRAM-MD5 or plain password, not NOPWD).
|
|
Outbound sessions we initiated are treated as secure
|
|
implicitly -- we picked the peer. For this inbound example,
|
|
unauthenticated peers (NOPWD) land in unsecure. }
|
|
if Length(RemoteAddrs) > 0 then
|
|
AddrStr := FTNAddressToString(RemoteAddrs[0])
|
|
else
|
|
AddrStr := '(no addr)';
|
|
|
|
if Authenticated and (AuthMethod in [bpAuthCRAM, bpAuthPlain, bpAuthED25519]) then
|
|
begin
|
|
InboundDir := SecureDir;
|
|
WriteLn(Format(' routing [%s] -> secure (auth=%d dir=%s)',
|
|
[AddrStr, Ord(AuthMethod), InboundDir]));
|
|
end
|
|
else
|
|
begin
|
|
InboundDir := UnsecureDir;
|
|
WriteLn(Format(' routing [%s] -> unsecure (auth=%d dir=%s)',
|
|
[AddrStr, Ord(AuthMethod), InboundDir]));
|
|
end;
|
|
end;
|
|
|
|
procedure Usage;
|
|
begin
|
|
WriteLn('usage: example_inbound port password secure-dir unsecure-dir [temp-dir]');
|
|
Halt(2);
|
|
end;
|
|
|
|
procedure RunOneSession(ClientSock: TSocket;
|
|
const Pwd, SecureDir, UnsecureDir, TempDir: string;
|
|
Logger: TConsoleLogger);
|
|
var
|
|
Cfg: TBPSessionConfig;
|
|
Transport: TBPTcpTransport;
|
|
Provider: TBPFsProvider;
|
|
Session: TBPSession;
|
|
Policy: TSessionPolicy;
|
|
begin
|
|
Policy := TSessionPolicy.Create;
|
|
Policy.Password := Pwd;
|
|
Policy.SecureDir := SecureDir;
|
|
Policy.UnsecureDir := UnsecureDir;
|
|
Policy.PeerPubHex := GetEnvironmentVariable('FPC_BINKP_PEERKEY');
|
|
|
|
{ Provider initialised with the unsecure dir as its default;
|
|
OnPostAuth overrides per session via the OverrideDir path. }
|
|
Provider := TBPFsProvider.Create(UnsecureDir, TempDir);
|
|
Transport := TBPTcpTransport.CreateFromFd(ClientSock, '');
|
|
|
|
BPConfigDefaults(Cfg);
|
|
SetLength(Cfg.LocalAddrs, 1);
|
|
Cfg.LocalAddrs[0] := MakeFTNAddress(1, 218, 700, 0);
|
|
Cfg.SystemName := 'fpc-binkp example-inbound';
|
|
Cfg.MailerName := 'example_inbound/0.1';
|
|
Cfg.Transport := Transport;
|
|
Cfg.Provider := Provider;
|
|
Cfg.Log := @Logger.Log;
|
|
Cfg.OnLookupPassword := @Policy.Lookup;
|
|
Cfg.OnLookupPubKey := @Policy.LookupPub;
|
|
Cfg.OnPostAuth := @Policy.PostAuth;
|
|
Cfg.PrivateKey := GetEnvironmentVariable('FPC_BINKP_PRIVKEY');
|
|
|
|
Session := TBPSession.Create(bpDirInbound, Cfg);
|
|
try
|
|
while Session.NextStep do
|
|
Transport.WaitReady(True, False, 50);
|
|
WriteLn(Format('session end: success=%s files_in=%d files_out=%d',
|
|
[BoolToStr(Session.Result_.Success, True),
|
|
Session.Result_.FilesReceived, Session.Result_.FilesSent]));
|
|
finally
|
|
Session.Free;
|
|
Policy.Free;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
Port: Word;
|
|
Pwd, SecureDir, UnsecureDir, TempDir: string;
|
|
ListenSock, ClientSock: TSocket;
|
|
BindAddr: TInetSockAddr;
|
|
ClientAddr: TInetSockAddr;
|
|
AddrLen: TSockLen;
|
|
OptVal: LongInt;
|
|
Logger: TConsoleLogger;
|
|
begin
|
|
if ParamCount < 4 then Usage;
|
|
Port := StrToIntDef(ParamStr(1), 24554);
|
|
Pwd := ParamStr(2);
|
|
SecureDir := ParamStr(3);
|
|
UnsecureDir := ParamStr(4);
|
|
if ParamCount >= 5 then TempDir := ParamStr(5)
|
|
else TempDir := '/tmp/fpcbinkp';
|
|
Logger := TConsoleLogger.Create(llDebug);
|
|
|
|
ListenSock := fpSocket(AF_INET, SOCK_STREAM, 0);
|
|
if ListenSock < 0 then
|
|
begin
|
|
WriteLn('socket() failed');
|
|
Halt(3);
|
|
end;
|
|
|
|
OptVal := 1;
|
|
fpSetSockOpt(ListenSock, SOL_SOCKET, SO_REUSEADDR,
|
|
@OptVal, SizeOf(OptVal));
|
|
|
|
FillChar(BindAddr, SizeOf(BindAddr), 0);
|
|
BindAddr.sin_family := AF_INET;
|
|
BindAddr.sin_port := htons(Port);
|
|
BindAddr.sin_addr.s_addr := 0; { INADDR_ANY }
|
|
|
|
if fpBind(ListenSock, @BindAddr, SizeOf(BindAddr)) <> 0 then
|
|
begin
|
|
WriteLn('bind() failed');
|
|
Halt(4);
|
|
end;
|
|
if fpListen(ListenSock, 5) <> 0 then
|
|
begin
|
|
WriteLn('listen() failed');
|
|
Halt(5);
|
|
end;
|
|
|
|
WriteLn(Format('listening on 0.0.0.0:%d', [Port]));
|
|
WriteLn(Format(' secure -> %s', [SecureDir]));
|
|
WriteLn(Format(' unsecure -> %s', [UnsecureDir]));
|
|
WriteLn(Format(' temp -> %s', [TempDir]));
|
|
|
|
while True do
|
|
begin
|
|
AddrLen := SizeOf(ClientAddr);
|
|
ClientSock := fpAccept(ListenSock, @ClientAddr, @AddrLen);
|
|
if ClientSock < 0 then Continue;
|
|
WriteLn('accepted connection');
|
|
try
|
|
RunOneSession(ClientSock, Pwd, SecureDir, UnsecureDir, TempDir, Logger);
|
|
except
|
|
on E: Exception do
|
|
WriteLn('session error: ', E.Message);
|
|
end;
|
|
end;
|
|
end.
|