FTP Server
FTP Server 应用图标

macOS 上的原生 FTP 服务端

用 Swift 从零实现,只依赖系统框架。下载即用,无需配置任何运行环境。

100/100 协议测试通过 零第三方依赖 约 3 MB RFC 959 · 3659 · 2428 macOS 14+ MIT 许可
下载 macOS 版 查看安装说明 通用二进制 · Apple Silicon 与 Intel 均可运行 · 当前 v1.0.0
下载

下载与安装

单个压缩包内含完整应用,解压即可使用。下载链接固定不变,始终指向最新版本。

下载 FTPServer for macOS
FTPServer-macOS-universal.zip
版本 v1.0.0 2.2 MB macOS 14.0+ 通用二进制 arm64 + x86_64 MIT 许可
  1. 下载并解压,把 FTPServer.app 拖进「应用程序」文件夹 压缩包内另附 LICENSE 与一份使用说明
  2. 第一次打开:在它上面按住 Control 点按(或右键)→ 选择「打开」, 在弹出的确认框里再点一次「打开」 放行一次之后即可正常双击启动,不必重复此步骤
  3. 启动后在「设置」里确认端口与共享目录,在「用户账号」里添加账号 首次启动自动创建 ~/FTP Share 与只读的 ~/FTP Share/Public

第一次打开为什么会被系统拦下?

本应用使用 ad-hoc(临时)签名。从网络下载后,macOS 会给它加上隔离属性, 双击时提示「无法验证开发者」或「已损坏」。这不是文件损坏,也不是下载出错。

按上面第 2 步放行一次即可。若仍被拦下,在「终端」里执行下面这行(假定已放进「应用程序」文件夹):

xattr -dr com.apple.quarantine /Applications/FTPServer.app

要彻底消除这个提示需要开发者证书签名与公证;当前项目以开源方式分发, 从源码构建是绕开签名问题的另一条路。

校验下载

把下载到的文件与校验文件放在同一目录,然后执行:

shasum -a 256 -c FTPServer-macOS-universal.zip.sha256

预期输出 FTPServer-macOS-universal.zip: OK,即与校验文件中记录的实际哈希一致。

FTP Server 主界面:侧边栏导航与概览页的实时速率曲线
概览页:运行状态、接入地址一键复制、实时速率曲线与累计统计。界面跟随系统外观。
功能

不只是能传文件

协议正确性、边界安全与可观测性都被当作一等需求实现,而不是事后补齐。

三种数据通道

被动模式 PASV / EPSV、主动模式 PORT 全部实现,并对 PORT 施加回连地址校验以阻断 FTP bounce 攻击。

断点续传

REST 记录偏移配合 RETR / STOR 续传,APPE 追加写,大文件中途断掉不必重头再来。

原生界面

SwiftUI 编写。实时速率曲线、活动会话管理、分级日志搜索、多账号管理,浅色深色与跟随系统。

菜单栏常驻

可关闭 Dock 图标退居菜单栏,服务继续在后台运行;从菜单栏一键恢复主窗口或复制接入地址。

路径边界

越出根目录的 .. 一律明确拒绝,符号链接写入前解析校验。读、写、删、改名四条路径同等对待。

健壮性

空闲超时、数据停滞看门狗、ABOR 中止、异常断开自动清理,不会留下半开连接。

协议

命令支持

FEAT 向客户端声明 SIZEMDTMMLSDMLSTREST STREAMUTF8TVFSEPSV

类别命令说明
会话 USER PASS ACCT QUIT NOOP SYST HELP 登录、会话收尾与基础信息
目录 PWD CWD CDUP MKD RMD DELE RNFR RNTO XPWD XCWD XCUP XMKD XRMD 旧式别名
列举 LIST NLST MLSD MLST STAT MLSD / MLST 按 RFC 3659 输出结构化事实与 perm
传输 RETR STOR APPE REST ABOR ALLO 下载、上传、追加、断点续传与中止
数据通道 PASV EPSV PORT 被动与主动模式,含 EPSV ALL
参数与元数据 TYPE STRU MODE SIZE MDTM FEAT OPTS ASCII / 二进制双模式,UTF8 与 MLST 选项
未实现 STOU REIN SMNT SITE AUTH 明确返回 502 / 503,不做静默忽略
安全

边界与鉴权

共享目录之外的任何位置都不应被触碰——这一条在读、写、删、改名四条路径上分别验证。

路径边界

客户端路径先逐段消除 ...越出根目录的 .. 是明确拒绝, 而不是静默钳制到根目录——后者看似安全,实则会让客户端误判自己的位置。

