文件

211 行
9.6 KiB
C

/* ssh.h —— PazeSSH 公共 API
*
* 目标:仅依赖 libc + 内部 crypto,跨平台 SSH-2 客户端/服务器。
* 预算:SSH 全量 ≤ 25,000 行。
*
* 支持:
* - KEX: curve25519-sha256@libssh.org, ecdh-sha2-nistp256
* - Host keys: ssh-ed25519, ecdsa-sha2-nistp256, rsa-sha2-256
* - Encryption: chacha20-poly1305@openssh.com, aes128-gcm@openssh.com
* - MAC: hmac-sha2-256
* - Auth: publickey, password
* - Channels: session, shell, exec, pty-req
*/
#ifndef PAZE_SSH_H
#define PAZE_SSH_H
#include "paze/paze_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Opaque SSH session */
typedef struct ssh_session ssh_session_t;
/* Factory: allocate and initialize a new SSH session.
* is_server: 0=client, 1=server */
ssh_session_t *ssh_session_new(int is_server);
void ssh_session_free(ssh_session_t *s);
/* Set custom I/O callbacks */
void ssh_session_set_io(ssh_session_t *s, void *ctx,
int (*read_fn)(void *, uint8_t *, size_t),
int (*write_fn)(void *, const uint8_t *, size_t));
/* Set peer IP(服务端 accept 后设置,用于认证失败日志的 fail2ban 兼容) */
void ssh_session_set_peer_ip(ssh_session_t *s, const char *ip);
/* 服务端仅接受 keyboard-interactive(1=启用,用于测试/策略) */
void ssh_session_set_kbdint_only(ssh_session_t *s, int on);
/* Return the underlying socket handle for event loops:
* Windows: SOCKET, POSIX: int fd. -1 if none. */
long ssh_session_socket(ssh_session_t *s);
/* Return the I/O context set via ssh_session_set_io (or the socket pointer
* set by ssh_client_connect). For proxy-jump sessions the caller allocated
* this context and is responsible for freeing it. */
void *ssh_session_io_ctx(ssh_session_t *s);
/* StrictHostKeyChecking mode:
* mode: 0 = ask (default), 1 = accept-new, 2 = no (skip check) */
void ssh_session_set_hostkey_check(ssh_session_t *s, int mode);
/* -v: 打开详细日志 (on != 0) */
void ssh_session_set_verbose(ssh_session_t *s, int on);
/* -c: 指定加密算法,如 "chacha20-poly1305@openssh.com";cipher 为 NULL
* 或空串时恢复默认协商列表。 */
void ssh_session_set_cipher(ssh_session_t *s, const char *cipher);
/* ---- 用户库 (pssh sshd) ----
* 每个用户: 用户名 + SHA256(密码) + 权限级别。
* users.conf 行格式: 用户名:SHA256hex(64):权限
* 权限: 0=受限(仅通道回显), 1=普通(允许 shell/exec/pty-req),
* 2=管理员(全部, 含端口转发) */
#define PSSH_PERM_GUEST 0
#define PSSH_PERM_USER 1
#define PSSH_PERM_ADMIN 2
typedef struct {
char name[64];
uint8_t pwhash[32]; /* SHA256(password) 原始字节 */
int perm;
} ssh_user_t;
/* 解析 users.conf 文本,最多 max 个用户;返回用户数(负数=格式错误) */
int ssh_userdb_parse(const char *text, ssh_user_t *users, int max);
/* 校验密码:0=通过, -1=不通过 */
int ssh_userdb_check(const ssh_user_t *u, const char *password);
/* 服务端绑定用户库(认证用);users 由调用方持有,会话生命周期内须有效 */
void ssh_session_set_users(ssh_session_t *s, const ssh_user_t *users, int nusers);
/* Return the server host key blob received during KEX (for ssh-keyscan).
* Pointer is valid until the session is freed; returns NULL if unavailable. */
const uint8_t *ssh_session_server_key(const ssh_session_t *s, size_t *len);
/* ---- Client ---- */
int ssh_client_connect(ssh_session_t *s, const char *host, uint16_t port);
/* 在已通过 ssh_session_set_io 设置的传输上完成握手(代理跳转用) */
int ssh_client_connect_io(ssh_session_t *s, const char *host, uint16_t port);
/* 发送 keepalive(keepalive@openssh.com global request,fire-and-forget)。
* 用于保持空闲连接不被 NAT/防火墙回收;对端回复仅表明连接存活。 */
int ssh_keepalive_send(ssh_session_t *s);
/* Authenticate with password. Returns 0 on success. */
int ssh_auth_client_loop(ssh_session_t *s,
const char *username,
const char *password,
const uint8_t *privkey_blob, size_t privkey_blob_len);
struct ssh_agent; /* paze/ssh_agent.h */
/* Authenticate via an SSH agent (tries each identity, falls back to password). */
int ssh_auth_client_agent_loop(ssh_session_t *s,
const char *username,
const char *password,
struct ssh_agent *agent);
/* ---- Channels ---- */
int ssh_channel_open(ssh_session_t *s, uint32_t *local_id,
const char *type, uint32_t window, uint32_t max_pkt);
int ssh_channel_request_pty(ssh_session_t *s, uint32_t local_id,
const char *term, int cols, int rows);
int ssh_channel_request_shell(ssh_session_t *s, uint32_t local_id);
int ssh_channel_request_exec(ssh_session_t *s, uint32_t local_id,
const char *cmd);
int ssh_channel_send_data(ssh_session_t *s, uint32_t local_id,
const uint8_t *data, size_t len);
int ssh_channel_recv_data(ssh_session_t *s,
uint32_t *remote_id, uint8_t *data, size_t *len);
int ssh_channel_close(ssh_session_t *s, uint32_t local_id);
/* 发送 CHANNEL_EOF:通知对端本方不再发送数据(exec 收尾必需) */
int ssh_channel_send_eof(ssh_session_t *s, uint32_t local_id);
/* 读取通道退出状态;未收到 exit-status 返回 -1 */
int ssh_channel_exit_status(ssh_session_t *s, uint32_t local_id, int *status);
/* ---- TCP/IP 转发 (RFC 4254 §7) ---- */
/* 打开 direct-tcpip 通道(本地转发 -L / 动态转发 -D 用) */
int ssh_channel_open_direct(ssh_session_t *s, uint32_t *local_id,
const char *host, uint32_t port,
const char *origin, uint32_t oport);
/* 接收服务器主动打开的 forwarded-tcpip 通道(-R 用)。
* 非 CHANNEL_OPEN 时放回内部缓冲并返回 -2。 */
int ssh_channel_accept_forwarded(ssh_session_t *s, uint32_t *local_id,
char *host, size_t hlen, uint32_t *port);
/* 请求服务器开启远程转发 tcpip-forward(-R 用) */
int ssh_global_request_tcpip_forward(ssh_session_t *s, const char *bind,
uint32_t port, uint32_t *actual_port);
/* 事件循环:SSH socket 是否有完整包可读(1=有,0=无,-1=错误) */
int ssh_session_data_ready(ssh_session_t *s);
/* ---- SCP (传统 rcp 协议客户端, 与 OpenSSH scp -t/-f 兼容) ---- */
/* 上传本地文件/目录 (dest_name 不含 '/'); recursive 启用目录递归 */
int ssh_scp_upload(ssh_session_t *s, uint32_t ch, const char *local_path,
const char *dest_name, int recursive, int preserve);
/* 下载到本地目录; save_name 非 NULL 时作为单文件显式目标名 */
int ssh_scp_download(ssh_session_t *s, uint32_t ch, const char *local_dir,
const char *save_name, int preserve);
/* ---- SFTP 子系统客户端 (与 OpenSSH sftp-server / scp -s 兼容) ---- */
typedef struct ssh_sftp ssh_sftp_t;
/* open pflags (SFTP v3) */
#define SSH_FXF_READ 0x00000001
#define SSH_FXF_WRITE 0x00000002
#define SSH_FXF_APPEND 0x00000004
#define SSH_FXF_CREAT 0x00000008
#define SSH_FXF_TRUNC 0x00000010
#define SSH_FXF_EXCL 0x00000020
/* 打开 SFTP 会话:请求 subsystem "sftp" 并协商版本;成功返回 0 */
int ssh_sftp_start(ssh_sftp_t **out, ssh_session_t *s, uint32_t ch);
void ssh_sftp_free(ssh_sftp_t *sf);
/* 打开/关闭文件。handle 为服务端返回的不透明字节串 */
int ssh_sftp_open(ssh_sftp_t *sf, const char *path, uint32_t pflags,
uint8_t *handle, size_t *hlen);
int ssh_sftp_close(ssh_sftp_t *sf, const uint8_t *handle, size_t hlen);
/* 读写:返回实际传输字节数(0=EOF),-1=错误(见 *code) */
int ssh_sftp_read(ssh_sftp_t *sf, const uint8_t *handle, size_t hlen,
uint64_t off, uint8_t *buf, uint32_t len, int *code);
int ssh_sftp_write(ssh_sftp_t *sf, const uint8_t *handle, size_t hlen,
uint64_t off, const uint8_t *buf, uint32_t len, int *code);
/* 目录:opendir 后反复 readdir(1=有条目,0=目录结束) */
int ssh_sftp_opendir(ssh_sftp_t *sf, const char *path,
uint8_t *handle, size_t *hlen);
typedef struct {
char name[512];
uint32_t flags; /* attrs flags */
uint64_t size;
uint32_t perms; /* 高 4 位为文件类型(S_IFDIR 等) */
uint32_t atime, mtime;
} ssh_sftp_ent_t;
int ssh_sftp_readdir(ssh_sftp_t *sf, const uint8_t *handle, size_t hlen,
ssh_sftp_ent_t *ent);
/* 路径操作:stat/lstat(attrs 置位情况同 ssh_sftp_ent_t),realpath 输出绝对路径 */
int ssh_sftp_stat(ssh_sftp_t *sf, const char *path, ssh_sftp_ent_t *ent);
int ssh_sftp_lstat(ssh_sftp_t *sf, const char *path, ssh_sftp_ent_t *ent);
int ssh_sftp_realpath(ssh_sftp_t *sf, const char *path, char *out, size_t n);
int ssh_sftp_mkdir(ssh_sftp_t *sf, const char *path, uint32_t perms);
int ssh_sftp_rmdir(ssh_sftp_t *sf, const char *path);
int ssh_sftp_remove(ssh_sftp_t *sf, const char *path);
int ssh_sftp_rename(ssh_sftp_t *sf, const char *oldp, const char *newp);
int ssh_sftp_setstat(ssh_sftp_t *sf, const char *path,
uint32_t atime, uint32_t mtime);
int ssh_sftp_fsetstat(ssh_sftp_t *sf, const uint8_t *handle, size_t hlen,
uint32_t atime, uint32_t mtime);
/* ---- Server ---- */
/* 单连接服务端:版本/KEX/认证(password+publickey)后进入通道循环。
* authorized_keys 非空时启用 publickey 认证(OpenSSH 文本格式)。 */
int ssh_server_loop(ssh_session_t *s,
const uint8_t *authorized_keys, size_t ak_len);
#ifdef __cplusplus
}
#endif
#endif /* PAZE_SSH_H */