文件
PazeSSH/src/ssh/sftp_server.c
T
JGZYES 3e8a5442fa 实现 TLS 1.3 PSK 会话恢复,构建输出移至 bin/ 并新增 standalone/psftp 命令
TLS 1.3 PSK 会话恢复: NewSessionTicket 签发/解析、ticket+binder 校验、selected_identity 回选、恢复握手免证书(pazessl -sess_in/-sess_out 端到端验证); 构建脚本输出 bin/ 并支持独立命令与 psftp; 修复 SFTP 二进制传输与 copy_id -P; 补充 tests/ 调试与 verify_tls
2026-08-13 19:30:07 +08:00

922 行
29 KiB
C

/* sftp_server.c —— sshd SFTP 子系统 (协议 v3, 与 OpenSSH sftp-server 兼容)
*
* 从 SSH 通道的 CHANNEL_DATA 中解析 SFTP 请求,操作真实文件系统并回发应答。
* 路径均为 UTF-8(Windows 侧转换为 UTF-16 调用宽字符 API)。
*/
#include "sftp_common.h"
#include "sftp_server.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utime.h>
#include <direct.h>
#include <wchar.h>
#else
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <utime.h>
#endif
/* ============================================================
* 平台:UTF-8 <-> UTF-16 (Windows)
* ============================================================ */
#ifdef _WIN32
static int utf8_to_wide(const char *in, wchar_t *out, size_t n) {
if (!in || !out || !n) return -1;
return MultiByteToWideChar(CP_UTF8, 0, in, -1, out, (int)n) > 0 ? 0 : -1;
}
static char *wide_to_utf8(const wchar_t *in) {
if (!in) return NULL;
int n = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, NULL, NULL);
char *s = (char *)malloc(n ? (size_t)n : 1);
if (s && n) WideCharToMultiByte(CP_UTF8, 0, in, -1, s, n, NULL, NULL);
if (s) s[n ? n - 1 : 0] = '\0';
return s;
}
static void backslash_to_slash(char *p) {
for (; *p; p++) if (*p == '\\') *p = '/';
}
#endif
/* ============================================================
* 文件操作抽象
* ============================================================ */
typedef struct {
uint64_t size;
uint32_t perms;
uint32_t atime, mtime;
} srv_stat_t;
static int srv_open_file(const char *path, uint32_t pflags) {
int rd = (pflags & SSH_FXF_READ) != 0;
int wr = (pflags & SSH_FXF_WRITE) != 0;
int of = rd ? (wr ? O_RDWR : O_RDONLY) : (wr ? O_WRONLY : 0);
if (pflags & SSH_FXF_APPEND) of |= O_APPEND;
if (pflags & SSH_FXF_CREAT) of |= O_CREAT;
if (pflags & SSH_FXF_TRUNC) of |= O_TRUNC;
if (pflags & SSH_FXF_EXCL) of |= O_EXCL;
#ifdef _WIN32
of |= O_BINARY; /* 二进制模式:否则 _write 会把 \n 转成 \r\n,损坏上传内容 */
wchar_t w[2048];
if (utf8_to_wide(path, w, 2048) < 0) return -1;
return _wopen(w, of, _S_IREAD | _S_IWRITE);
#else
return open(path, of, 0666);
#endif
}
static int srv_pread(int fd, uint64_t off, void *buf, size_t n) {
#ifdef _WIN32
if (_lseeki64(fd, (__int64)off, SEEK_SET) < 0) return -1;
return (int)_read(fd, buf, (unsigned)n);
#else
ssize_t r = pread(fd, buf, n, (off_t)off);
return (int)r;
#endif
}
static int srv_pwrite(int fd, uint64_t off, const void *buf, size_t n) {
#ifdef _WIN32
if (_lseeki64(fd, (__int64)off, SEEK_SET) < 0) return -1;
return (int)_write(fd, buf, (unsigned)n);
#else
ssize_t r = pwrite(fd, buf, n, (off_t)off);
return (int)r;
#endif
}
static int srv_stat_path(const char *path, srv_stat_t *st, int follow_link) {
#ifdef _WIN32
wchar_t w[2048];
if (utf8_to_wide(path, w, 2048) < 0) return -1;
struct _stat64 s;
if (_wstat64(w, &s) != 0) return -1;
if (st) {
st->size = (uint64_t)s.st_size;
st->perms = (uint32_t)s.st_mode;
st->atime = (uint32_t)s.st_atime;
st->mtime = (uint32_t)s.st_mtime;
}
(void)follow_link;
return 0;
#else
struct stat s;
if (follow_link ? stat(path, &s) : lstat(path, &s) != 0) return -1;
if (st) {
st->size = (uint64_t)s.st_size;
st->perms = (uint32_t)s.st_mode;
st->atime = (uint32_t)s.st_atime;
st->mtime = (uint32_t)s.st_mtime;
}
return 0;
#endif
}
static int srv_fstat_fd(int fd, srv_stat_t *st) {
#ifdef _WIN32
struct _stat64 s;
if (_fstat64(fd, &s) != 0) return -1;
st->size = (uint64_t)s.st_size;
st->perms = (uint32_t)s.st_mode;
st->atime = (uint32_t)s.st_atime;
st->mtime = (uint32_t)s.st_mtime;
#else
struct stat s;
if (fstat(fd, &s) != 0) return -1;
st->size = (uint64_t)s.st_size;
st->perms = (uint32_t)s.st_mode;
st->atime = (uint32_t)s.st_atime;
st->mtime = (uint32_t)s.st_mtime;
#endif
return 0;
}
static void srv_close_fd(int fd) {
#ifdef _WIN32
_close(fd);
#else
close(fd);
#endif
}
static int srv_mkdir(const char *path, uint32_t perms) {
#ifdef _WIN32
(void)perms;
wchar_t w[2048];
if (utf8_to_wide(path, w, 2048) < 0) return -1;
return _wmkdir(w);
#else
return mkdir(path, perms ? (perms & 07777) : 0755);
#endif
}
static int srv_rmdir(const char *path) {
#ifdef _WIN32
wchar_t w[2048];
if (utf8_to_wide(path, w, 2048) < 0) return -1;
return _wrmdir(w);
#else
return rmdir(path);
#endif
}
static int srv_unlink(const char *path) {
#ifdef _WIN32
wchar_t w[2048];
if (utf8_to_wide(path, w, 2048) < 0) return -1;
return _wremove(w);
#else
return unlink(path);
#endif
}
static int srv_rename(const char *oldp, const char *newp) {
#ifdef _WIN32
wchar_t wo[2048], wn[2048];
if (utf8_to_wide(oldp, wo, 2048) < 0 || utf8_to_wide(newp, wn, 2048) < 0)
return -1;
/* MoveFileExW:支持覆盖已存在目标(与 POSIX rename 语义一致) */
if (MoveFileExW(wo, wn, MOVEFILE_REPLACE_EXISTING)) return 0;
/* 目标为目录时 MOVEFILE_REPLACE_EXISTING 会失败,退回 _wrename */
return _wrename(wo, wn);
#else
return rename(oldp, newp);
#endif
}
static int srv_settime(const char *path, uint32_t atime, uint32_t mtime) {
#ifdef _WIN32
struct _utimbuf tb; /* MinGW/MSVC 均提供 */
tb.actime = (time_t)atime;
tb.modtime = (time_t)mtime;
wchar_t w[2048];
if (utf8_to_wide(path, w, 2048) < 0) return -1;
return _wutime(w, &tb);
#else
struct utimbuf tb;
tb.actime = (time_t)atime;
tb.modtime = (time_t)mtime;
return utime(path, &tb);
#endif
}
static int srv_fsettime(int fd, uint32_t atime, uint32_t mtime) {
#ifdef _WIN32
struct _utimbuf tb;
tb.actime = (time_t)atime;
tb.modtime = (time_t)mtime;
return _futime(fd, &tb);
#else
struct timespec ts[2];
ts[0].tv_sec = atime; ts[0].tv_nsec = 0;
ts[1].tv_sec = mtime; ts[1].tv_nsec = 0;
return futimens(fd, ts);
#endif
}
/* ============================================================
* 目录枚举
* ============================================================ */
typedef struct {
int used;
int kind; /* 0=文件, 1=目录 */
int fd; /* kind=0 */
intptr_t dhandle; /* kind=1 */
#ifdef _WIN32
struct _wfinddata64_t dcur;
int d_valid, d_done;
#else
char dpath[1024];
#endif
} srv_handle_t;
#define SFTP_MAX_HANDLES 64
struct srv_sftp {
sftp_rbuf_t in;
int version_ok;
srv_handle_t h[SFTP_MAX_HANDLES];
};
/* 构造目录枚举模式,打开首个条目;返回 0=成功 */
static int dir_open(const char *path, srv_handle_t *hh) {
#ifdef _WIN32
size_t pl = strlen(path);
char pat[1024];
if (pl >= sizeof(pat) - 2) return -1;
if (pl && path[pl - 1] == '/')
snprintf(pat, sizeof(pat), "%s*", path);
else
snprintf(pat, sizeof(pat), "%s/*", path);
wchar_t wp[2048];
if (utf8_to_wide(pat, wp, 2048) < 0) return -1;
intptr_t fh = _wfindfirst64(wp, &hh->dcur);
if (fh == -1) return -1;
hh->dhandle = fh;
hh->d_valid = 1;
hh->d_done = 0;
return 0;
#else
DIR *d = opendir(path);
if (!d) return -1;
hh->dhandle = (intptr_t)d;
snprintf(hh->dpath, sizeof(hh->dpath), "%s", path);
size_t dl = strlen(hh->dpath);
if (dl > 1 && hh->dpath[dl - 1] == '/') hh->dpath[dl - 1] = '\0';
return 0;
#endif
}
/* 取下一个条目;返回 1=有条目, 0=结束 */
static int dir_next(srv_handle_t *hh, char *name, size_t nlen,
srv_stat_t *st) {
#ifdef _WIN32
if (hh->d_done) return 0;
if (hh->d_valid) {
hh->d_valid = 0;
} else if (_wfindnext64(hh->dhandle, &hh->dcur) != 0) {
hh->d_done = 1;
return 0;
}
char *u8 = wide_to_utf8(hh->dcur.name);
if (!u8) return 0;
size_t nl = strlen(u8);
if (nl >= nlen) { free(u8); return 0; }
memcpy(name, u8, nl);
name[nl] = '\0';
free(u8);
st->size = (uint64_t)hh->dcur.size;
st->perms = (hh->dcur.attrib & _A_SUBDIR)
? (SFTP_S_IFDIR | 0755)
: (SFTP_S_IFREG | 0644);
st->atime = (uint32_t)hh->dcur.time_access;
st->mtime = (uint32_t)hh->dcur.time_write;
return 1;
#else
struct dirent *e = readdir((DIR *)hh->dhandle);
if (!e) return 0;
size_t nl = strlen(e->d_name);
if (nl >= nlen) return 0;
memcpy(name, e->d_name, nl);
name[nl] = '\0';
char full[1100];
snprintf(full, sizeof(full), "%s/%s", hh->dpath, e->d_name);
if (srv_stat_path(full, st, 1) < 0) {
st->size = 0;
st->perms = SFTP_S_IFREG | 0644;
st->atime = st->mtime = 0;
}
return 1;
#endif
}
static void dir_close(srv_handle_t *hh) {
#ifdef _WIN32
_findclose(hh->dhandle);
#else
closedir((DIR *)hh->dhandle);
#endif
}
/* ============================================================
* 句柄表
* ============================================================ */
static srv_handle_t *handle_get(srv_sftp_t *sf, const uint8_t *h, size_t hl) {
if (hl < 4) return NULL;
uint32_t id = paze_load32_be(h);
if (id < 1 || id >= SFTP_MAX_HANDLES) return NULL;
srv_handle_t *hh = &sf->h[id];
return hh->used ? hh : NULL;
}
static uint32_t handle_alloc(srv_sftp_t *sf, srv_handle_t **out) {
for (uint32_t i = 1; i < SFTP_MAX_HANDLES; i++) {
if (!sf->h[i].used) {
memset(&sf->h[i], 0, sizeof(sf->h[i]));
sf->h[i].used = 1;
sf->h[i].fd = -1;
*out = &sf->h[i];
return i;
}
}
return 0;
}
static void handle_free(srv_sftp_t *sf, uint32_t id) {
if (id >= 1 && id < SFTP_MAX_HANDLES) sf->h[id].used = 0;
}
/* ============================================================
* 应答构建
* ============================================================ */
/* 每条应答消息需带 uint32 长度前缀(不含自身)。m_begin 写占位,
* m_end 回填实际长度。 */
static void m_begin(sftp_wbuf_t *out, size_t *pos) {
*pos = out->len;
sftp_w_uint32(out, 0);
}
static void m_end(sftp_wbuf_t *out, size_t pos) {
paze_store32_be(out->d + pos, (uint32_t)(out->len - pos - 4));
}
static void sftp_status(sftp_wbuf_t *out, uint32_t id, uint32_t code,
const char *msg) {
sftp_w_byte(out, SFTP_STATUS);
sftp_w_uint32(out, id);
sftp_w_uint32(out, code);
sftp_w_cstr(out, msg ? msg : "");
sftp_w_cstr(out, "");
}
static void sftp_attrs_write(sftp_wbuf_t *out, const srv_stat_t *st) {
sftp_w_uint32(out, SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID |
SSH_FILEXFER_ATTR_PERMISSIONS |
SSH_FILEXFER_ATTR_ACMODTIME);
sftp_w_uint64(out, st->size);
sftp_w_uint32(out, 0); /* uid */
sftp_w_uint32(out, 0); /* gid */
sftp_w_uint32(out, st->perms);
sftp_w_uint32(out, st->atime);
sftp_w_uint32(out, st->mtime);
}
static void mode_string(uint32_t perms, char out[11]) {
static const char *rwx = "rwxrwxrwx";
int t = (int)(perms & SFTP_S_IFMT);
char c = (t == SFTP_S_IFDIR) ? 'd' : (t == SFTP_S_IFLNK) ? 'l' : '-';
out[0] = c;
for (int i = 0; i < 9; i++) {
out[i + 1] = (perms & (0400 >> i)) ? rwx[i] : '-';
}
out[10] = '\0';
}
/* NAME 包条目:filename + longname + attrs */
static void sftp_name_ent(sftp_wbuf_t *out, const char *name,
const srv_stat_t *st) {
sftp_w_cstr(out, name);
char mode[11], ln[512];
uint32_t perms = st ? st->perms : (SFTP_S_IFREG | 0644);
mode_string(perms, mode);
snprintf(ln, sizeof(ln), "%s %12llu %u %u %s", mode,
(unsigned long long)(st ? st->size : 0),
st ? st->atime : 0, st ? st->mtime : 0, name);
sftp_w_cstr(out, ln);
if (st)
sftp_attrs_write(out, st);
else
sftp_w_uint32(out, 0); /* attrs flags = 0 */
}
/* ============================================================
* 请求解析辅助(带越界检查)
* ============================================================ */
typedef struct {
const uint8_t *p;
size_t plen;
size_t off;
} sp_t;
/* SFTP 绝对路径("/x")映射为相对服务端工作目录的路径:
* Windows 下 "/x" 会被解析为盘符根,而 sshd 的语义是相对当前工作目录 */
static void sftp_abs_workdir(char *p) {
while (p[0] == '/') memmove(p, p + 1, strlen(p));
}
static int sp_u32(sp_t *t, uint32_t *v) {
if (t->off + 4 > t->plen) return -1;
*v = paze_load32_be(t->p + t->off);
t->off += 4;
return 0;
}
static int sp_u64(sp_t *t, uint64_t *v) {
if (t->off + 8 > t->plen) return -1;
*v = paze_load64_be(t->p + t->off);
t->off += 8;
return 0;
}
static int sp_str(sp_t *t, const uint8_t **data, size_t *len) {
uint32_t l;
if (sp_u32(t, &l) < 0) return -1;
if (t->off + l > t->plen) return -1;
*data = t->p + t->off;
*len = l;
t->off += l;
return 0;
}
static int sp_handle(sp_t *t, srv_sftp_t *sf, srv_handle_t **hh) {
const uint8_t *h;
size_t hl;
if (sp_str(t, &h, &hl) < 0) return -1;
*hh = handle_get(sf, h, hl);
return 0;
}
/* ============================================================
* 请求分发
* ============================================================ */
static void sftp_dispatch_body(ssh_session_t *s, ssh_channel_t *ch,
srv_sftp_t *sf, uint8_t type,
const uint8_t *payload, size_t plen,
sftp_wbuf_t *out);
/* 分发一条请求并构建应答(自动加长度前缀) */
static void sftp_dispatch(ssh_session_t *s, ssh_channel_t *ch,
srv_sftp_t *sf, uint8_t type,
const uint8_t *payload, size_t plen,
sftp_wbuf_t *out) {
size_t mpos;
m_begin(out, &mpos);
sftp_dispatch_body(s, ch, sf, type, payload, plen, out);
m_end(out, mpos);
}
void sftp_server_free(srv_sftp_t *sf) {
if (!sf) return;
for (uint32_t i = 1; i < SFTP_MAX_HANDLES; i++) {
if (!sf->h[i].used) continue;
if (sf->h[i].kind == 0 && sf->h[i].fd >= 0) srv_close_fd(sf->h[i].fd);
else if (sf->h[i].kind == 1) dir_close(&sf->h[i]);
}
sftp_rbuf_free(&sf->in);
free(sf);
}
srv_sftp_t *sftp_server_new(void) {
srv_sftp_t *sf = (srv_sftp_t *)calloc(1, sizeof(*sf));
if (!sf) return NULL;
sftp_rbuf_init(&sf->in);
return sf;
}
static void sftp_dispatch_body(ssh_session_t *s, ssh_channel_t *ch,
srv_sftp_t *sf, uint8_t type,
const uint8_t *payload, size_t plen,
sftp_wbuf_t *out) {
(void)ch;
(void)s;
sp_t t = { payload, plen, 0 };
uint32_t id = 0;
if (type == SFTP_INIT) {
if (sp_u32(&t, &id) < 0) return;
sf->version_ok = 1;
sftp_w_byte(out, SFTP_VERSION);
sftp_w_uint32(out, SFTP_VERSION_SUPPORTED);
return;
}
if (sp_u32(&t, &id) < 0) {
sftp_status(out, 0, SSH_FX_BAD_MESSAGE, "bad request");
return;
}
if (!sf->version_ok) {
sftp_status(out, id, SSH_FX_BAD_MESSAGE, "init required");
return;
}
switch (type) {
case SFTP_OPEN: {
const uint8_t *path;
size_t pathlen;
uint32_t pflags;
if (sp_str(&t, &path, &pathlen) < 0 || sp_u32(&t, &pflags) < 0) {
sftp_status(out, id, SSH_FX_BAD_MESSAGE, NULL);
return;
}
char pathbuf[2048];
size_t pn = pathlen < sizeof(pathbuf) - 1 ? pathlen : sizeof(pathbuf) - 1;
memcpy(pathbuf, path, pn);
pathbuf[pn] = '\0';
sftp_abs_workdir(pathbuf);
srv_handle_t *hh;
uint32_t hid = handle_alloc(sf, &hh);
if (!hid) { sftp_status(out, id, SSH_FX_FAILURE, "no handles"); return; }
int fd = srv_open_file(pathbuf, pflags);
if (fd < 0) {
handle_free(sf, hid);
sftp_status(out, id, SSH_FX_NO_SUCH_FILE, NULL);
return;
}
hh->kind = 0;
hh->fd = fd;
sftp_w_byte(out, SFTP_HANDLE);
sftp_w_uint32(out, id);
uint8_t hb[4];
paze_store32_be(hb, hid);
sftp_w_str(out, hb, 4);
return;
}
case SFTP_CLOSE: {
srv_handle_t *hh;
if (sp_handle(&t, sf, &hh) < 0 || !hh) {
sftp_status(out, id, SSH_FX_FAILURE, NULL);
return;
}
if (hh->kind == 0 && hh->fd >= 0) srv_close_fd(hh->fd);
else if (hh->kind == 1) dir_close(hh);
handle_free(sf, (uint32_t)(hh - sf->h));
sftp_status(out, id, SSH_FX_OK, NULL);
return;
}
case SFTP_READ: {
srv_handle_t *hh;
uint64_t off;
uint32_t len;
if (sp_handle(&t, sf, &hh) < 0 || sp_u64(&t, &off) < 0 ||
sp_u32(&t, &len) < 0) {
sftp_status(out, id, SSH_FX_BAD_MESSAGE, NULL);
return;
}
if (!hh || hh->kind != 0) {
sftp_status(out, id, SSH_FX_FAILURE, NULL);
return;
}
if (len > SFTP_IO_BLOCK) len = SFTP_IO_BLOCK;
uint8_t *buf = (uint8_t *)malloc(len ? len : 1);
if (!buf) { sftp_status(out, id, SSH_FX_FAILURE, NULL); return; }
int r = srv_pread(hh->fd, off, buf, len);
if (r > 0) {
sftp_w_byte(out, SFTP_DATA);
sftp_w_uint32(out, id);
sftp_w_str(out, buf, (uint32_t)r);
} else if (r == 0) {
sftp_status(out, id, SSH_FX_EOF, NULL);
} else {
sftp_status(out, id, SSH_FX_FAILURE, NULL);
}
free(buf);
return;
}
case SFTP_WRITE: {
srv_handle_t *hh;
uint64_t off;
const uint8_t *data;
size_t dlen;
if (sp_handle(&t, sf, &hh) < 0 || sp_u64(&t, &off) < 0 ||
sp_str(&t, &data, &dlen) < 0) {
sftp_status(out, id, SSH_FX_BAD_MESSAGE, NULL);
return;
}
if (!hh || hh->kind != 0) {
sftp_status(out, id, SSH_FX_FAILURE, NULL);
return;
}
int r = srv_pwrite(hh->fd, off, data, dlen);
if (r == (int)dlen)
sftp_status(out, id, SSH_FX_OK, NULL);
else
sftp_status(out, id, SSH_FX_FAILURE, NULL);
return;
}
case SFTP_LSTAT:
case SFTP_STAT: {
const uint8_t *path;
size_t pathlen;
if (sp_str(&t, &path, &pathlen) < 0) {
sftp_status(out, id, SSH_FX_BAD_MESSAGE, NULL);
return;
}
char pathbuf[2048];
size_t pn = pathlen < sizeof(pathbuf) - 1 ? pathlen : sizeof(pathbuf) - 1;
memcpy(pathbuf, path, pn);
pathbuf[pn] = '\0';
sftp_abs_workdir(pathbuf);
srv_stat_t st;
if (srv_stat_path(pathbuf, &st, type == SFTP_STAT) < 0) {
sftp_status(out, id, SSH_FX_NO_SUCH_FILE, NULL);
return;
}
sftp_w_byte(out, SFTP_ATTRS);
sftp_w_uint32(out, id);
sftp_attrs_write(out, &st);
return;
}
case SFTP_FSTAT: {
srv_handle_t *hh;
if (sp_handle(&t, sf, &hh) < 0 || !hh || hh->kind != 0) {
sftp_status(out, id, SSH_FX_FAILURE, NULL);
return;
}
srv_stat_t st;
if (srv_fstat_fd(hh->fd, &st) < 0) {
sftp_status(out, id, SSH_FX_FAILURE, NULL);
return;
}
sftp_w_byte(out, SFTP_ATTRS);
sftp_w_uint32(out, id);
sftp_attrs_write(out, &st);
return;
}
case SFTP_OPENDIR: {
const uint8_t *path;
size_t pathlen;
if (sp_str(&t, &path, &pathlen) < 0) {
sftp_status(out, id, SSH_FX_BAD_MESSAGE, NULL);
return;
}
char pathbuf[2048];
size_t pn = pathlen < sizeof(pathbuf) - 1 ? pathlen : sizeof(pathbuf) - 1;
memcpy(pathbuf, path, pn);
pathbuf[pn] = '\0';
sftp_abs_workdir(pathbuf);
srv_handle_t *hh;
uint32_t hid = handle_alloc(sf, &hh);
if (!hid) { sftp_status(out, id, SSH_FX_FAILURE, NULL); return; }
if (dir_open(pathbuf, hh) < 0) {
handle_free(sf, hid);
sftp_status(out, id, SSH_FX_NO_SUCH_FILE, NULL);
return;
}
hh->kind = 1;
sftp_w_byte(out, SFTP_HANDLE);
sftp_w_uint32(out, id);
uint8_t hb[4];
paze_store32_be(hb, hid);
sftp_w_str(out, hb, 4);
return;
}
case SFTP_READDIR: {
srv_handle_t *hh;
if (sp_handle(&t, sf, &hh) < 0 || !hh || hh->kind != 1) {
sftp_status(out, id, SSH_FX_FAILURE, NULL);
return;
}
size_t msg_start = out->len;
sftp_w_byte(out, SFTP_NAME);
sftp_w_uint32(out, id);
size_t cnt_pos = out->len;
sftp_w_uint32(out, 0); /* count 占位 */
uint32_t cnt = 0;
size_t budget = 0;
char name[512];
srv_stat_t st;
while (cnt < 256 && budget < 24000) {
if (!dir_next(hh, name, sizeof(name), &st)) break;
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
size_t before = out->len;
sftp_name_ent(out, name, &st);
budget += out->len - before;
cnt++;
}
if (cnt == 0) {
/* 目录已读完:回退 NAME 包,改发 STATUS(EOF) */
out->len = msg_start;
sftp_status(out, id, SSH_FX_EOF, NULL);
return;
}
paze_store32_be(out->d + cnt_pos, cnt);
return;
}
case SFTP_REMOVE: {
const uint8_t *path;
size_t pathlen;
if (sp_str(&t, &path, &pathlen) < 0) {
sftp_status(out, id, SSH_FX_BAD_MESSAGE, NULL);
return;
}
char pathbuf[2048];
size_t pn = pathlen < sizeof(pathbuf) - 1 ? pathlen : sizeof(pathbuf) - 1;
memcpy(pathbuf, path, pn);
pathbuf[pn] = '\0';
sftp_abs_workdir(pathbuf);
sftp_status(out, id, srv_unlink(pathbuf) == 0 ? SSH_FX_OK
: SSH_FX_NO_SUCH_FILE, NULL);
return;
}
case SFTP_MKDIR: {
const uint8_t *path;
size_t pathlen;
uint32_t aflags = 0, perms = 0755;
if (sp_str(&t, &path, &pathlen) < 0) {
sftp_status(out, id, SSH_FX_BAD_MESSAGE, NULL);
return;
}
/* attrs:仅取 perms 字段 */
(void)sftp_parse_attrs(payload, plen, t.off, &aflags, NULL,
NULL, NULL, &perms, NULL, NULL);
char pathbuf[2048];
size_t pn = pathlen < sizeof(pathbuf) - 1 ? pathlen : sizeof(pathbuf) - 1;
memcpy(pathbuf, path, pn);
pathbuf[pn] = '\0';
sftp_abs_workdir(pathbuf);
if (srv_mkdir(pathbuf, perms) == 0)
sftp_status(out, id, SSH_FX_OK, NULL);
else if (srv_stat_path(pathbuf, NULL, 1) == 0)
sftp_status(out, id, SSH_FX_FAILURE, "exists");
else
sftp_status(out, id, SSH_FX_FAILURE, NULL);
return;
}
case SFTP_RMDIR: {
const uint8_t *path;
size_t pathlen;
if (sp_str(&t, &path, &pathlen) < 0) {
sftp_status(out, id, SSH_FX_BAD_MESSAGE, NULL);
return;
}
char pathbuf[2048];
size_t pn = pathlen < sizeof(pathbuf) - 1 ? pathlen : sizeof(pathbuf) - 1;
memcpy(pathbuf, path, pn);
pathbuf[pn] = '\0';
sftp_abs_workdir(pathbuf);
sftp_status(out, id, srv_rmdir(pathbuf) == 0 ? SSH_FX_OK
: SSH_FX_FAILURE, NULL);
return;
}
case SFTP_REALPATH: {
const uint8_t *path;
size_t pathlen;
if (sp_str(&t, &path, &pathlen) < 0) {
sftp_status(out, id, SSH_FX_BAD_MESSAGE, NULL);
return;
}
char pathbuf[2048];
size_t pn = pathlen < sizeof(pathbuf) - 1 ? pathlen : sizeof(pathbuf) - 1;
memcpy(pathbuf, path, pn);
pathbuf[pn] = '\0';
sftp_abs_workdir(pathbuf);
#ifdef _WIN32
wchar_t win[2048], wout[4096];
if (utf8_to_wide(pathbuf, win, 2048) < 0 ||
!_wfullpath(wout, win, 4096)) {
sftp_status(out, id, SSH_FX_NO_SUCH_FILE, NULL);
return;
}
char *u8 = wide_to_utf8(wout);
if (!u8) { sftp_status(out, id, SSH_FX_FAILURE, NULL); return; }
backslash_to_slash(u8);
sftp_w_byte(out, SFTP_NAME);
sftp_w_uint32(out, id);
sftp_w_uint32(out, 1);
sftp_name_ent(out, u8, NULL);
free(u8);
#else
char outbuf[4096];
if (!realpath(pathbuf, outbuf)) {
sftp_status(out, id, SSH_FX_NO_SUCH_FILE, NULL);
return;
}
sftp_w_byte(out, SFTP_NAME);
sftp_w_uint32(out, id);
sftp_w_uint32(out, 1);
sftp_name_ent(out, outbuf, NULL);
#endif
return;
}
case SFTP_RENAME: {
const uint8_t *a, *b;
size_t al, bl;
if (sp_str(&t, &a, &al) < 0 || sp_str(&t, &b, &bl) < 0) {
sftp_status(out, id, SSH_FX_BAD_MESSAGE, NULL);
return;
}
char pa[2048], pb[2048];
size_t an = al < sizeof(pa) - 1 ? al : sizeof(pa) - 1;
size_t bn = bl < sizeof(pb) - 1 ? bl : sizeof(pb) - 1;
memcpy(pa, a, an); pa[an] = '\0';
memcpy(pb, b, bn); pb[bn] = '\0';
sftp_abs_workdir(pa);
sftp_abs_workdir(pb);
sftp_status(out, id, srv_rename(pa, pb) == 0 ? SSH_FX_OK
: SSH_FX_FAILURE, NULL);
return;
}
case SFTP_SETSTAT: {
const uint8_t *path;
size_t pathlen;
if (sp_str(&t, &path, &pathlen) < 0) {
sftp_status(out, id, SSH_FX_BAD_MESSAGE, NULL);
return;
}
uint32_t aflags = 0, atime = 0, mtime = 0;
(void)sftp_parse_attrs(payload, plen, t.off, &aflags, NULL,
NULL, NULL, NULL, &atime, &mtime);
char pathbuf[2048];
size_t pn = pathlen < sizeof(pathbuf) - 1 ? pathlen : sizeof(pathbuf) - 1;
memcpy(pathbuf, path, pn);
pathbuf[pn] = '\0';
sftp_abs_workdir(pathbuf);
int rc = SSH_FX_OK;
if (aflags & SSH_FILEXFER_ATTR_ACMODTIME)
rc = srv_settime(pathbuf, atime, mtime) == 0 ? SSH_FX_OK : SSH_FX_FAILURE;
sftp_status(out, id, (uint32_t)rc, NULL);
return;
}
case SFTP_FSETSTAT: {
srv_handle_t *hh;
if (sp_handle(&t, sf, &hh) < 0 || !hh || hh->kind != 0) {
sftp_status(out, id, SSH_FX_FAILURE, NULL);
return;
}
uint32_t aflags = 0, atime = 0, mtime = 0;
(void)sftp_parse_attrs(payload, plen, t.off, &aflags, NULL,
NULL, NULL, NULL, &atime, &mtime);
int rc = SSH_FX_OK;
if (aflags & SSH_FILEXFER_ATTR_ACMODTIME)
rc = srv_fsettime(hh->fd, atime, mtime) == 0 ? SSH_FX_OK
: SSH_FX_FAILURE;
sftp_status(out, id, (uint32_t)rc, NULL);
return;
}
case SFTP_READLINK:
case SFTP_SYMLINK:
sftp_status(out, id, SSH_FX_OP_UNSUPPORTED, NULL);
return;
case SFTP_EXTENDED: {
const uint8_t *ename;
size_t elen;
if (sp_str(&t, &ename, &elen) < 0) {
sftp_status(out, id, SSH_FX_BAD_MESSAGE, NULL);
return;
}
if (elen == 24 && memcmp(ename, "posix-rename@openssh.com", 24) == 0) {
const uint8_t *a, *b;
size_t al, bl;
if (sp_str(&t, &a, &al) < 0 || sp_str(&t, &b, &bl) < 0) {
sftp_status(out, id, SSH_FX_BAD_MESSAGE, NULL);
return;
}
char pa[2048], pb[2048];
size_t an = al < sizeof(pa) - 1 ? al : sizeof(pa) - 1;
size_t bn = bl < sizeof(pb) - 1 ? bl : sizeof(pb) - 1;
memcpy(pa, a, an); pa[an] = '\0';
memcpy(pb, b, bn); pb[bn] = '\0';
sftp_abs_workdir(pa);
sftp_abs_workdir(pb);
sftp_status(out, id, srv_rename(pa, pb) == 0 ? SSH_FX_OK
: SSH_FX_FAILURE, NULL);
} else {
sftp_status(out, id, SSH_FX_OP_UNSUPPORTED, NULL);
}
return;
}
default:
sftp_status(out, id, SSH_FX_OP_UNSUPPORTED, NULL);
return;
}
}
/* ============================================================
* 入口:处理通道数据
* ============================================================ */
int sftp_server_feed(ssh_session_t *s, ssh_channel_t *ch, srv_sftp_t *sf,
const uint8_t *data, size_t len) {
sftp_rbuf_push(&sf->in, data, len);
sftp_wbuf_t out;
sftp_wbuf_init(&out);
for (;;) {
uint8_t type;
uint8_t *payload;
size_t plen;
int pr = sftp_rbuf_pop(&sf->in, &type, &payload, &plen);
if (pr <= 0) break; /* 数据不足或无法解析 */
sftp_dispatch(s, ch, sf, type, payload, plen, &out);
free(payload);
}
if (out.len) ssh_server_channel_send_data(s, ch, out.d, out.len);
sftp_wbuf_free(&out);
return 0;
}