随后再做一次符号链接解析后的包含性校验,阻断根目录内的软链接逃逸。 写入路径(STOR / APPE / MKD)校验的是父目录, 因此另外单独确认目标自身不是指向根目录之外的软链接。

鉴权与最小暴露

口令以加盐 SHA256 存储,校验使用恒定时间比较,避免通过响应耗时侧信道推断口令。

登录失败不区分「账号不存在」与「口令错误」,避免账号枚举; 日志中 PASS / ACCT 参数一律打码; PORT 命令限定回连地址必须与控制连接来源一致。

关于口令哈希:当前未做密钥拉伸(无 PBKDF2 / scrypt)。 对局域网内的可信共享场景,加盐 SHA256 已足够;若要将服务暴露到公网, 建议先引入 KDF 并启用加密传输。

使用

安装之后怎么用

默认监听 2121 端口(21 端口需要 root 权限,刻意避开以便双击即用), 首次启动自动创建共享目录。

图形界面

侧边栏分五区:概览、活动会话、运行日志、用户账号、设置。 在「用户账号」里为每个账号指定独立根目录与权限;在「设置」里改端口、绑定范围与超时。

菜单栏常驻:可在设置中选择关闭窗口后「最小化到菜单栏」,服务继续在后台运行,也可直接退出软件。

命令行客户端

curl ftp://127.0.0.1:2121/
curl -u alice:secret -T local.txt \
  ftp://127.0.0.1:2121/remote.txt

图形客户端可选 Cyberduck、FileZilla、Transmit 等。

访达无法连接本服务:macOS 自 10.13 起已移除 FTP 支持。 这不是本应用的限制,任何 FTP 服务端在访达里都连不上。

源码

从源码构建

不想处理签名提示,或者要改点什么,可以直接构建。需要 macOS 14 以上与完整 Xcode 或 Swift 工具链。

  1. 获取源码并编译

    git clone https://github.com/zdx8/FTPServer-macOS.git
    cd FTPServer-macOS
    swift build -c release --disable-sandbox
  2. 打包成可双击的应用

    bash Scripts/package_app.sh

    脚本会生成图标、组装 dist/FTPServer.app,并递增构建号、刷新系统图标缓存。 加 UNIVERSAL=1 可产出同时支持两种架构的通用二进制。

  3. 启动

    open dist/FTPServer.app

同一份二进制的无界面模式

便于脚本化或被 launchd 驱动,无需图形界面。

FTPServer --headless \
  --port 2121 \
  --root ~/FTP\ Share \
  --user alice secret ~/alice-share rwmd \
  --user guest guest  ~/FTP\ Share/Public r

生成发布附件

bash Scripts/make_release.sh

产出通用二进制的压缩包与 SHA256 校验文件,可直接作为 Release 附件。

验证

测试与验证

三层测试都可独立运行,完全不依赖图形界面。

层级方式覆盖内容
协议一致性 Scripts/e2e_test.py
Python ftplib · 100 项
认证与权限拒绝、三种数据通道、四种列举、5 MB 随机数据哈希校验、空文件、断点续传、ASCII 往返、Unicode 文件名、8 路并发、7 类路径逃逸与 5 类符号链接写入逃逸
Core 自检 Scripts/selfcheck
Swift,无界面
会话发布链路、两种绑定模式的实际绑定地址、配置向后兼容、符号链接写入防护、下载源中途截断检测
界面层 Scripts/uicheck
Swift,无界面
视图离屏渲染回归,以及「真实客户端 → 服务器 → 视图模型 → 渲染」的端到端链路
python3 Scripts/e2e_test.py \
  "$(swift build -c release --show-bin-path --disable-sandbox)/FTPServer" 21321
边界

已知限制

以下是明确的未实现项,而非缺陷——写在这里是为了让选型有据可依。

暂不支持加密与 SSH

AUTH TLS 返回 502。FTPS 需要引入 TLS 通道,SFTP 基于 SSH 协议、实现量是 FTP 的数倍。当前仅适合局域网内的明文传输场景。

无配额与速率限制

没有磁盘配额、没有带宽限制、没有目录级 ACL。权限粒度是账号级的四个开关:下载、上传、删除、建目录。

签名仅限本机

打包使用 ad-hoc 临时签名,拷到其他机器会被 Gatekeeper 拦截。对外分发需要开发者证书签名与公证。

少量命令未实现

STOU REIN SMNT SITE 统一返回 502,不做静默忽略——客户端能明确知道服务器不支持。