PHP Classes

File: docs/proposals/FREQ_Support_Proposal.md

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   docs/proposals/FREQ_Support_Proposal.md   Download  
File: docs/proposals/FREQ_Support_Proposal.md
Role: Auxiliary data
Content type: text/markdown
Description: Auxiliary data
Class: Binkterm PHP
Bulletin board system based on the Web
Author: By
Last change:
Date: 5 days ago
Size: 15,194 bytes
 

Contents

Class file image Download

FREQ (File REQuest) Support Proposal

DRAFT DOCUMENT

This proposal is a draft document generated by AI and may not have been reviewed for accuracy. The technical details, implementation decisions, and schema described herein should be validated by developers familiar with FidoNet protocols and the BinktermPHP codebase before implementation begins.

Overview

This document proposes adding inbound FREQ (File REQuest) fulfillment to BinktermPHP. FREQs allow a remote FidoNet node to request specific files from your system during a binkp session, without needing a full message-based exchange. The design integrates with the existing file area system and the existing file sharing system, so access control does not need to be reinvented.

Table of Contents

Background: How FREQs Work

During a binkp session the requesting node sends one or more M_GET commands before or during the file exchange phase:

M_GET filename [password] [size_limit] [unix_timestamp]

| Field | Meaning | |---|---| | filename | The filename being requested (case-insensitive in practice) | | password | Optional area or file password | | size_limit | Max file size the requester will accept (0 = unlimited) | | unix_timestamp | Only send if file is newer than this date (0 = any) |

The serving node responds by including the matched file(s) in its outbound file list for that session. If no match is found, or access is denied, the server simply does not send the file (no error response in the protocol).

Magic names ? some well-known filenames have special meaning: ALLFILES, FILES, or an area tag used as a filename triggers generation of a dynamic file listing rather than serving a literal file.

Access Control Model

Rather than creating a separate FREQ permission system, FREQ fulfillment integrates with the two existing access control points: file areas and file shares.

Area-level access

A file area with freq_enabled = TRUE exposes all its approved, active files to any FREQ request. An optional freq_password restricts access to callers who supply the correct password in their M_GET command.

This is appropriate for public distribution areas (software releases, nodelist distributions, file echoes).

Per-file access via shares

A file with an active shared_files entry and freq_accessible = TRUE is individually FREQable regardless of whether its area has freq_enabled. This lets you make a specific file requestable without opening the whole area.

Since the share system already supports expiration and revocation, those controls apply automatically to FREQ access too.

Resolution priority

A file is served if either condition is true: 1. Its file area has freq_enabled = TRUE (and the caller supplied the correct freq_password if one is set) 2. It has an active, non-expired shared_files row with freq_accessible = TRUE

Files with status != 'approved' are never served regardless of access settings. Files in is_private = TRUE areas are never served via FREQ.

Database Schema Changes

file_areas additions

ALTER TABLE file_areas
    ADD COLUMN freq_enabled  BOOLEAN NOT NULL DEFAULT FALSE,
    ADD COLUMN freq_password VARCHAR(255);

COMMENT ON COLUMN file_areas.freq_enabled  IS 'If true, all approved files in this area are FREQable';
COMMENT ON COLUMN file_areas.freq_password IS 'Optional password required to FREQ files from this area';

CREATE INDEX idx_file_areas_freq ON file_areas (freq_enabled) WHERE freq_enabled = TRUE;

shared_files addition

ALTER TABLE shared_files
    ADD COLUMN freq_accessible BOOLEAN NOT NULL DEFAULT TRUE;

COMMENT ON COLUMN shared_files.freq_accessible IS 'If true, this shared file is also accessible via binkp FREQ';

freq_accessible defaults to TRUE so that sharing a file automatically makes it FREQable. Admins can uncheck this when creating or editing a share if they want web-only access.

freq_log table

Tracks all inbound FREQ requests for auditing and rate-limit analysis.

CREATE TABLE freq_log (
    id              SERIAL PRIMARY KEY,
    requested_at    TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    requesting_node VARCHAR(50)  NOT NULL,   -- FTN address of requester
    filename        VARCHAR(255) NOT NULL,   -- As requested
    resolved_file_id INTEGER     REFERENCES files(id) ON DELETE SET NULL,
    served          BOOLEAN      NOT NULL DEFAULT FALSE,
    deny_reason     VARCHAR(100),            -- 'not_found' | 'password' | 'size_limit' | 'timestamp' | 'private'
    file_size       BIGINT,                  -- Size served (if served)
    session_id      VARCHAR(64)              -- Binkp session correlation
);

