ftn

package
v0.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Index

Constants

View Source
const (
	MsgAttrPrivate  = 0x0001
	MsgAttrCrash    = 0x0002
	MsgAttrReceived = 0x0004
	MsgAttrSent     = 0x0008
	MsgAttrFile     = 0x0010
	MsgAttrTransit  = 0x0020
	MsgAttrOrphan   = 0x0040
	MsgAttrKillSent = 0x0080
	MsgAttrLocal    = 0x0100
	MsgAttrHold     = 0x0200
	MsgAttrFRQ      = 0x0800
)

Packed message attribute flags (FTS-0001).

View Source
const CWValidation = 0x0100

CWValidation is the capability word validation value per FSC-0048.

View Source
const DefaultBinkpPort = 24554

DefaultBinkpPort is the standard BinkP TCP port.

View Source
const MaxFieldLen = 256

MaxFieldLen limits null-terminated string fields.

View Source
const PacketHeaderSize = 58

PacketHeaderSize is the fixed size of a Type-2+ packet header.

View Source
const PacketType2Plus = 2

PacketType2Plus is the packet version identifier for Type-2+ packets.

Variables

View Source
var (
	ErrInvalidPacketType = errors.New("ftn: invalid packet type (expected 2)")
	ErrTruncatedPacket   = errors.New("ftn: truncated packet data")
	ErrTruncatedMessage  = errors.New("ftn: truncated message in packet")
	ErrPacketTooLarge    = errors.New("ftn: packet exceeds size limit")
)

Errors

Functions

func BundleExtension

func BundleExtension(name string) bool

BundleExtension reports whether a filename looks like an FTN echomail bundle based on its extension. Binkd uses day-of-week suffixes for bundles:

.mo0 .tu0 .we0 .th0 .fr0 .sa0 .su0  (day-based, normal)
.mo1 .tu1 ... .mo9 ... .saZ         (day-based, overflow: 0-9, a-z)
.out                                 (normal outbound bundle)
.zip                                 (explicit ZIP bundle)

The trailing character is alphanumeric per FTS-5005; overflow uses 0-9 then a-z.

func BundleFileName

func BundleFileName(destNet, destNode uint16, dayIndex int) string

BundleFileName returns the standard FTN bundle filename for a given destination link and day-of-week index (0=Mon, 6=Sun), using the BSO naming convention: NNNNFFFF.DDD where NNNN=destNet (4 hex digits), FFFF=destNode (4 hex digits), DDD=day extension (e.g., mo0).

func CreateBundle

func CreateBundle(bundlePath string, pktPaths []string) (int, error)

CreateBundle creates a ZIP bundle archive at bundlePath containing the .PKT files listed in pktPaths. Returns the number of files bundled. It writes to a temporary file and renames on success so a partial bundle is never left at bundlePath if an error occurs.

func DecodeFTNString

func DecodeFTNString(s, chrs string) string

DecodeFTNString converts an FTN packet header string (From, To, Subject) from its source encoding to UTF-8 for storage and display.

FTN messages traditionally use CP437 encoding for header fields. Modern software (e.g., Synchronet) may send UTF-8 encoded strings, signaled by a CHRS kludge value containing "UTF-8" (e.g., "UTF-8 4").

If chrs contains "UTF-8" (case-insensitive), the string is assumed to be valid UTF-8 and is returned unchanged. Otherwise, bytes 0x80–0xFF are treated as CP437 and converted to their Unicode equivalents.

func EnsureBinkdConf added in v0.5.0

func EnsureBinkdConf(bbsRoot string, ftnCfg config.FTNConfig, server config.ServerConfig) (created bool, err error)

EnsureBinkdConf regenerates <bbsRoot>/data/ftn/binkd.conf from configuration when the file is missing (e.g. deleted for a reset — the FTN Setup Wizard refuses to re-run for an existing network, so this is the only recovery path). It is a no-op when the file exists or when no network has a parseable own address. created is true only when a new file was written.

func ExtractBundle

func ExtractBundle(srcPath, destDir string) ([]string, error)

ExtractBundle extracts .PKT files from a ZIP bundle at srcPath into destDir. Returns the paths of extracted .PKT files.

func FormatFTNDateTime

func FormatFTNDateTime(t time.Time) string

FormatFTNDateTime formats a time in FTN packed message format. Format: "DD Mon YY HH:MM:SS" (note: double space before time).

func FormatPackedMessageBody

