From 825a9d32e4fe33053a0ba6cd51285429d98e3d64 Mon Sep 17 00:00:00 2001 From: JGZYES Date: Fri, 14 Aug 2026 14:41:27 +0800 Subject: [PATCH] =?UTF-8?q?agent=20=E8=BD=AC=E5=8F=91(-A):=20pssh=20ssh=20?= =?UTF-8?q?-A=20=E5=BC=80=20auth-agent@openssh.com=20=E9=80=9A=E9=81=93?= =?UTF-8?q?=E5=B9=B6=E4=BA=8B=E4=BB=B6=E5=BE=AA=E7=8E=AF=E6=A1=A5=E6=8E=A5?= =?UTF-8?q?=E6=9C=AC=E5=9C=B0=20agent=20socket(=E6=96=B0=E5=A2=9E=20ssh=5F?= =?UTF-8?q?agent=5Ffd);=20POSIX=20=E5=8F=AF=E7=94=A8,=20Windows(=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E7=AE=A1=E9=81=93=E6=97=A0=20select=20fd)=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E6=97=B6=E6=98=8E=E7=A1=AE=E7=A6=81=E7=94=A8=E5=B9=B6?= =?UTF-8?q?=E6=8F=90=E7=A4=BA,=20=E6=97=A0=E6=9C=AC=E5=9C=B0=20agent=20?= =?UTF-8?q?=E6=97=B6=E6=8F=90=E7=A4=BA;=20STATUS/AGENT/CHANGELOG=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENT.MD | 5 +++++ CHANGELOG.md | 6 ++++++ STATUS.md | 2 +- apps/ssh/main.c | 46 ++++++++++++++++++++++++++++++++++++++++ include/paze/ssh_agent.h | 3 +++ src/ssh/ssh_agent.c | 9 ++++++++ 6 files changed, 70 insertions(+), 1 deletion(-) diff --git a/AGENT.MD b/AGENT.MD index 236abe8..e97c02e 100644 --- a/AGENT.MD +++ b/AGENT.MD @@ -102,4 +102,9 @@ session ticket key 支持持久化到文件:新增 `paze_tls_config_set_session_ - 服务端:方法含 `keyboard-interactive` 并复用用户库密码校验;`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`。 + diff --git a/CHANGELOG.md b/CHANGELOG.md index f2bcc36..be0c454 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ ## [开发主线] — 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) - 客户端:password 失败后 fallback 到 keyboard-interactive(发请求、收 `USERAUTH_INFO_REQUEST`、以密码响应首个 prompt)。 diff --git a/STATUS.md b/STATUS.md index 4f5f2cc..893bc52 100644 --- a/STATUS.md +++ b/STATUS.md @@ -44,7 +44,7 @@ - 通道:session(exec/shell 真实进程执行)、direct-tcpip(出站桥接) - **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 keygen` / `keyscan` / `copy-id` — 密钥工具 diff --git a/apps/ssh/main.c b/apps/ssh/main.c index 27fbb13..9151ce2 100644 --- a/apps/ssh/main.c +++ b/apps/ssh/main.c @@ -341,6 +341,7 @@ int pssh_cmd_ssh(int argc, char **argv) { const char *stdio_target = NULL; int stdio_port = 0; int no_command = 0; + int agent_fwd = 0; /* -A:agent 转发(POSIX;Windows agent 为命名管道不支持) */ char stdio_host[256]; const char *user_cfg = NULL; /* -F :显式指定 config 文件 */ @@ -404,6 +405,9 @@ int pssh_cmd_ssh(int argc, char **argv) { else if (strcmp(a, "-N") == 0) { no_command = 1; /* 仅转发,不执行命令/不建会话 */ } + else if (strcmp(a, "-A") == 0) { + agent_fwd = 1; /* 开启 agent 转发(POSIX) */ + } 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) { @@ -587,6 +591,28 @@ int pssh_cmd_ssh(int argc, char **argv) { 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); if (sel_sock < 0) goto done; @@ -614,11 +640,24 @@ int pssh_cmd_ssh(int argc, char **argv) { #endif } 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}; int sret = select(maxfd + 1, &rfds, NULL, NULL, &tv); 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)) { /* 先消费服务器主动推送的 forwarded-tcpip 通道打开。 网关同时看内层 pending 与外层缓冲/socket,防止 -J 场景 @@ -675,6 +714,12 @@ int pssh_cmd_ssh(int argc, char **argv) { if (rid == ch) { fwrite(rbuf, 1, n, 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 { ssh_fwd_channel_data(fwd, rid, rbuf, n); } @@ -735,6 +780,7 @@ done: if (shell_raw) raw_off(); #endif ssh_fwd_free(fwd); + if (ag) { ssh_agent_close(ag); ag = NULL; } /* agent 转发清理 */ if (jump) { free(ssh_session_io_ctx(s)); /* jump_ctx_t */ ssh_session_free(jump); diff --git a/include/paze/ssh_agent.h b/include/paze/ssh_agent.h index d34e017..705ac42 100644 --- a/include/paze/ssh_agent.h +++ b/include/paze/ssh_agent.h @@ -43,6 +43,9 @@ typedef struct ssh_agent ssh_agent_t; /* ---- client ---- */ int ssh_agent_connect(ssh_agent_t **out); 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] 为长度, * *comments[i] 为注释。用 ssh_agent_list_free 释放。成功返回 0,count 为身份数。 */ diff --git a/src/ssh/ssh_agent.c b/src/ssh/ssh_agent.c index f56667d..a0d1411 100644 --- a/src/ssh/ssh_agent.c +++ b/src/ssh/ssh_agent.c @@ -146,6 +146,15 @@ void ssh_agent_close(ssh_agent_t *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, char ***comments, int *count) { if (!a || !blobs || !blens || !comments || !count) return -1;