All checks were successful
Build & Release Plugin / build (push) Successful in 10s
25 new Pascal units (12,912 lines) converted from Xenia Mailer source: - CRC-16/32 engine (all 4 variants, verified against test vectors) - Zmodem: framing engine, send/receive with CRC-16/32, resume, WaZoo 8K - EMSI: handshake engine with MD5 shared-secret, field parsing, DAT build - Hydra: bidirectional engine with full rxpkt packet assembly (hex/bin/asc/uue) - SEAlink: block transfer + YooHoo/2U2 handshake (FTS-0006) - Session: protocol detection (EMSI/YooHoo/TSYNC/FTS-0001), negotiation - Period scheduler: bitmask-based time windows (2020-2099) - Event scheduler: Xenia-style named events with MAT_* behavior flags - BSO outbound scanner with status tracking - WaZoo send orchestration (mail bundles, file attaches, requests) - File request processor with SRIF external processor support - FAX Class 2 reception (ZFAX/QFX/raw formats) with T.30 parsing - Modem AT command engine with response parsing, caller ID, MNP detection - Login script engine (send/expect with sub-expects) - WFC main loop, nodelist lookup, BBS caller detection - All types/constants with platform ifdefs (Linux/FreeBSD/Windows/OS2) WFC screen redesigned to match Xenia's classic layout: - Blue top bar with system name, AKA address, live clock - Schedule/Status/Stats/Outbound panels in Xenia's exact arrangement - Activity panel with modem AT commands/responses - Session overlay with mail/file counters and transfer progress bar - CGA/VGA color scheme (cyan labels, green OK, red errors) - Full real-time via WebSocket — no polling, incremental DOM updates Events tab rebuilt with proper inline editor (checkboxes for days and behavior flags, time/duration inputs) replacing prompt() dialogs. Sessions and Nodes tabs auto-refresh via WebSocket events. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
192 lines
5.8 KiB
ObjectPascal
192 lines
5.8 KiB
ObjectPascal
{ ======================================================================== }
|
|
{ fw_getnode.pas — Nodelist lookup utilities }
|
|
{ Converted from Xenia Mailer getnode.c }
|
|
{ Original: (C) 1987-2001 Arjen G. Lentz }
|
|
{ }
|
|
{ FidoNet address parsing (z:n/n.p@domain format), password lookup, }
|
|
{ AKA alias selection, security level determination. }
|
|
{ }
|
|
{ Most of the heavy nodelist work is in fw_fido_nodelist.pas (already }
|
|
{ in the mailer plugin). This unit provides the session-level helpers. }
|
|
{ ======================================================================== }
|
|
unit fw_getnode;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
SysUtils, fw_fido_types, fw_mailer_types, fw_misc;
|
|
|
|
type
|
|
{ ── Security levels for file request processing ── }
|
|
TSecurityLevel = (
|
|
slUnlisted, { 0: Not in nodelist }
|
|
slListed, { 1: Listed node, no password }
|
|
slKnown, { 2: Known node (we have pw configured) }
|
|
slPassword, { 3: Password verified but not in pwd list }
|
|
slProtected, { 4: Password verified and in pwd list }
|
|
slTopSecurity { 5: Highest security }
|
|
);
|
|
|
|
{ ── Parse a FidoNet address string ── }
|
|
{ Supports formats: z:n/n.p, z:n/n, n/n.p, n/n, n:n/n@domain }
|
|
function ParseFidoAddress(const AStr: string;
|
|
out AZone, ANet, ANode, APoint: Integer;
|
|
ADefaultZone: Integer = 1): Boolean;
|
|
|
|
{ Format a FidoNet address as string }
|
|
function FormatFidoAddress(AZone, ANet, ANode, APoint: Integer;
|
|
const ADomain: string = ''): string;
|
|
|
|
{ ── Password lookup ── }
|
|
function FindPassword(const APasswords: TAddressPasswordArray;
|
|
AZone, ANet, ANode, APoint: Integer): string;
|
|
|
|
{ ── AKA alias selection ── }
|
|
{ Select which of our AKAs to present to a given remote address. }
|
|
function SelectAKA(const AAliases: TAKAAliasArray;
|
|
const AOurAKAs: array of TFidoAddress; ANumAKAs: Integer;
|
|
ARemZone, ARemNet, ARemNode, ARemPoint: Integer): Integer;
|
|
|
|
{ ── Security level determination ── }
|
|
function GetSecurityLevel(APasswordMatch: Boolean;
|
|
AWeHavePassword: Boolean;
|
|
ANodeListed: Boolean): Integer;
|
|
|
|
{ ── Remap point address ── }
|
|
{ Convert boss-routed point addresses to proper 4D representation }
|
|
procedure RemapAddress(var AZone, ANet, ANode, APoint, APointNet: Integer);
|
|
|
|
implementation
|
|
|
|
function ParseFidoAddress(const AStr: string;
|
|
out AZone, ANet, ANode, APoint: Integer;
|
|
ADefaultZone: Integer): Boolean;
|
|
var
|
|
S, Part: string;
|
|
ColonPos, SlashPos, DotPos, AtPos: Integer;
|
|
begin
|
|
Result := False;
|
|
AZone := ADefaultZone;
|
|
ANet := 0;
|
|
ANode := 0;
|
|
APoint := 0;
|
|
|
|
S := Trim(AStr);
|
|
if S = '' then Exit;
|
|
|
|
{ Strip domain (@domain) }
|
|
AtPos := Pos('@', S);
|
|
if AtPos > 0 then
|
|
S := Copy(S, 1, AtPos - 1);
|
|
|
|
{ Parse zone:net/node.point }
|
|
ColonPos := Pos(':', S);
|
|
SlashPos := Pos('/', S);
|
|
DotPos := Pos('.', S);
|
|
|
|
if SlashPos = 0 then Exit; { Must have at least net/node }
|
|
|
|
if ColonPos > 0 then
|
|
begin
|
|
AZone := StrToIntDef(Copy(S, 1, ColonPos - 1), 0);
|
|
if AZone = 0 then Exit;
|
|
S := Copy(S, ColonPos + 1, MaxInt);
|
|
SlashPos := Pos('/', S);
|
|
DotPos := Pos('.', S);
|
|
end;
|
|
|
|
ANet := StrToIntDef(Copy(S, 1, SlashPos - 1), -1);
|
|
if ANet < 0 then Exit;
|
|
|
|
if DotPos > SlashPos then
|
|
begin
|
|
ANode := StrToIntDef(Copy(S, SlashPos + 1, DotPos - SlashPos - 1), -1);
|
|
APoint := StrToIntDef(Copy(S, DotPos + 1, MaxInt), 0);
|
|
end
|
|
else
|
|
ANode := StrToIntDef(Copy(S, SlashPos + 1, MaxInt), -1);
|
|
|
|
if ANode < 0 then Exit;
|
|
Result := True;
|
|
end;
|
|
|
|
function FormatFidoAddress(AZone, ANet, ANode, APoint: Integer;
|
|
const ADomain: string): string;
|
|
begin
|
|
if APoint > 0 then
|
|
Result := Format('%d:%d/%d.%d', [AZone, ANet, ANode, APoint])
|
|
else
|
|
Result := Format('%d:%d/%d', [AZone, ANet, ANode]);
|
|
|
|
if ADomain <> '' then
|
|
Result := Result + '@' + ADomain;
|
|
end;
|
|
|
|
function FindPassword(const APasswords: TAddressPasswordArray;
|
|
AZone, ANet, ANode, APoint: Integer): string;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
Result := '';
|
|
for I := 0 to High(APasswords) do
|
|
if (APasswords[I].Zone = AZone) and (APasswords[I].Net = ANet) and
|
|
(APasswords[I].Node = ANode) and (APasswords[I].Point = APoint) then
|
|
Exit(APasswords[I].Password);
|
|
end;
|
|
|
|
function SelectAKA(const AAliases: TAKAAliasArray;
|
|
const AOurAKAs: array of TFidoAddress; ANumAKAs: Integer;
|
|
ARemZone, ARemNet, ARemNode, ARemPoint: Integer): Integer;
|
|
var
|
|
I: Integer;
|
|
AddrStr: string;
|
|
begin
|
|
Result := 0; { Default to first AKA }
|
|
|
|
AddrStr := FormatFidoAddress(ARemZone, ARemNet, ARemNode, ARemPoint);
|
|
|
|
{ Check alias patterns }
|
|
for I := 0 to High(AAliases) do
|
|
begin
|
|
if PatternMatch(AAliases[I].Pattern, AddrStr) then
|
|
begin
|
|
if (AAliases[I].MyAKA >= 0) and (AAliases[I].MyAKA < ANumAKAs) then
|
|
Exit(AAliases[I].MyAKA);
|
|
end;
|
|
end;
|
|
|
|
{ Default: pick AKA in same zone }
|
|
for I := 0 to ANumAKAs - 1 do
|
|
if AOurAKAs[I].Zone = ARemZone then
|
|
Exit(I);
|
|
end;
|
|
|
|
function GetSecurityLevel(APasswordMatch: Boolean;
|
|
AWeHavePassword: Boolean;
|
|
ANodeListed: Boolean): Integer;
|
|
begin
|
|
if APasswordMatch and AWeHavePassword then
|
|
Result := Ord(slProtected)
|
|
else if AWeHavePassword then
|
|
Result := Ord(slKnown)
|
|
else if ANodeListed then
|
|
Result := Ord(slListed)
|
|
else
|
|
Result := Ord(slUnlisted);
|
|
end;
|
|
|
|
procedure RemapAddress(var AZone, ANet, ANode, APoint, APointNet: Integer);
|
|
begin
|
|
{ If node is -1 (0xFFFF), this is a point using fake net }
|
|
if ANode = -1 then
|
|
begin
|
|
APointNet := ANet;
|
|
APoint := ANode; { Actually the point number was in node field }
|
|
{ Need boss node info from nodelist to fully resolve }
|
|
end;
|
|
end;
|
|
|
|
end.
|