- tls_handshake12.c: 完整 TLS 1.2 client/server 状态机(ECDHE-RSA/ECDSA/RSA AES-GCM + CHACHA20-POLY1305),支持 1.3 收到 1.2 ServerHello/ClientHello 后回退 - tls_record.c: 修正 TLS 1.2 AEAD-GCM 两处 RFC 偏差: AAD length 字段应为明文长度 ctlen(RFC 5246 6.2.3.3)原为 ct+16; GCMNonce = fixed_iv(4) || explicit_nonce(8)(RFC 5288 3)原顺序相反 - tls_handshake13.c: CV/Finished transcript 处理、1.3 套件记录版本修正、链式证书构造 - x509.c: AttributeTypeAndValue 补 SEQUENCE; validity 2050 年前用 UTCTime - apps/pazessl: s_client/s_server 增加 -tls1_2 - 新增 build.ps1、build/run_tls_test.ps1、build/run_openssl_interop.ps1 验证: 自测 4/4(1.2/1.3 双向 + 回退); OpenSSL 互操作 4/4(s_client/s_server 双向 1.2/1.3)
601 行
22 KiB
C
601 行
22 KiB
C
/* pssh ssh —— SSH2 客户端 (平替 ssh)
|
|
* 支持:交互 shell / 执行命令 / 端口转发 -L -R -D(SOCKS5) / 代理跳转 -J /
|
|
* config 文件 ~/.pssh/config.conf / 公钥与 agent 认证
|
|
*/
|
|
#include "paze/ssh.h"
|
|
#include "paze/ssh_agent.h"
|
|
#include "paze/ssh_config.h"
|
|
#include "paze/ssh_tcpip.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <winsock2.h>
|
|
#include <windows.h>
|
|
#include <conio.h>
|
|
#include <fcntl.h>
|
|
#include <io.h>
|
|
#define GETCH _getch
|
|
#define KBHIT _kbhit
|
|
#define STDIN_FD 0
|
|
#else
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
#include <sys/select.h>
|
|
#include <sys/time.h>
|
|
static struct termios orig_tio;
|
|
static void raw_on(void) {
|
|
struct termios t;
|
|
tcgetattr(STDIN_FILENO, &orig_tio);
|
|
t = orig_tio;
|
|
t.c_lflag &= ~(ICANON | ECHO);
|
|
t.c_cc[VMIN] = 0;
|
|
t.c_cc[VTIME] = 0;
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &t);
|
|
}
|
|
static void raw_off(void) {
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &orig_tio);
|
|
}
|
|
#define STDIN_FD STDIN_FILENO
|
|
#endif
|
|
|
|
/* 转发规格 */
|
|
typedef struct {
|
|
int kind; /* SSH_FWD_L / SSH_FWD_R / SSH_FWD_D */
|
|
char bind[128];
|
|
int bind_port;
|
|
char target[256];
|
|
int target_port;
|
|
} fwd_spec_t;
|
|
|
|
#define PSSH_VERSION "Beta-0.0.1"
|
|
|
|
/* -v / -c 选项(跨 connect_target 传递) */
|
|
static int g_verbose = 0;
|
|
static const char *g_cipher = NULL;
|
|
|
|
/* 选项缺少参数时的用法提示;缺参返回 -1 */
|
|
static int opt_missing(int argc, int i, const char *opt, const char *hint) {
|
|
if (i + 1 < argc) return 0;
|
|
fprintf(stderr, "pssh: option '%s' requires an argument\n"
|
|
" %s %s\n", opt, opt, hint);
|
|
return -1;
|
|
}
|
|
|
|
/* -L/-R 解析: [bind:]port:host:hostport */
|
|
static int parse_fwd(const char *spec, fwd_spec_t *out) {
|
|
const char *c1 = strrchr(spec, ':'); /* host:hostport 前的冒号 */
|
|
if (!c1) return -1;
|
|
const char *c2 = NULL;
|
|
for (const char *p = spec; p < c1; p++) if (*p == ':') c2 = p;
|
|
if (!c2) return -1;
|
|
const char *c3 = NULL;
|
|
for (const char *p = spec; p < c2; p++) if (*p == ':') c3 = p;
|
|
if (c3) { /* bind:port:host:hostport */
|
|
size_t bl = (size_t)(c3 - spec);
|
|
if (bl >= sizeof(out->bind)) bl = sizeof(out->bind) - 1;
|
|
memcpy(out->bind, spec, bl); out->bind[bl] = '\0';
|
|
out->bind_port = atoi(c3 + 1);
|
|
} else {
|
|
snprintf(out->bind, sizeof(out->bind), "127.0.0.1");
|
|
out->bind_port = atoi(spec);
|
|
}
|
|
size_t hl = (size_t)(c1 - (c2 + 1));
|
|
if (hl >= sizeof(out->target)) hl = sizeof(out->target) - 1;
|
|
memcpy(out->target, c2 + 1, hl); out->target[hl] = '\0';
|
|
out->target_port = atoi(c1 + 1);
|
|
return (out->bind_port > 0 && out->target_port > 0) ? 0 : -1;
|
|
}
|
|
|
|
/* [user@]host[:port] 解析 */
|
|
static void parse_userhost(const char *spec, char *user, size_t ulen,
|
|
char *host, size_t hlen, int *port) {
|
|
char buf[512];
|
|
snprintf(buf, sizeof(buf), "%s", spec);
|
|
char *at = strchr(buf, '@');
|
|
char *hs = buf;
|
|
if (user && ulen) user[0] = '\0';
|
|
if (at) {
|
|
*at = '\0';
|
|
if (user && ulen) {
|
|
size_t sl = strlen(buf);
|
|
if (sl >= ulen) sl = ulen - 1;
|
|
memcpy(user, buf, sl);
|
|
user[sl] = '\0';
|
|
}
|
|
hs = at + 1;
|
|
}
|
|
char *colon = strchr(hs, ':');
|
|
if (colon) { *colon = '\0'; if (port) *port = atoi(colon + 1); }
|
|
{
|
|
size_t sl = strlen(hs);
|
|
if (sl >= hlen) sl = hlen - 1;
|
|
memcpy(host, hs, sl);
|
|
host[sl] = '\0';
|
|
}
|
|
}
|
|
|
|
/* 交互输入密码(以 * 显示) */
|
|
static void input_password(char *pw, size_t n) {
|
|
fprintf(stderr, "Password: ");
|
|
int idx = 0, ch;
|
|
while (idx < (int)n - 1) {
|
|
ch = GETCH();
|
|
if (ch == '\r' || ch == '\n') break;
|
|
if (ch == '\b' || ch == 0x7f) {
|
|
if (idx > 0) { idx--; fprintf(stderr, "\b \b"); }
|
|
continue;
|
|
}
|
|
if (ch < 32) continue;
|
|
pw[idx++] = (char)ch;
|
|
fprintf(stderr, "*");
|
|
}
|
|
pw[idx] = '\0';
|
|
fprintf(stderr, "\n");
|
|
}
|
|
|
|
/* 认证:优先 -i 私钥,否则 agent,最后回退密码。返回 1=成功。 */
|
|
static int auth_session(ssh_session_t *s, const char *user,
|
|
const char *password, const char *keyfile) {
|
|
uint8_t *keydata = NULL; size_t keylen = 0;
|
|
if (keyfile) {
|
|
FILE *kf = fopen(keyfile, "rb");
|
|
if (kf) {
|
|
fseek(kf, 0, SEEK_END);
|
|
long sz = ftell(kf);
|
|
rewind(kf);
|
|
if (sz > 0 && sz < 4 * 1024 * 1024) {
|
|
keydata = (uint8_t *)malloc((size_t)sz);
|
|
if (keydata && fread(keydata, 1, (size_t)sz, kf) != (size_t)sz) {
|
|
free(keydata); keydata = NULL;
|
|
} else keylen = (size_t)sz;
|
|
}
|
|
fclose(kf);
|
|
}
|
|
if (!keydata)
|
|
fprintf(stderr, "pssh: warning: cannot read key file %s\n", keyfile);
|
|
}
|
|
int ok = 0;
|
|
if (keyfile) {
|
|
ok = ssh_auth_client_loop(s, user, password, keydata, keylen) == 0;
|
|
} else {
|
|
ssh_agent_t *agent = NULL;
|
|
if (ssh_agent_connect(&agent) == 0) {
|
|
ok = ssh_auth_client_agent_loop(s, user, password, agent) == 0;
|
|
ssh_agent_close(agent);
|
|
}
|
|
if (!ok)
|
|
ok = ssh_auth_client_loop(s, user, password, NULL, 0) == 0;
|
|
}
|
|
free(keydata);
|
|
return ok;
|
|
}
|
|
|
|
/* ---------------- 代理跳转 (-J):跳板通道上的嵌套会话 ---------------- */
|
|
|
|
typedef struct {
|
|
ssh_session_t *jump;
|
|
uint32_t ch;
|
|
} jump_ctx_t;
|
|
|
|
static int jump_read(void *ctx, uint8_t *buf, size_t n) {
|
|
jump_ctx_t *jc = (jump_ctx_t *)ctx;
|
|
size_t got = 0;
|
|
while (got < n) {
|
|
size_t avail = n - got;
|
|
uint32_t rid;
|
|
if (ssh_channel_recv_data(jc->jump, &rid, buf + got, &avail) < 0)
|
|
return -1;
|
|
got += avail;
|
|
/* avail==0:消费了非数据包(如 WINDOW_ADJUST),继续等数据 */
|
|
}
|
|
return (int)got;
|
|
}
|
|
|
|
static int jump_write(void *ctx, const uint8_t *buf, size_t n) {
|
|
jump_ctx_t *jc = (jump_ctx_t *)ctx;
|
|
return ssh_channel_send_data(jc->jump, jc->ch, buf, n) < 0 ? -1 : (int)n;
|
|
}
|
|
|
|
/* 连接目标(可选经跳板)。jump_out 输出跳板会话(非 NULL 时由调用方释放)。
|
|
* 返回已通过握手、未认证的会话。 */
|
|
static ssh_session_t *connect_target(const char *host, int port,
|
|
const char *jump_spec,
|
|
const char *user, const char *password,
|
|
const char *keyfile, int hkmode,
|
|
ssh_session_t **jump_out) {
|
|
*jump_out = NULL;
|
|
ssh_session_t *s = ssh_session_new(0);
|
|
if (!s) return NULL;
|
|
ssh_session_set_hostkey_check(s, hkmode);
|
|
ssh_session_set_verbose(s, g_verbose);
|
|
ssh_session_set_cipher(s, g_cipher);
|
|
|
|
if (jump_spec && *jump_spec) {
|
|
char juser[128], jhost[256];
|
|
int jport = 22;
|
|
parse_userhost(jump_spec, juser, sizeof(juser), jhost, sizeof(jhost), &jport);
|
|
if (!juser[0]) snprintf(juser, sizeof(juser), "%s", user);
|
|
fprintf(stderr, "pssh: proxyjump %s@%s:%d\n", juser, jhost, jport);
|
|
ssh_session_t *j = ssh_session_new(0);
|
|
if (!j) { ssh_session_free(s); return NULL; }
|
|
ssh_session_set_hostkey_check(j, hkmode);
|
|
ssh_session_set_verbose(j, g_verbose);
|
|
ssh_session_set_cipher(j, g_cipher);
|
|
if (ssh_client_connect(j, jhost, jport) < 0) {
|
|
fprintf(stderr, "pssh: proxyjump: connect %s failed\n", jhost);
|
|
ssh_session_free(j); ssh_session_free(s); return NULL;
|
|
}
|
|
if (!auth_session(j, juser, password, keyfile)) {
|
|
fprintf(stderr, "pssh: proxyjump: auth failed\n");
|
|
ssh_session_free(j); ssh_session_free(s); return NULL;
|
|
}
|
|
uint32_t jch;
|
|
if (ssh_channel_open_direct(j, &jch, host, (uint32_t)port,
|
|
"127.0.0.1", 0) < 0) {
|
|
fprintf(stderr, "pssh: proxyjump: open channel to %s:%d failed\n",
|
|
host, port);
|
|
ssh_session_free(j); ssh_session_free(s); return NULL;
|
|
}
|
|
jump_ctx_t *jc = (jump_ctx_t *)malloc(sizeof(jump_ctx_t));
|
|
if (!jc) { ssh_session_free(j); ssh_session_free(s); return NULL; }
|
|
jc->jump = j;
|
|
jc->ch = jch;
|
|
ssh_session_set_io(s, jc, jump_read, jump_write);
|
|
if (ssh_client_connect_io(s, host, port) < 0) {
|
|
fprintf(stderr, "pssh: proxyjump: handshake to %s failed\n", host);
|
|
free(jc); ssh_session_free(j); ssh_session_free(s); return NULL;
|
|
}
|
|
*jump_out = j;
|
|
} else {
|
|
if (ssh_client_connect(s, host, port) < 0) {
|
|
fprintf(stderr, "pssh: connection failed\n");
|
|
ssh_session_free(s); return NULL;
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
int pssh_cmd_ssh(int argc, char **argv) {
|
|
char *host = NULL;
|
|
int port = 22;
|
|
const char *user = NULL, *cmd = NULL;
|
|
const char *password_arg = NULL;
|
|
const char *keyfile = NULL;
|
|
int hostkey_mode = 0;
|
|
const char *jump_spec = NULL;
|
|
fwd_spec_t fwds[16];
|
|
int nfwds = 0;
|
|
char arg_user[128], arg_host[256]; /* [user@]host[:port] 解析缓冲 */
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
const char *a = argv[i];
|
|
if (strcmp(a, "-V") == 0) {
|
|
fprintf(stderr, "pssh %s\n", PSSH_VERSION);
|
|
return 0;
|
|
}
|
|
else if (strcmp(a, "-v") == 0) { g_verbose = 1; }
|
|
else if (strcmp(a, "-c") == 0) {
|
|
if (opt_missing(argc, i, "-c",
|
|
"<cipher> (chacha20-poly1305@openssh.com|aes128-gcm@openssh.com|aes256-gcm@openssh.com)") < 0) return 1;
|
|
const char *c = argv[++i];
|
|
if (strcmp(c, "chacha20-poly1305@openssh.com") != 0 &&
|
|
strcmp(c, "aes128-gcm@openssh.com") != 0 &&
|
|
strcmp(c, "aes256-gcm@openssh.com") != 0) {
|
|
fprintf(stderr, "pssh: unsupported cipher '%s' "
|
|
"(chacha20-poly1305@openssh.com, aes128-gcm@openssh.com, "
|
|
"aes256-gcm@openssh.com)\n", c);
|
|
return 1;
|
|
}
|
|
g_cipher = c;
|
|
}
|
|
else if (strcmp(a, "-p") == 0) {
|
|
if (opt_missing(argc, i, "-p", "<port>") < 0) return 1;
|
|
port = atoi(argv[++i]);
|
|
}
|
|
else if (strncmp(a, "-p", 2) == 0 && strlen(a) > 2 && strncmp(a, "-pwd", 4) != 0) { port = atoi(a + 2); }
|
|
else if (strcmp(a, "-pwd") == 0) {
|
|
if (opt_missing(argc, i, "-pwd", "<password>") < 0) return 1;
|
|
password_arg = argv[++i];
|
|
}
|
|
else if (strncmp(a, "-pwd=", 5) == 0) { password_arg = a + 5; }
|
|
else if (strcmp(a, "-l") == 0) {
|
|
if (opt_missing(argc, i, "-l", "<user>") < 0) return 1;
|
|
user = argv[++i];
|
|
}
|
|
else if (strcmp(a, "-i") == 0) {
|
|
if (opt_missing(argc, i, "-i", "<keyfile>") < 0) return 1;
|
|
keyfile = argv[++i];
|
|
}
|
|
else if (strcmp(a, "-J") == 0) {
|
|
if (opt_missing(argc, i, "-J", "<user@host[:port]>") < 0) return 1;
|
|
jump_spec = argv[++i];
|
|
}
|
|
else if (strcmp(a, "-L") == 0) {
|
|
if (opt_missing(argc, i, "-L", "[bind:]port:host:hostport") < 0) return 1;
|
|
if (nfwds < 16 && parse_fwd(argv[++i], &fwds[nfwds]) == 0) {
|
|
fwds[nfwds].kind = SSH_FWD_L;
|
|
nfwds++;
|
|
}
|
|
} else if (strcmp(a, "-R") == 0) {
|
|
if (opt_missing(argc, i, "-R", "[bind:]port:host:hostport") < 0) return 1;
|
|
if (nfwds < 16 && parse_fwd(argv[++i], &fwds[nfwds]) == 0) {
|
|
fwds[nfwds].kind = SSH_FWD_R;
|
|
nfwds++;
|
|
}
|
|
} else if (strcmp(a, "-D") == 0) {
|
|
if (opt_missing(argc, i, "-D", "<port>") < 0) return 1;
|
|
if (nfwds < 16) {
|
|
fwds[nfwds].kind = SSH_FWD_D;
|
|
snprintf(fwds[nfwds].bind, sizeof(fwds[nfwds].bind), "127.0.0.1");
|
|
fwds[nfwds].bind_port = atoi(argv[++i]);
|
|
fwds[nfwds].target[0] = '\0';
|
|
fwds[nfwds].target_port = 0;
|
|
if (fwds[nfwds].bind_port > 0) nfwds++;
|
|
}
|
|
} else if (strcmp(a, "-o") == 0) {
|
|
if (opt_missing(argc, i, "-o", "StrictHostKeyChecking=no|accept-new|yes") < 0) return 1;
|
|
const char *kv = argv[++i];
|
|
if (strncmp(kv, "StrictHostKeyChecking=", 22) == 0) {
|
|
const char *v = kv + 22;
|
|
if (strcmp(v, "no") == 0 || strcmp(v, "off") == 0) hostkey_mode = 2;
|
|
else if (strcmp(v, "accept-new") == 0) hostkey_mode = 1;
|
|
else hostkey_mode = 0;
|
|
}
|
|
} else if (a[0] == '-') {
|
|
continue;
|
|
} else if (!host) {
|
|
/* [user@]host[:port]:端口随主机一起解析(如 root@host:6666) */
|
|
int p2 = 0;
|
|
parse_userhost(argv[i], arg_user, sizeof(arg_user),
|
|
arg_host, sizeof(arg_host), &p2);
|
|
if (arg_user[0]) user = arg_user;
|
|
if (p2 > 0) port = p2;
|
|
host = arg_host;
|
|
} else {
|
|
cmd = argv[i];
|
|
}
|
|
}
|
|
|
|
/* ---- config 文件 ~/.pssh/config.conf ---- */
|
|
char cfg_path[1024];
|
|
{
|
|
const char *home = getenv("USERPROFILE");
|
|
if (!home) home = getenv("HOME");
|
|
if (home) snprintf(cfg_path, sizeof(cfg_path), "%s/.pssh/config.conf", home);
|
|
else snprintf(cfg_path, sizeof(cfg_path), ".pssh/config.conf");
|
|
}
|
|
char cfg_host[256];
|
|
if (host) {
|
|
pssh_config_entry_t *cfgs = NULL;
|
|
int ncfg = 0;
|
|
if (pssh_config_load(cfg_path, &cfgs, &ncfg) >= 0) {
|
|
pssh_config_entry_t m;
|
|
memset(&m, 0, sizeof(m));
|
|
m.hostkey_mode = -1;
|
|
if (pssh_config_match(cfgs, ncfg, host, &m) > 0) {
|
|
if (m.hostname[0]) {
|
|
snprintf(cfg_host, sizeof(cfg_host), "%s", m.hostname);
|
|
host = cfg_host;
|
|
}
|
|
if (m.user[0] && !user) user = m.user;
|
|
if (m.port != 0 && port == 22) port = m.port;
|
|
if (m.identity_file[0] && !keyfile) keyfile = m.identity_file;
|
|
if (m.proxyjump[0] && !jump_spec) jump_spec = m.proxyjump;
|
|
if (m.hostkey_mode != -1) hostkey_mode = m.hostkey_mode;
|
|
}
|
|
free(cfgs);
|
|
}
|
|
}
|
|
|
|
if (!host) {
|
|
fprintf(stderr, "pssh - SSH2 客户端 (平替 ssh)\n"
|
|
"用法: pssh [选项] [user@]host [command]\n"
|
|
"选项: -p <port> -l <user> -pwd <pw> -i <key> -J <user@jump[:port]>\n"
|
|
" -L [bind:]port:host:hostport -R [bind:]port:host:hostport\n"
|
|
" -D <port> -o StrictHostKeyChecking=no|accept-new|yes\n");
|
|
return 1;
|
|
}
|
|
if (!user) {
|
|
const char *envu = getenv("USER");
|
|
if (!envu) envu = getenv("USERNAME");
|
|
user = envu ? envu : "root";
|
|
}
|
|
|
|
char pw_buf[256];
|
|
const char *password = password_arg;
|
|
if (!password) {
|
|
input_password(pw_buf, sizeof(pw_buf));
|
|
password = pw_buf;
|
|
}
|
|
|
|
/* ---- 连接(可选经跳板) + 认证 ---- */
|
|
ssh_session_t *jump = NULL;
|
|
ssh_session_t *s = connect_target(host, port, jump_spec, user, password,
|
|
keyfile, hostkey_mode, &jump);
|
|
if (!s) return 1;
|
|
if (!auth_session(s, user, password, keyfile)) {
|
|
fprintf(stderr, "pssh: auth failed\n");
|
|
if (jump) { ssh_session_free(jump); }
|
|
ssh_session_free(s);
|
|
return 1;
|
|
}
|
|
|
|
/* ---- 建立转发 ---- */
|
|
ssh_fwd_t *fwd = ssh_fwd_new();
|
|
for (int i = 0; i < nfwds; i++) {
|
|
fwd_spec_t *fs = &fwds[i];
|
|
if (fs->kind == SSH_FWD_R) {
|
|
uint32_t ap = 0;
|
|
ssh_fwd_add_remote(fwd, s, fs->bind, fs->bind_port,
|
|
fs->target, fs->target_port, &ap);
|
|
} else {
|
|
ssh_fwd_add_local(fwd, s, fs->kind, fs->bind, fs->bind_port,
|
|
fs->target, fs->target_port);
|
|
}
|
|
}
|
|
|
|
/* ---- 会话通道 ---- */
|
|
uint32_t ch = 0;
|
|
int interactive = 0;
|
|
if (cmd) {
|
|
if (ssh_channel_open(s, &ch, "session", 0, 0) < 0) {
|
|
fprintf(stderr, "pssh: channel open failed\n");
|
|
goto done;
|
|
}
|
|
ssh_channel_request_exec(s, ch, cmd);
|
|
} else {
|
|
interactive = 1;
|
|
if (ssh_channel_open(s, &ch, "session", 0, 0) < 0) {
|
|
fprintf(stderr, "pssh: channel open failed\n");
|
|
goto done;
|
|
}
|
|
#ifdef _WIN32
|
|
SetConsoleOutputCP(CP_UTF8);
|
|
#else
|
|
raw_on();
|
|
#endif
|
|
ssh_channel_request_pty(s, ch, "xterm", 80, 24);
|
|
ssh_channel_request_shell(s, ch);
|
|
}
|
|
|
|
/* ---- 事件循环 ---- */
|
|
long sel_sock = jump ? ssh_session_socket(jump) : ssh_session_socket(s);
|
|
if (sel_sock < 0) goto done;
|
|
|
|
uint8_t rbuf[32768];
|
|
for (;;) {
|
|
fd_set rfds;
|
|
FD_ZERO(&rfds);
|
|
int maxfd = 0;
|
|
FD_SET((SOCKET)sel_sock, &rfds);
|
|
if ((int)sel_sock > maxfd) maxfd = (int)sel_sock;
|
|
if (interactive) {
|
|
#ifndef _WIN32
|
|
FD_SET(STDIN_FD, &rfds);
|
|
if (STDIN_FD > maxfd) maxfd = STDIN_FD;
|
|
#endif
|
|
}
|
|
ssh_fwd_prepare(fwd, &rfds, NULL, &maxfd);
|
|
|
|
struct timeval tv = {0, 100000};
|
|
int sret = select(maxfd + 1, &rfds, NULL, NULL, &tv);
|
|
if (sret < 0) break;
|
|
|
|
if (sret > 0 && FD_ISSET((SOCKET)sel_sock, &rfds)) {
|
|
/* 先消费服务器主动推送的 forwarded-tcpip 通道打开。
|
|
网关同时看内层 pending 与外层缓冲/socket,防止 -J 场景
|
|
内层已缓存但外层 socket 空的包被滞留。 */
|
|
for (;;) {
|
|
if (ssh_session_data_ready(s) <= 0 &&
|
|
ssh_session_data_ready(jump ? jump : s) <= 0) break;
|
|
uint32_t ach;
|
|
char ahost[256];
|
|
uint32_t aport;
|
|
int ar = ssh_channel_accept_forwarded(s, &ach, ahost,
|
|
sizeof(ahost), &aport);
|
|
if (ar == 0) {
|
|
if (ssh_fwd_remote_connect(fwd, s, ach, ahost, aport) < 0)
|
|
ssh_channel_close(s, ach);
|
|
continue;
|
|
}
|
|
break; /* -2=非 OPEN 留给数据循环; <0=错误 */
|
|
}
|
|
/* 收通道数据并分发 */
|
|
for (;;) {
|
|
if (ssh_session_data_ready(s) <= 0 &&
|
|
ssh_session_data_ready(jump ? jump : s) <= 0) break;
|
|
uint32_t rid;
|
|
size_t n = sizeof(rbuf);
|
|
int r = ssh_channel_recv_data(s, &rid, rbuf, &n);
|
|
if (r < 0) {
|
|
/* 通道关闭/EOF:主会话通道 → 结束;转发通道 → 清理后继续 */
|
|
if (rid == ch) goto done;
|
|
ssh_fwd_channel_closed(fwd, rid);
|
|
continue;
|
|
}
|
|
if (n == 0) {
|
|
/* 控制包已消费;先取 pending 中可能有的 forwarded-open */
|
|
while (ssh_session_data_ready(s) > 0) {
|
|
uint32_t ach;
|
|
char ahost[256];
|
|
uint32_t aport;
|
|
int ar = ssh_channel_accept_forwarded(s, &ach, ahost,
|
|
sizeof(ahost), &aport);
|
|
if (ar == 0) {
|
|
if (ssh_fwd_remote_connect(fwd, s, ach, ahost, aport) < 0)
|
|
ssh_channel_close(s, ach);
|
|
continue;
|
|
}
|
|
break; /* -2: 非 OPEN 留在 pending; <0: 错误 */
|
|
}
|
|
/* 内层 pending / 外层缓冲或 socket 还有数据 → 继续收 */
|
|
if (ssh_session_data_ready(s) > 0 ||
|
|
ssh_session_data_ready(jump ? jump : s) > 0)
|
|
continue;
|
|
break;
|
|
}
|
|
if (rid == ch) {
|
|
fwrite(rbuf, 1, n, stdout);
|
|
fflush(stdout);
|
|
} else {
|
|
ssh_fwd_channel_data(fwd, rid, rbuf, n);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 转发本地事件 */
|
|
ssh_fwd_pump(fwd, s, &rfds, NULL);
|
|
|
|
/* 键盘输入(交互) */
|
|
if (interactive) {
|
|
#ifdef _WIN32
|
|
while (KBHIT()) {
|
|
int c = GETCH();
|
|
if (c == 0 || c == 0xE0) {
|
|
int sc = GETCH();
|
|
uint8_t seq[3];
|
|
size_t n = 0;
|
|
switch (sc) {
|
|
case 72: seq[0]=0x1b; seq[1]='['; seq[2]='A'; n=3; break;
|
|
case 80: seq[0]=0x1b; seq[1]='['; seq[2]='B'; n=3; break;
|
|
case 75: seq[0]=0x1b; seq[1]='['; seq[2]='D'; n=3; break;
|
|
case 77: seq[0]=0x1b; seq[1]='['; seq[2]='C'; n=3; break;
|
|
default: break;
|
|
}
|
|
if (n) ssh_channel_send_data(s, ch, seq, n);
|
|
continue;
|
|
}
|
|
uint8_t b = (uint8_t)c;
|
|
if (ssh_channel_send_data(s, ch, &b, 1) < 0) goto done;
|
|
}
|
|
#else
|
|
fd_set ifds;
|
|
FD_ZERO(&ifds);
|
|
FD_SET(STDIN_FILENO, &ifds);
|
|
struct timeval itv = {0, 0};
|
|
if (select(STDIN_FILENO + 1, &ifds, NULL, NULL, &itv) > 0) {
|
|
uint8_t kb;
|
|
if (read(STDIN_FILENO, &kb, 1) == 1)
|
|
if (ssh_channel_send_data(s, ch, &kb, 1) < 0) goto done;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
done:
|
|
int exit_code = 0;
|
|
if (ch) ssh_channel_exit_status(s, ch, &exit_code);
|
|
#ifndef _WIN32
|
|
if (interactive) raw_off();
|
|
#endif
|
|
ssh_fwd_free(fwd);
|
|
if (jump) {
|
|
free(ssh_session_io_ctx(s)); /* jump_ctx_t */
|
|
ssh_session_free(jump);
|
|
}
|
|
ssh_session_free(s);
|
|
return exit_code;
|
|
}
|