func FormatPackedMessageBody(parsed *ParsedBody) string

FormatPackedMessageBody reassembles a message body from its components. Returns the body with FTN line endings (\r).

func HasPlaceholders added in v0.3.0

func HasPlaceholders(content, bbsRoot string) bool

HasPlaceholders reports whether binkd.conf content still contains template placeholder lines, meaning the FTN Setup Wizard has not been run. bbsRoot is the real BBS root, exempted as in isPlaceholderLine.

func IsZIPBundle

func IsZIPBundle(path string) (bool, error)

IsZIPBundle reports whether the file at path begins with the ZIP magic bytes.

func ParseFTNDateTime

func ParseFTNDateTime(s string) (time.Time, error)

ParseFTNDateTime parses an FTN datetime string back to time.Time.

func ReadPacket

func ReadPacket(r io.Reader) (*PacketHeader, []*PackedMessage, error)

ReadPacket parses a complete .PKT file from the reader.

func RegenerateBinkdConf added in v0.4.0

func RegenerateBinkdConf(confPath string, cfg BinkdConfig, nodes []BinkdNode) error

RegenerateBinkdConf writes a complete binkd.conf from configuration alone: identity, domains, addresses, derived paths, and one node line per hub. Used when binkd.conf is missing (e.g. deleted for a reset) — the FTN Setup Wizard refuses to re-run for an existing network, so without this the file could not be recreated. The caller supplies real values; iport/loglevel are template defaults here and are corrected by SyncBinkdSettings afterwards.

func SyncBinkdConf added in v0.2.0

func SyncBinkdConf(confPath string, identity BinkdIdentity, links map[string]BinkdLinkSync) error

SyncBinkdConf updates binkd.conf to reflect the current FTN links and BBS identity fields (sysname, sysop, location). Node lines are upserted from links: an existing line (matched by address) gets its hostname and password refreshed, and a configured link with a hostname but no node line has one appended — so a hub change in the TUI fully propagates. Only lines that differ are rewritten; if nothing changed the file is not touched. Called from saveAll so TUI edits are reflected automatically.

links maps "address@network" (e.g. "21:1/100@fsxnet") to its sync values.

func SyncBinkdSettings added in v0.2.0

func SyncBinkdSettings(confPath string, port, logLevel int) error

SyncBinkdSettings updates the iport and loglevel lines in binkd.conf to match the configured values. The file is only rewritten when a value differs; a missing binkd.conf is a no-op (the FTN Setup Wizard creates it). Non-positive port/logLevel values leave the corresponding line untouched.

func UpdateBinkdConf added in v0.2.0

func UpdateBinkdConf(confPath string, cfg BinkdConfig) error

UpdateBinkdConf reads an existing binkd.conf, strips placeholder lines, injects real domain/address/node/sysname values from cfg, and writes the result back. Existing wizard-managed node blocks (from prior runs) are preserved. If the file doesn't exist, a fresh one is generated.

func ValidateAddress added in v0.2.0

func ValidateAddress(s string) error

ValidateAddress checks whether s is a valid FTN address string.

func WritePacket

func WritePacket(w io.Writer, hdr *PacketHeader, msgs []*PackedMessage) error

WritePacket writes a complete .PKT file to the writer.

Types

type Address added in v0.2.0

type Address struct {
	Zone  int
	Net   int
	Node  int
	Point int
}

Address represents an FTN address in zone:net/node.point format.

func ParseAddress added in v0.2.0

func ParseAddress(s string) (Address, error)

ParseAddress parses an FTN address string like "21:1/100" or "21:1/100.5".

func (Address) String added in v0.2.0

func (a Address) String() string

String returns the canonical string representation of the address. Point is omitted when zero (e.g. "21:1/100" instead of "21:1/100.0").

type BinkdConfig added in v0.2.0

type BinkdConfig struct {
	BBSRoot   string // absolute path to BBS root directory
	BoardName string // BBS name for sysname
	SysopName string // sysop name (falls back to "SysOp")
	Location  string // BBS location (falls back to "Earth")

	// Domains maps network name to zone (e.g. "fsxnet" -> 21).
	Domains map[string]int

	// Addresses lists all "address" lines (e.g. "21:4/158@fsxnet").
	Addresses []string

	// Node is the new hub node to add.
	Node BinkdNode
}

BinkdConfig holds all data needed to generate or update binkd.conf.

type BinkdIdentity added in v0.2.0