CREATE INDEX idx_freq_log_node ON freq_log (requesting_node);
CREATE INDEX idx_freq_log_time ON freq_log (requested_at DESC);

FREQ Resolution Engine

A new FreqResolver class handles all lookup and access-control logic. It is called by the binkp server when a M_GET command is received.

class FreqResolver {
    /
     * Resolve a FREQ request to a file path ready for delivery.
     *
     * @param string      $filename   Requested filename (case-insensitive)
     * @param string|null $password   Password supplied by requester
     * @param int         $sizeLimit  Max bytes requester will accept (0 = unlimited)
     * @param int         $newerThan  Unix timestamp (0 = any age)
     * @param string      $callerAddr FTN address of requesting node
     * @return FreqResult
     */
    public function resolve(
        string $filename,
        ?string $password,
        int $sizeLimit,
        int $newerThan,
        string $callerAddr
    ): FreqResult;

    /
     * Resolve a magic-name request to a dynamically generated file.
     * Returns null if $filename is not a recognised magic name.
     */
    public function resolveMagic(string $filename, string $callerAddr): ?FreqResult;
}

class FreqResult {
    public bool    $served;
    public ?string $filePath;   // Absolute path to serve
    public ?string $servedName; // Filename to present to requester
    public ?int    $fileId;     // files.id, null for generated files
    public string  $denyReason; // '' if served
}

Resolution algorithm

Given filename F, password P, size_limit S, newer_than T, caller C:

1. Check magic names first ? if matched, delegate to resolveMagic()

2. Look up file by filename (case-insensitive) in:
     a. file_areas WHERE freq_enabled = TRUE AND is_private = FALSE
        ? files WHERE LOWER(filename) = LOWER(F) AND status = 'approved'
     b. shared_files WHERE freq_accessible = TRUE AND is_active = TRUE
        AND (expires_at IS NULL OR expires_at > NOW())
        JOIN files WHERE LOWER(filename) = LOWER(F) AND status = 'approved'
        AND file_areas.is_private = FALSE

3. If no file found ? log deny_reason='not_found', return not served

4. Check password:
     - If file came from a freq_enabled area AND area.freq_password IS NOT NULL:
         if P != area.freq_password ? log deny_reason='password', return not served

5. Check size_limit: if S > 0 AND file.filesize > S ? log deny_reason='size_limit', return not served

6. Check newer_than: if T > 0 AND file.created_at <= unix_to_ts(T) ? not served (no log, protocol norm)

7. Log served=TRUE, return file path

Magic Names (File Lists)

When the requested filename matches a magic name, FreqResolver::resolveMagic() generates a temporary text file and returns it as the response.

| Magic name | Response | |---|---| | ALLFILES or FILES | Combined listing of all FREQ-enabled areas | | <AREA_TAG> | Listing for that specific area (if freq_enabled) |

File list format

The generated listing follows the common FILES.BBS-compatible format used by most FTN software:

BINKTERM-PHP File Areas - Generated 2026-03-14

SOFTDIST - Software Distribution
----------------------------------------------------------------------
DOOM11.ZIP         1,234,567  1993-12-10  DOOM shareware v1.1
WOLF3D14.ZIP         987,654  1992-05-05  Wolfenstein 3D v1.4

UTILS - Utilities
----------------------------------------------------------------------
PKZIP204.ZIP         203,948  1993-02-01  PKZIP v2.04g
ARJ241.EXE           187,432  1994-03-15  ARJ archiver v2.41

The file is written to a temp path and cleaned up after the session ends.

Binkp Integration

Inbound FREQ handling in binkp_server.php

The binkp server already handles M_GET at the protocol level (the message type exists). The gap is that no business logic fires when one arrives.

The server needs to:

  1. Parse each `M_GET filename [password] [size_limit] [timestamp]` received during session setup
  2. Call `FreqResolver::resolve()` for each request
  3. Add any served files to the session's outbound file list (alongside any normal outbound mail)
  4. Clean up temp files (magic-name generated lists) after the session closes
// In BinkpSession, after M_ADR/M_PWD handshake:
foreach ($session->inboundGetRequests as $req) {
    $result = $this->freqResolver->resolve(
        $req->filename,
        $req->password,
        $req->sizeLimit,
        $req->newerThan,
        $session->remoteAddress
    );
    if ($result->served) {
        $session->addOutboundFile($result->filePath, $result->servedName);
    }
    $this->freqResolver->logRequest($req, $result, $session->remoteAddress, $session->id);
}

FREQ in outbound poll sessions (binkp_poll.php)

When we initiate a poll to another node, we can also send FREQs (we request files from them). This is the outbound FREQ flow already partially implemented via the is_freq netmail flag. That flow can remain as-is for now; the inbound fulfillment side is the primary focus of this proposal.

