参考 WinSSHD 的极简哲学,用 C++17 + libssh + OpenSSL 重写为 Windows / Linux / macOS 全平台可用、可审计的轻量 SSH 服务端。
相对于 WinSSHD 仅支持 Windows,本项目补齐了 Linux/macOS 的 PTY 与文件系统抽象,保留全部核心特性。
| 能力 | WinSSHD (原始) | MiniSSHD-C++ (本项目) |
|---|---|---|
| 协议 | SSHv2 (libssh) | SSHv2 (libssh 0.11) |
| 主机密钥 | Ed25519 自动生成 | Ed25519 自动生成,SHA256 指纹 |
| 密码认证 | PBKDF2-HMAC-SHA256 (600k) | 同算法 (OpenSSL),5次/45s 限制 |
| 公钥认证 | authorized_keys |
兼容 OpenSSH,ed25519/rsa/ecdsa |
| 交互终端 | ConPTY + PowerShell/cmd | 自适应: Linux/macOS posix_openpt 真 PTY;Windows ConPTY (CreatePseudoConsole) → pipe 回落 |
| 窗口大小 | 同步 | 同步 (TIOCSWINSZ / ResizePseudoConsole) |
| 单命令 | ssh host "whoami" |
同,超时 30s,file_root 为 cwd |
| SFTP v3 | 上传/下载/目录/重命名/删除/属性/断点续传 | 同,SftpServer 沙箱实现 |
| SCP | 现代 scp 经 SFTP (-O 拒绝) |
同 |
| 沙箱 | 虚拟根 ..、盘符、重解析点 |
跨平台沙箱: .. 越界、C:/\\share、symlink 逃逸、父目录遍历、重解析点 |
| ANSI | 默认过滤,可 --keep-ansi |
0.2.1 起默认保留 ANSI/VT;要过滤显式加 --strip-ansi (仅作用于 PTY 桥接) |
| 并发 | 16 连接,Job Object 清理 | 16 连接,std::thread + atomic 计数,SIGTERM/TerminateProcess |
| 配置 | %LOCALAPPDATA%\WinSSHD\config.ini |
跨平台: Windows %LOCALAPPDATA%\MiniSSHD、macOS ~/Library/Application Support/MiniSSHD、Linux ~/.config/MiniSSHD |
| CLI | --bind/--port/--shell/--file-root/--data-dir |
完全兼容,另有 --config/--user/--no-shell/--no-sftp |
| 依赖 | 编译 libssh+OpenSSL | libssh + OpenSSL,vcpkg 或系统包均可 |
有意未实现 (与 WinSSHD 一致)
旧版 SCP/RCP (scp -O)、SFTP symlink/junction、端口转发/X11/Agent、多 channel 复用、持久化、用户模拟。
- 编译器: C++17 (GCC 11+, Clang 14+, MSVC 2022)
- CMake: 3.20+
- 依赖:
libssh >=0.10,OpenSSL >=1.1.1(通过vcpkg或系统apt/brew) - 平台:
- Linux (tested Debian 13, Ubuntu 22.04)
- macOS 12+ (需
brew install libssh openssl) - Windows 10 1809+ (需 ConPTY, 可选
vcpkg静态 CRT)
# Debian/Ubuntu
sudo apt install cmake g++ libssh-dev libssl-dev
# macOS
brew install cmake libssh openssl
git clone <this-repo> MiniSSHD-cpp
cd MiniSSHD-cpp
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
./build/minisshd --helpgit clone https://github.com/microsoft/vcpkg
.\vcpkg\bootstrap-vcpkg.bat
.\vcpkg\vcpkg install libssh[core] openssl --triplet x64-windows-static
git clone <this-repo> MiniSSHD-cpp
cd MiniSSHD-cpp
cmake -B build -DCMAKE_TOOLCHAIN_FILE=../vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
cmake --build build --config Release
.\build\Release\minisshd.exe --helpsudo apt install g++-mingw-w64-x86-64-posix cmake ninja-build
./build-mingw-cross.sh # 参考 WinSSHD 的脚本,或手动:
cmake -B build-mingw --toolchain cmake/toolchains/mingw-w64-x86_64.cmake
cmake --build build-mingw# 首次运行向导 (自动创建数据目录、生成 Ed25519 密钥、设置密码)
./build/minisshd
# 指定参数前台运行
./build/minisshd --bind 127.0.0.1 --port 2222 --keep-ansi --shell "/bin/bash -l"
# 仅本地测试 (推荐)
./build/minisshd --bind 127.0.0.1 --port 2222
# 首次运行也可以直接带上参数:向导会跳过命令行已给出的项,
# 并把 --port/--bind/--file-root/--user 写回新建的 config.ini
./build/minisshd --port 2222 --file-root /srv/sftp-root0.2.1 起 默认保留 ANSI/VT(不再需要
--keep-ansi),需要过滤时显式加--strip-ansi; 面向用户的提示、警告与日志已全部中文化,[session]/[server]/[SFTP]/[auth]这类标签保持 ASCII 以便grep与日志采集。
连接
# 交互终端
ssh -p 2222 admin@127.0.0.1
# 单命令
ssh -p 2222 admin@127.0.0.1 "whoami; pwd; ls -l"
# SFTP
sftp -P 2222 admin@127.0.0.1
sftp> put local.zip /uploads/local.zip
sftp> get /result.txt ./result.txt
# 现代 scp (经 SFTP)
scp -P 2222 ./local.zip admin@127.0.0.1:/local.zip
scp -P 2222 admin@127.0.0.1:/result.txt ./result.txt首次连接核对服务端打印的
SHA256指纹。
公钥认证
ssh-keygen -t ed25519
./build/minisshd --add-key ~/.ssh/id_ed25519.pub
# 或手动追加到 ~/.config/MiniSSHD/authorized_keysminisshd [选项]
--config <path> 指定 config.ini 路径 (默认: <data-dir>/config.ini)
--data-dir <path> 数据目录: host key / authorized_keys / 默认配置文件所在
--user <name> 覆盖登录用户名 (写回 config.ini)
--port <1-65535> 覆盖监听端口 (写回 config.ini)
--bind <address> 覆盖监听地址 (写回 config.ini)
--file-root <path> SFTP 虚拟根, 远程 / 映射至此 (写回 config.ini)
--shell <cmd> Shell, 如 "powershell", "cmd", "/bin/bash" (仅本次)
--no-shell 本次禁用交互式 shell, exec 通道仍可用 (仅本次)
--no-sftp 本次禁用 SFTP 子系统 (仅本次)
--strip-ansi 过滤终端输出中的 ANSI/VT 序列 (仅本次)
--keep-ansi 保留 ANSI/VT 序列 —— 0.2.1 起的默认行为
--reset-password 重设登录密码 (写回 config.ini) 后退出
--add-key <file.pub> 添加公钥后退出 (--add-pubkey 同义)
--help / --version 帮助 / 版本
写回规则: 只有 --user/--port/--bind/--file-root 会落盘到 config.ini,其余为运行期开关;
值与配置一致时不重复写文件。首次运行的向导同样会直接采用命令行已给出的 --user/--port/--file-root,
不再重复询问,避免"向导把 CLI 值覆盖回旧默认"的情况。
所有交互 (首次运行向导、--reset-password) 都可按 Ctrl+C 取消,取消时不修改 config.ini
并以退出码 130 结束;stdin 结束 (EOF,例如管道喂完) 同样视为取消,不会空转。
跨平台路径 (defaultDataDirectory()):
- Windows:
%LOCALAPPDATA%\MiniSSHD\ - macOS:
~/Library/Application Support/MiniSSHD/ - Linux:
~/.config/MiniSSHD/(或$XDG_CONFIG_HOME/MiniSSHD)
内容:
config.ini—username,salt,password_hash,iterations,bind,port,strip_ansi,allow_empty_password,file_root,shell(strip_ansi自 0.2.1 起默认0=保留 ANSI;老配置里已写过1的会继续生效,可用--keep-ansi临时覆盖)password_hash为空 且allow_empty_password=1时才允许空密码登录;只有前者为空会被拒绝启动ssh_host_ed25519_key— Ed25519 私钥 (0600)authorized_keys— OpenSSH 格式公钥
minisshd (C++17)
├── src/main.cpp CLI + 向导 + 信号处理
├── src/config.{h,cpp} 跨平台路径、PBKDF2、INI 读写
├── src/auth.{h,cpp} authorized_keys 解析、PBKDF2 校验
├── src/pty.{h} + pty_unix.cpp / pty_win.cpp
│ ├── Unix: posix_openpt + fork + exec + TIOCSWINSZ/SIGWINCH
│ └── Win: ConPTY (CreatePseudoConsole) → pipe 回落
├── src/sftp_server.{h,cpp}
│ ├── SftpSandbox: realPath()/canonical()/checkSymlinkEscape()
│ └── SftpServer: libssh sftp_server_new + 消息循环 (OPEN/CLOSE/READ/WRITE/STAT/MKDIR/...)
├── src/session.{h,cpp} 握手、认证 (5次/45s)、channel 分发 (shell/exec/sftp)
└── src/server.{h,cpp} 监听、16并发、指纹打印、优雅退出
关键设计 (vs WinSSHD):
- SSH 层: 保留
libssh(WinSSHD 亦用),但去掉WIN32限制,新增WITH_SERVER编译定义以启用sftp_server_new。 - PTY 层: 抽象
IPty接口,UnixPty用posix_openpt(避免openpty的 glibc 兼容问题),WinConPty用CreatePseudoConsole,失败回落WinPipePty。 - 沙箱: 统一
lexically_normal+canonical().relative_to(root)双重校验,拒绝C:盘符与symlink逃逸。 - 安全: OpenSSL
PKCS5_PBKDF2_HMAC+RAND_bytes,chmod 600,OPENSSL_cleanse清理密码。
# 单元 (Python 侧)
pytest tests/ -v # 若保留 Python 实现
# C++ 集成 (需已编译)
python3 /tmp/test_minisshd.py # exec + sftp
python3 /tmp/test_shell2.py # shell + sftp 沙箱
# 手动
./build/minisshd --data-dir /tmp/minisshd_test --bind 127.0.0.1 --port 2222 &
ssh -p 2222 admin@127.0.0.1 "echo ok"
sftp -P 2222 admin@127.0.0.1 <<< $'ls -l /\nput README.md /test.md\n'
kill %1- 仅在受控机器运行,勿直接暴露公网。
- 优先公钥,强密码;远程权限 = 启动用户,勿用 root/Admin。
- 及时
apt/brew upgrade libssh openssl并重编。 - 私钥
0600,共享目录请设严格 ACL。 - 空密码:
--reset-password里把密码留空会启用空密码登录 —— 只要猜对用户名即可拿到完整 shell 与整个file_root。因此它默认关闭,需要两次输入大写YES确认,并在启动横幅、 以及每一次空密码成功登录时都打印严重警告。仅建议绑127.0.0.1做测试; 关闭方法:再次--reset-password设置真实密码 (开关自动回到0)。
- 继承: 配置格式、CLI、认证、沙箱、安全哲学完全对标,可无缝替换。
- 扩展: 跨平台 PTY、POSIX 文件系统、CMake 统一构建、OpenSSL 替代 BCrypt。
- 致谢: WinSSHD 的精简设计与 SFTP 沙箱实现。
本项目采用 MIT License(与 WinSSHD 一致),完整条款见仓库根目录的 LICENSE。
第三方组件许可证(静态发布时需一并尊重,此处列出):
| 组件 | 许可证 | 说明 |
|---|---|---|
libssh |
LGPL-2.1-or-later | 本仓库 Release 的静态构建将其静态链接;如需替换/重链,见下方说明 |
OpenSSL |
Apache-2.0 | 含 OpenSSL 例外条款,可与 GPL 程序链接 |
关于 LGPL 与静态链接:LGPL-2.1 第 6 条要求静态链接的组合作品允许用户重新链接。 若你要分发静态构建,请保留本文件与上游版权说明,并公开可用于重新链接的构建方式 (例如直接用发行版自带的
libssh/OpenSSL开发包做默认构建,即为动态链接):cmake -B build -DCMAKE_BUILD_TYPE=Release # 默认链接系统 libssh/libssl (.so/.dll) cmake --build build -j只做源码分发时本条不适用。
CMakeLists.txt里的MINISSHE_STATIC_LINK目前是未接线的空选项,不要指望它切换链接方式。