type BinkdIdentity struct {
	BoardName string // sysname
	SysopName string // sysop
	Location  string // location
}

BinkdIdentity holds BBS identity fields synced to binkd.conf.

type BinkdLinkSync added in v0.4.0

type BinkdLinkSync struct {
	SessionPwd string
	HostPort   string
}

BinkdLinkSync carries the per-link values synced into a binkd.conf node line. HostPort is "hostname:port"; when empty only the password of an existing line is synced and no new line is created (host unknown).

type BinkdNode added in v0.2.0

type BinkdNode struct {
	Address     string // FTN address (e.g. "21:1/100@fsxnet")
	Hostname    string // host:port
	SessionPwd  string // session password ("-" if none)
	NetworkName string // used for section comment markers
}

BinkdNode holds the parameters for a binkd node line.

type EchoArea added in v0.2.0

type EchoArea struct {
	Tag         string
	Description string
}

EchoArea represents a single area from a backbone.na file.

func CleanEcholist added in v0.2.0

func CleanEcholist(areas []EchoArea, excludeTags []string, titlePrefix string) []EchoArea

CleanEcholist applies network-specific cleanup rules to a parsed echolist. It removes areas whose tags match any exclude pattern and strips the titlePrefix from area descriptions.

func DownloadEcholist added in v0.2.0

func DownloadEcholist(ctx context.Context, url string) ([]EchoArea, error)

DownloadEcholist fetches an echolist from a URL, parses it, and returns the areas. The request is bounded by the given context.

func ParseEcholist added in v0.2.0

func ParseEcholist(r io.Reader) ([]EchoArea, error)

ParseEcholist parses a backbone.na format file. Format: TAG<whitespace>Description (one per line). Lines starting with ';' are comments. Blank lines are skipped.

type NodeLookup added in v0.5.0

type NodeLookup struct {
	Self     *NodelistEntry // nil when the address is not in the nodelist
	Uplink   *NodelistEntry // hub/host/zone entry chosen as the uplink
	Hostname string         // resolved BinkP hostname for Uplink
	Port     int            // resolved BinkP port for Uplink
	Inferred bool           // true when Self is nil (uplink inferred from the net segment)
}

NodeLookup is the result of resolving a node and its uplink hub.

type Nodelist added in v0.5.0

type Nodelist struct {
	Entries []NodelistEntry
}

Nodelist is a parsed nodelist. Entry order is preserved because FTS-5000 expresses segment structure (which hub a node belongs to) purely by order.

func DownloadNodelist added in v0.5.0

func DownloadNodelist(ctx context.Context, url string) (*Nodelist, error)

DownloadNodelist fetches a nodelist from url and parses it. Zip payloads (detected by magic bytes, not URL extension) are unwrapped, extracting the member that looks most like a nodelist.

func ParseNodelist added in v0.5.0

func ParseNodelist(r io.Reader) (*Nodelist, error)

ParseNodelist parses an FTS-5000 distribution nodelist. Comment lines (';'), blank lines, malformed lines, and lines before the first Zone line are skipped rather than failing the whole list.

func (*Nodelist) Lookup added in v0.5.0

func (nl *Nodelist) Lookup(addr Address, dnsSuffix string) (*NodeLookup, error)

Lookup finds addr's entry and its uplink hub. The uplink is the nearest enclosing Hub segment, else the net's Host, else the Zone entry — skipping entries that are Down/Hold or have no resolvable hostname. dnsSuffix (e.g. "binkp.net"), when non-empty, derives a hostname for uplinks that carry no INA/IBN hostname flag.

type NodelistEntry added in v0.5.0

type NodelistEntry struct {
	Keyword  string  // "Zone", "Region", "Host", "Hub", "Pvt", "Down", "Hold", or "" for a plain node
	Address  Address // fully resolved zone:net/node
	Name     string  // system name, underscores translated to spaces
	Location string
	Sysop    string
	Flags    []string // raw flag fields after the baud field (e.g. "CM", "INA:host", "IBN:24556")
}

NodelistEntry is one parsed line of an FTS-5000 nodelist.

type PackedMessage

type PackedMessage struct {
	MsgType  uint16 // Always 2 for stored messages
	OrigNode uint16
	DestNode uint16
	OrigNet  uint16
	DestNet  uint16
	Attr     uint16 // Message attribute flags
	Cost     uint16
	DateTime string // "DD Mon YY  HH:MM:SS\x00" format
	To       string // Max 36 chars
	From     string // Max 36 chars
	Subject  string // Max 72 chars
	Body     string // Full message body including kludges
}

