# Protocols

All protocols share users, roots, permissions, quotas, rate limits,
bans, and the audit log. Paths are virtual: `/` is the user's root, and
`..` past it is refused with `invalid_path` everywhere.

## FTP and FTPS

| Group | Commands |
| --- | --- |
| Session | `USER` `PASS` `ACCT` `QUIT` `SYST` `NOOP` `HELP` `FEAT` `OPTS` `SITE HELP` `HOST` |
| Navigation | `PWD` `CWD` `CDUP` (and `X` variants) |
| Transfer setup | `TYPE A/I` `MODE S` `STRU F` `PASV` `EPSV` `EPSV ALL` `PORT` `EPRT` `REST` `ABOR` |
| Transfers | `RETR` `STOR` `STOU` `APPE` `LIST` `NLST` `MLSD` |
| Files | `DELE` `MKD` `RMD` `RNFR` `RNTO` `SIZE` `MDTM` `MFMT` `MLST` |
| TLS | `AUTH TLS` `PBSZ 0` `PROT P/C` (`CCC` refused) |

- Replies carry the error code first: `550 permission_denied: ...`.
- `MLSD`/`MLST` facts: `type`, `size`, `modify`, `perm`, `unique`;
  `OPTS MLST type;size;` selects a subset. `perm` reflects each entry's
  own directory rules.
- `HOST name` (RFC 7151) must come before `USER`. With
  `[ftp].virtual_hosts` set, other names get `504`; a user with `hosts`
  logs in only under those names.
- `MFMT YYYYMMDDHHMMSS path` sets the modification time (UTC, needs `w`).
- `RNTO` replaces an existing target. `TYPE A` translates line endings;
  `TYPE I` is byte-exact.
- Passive data connections must come from the client's address
  (`passive_promiscuous = true` turns this off for exotic NATs). Active
  mode connects only to the client's own address, port 1024 or higher.
- Behind NAT, set `[ftp].passive_external_ip` and open
  `passive_port_range`.
- FTPS data connections resume the control connection's TLS session;
  `[ftps].require_session_reuse = true` refuses those that do not.
  The data channel never issues TLS tickets (upload-only clients would
  leave them unread and reset the connection).

## SFTP

SFTP version 3 over SSH-2, OpenSSH wire numbering.

| Item | Value |
| --- | --- |
| Requests | `OPEN` `CLOSE` `READ` `WRITE` `STAT` `LSTAT` `FSTAT` `SETSTAT` `FSETSTAT` `OPENDIR` `READDIR` `REMOVE` `MKDIR` `RMDIR` `RENAME` `READLINK` `SYMLINK` `REALPATH` |
| Extensions | `posix-rename` `statvfs` `fstatvfs` `expand-path` `limits` `fsync` (all `@openssh.com`) |
| Key exchange | `mlkem768x25519-sha256` (post-quantum hybrid), `curve25519-sha256`, `ecdh-sha2-nistp256`; strict KEX |
| Ciphers | `aes256-gcm@openssh.com`, `aes128-gcm@openssh.com`; `chacha20-poly1305@openssh.com` first only on CPUs without AES instructions. `[sftp].ciphers` sets the list |
| Host keys | Ed25519 (generated) or ECDSA; RSA host keys are refused |
| Client auth | Password, or public key: Ed25519, ECDSA, RSA (SHA-2) |
| Limits | 256 KiB packets, 255 KiB reads/writes, 256 open handles |

- Plain `RENAME` will not overwrite (draft v3); `posix-rename` does.
  OpenSSH's `sftp` uses `posix-rename` when offered.
- `df` in `sftp` shows the quota as the disk size when one is set.
- `SETSTAT` applies size, time, and permission bits; setuid, setgid, and
  sticky bits are stripped.
- `scp` works in its default SFTP mode. Shell, exec, and forwarding are
  refused.

## WebDAV/HTTP (read-only)

Set `[webdav].bind` (and `tls = true` for HTTPS with the FTPS
certificate). Clients: Finder, Windows Explorer, `rclone`, `cadaver`,
browsers, `curl`.

| Method | Behaviour |
| --- | --- |
| `OPTIONS` | `DAV: 1`, allowed methods |
| `PROPFIND` | Depth 0 or 1 (infinity is treated as 1); needs `l` |
| `GET`, `HEAD` | Files with single `Range` (needs `r`); directories as HTML, or JSON with `Accept: application/json` (needs `l`) |
| Anything else | `403 permission_denied` |

```sh
curl -u alice:password http://127.0.0.1:8081/docs/report.pdf -o report.pdf
curl -u alice:password -X PROPFIND -H 'Depth: 1' http://127.0.0.1:8081/docs/
```

Authentication is HTTP Basic against the same users (bans and LDAP
apply); verified credentials are cached for 60 seconds.
