agent 转发(-A): pssh ssh -A 开 auth-agent@openssh.com 通道并事件循环桥接本地 agent socket(新增 ssh_agent_fd); POSIX 可用, Windows(命名管道无 select fd)运行时明确禁用并提示, 无本地 agent 时提示; STATUS/AGENT/CHANGELOG 更新
这个提交包含在:
@@ -102,4 +102,9 @@ session ticket key 支持持久化到文件:新增 `paze_tls_config_set_session_
|
|||||||
- 服务端:方法含 `keyboard-interactive` 并复用用户库密码校验;`psshd -kbdint` 强制仅接受 ki。
|
- 服务端:方法含 `keyboard-interactive` 并复用用户库密码校验;`psshd -kbdint` 强制仅接受 ki。
|
||||||
- 验证:`pssh -pwd` 连 `psshd -kbdint` 经 ki 认证成功。
|
- 验证:`pssh -pwd` 连 `psshd -kbdint` 经 ki 认证成功。
|
||||||
|
|
||||||
|
### 10. agent 转发(-A)(✅ 已完成 2026-08-14, POSIX)
|
||||||
|
|
||||||
|
- `pssh ssh -A`:开 `auth-agent@openssh.com` 通道,事件循环桥接本地 agent socket 与通道(`ssh_agent_fd`)。
|
||||||
|
- 限制:PazeSSH 仅 POSIX 可用;Windows agent 为命名管道(无 select fd)运行时明确禁用并提示;无本地 agent 时报 `cannot connect local agent`。
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+6
@@ -4,6 +4,12 @@
|
|||||||
|
|
||||||
## [开发主线] — 2026-08-14
|
## [开发主线] — 2026-08-14
|
||||||
|
|
||||||
|
### 新增 —— agent 转发(-A)
|
||||||
|
|
||||||
|
- `pssh ssh -A`:打开 `auth-agent@openssh.com` 通道,事件循环把本地 agent socket 与通道双向桥接(`ssh_agent_fd` 暴露 POSIX fd)。
|
||||||
|
- 平台限制:PazeSSH 在 **POSIX** 上可用;Windows 上 agent 为命名管道、无可供 select 的 fd,**运行时明确禁用并提示**(不崩溃)。
|
||||||
|
- 无本地 agent 时给出明确提示(`cannot connect local agent`)。
|
||||||
|
|
||||||
### 新增 —— keyboard-interactive 认证 (RFC 4256)
|
### 新增 —— keyboard-interactive 认证 (RFC 4256)
|
||||||
|
|
||||||
- 客户端:password 失败后 fallback 到 keyboard-interactive(发请求、收 `USERAUTH_INFO_REQUEST`、以密码响应首个 prompt)。
|
- 客户端:password 失败后 fallback 到 keyboard-interactive(发请求、收 `USERAUTH_INFO_REQUEST`、以密码响应首个 prompt)。
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
- 通道:session(exec/shell 真实进程执行)、direct-tcpip(出站桥接)
|
- 通道:session(exec/shell 真实进程执行)、direct-tcpip(出站桥接)
|
||||||
- **SFTP 子系统 v3**:OPEN/READ/WRITE/OPENDIR/READDIR/STAT/REALPATH 等,与 OpenSSH sftp / scp -s 互操作(Git OpenSSH 实测通过)
|
- **SFTP 子系统 v3**:OPEN/READ/WRITE/OPENDIR/READDIR/STAT/REALPATH 等,与 OpenSSH sftp / scp -s 互操作(Git OpenSSH 实测通过)
|
||||||
- 客户端命令:
|
- 客户端命令:
|
||||||
- `pssh ssh` — 远程 shell / 执行命令,`-L`/`-R`/`-D` 端口转发,`-J` 代理跳板(单跳)、`-W host:port` stdio 转发、`-N` 仅转发;config 读取 `-F` / `~/.ssh/config`(OpenSSH 兼容) / `~/.pssh/config.conf`;`-o ServerAliveInterval=N` 空闲心跳保活(keepalive@openssh.com)
|
- `pssh ssh` — 远程 shell / 执行命令,`-L`/`-R`/`-D` 端口转发,`-J` 代理跳板(单跳)、`-W host:port` stdio 转发、`-N` 仅转发、`-A` agent 转发(POSIX;Windows agent 为命名管道不支持);config 读取 `-F` / `~/.ssh/config`(OpenSSH 兼容) / `~/.pssh/config.conf`;`-o ServerAliveInterval=N` 空闲心跳保活(keepalive@openssh.com)
|
||||||
|
|
||||||
- `pssh scp` — SFTP 优先(OpenSSH 9.x 默认),回退 legacy SCP;`-r` 递归、`-p` 保留时间戳
|
- `pssh scp` — SFTP 优先(OpenSSH 9.x 默认),回退 legacy SCP;`-r` 递归、`-p` 保留时间戳
|
||||||
- `pssh keygen` / `keyscan` / `copy-id` — 密钥工具
|
- `pssh keygen` / `keyscan` / `copy-id` — 密钥工具
|
||||||
|
|||||||
+46
@@ -341,6 +341,7 @@ int pssh_cmd_ssh(int argc, char **argv) {
|
|||||||
const char *stdio_target = NULL;
|
const char *stdio_target = NULL;
|
||||||
int stdio_port = 0;
|
int stdio_port = 0;
|
||||||
int no_command = 0;
|
int no_command = 0;
|
||||||
|
int agent_fwd = 0; /* -A:agent 转发(POSIX;Windows agent 为命名管道不支持) */
|
||||||
char stdio_host[256];
|
char stdio_host[256];
|
||||||
const char *user_cfg = NULL; /* -F <file>:显式指定 config 文件 */
|
const char *user_cfg = NULL; /* -F <file>:显式指定 config 文件 */
|
||||||
|
|
||||||
@@ -404,6 +405,9 @@ int pssh_cmd_ssh(int argc, char **argv) {
|
|||||||
else if (strcmp(a, "-N") == 0) {
|
else if (strcmp(a, "-N") == 0) {
|
||||||
no_command = 1; /* 仅转发,不执行命令/不建会话 */
|
no_command = 1; /* 仅转发,不执行命令/不建会话 */
|
||||||
}
|
}
|
||||||
|
else if (strcmp(a, "-A") == 0) {
|
||||||
|
agent_fwd = 1; /* 开启 agent 转发(POSIX) */
|
||||||
|
}
|
||||||
else if (strcmp(a, "-L") == 0) {
|
else if (strcmp(a, "-L") == 0) {
|
||||||
if (opt_missing(argc, i, "-L", "[bind:]port:host:hostport") < 0) return 1;
|
if (opt_missing(argc, i, "-L", "[bind:]port:host:hostport") < 0) return 1;
|
||||||
if (nfwds < 16 && parse_fwd(argv[++i], &fwds[nfwds]) == 0) {
|
if (nfwds < 16 && parse_fwd(argv[++i], &fwds[nfwds]) == 0) {
|
||||||
@@ -587,6 +591,28 @@ int pssh_cmd_ssh(int argc, char **argv) {
|
|||||||
ssh_channel_request_shell(s, ch);
|
ssh_channel_request_shell(s, ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- agent 转发(-A, POSIX):建立 auth-agent@openssh.com 通道 + 本地 agent ---- */
|
||||||
|
ssh_agent_t *ag = NULL; uint32_t ach = 0; int ag_fd = -1;
|
||||||
|
if (agent_fwd) {
|
||||||
|
ag_fd = -1;
|
||||||
|
if (ssh_agent_connect(&ag) == 0) {
|
||||||
|
ag_fd = ssh_agent_fd(ag);
|
||||||
|
if (ag_fd < 0) { /* Windows:命名管道不支持 select,禁用 */
|
||||||
|
ssh_agent_close(ag); ag = NULL;
|
||||||
|
fprintf(stderr, "pssh: agent 转发在 Windows 不受支持(skip -A)\n");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (ssh_channel_open(s, &ach, "auth-agent@openssh.com", 0, 0) < 0) {
|
||||||
|
ssh_agent_close(ag); ag = NULL;
|
||||||
|
fprintf(stderr, "pssh: agent forwarding channel open failed (server may not support)\n");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "pssh: cannot connect local agent (ssh-agent not running)\n");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- 事件循环 ---- */
|
/* ---- 事件循环 ---- */
|
||||||
long sel_sock = jump ? ssh_session_socket(jump) : ssh_session_socket(s);
|
long sel_sock = jump ? ssh_session_socket(jump) : ssh_session_socket(s);
|
||||||
if (sel_sock < 0) goto done;
|
if (sel_sock < 0) goto done;
|
||||||
@@ -614,11 +640,24 @@ int pssh_cmd_ssh(int argc, char **argv) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
ssh_fwd_prepare(fwd, &rfds, NULL, &maxfd);
|
ssh_fwd_prepare(fwd, &rfds, NULL, &maxfd);
|
||||||
|
#ifndef _WIN32
|
||||||
|
if (ag_fd >= 0) { FD_SET(ag_fd, &rfds); if (ag_fd > maxfd) maxfd = ag_fd; }
|
||||||
|
#endif
|
||||||
|
|
||||||
struct timeval tv = {0, 100000};
|
struct timeval tv = {0, 100000};
|
||||||
int sret = select(maxfd + 1, &rfds, NULL, NULL, &tv);
|
int sret = select(maxfd + 1, &rfds, NULL, NULL, &tv);
|
||||||
if (sret < 0) break;
|
if (sret < 0) break;
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
/* agent 转发:本地 agent fd 可读 → 转发到 auth-agent 通道 */
|
||||||
|
if (ag_fd >= 0 && sret > 0 && FD_ISSET(ag_fd, &rfds)) {
|
||||||
|
uint8_t abuf[8192];
|
||||||
|
ssize_t an = read(ag_fd, abuf, sizeof(abuf));
|
||||||
|
if (an < 0) break;
|
||||||
|
if (an > 0 && ssh_channel_send_data(s, ach, abuf, (size_t)an) < 0) break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (sret > 0 && FD_ISSET((SOCKET)sel_sock, &rfds)) {
|
if (sret > 0 && FD_ISSET((SOCKET)sel_sock, &rfds)) {
|
||||||
/* 先消费服务器主动推送的 forwarded-tcpip 通道打开。
|
/* 先消费服务器主动推送的 forwarded-tcpip 通道打开。
|
||||||
网关同时看内层 pending 与外层缓冲/socket,防止 -J 场景
|
网关同时看内层 pending 与外层缓冲/socket,防止 -J 场景
|
||||||
@@ -675,6 +714,12 @@ int pssh_cmd_ssh(int argc, char **argv) {
|
|||||||
if (rid == ch) {
|
if (rid == ch) {
|
||||||
fwrite(rbuf, 1, n, stdout);
|
fwrite(rbuf, 1, n, stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
#ifndef _WIN32
|
||||||
|
} else if (ag_fd >= 0 && rid == ach) {
|
||||||
|
/* agent 通道数据 → 写本地 agent socket */
|
||||||
|
ssize_t wc = write(ag_fd, rbuf, n);
|
||||||
|
if (wc < 0) goto done;
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
ssh_fwd_channel_data(fwd, rid, rbuf, n);
|
ssh_fwd_channel_data(fwd, rid, rbuf, n);
|
||||||
}
|
}
|
||||||
@@ -735,6 +780,7 @@ done:
|
|||||||
if (shell_raw) raw_off();
|
if (shell_raw) raw_off();
|
||||||
#endif
|
#endif
|
||||||
ssh_fwd_free(fwd);
|
ssh_fwd_free(fwd);
|
||||||
|
if (ag) { ssh_agent_close(ag); ag = NULL; } /* agent 转发清理 */
|
||||||
if (jump) {
|
if (jump) {
|
||||||
free(ssh_session_io_ctx(s)); /* jump_ctx_t */
|
free(ssh_session_io_ctx(s)); /* jump_ctx_t */
|
||||||
ssh_session_free(jump);
|
ssh_session_free(jump);
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ typedef struct ssh_agent ssh_agent_t;
|
|||||||
/* ---- client ---- */
|
/* ---- client ---- */
|
||||||
int ssh_agent_connect(ssh_agent_t **out);
|
int ssh_agent_connect(ssh_agent_t **out);
|
||||||
void ssh_agent_close(ssh_agent_t *a);
|
void ssh_agent_close(ssh_agent_t *a);
|
||||||
|
/* 返回本地 agent 的 socket fd(供事件循环 select 接入转发桥);
|
||||||
|
* Windows 上为 -1(命名管道不支持作为 select fd,agent 转发在 Windows 不可用)。 */
|
||||||
|
int ssh_agent_fd(ssh_agent_t *a);
|
||||||
|
|
||||||
/* 列出身份。返回 malloc 的数组,*blobs[i] 为 pubblob,*blens[i] 为长度,
|
/* 列出身份。返回 malloc 的数组,*blobs[i] 为 pubblob,*blens[i] 为长度,
|
||||||
* *comments[i] 为注释。用 ssh_agent_list_free 释放。成功返回 0,count 为身份数。 */
|
* *comments[i] 为注释。用 ssh_agent_list_free 释放。成功返回 0,count 为身份数。 */
|
||||||
|
|||||||
@@ -146,6 +146,15 @@ void ssh_agent_close(ssh_agent_t *a) {
|
|||||||
free(a);
|
free(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ssh_agent_fd(ssh_agent_t *a) {
|
||||||
|
if (!a) return -1;
|
||||||
|
#ifdef _WIN32
|
||||||
|
return -1; /* 命名管道无法作为 select fd */
|
||||||
|
#else
|
||||||
|
return a->fd;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
int ssh_agent_list(ssh_agent_t *a, uint8_t ***blobs, size_t **blens,
|
int ssh_agent_list(ssh_agent_t *a, uint8_t ***blobs, size_t **blens,
|
||||||
char ***comments, int *count) {
|
char ***comments, int *count) {
|
||||||
if (!a || !blobs || !blens || !comments || !count) return -1;
|
if (!a || !blobs || !blens || !comments || !count) return -1;
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户