在工作中遇到一段代码,通过SSH_ASKPASS全局变量实现了ssh登录远程IP时的密码输入,chatgpt搜索了一下,其解释大致如下所示:
SSH_ASKPASS 是一个环境变量,它在 SSH 客户端需要用户输入密码时起作用。当 SSH 客户端检测到需要密码时,它会首先检查环境变量 SSH_ASKPASS 是否已设置。如果设置了 SSH_ASKPASS,那么 SSH 客户端会调用指定的程序来获取密码,而不是直接在终端中提示用户输入密码。
具体来说,SSH 客户端的工作流程如下:
- 客户端发起 SSH 连接请求。
- 当服务器需要验证客户端身份时,服务器向客户端发送一个密码提示请求。
- 客户端检查 SSH_ASKPASS 环境变量。
- 如果 SSH_ASKPASS 已设置,则客户端调用指定的程序来获取密码。
- 获取到密码后,客户端将密码传递给 SSH 客户端进行身份验证。
- 如果验证成功,SSH 连接建立成功;否则,连接失败。
通过设置 SSH_ASKPASS 环境变量,用户可以自定义一个程序来自动化密码输入过程,从而实现无需人工干预的 SSH 连接过程。这在自动化脚本或程序中特别有用,可以避免需要用户手动输入密码。
不过百度和谷歌却没搜索到人定义的关键信息来确认这点。让chatgpt帮我把ssh源码中的SSH_ASKPASS部分调用代码展示出来,可能理解有问题,每次都答非所问。自己下载了一份ssh源码,简单的用grep SSH_ASKPASS . -rns指令,检索到,应该是如下的代码中实现的调用:
struct notifier_ctx *
notify_start(int force_askpass, const char *fmt, ...)
{va_list args;char *prompt = NULL;pid_t pid = -1;void (*osigchld)(int) = NULL;const char *askpass, *s; struct notifier_ctx *ret = NULL;va_start(args, fmt);xvasprintf(&prompt, fmt, args);va_end(args);if (fflush(NULL) != 0)error_f("fflush: %s", strerror(errno));if (!force_askpass && isatty(STDERR_FILENO)) {writemsg(prompt);goto out_ctx;}if ((askpass = getenv("SSH_ASKPASS")) == NULL)askpass = _PATH_SSH_ASKPASS_DEFAULT;if (*askpass == '\0') {debug3_f("cannot notify: no askpass");goto out;}if (getenv("DISPLAY") == NULL && getenv("WAYLAND_DISPLAY") == NULL &&((s = getenv(SSH_ASKPASS_REQUIRE_ENV)) == NULL ||strcmp(s, "force") != 0)) {debug3_f("cannot notify: no display");goto out;}osigchld = ssh_signal(SIGCHLD, SIG_DFL);if ((pid = fork()) == -1) { error_f("fork: %s", strerror(errno));ssh_signal(SIGCHLD, osigchld);free(prompt);return NULL;}if (pid == 0) {if (stdfd_devnull(1, 1, 0) == -1)fatal_f("stdfd_devnull failed");closefrom(STDERR_FILENO + 1);setenv("SSH_ASKPASS_PROMPT", "none", 1); /* hint to UI */execlp(askpass, askpass, prompt, (char *)NULL);error_f("exec(%s): %s", askpass, strerror(errno));_exit(1);/* NOTREACHED */}out_ctx:if ((ret = calloc(1, sizeof(*ret))) == NULL) {if (pid != -1)kill(pid, SIGTERM);fatal_f("calloc failed");}ret->pid = pid; ret->osigchld = osigchld;out:free(prompt);return ret;
}
其中的关键调用指令就是execlp(askpass, askpass, prompt, (char *)NULL);
参数释义,第一个和第二个都是SSH_ASKPASS文件路径,prompt是提示词,也就是“please enter 192.68.1.10 password:”之类的打印信息,第四个参数无什么含义,不解释。
上面代码execlp调用SSH_ASKPASS后,无任何返回值判定和处理,那密码是怎么实现自动输入到stdin输入的呢?
看源码中,execlp是在fork的子进程中执行的,执行前,其通过stdfd_devnull(1, 1, 0)关闭了子进程的stdout、stderr和stdin的流,再使用closefrom(STDERR_FILENO + 1)关闭了大于STDERR_FILENO的所有流,个人猜测这样可以实现子进程的密码输出流作为ssh密码录入的stdin流。但是chatgpt的解释是关闭后,SSH_ASKPASS中任意print不会输出到任何地方,有点困惑。
所以,如果我们有需求,ssh登录时,期望自动输入密码,可以使用这个功能来自动后台运行,只需要在运行前指定系统的SSH_ASKPASS全局变量即可。下面写一个简单的SSH_ASKPASS功能函数。
#!/usr/bin/python3
import sysif "password" in sys.argv[1]:print("123456")