• Web Server: anonymous file-vpath downloads bypass the credit check (FT

    From Rob Swindell@1:103/705 to GitLab issue in main/sbbs on Tue Jul 21 19:22:24 2026
    open https://gitlab.synchro.net/main/sbbs/-/issues/1192

    ## Summary

    When the Web Server's file-area virtual path is enabled (`FileVPathPrefix`, e.g. `/files/`), an **unauthenticated** request downloads files with **no credit check and no credit charge**. The FTP Server applies the credit check to the same file for the same anonymous user.

    The result is that enabling `FileVPathPrefix` silently makes every file in any directory with empty ARS free to the entire internet, regardless of the credit values on those files.

    ## Why this looks like a defect rather than a policy choice

    Issue #446 reported anonymous FTP refusing a download with `550 Insufficient credit (177008 required)`. It was closed with:

    This is expected - not every directory on Vertrauen allows unlimited free downloads.

    This is not a Synchronet issue.

    So credit enforcement for anonymous downloads is the stated intended behavior. The Web Server does not implement it.

    ## Where the paths diverge

    **Web — anonymous requests skip the credit gate.** `check_ars()` handles `AUTHENTICATION_UNKNOWN` in its own branch, and for a file path it returns the result of `user_can_download()` alone (`src/sbbs3/websrvr.cpp:2085`):

    ```c
    if (session->user.number == 0) {
    switch (session->parsed_vpath) {
    case PARSED_VPATH_FULL:
    return user_can_download(&scfg, session->file.dir, &session->user, &session->client, NULL);
    ```

    The credit gate sits further down, *after* the password check, so only authenticated sessions reach it (`websrvr.cpp:2211`):

    ```c
    if (session->parsed_vpath == PARSED_VPATH_FULL) {
    if (download_is_free(&scfg, session->file.dir, &session->user, &session->client)
    || session->user.cdt >= session->file.cost)
    authorized = user_can_download(&scfg, session->file.dir, &session->user, &session->client, &reason);
    ```

    **FTP — the gate is unconditional.** `ftpsrvr.cpp:4672` applies it to every user, anonymous included:

    ```c
    if (!getsize && !getdate && !delecmd
    && !download_is_free(&scfg, dir, &user, &client)) {
    ...
    if (f.cost > user_available_credits(&user)) {
    sockprintf(sock, sess, "550 Insufficient credit (%lu required).", (ulong)f.cost);
    ```

    The charge side has the same asymmetry in effect: `user_downloaded_file()` runs for web downloads (`websrvr.cpp:6541`) and calls `subtract_cdt()`, but with a zeroed user #0 it does nothing, because `adjustuserval()` early-returns on `!VALID_USER_NUMBER(user->number)`.

    ## Reproduction

    On a system with `FileVPathPrefix=/files/`, a file directory whose `ars`, `download_ars` and the library's equivalents are all empty, and which is **not** flagged `FREE`:

    ```
    $ curl -sI http://<host>/files/<lib>/<dir>/<file.zip> | grep -i content-length Content-Length: 255869
    ```

    Downloads in full with no authentication. The same file's record carries `Cost 255869` (confirmed via `smbutil v`), and the same file over anonymous FTP is refused with `550 Insufficient credit`.

    Observed on Synchronet 3.22 for Linux.

    ## Scope and what is *not* affected

    - **Opt-in.** `FileVPathPrefix` is empty in stock `sbbs.ini`, so a default install is unaffected. This only applies to sysops who deliberately published their file areas over HTTP.
    - **ARS still applies.** `user_can_download()` continues to enforce library/directory `ar` and `dl_ar`, and an unauthenticated user has level 0, so any ARS referencing `LEVEL`/security/flags correctly excludes it. A sysop who has set `download_ars` is not exposed.
    - **No data corruption.** The accounting calls are safe no-ops for user #0 (`adjustuserval()` validates the user number; `subtract_cdt()` returns early on a zero amount).
    - **Per-file and system statistics still count** — `times_downloaded` and `inc_download_stats()` run regardless of user number. It is only per-user accounting that is skipped.

    ## Possible directions

    Not proposing a specific fix, since which is right depends on the intended policy:

    1. Apply the same `download_is_free() || cdt >= cost` gate in the `user.number == 0` branch, making the Web Server match FTP. An anonymous user has 0 credits, so this would make costed files unavailable anonymously — consistent with #446, but a behavior change for anyone relying on the current behavior.
    2. Leave the behavior and document it, so `FileVPathPrefix` carries an explicit warning that it exposes file areas without credit enforcement, with `download_ars` named as the control.
    3. Make it configurable — a web-server option for whether anonymous file-vpath downloads honor credits.

    Worth noting the current behavior is *convenient* for publishing an intentionally-public archive; the concern is that it is not discoverable from the configuration surface, and it contradicts the policy stated in #446.

    — *Authored by Claude (Claude Code), on behalf of @rswindell*
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)