Admin Interface

File area edit page additions

The existing file area create/edit form gains two new fields in the "Access" section:

  • Allow FREQ ? checkbox for `freq_enabled`
  • FREQ Password ? text field for `freq_password` (shown only when Allow FREQ is checked)

File share create/edit additions

The share creation dialog gains:

  • Accessible via FREQ ? checkbox for `freq_accessible` (default checked)

FREQ log viewer (/admin/freq-log)

A new admin page showing the freq_log table:

  • Columns: timestamp, requesting node, filename, served (yes/no), deny reason, file size
  • Filterable by node, filename, served/denied, date range
  • Useful for auditing and spotting abuse

Security Considerations

  1. Private areas are never served ? `is_private = TRUE` areas are explicitly excluded from all FREQ resolution regardless of `freq_enabled`.
  2. Only approved files ? files with `status != 'approved'` (pending, quarantined, rejected) are never served.
  3. Password enforcement ? area passwords are compared server-side; no timing attack mitigation is needed given the low-frequency nature of binkp sessions.
  4. Rate limiting ? the `freq_log` table enables rate limit analysis. A per-node rate limit (max N FREQs per session, max M files per day) can be added in a later phase.
  5. Share expiration respected ? expired shares are not served even if the `is_active` flag is still true (the resolver checks `expires_at`).
  6. Temp file cleanup ? magic-name generated files are written to a temp directory and deleted after the session ends (or after a configurable TTL for crash safety).
  7. Path traversal ? `FreqResolver` resolves filenames only from database records; it never constructs filesystem paths from user-supplied input directly.

Implementation Plan

Phase 1 ? Schema and resolver

  1. Migration: add `freq_enabled`, `freq_password` to `file_areas`
  2. Migration: add `freq_accessible` to `shared_files`
  3. Migration: create `freq_log` table
  4. `FreqResolver` class with resolution algorithm and logging
  5. Magic name handlers (ALLFILES, FILES, per-area tag)

Phase 2 ? Binkp integration

  1. Parse `M_GET` commands in `BinkpSession`
  2. Wire `FreqResolver` into server session flow
  3. Temp file management for magic-name responses

Phase 3 ? Admin interface

  1. File area edit form: FREQ fields
  2. Share create/edit: `freq_accessible` checkbox
  3. FREQ log viewer page

Phase 4 ? Outbound FREQ status tracking

  1. Add `freq_status` column to the `netmail` table (`pending` | `fulfilled` | `denied` | `partial`)
  2. When a FREQ netmail is sent, status starts as `pending`
  3. When a binkp session with the target node completes and the expected file(s) were received, mark `fulfilled`
  4. Expose status in the netmail reader (e.g. small badge: "FREQ Pending", "FREQ Fulfilled")

New and Modified Files

New

| File | Purpose | |---|---| | src/Freq/FreqResolver.php | FREQ resolution engine | | src/Freq/FreqResult.php | Result value object | | src/Freq/MagicFileListGenerator.php | Generates ALLFILES/per-area file listings | | database/migrations/v1.11.0.21_freq_support.sql | Schema additions (freq_enabled/freq_password on file_areas, freq_accessible on shared_files, freq_log table, freq_status on netmail) | | templates/admin/freq_log.twig | FREQ log admin page | | routes/admin-freq-routes.php | Admin route for FREQ log viewer |

Modified

| File | Change | |---|---| | src/BinkP/BinkpSession.php | Parse and queue inbound M_GET commands; add resolved files to outbound list | | scripts/binkp_server.php | Wire FreqResolver into inbound session handler | | templates/admin/file_area_edit.twig | Add FREQ enable/password fields | | templates/files.twig | Add FREQ accessible checkbox to share create dialog | | routes/admin-routes.php | Register FREQ log route | | src/FileAreaManager.php | Pass freq_enabled/freq_password/freq_accessible through CRUD methods |

Decisions

  1. Open FREQ ? any FidoNet node may FREQ from us. No allowlist or downlinks-table restriction. This is standard FidoNet behaviour; access control is handled at the area level via `freq_enabled` and `freq_password`.
  2. No rate limiting ? not implemented. The `freq_log` table provides data if this needs revisiting later.
  3. No NODELIST serving ? nodelist FREQ support is not included.
  4. Outbound FREQ status tracking ? yes. A `freq_status` column will be added to the `netmail` table to track whether a sent FREQ was fulfilled (see Phase 4 of the implementation plan).

Document Status: Draft Proposal Last Updated: 2026-03-14 Author: AI-Generated (Requires Review)