PackedMessage represents a single message within an FTN packet.

type PacketHeader

type PacketHeader struct {
	OrigNode  uint16
	DestNode  uint16
	Year      uint16
	Month     uint16 // 0-based (0=Jan)
	Day       uint16
	Hour      uint16
	Minute    uint16
	Second    uint16
	Baud      uint16 // Unused, set to 0
	PktType   uint16 // Must be 2
	OrigNet   uint16
	DestNet   uint16
	ProdCode  uint8
	ProdRev   uint8
	Password  [8]byte
	QOrigZone uint16 // QMail orig zone
	QDestZone uint16 // QMail dest zone
	AuxNet    uint16 // Auxiliary net (point routing)
	CWCopy    uint16 // Capability word validation copy (swapped)
	ProdCode2 uint8  // Product code high byte
	ProdRev2  uint8  // Product revision minor
	CapWord   uint16 // Capability word (bit 0 = Type-2+)
	OrigZone  uint16
	DestZone  uint16
	OrigPoint uint16
	DestPoint uint16
	ProdData  [4]byte // Product-specific data
}

PacketHeader represents an FTN Type-2+ packet header (58 bytes).

func NewPacketHeader

func NewPacketHeader(origZone, origNet, origNode, origPoint uint16,
	destZone, destNet, destNode, destPoint uint16,
	password string) *PacketHeader

NewPacketHeader creates a header with sensible defaults for the given addresses.

func ReadPacketHeaderFromFile

func ReadPacketHeaderFromFile(path string) (*PacketHeader, error)

ReadPacketHeaderFromFile reads only the 58-byte header from a .PKT file at path. This is more efficient than ReadPacket when only the destination address is needed.

type ParsedBody

type ParsedBody struct {
	Area    string   // AREA tag (echomail only, empty for netmail/local)
	Kludges []string // ^A kludge lines (without the ^A prefix)
	Text    string   // Message text (without kludges, SEEN-BY, PATH)
	SeenBy  []string // SEEN-BY lines (without "SEEN-BY: " prefix)
	Path    []string // PATH lines (without "\x01PATH: " prefix)
}

ParsedBody holds the components of a parsed FTN message body.

func ParsePackedMessageBody

func ParsePackedMessageBody(body string) *ParsedBody

ParsePackedMessageBody separates an FTN message body into its components. Kludge lines start with \x01 (SOH), SEEN-BY/PATH are at the end.

type RegistryNetwork added in v0.2.0

type RegistryNetwork struct {
	Zone             int      `json:"zone"`
	Name             string   `json:"name"`
	Description      string   `json:"description"`
	InfoURL          string   `json:"info_url,omitempty"`
	PackURL          string   `json:"pack_url,omitempty"`
	Coordinator      string   `json:"coordinator,omitempty"`
	CoordinatorEmail string   `json:"coordinator_email,omitempty"`
	CoordinatorFTN   string   `json:"coordinator_ftn,omitempty"`
	AlsoContact      string   `json:"also_contact,omitempty"`
	HubAddress       string   `json:"hub_address,omitempty"`
	HubHostname      string   `json:"hub_hostname,omitempty"`
	HubPort          int      `json:"hub_port,omitempty"`
	DNSSuffix        string   `json:"dns_suffix,omitempty"`
	EcholistURL      string   `json:"echolist_url,omitempty"`
	NodelistURL      string   `json:"nodelist_url,omitempty"`
	AreatagPrefix    string   `json:"areatag_prefix,omitempty"`
	AreatagExclude   []string `json:"areatag_exclude,omitempty"`
	AreatitlePrefix  string   `json:"areatitle_prefix,omitempty"`
	HandlesAllowed   bool     `json:"handles_allowed,omitempty"`
	AreaManager      string   `json:"area_manager,omitempty"`
}

RegistryNetwork represents a single FTN network from the embedded registry.

func LoadOverrideRegistry added in v0.2.0

func LoadOverrideRegistry(configPath string) ([]RegistryNetwork, error)

LoadOverrideRegistry loads an optional sysop-provided ftn_networks.json from the given config directory. Returns nil (no error) if the file does not exist.

func LoadRegistry added in v0.2.0

func LoadRegistry() ([]RegistryNetwork, error)

LoadRegistry returns the embedded FTN network registry.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL