/dev/tty 和 stdin 的一个不同

陪她去流浪 桃子 编辑 阅读次数:82

尝试给 ssh 写一个 ProxyCommand1 的实现的过程中发现:

  • 如果是使用 ssh -W,它可以顺利从终端中读取密码;
  • 如果是我自己的程序,则只能读取到主 ssh 发来的“版本号”。

我百思不得其解,不知问题在哪。 实际例子如下:

1
2
$ ssh -o ProxyCommand='ssh -W %h:%p xxx@zero.local' zero.local
xxx@zero.local's password:

换成我自己的测试程序(从标准输入读取密码):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func main() {
	fmt.Fprintf(os.Stderr, "Enter password: ")

	var password string
	n, err := fmt.Scanln(&password)
	if n != 1 || err != nil {
		panic("error reading password")
	}

	fmt.Fprintln(os.Stderr, "Password:", password)
}

编译成 ./test 后尝试执行一下:

1
2
3
$ ssh -o ProxyCommand='./test' zero.local
Enter password: Password: SSH-2.0-OpenSSH_9.8
Connection closed by UNKNOWN port 65535

有点儿意外,但是读了上面 ProxyCommand 的原理文章后,又觉得没有那么奇怪: 启用 ProxyCommand 后,主 ssh 进程确实是通过 stdin/stdout 和 ProxyCommand 通信的,所以读取到版本号字符串理所当然。

翻源代码

但是 ssh -W 是怎么绕过标准输入并从终端读取到密码的呢?

然后我跑去看了一下 openssh 的源代码有没有对此种情形的 ssh 进行特殊处理,答案是:没有

但是,在读密码函数处发现“奇怪”的地方,它/dev/tty

75
76
77
78
79
80
81
82
83
84
85
86
87
/*
* Read and write to /dev/tty if available.  If not, read from
* stdin and write to stderr unless a tty is required.
*/
if ((flags & RPP_STDIN) ||
	(input = output = open(_PATH_TTY, O_RDWR)) == -1) {
	if (flags & RPP_REQUIRE_TTY) {
		errno = ENOTTY;
		return(NULL);
	}
	input = STDIN_FILENO;
	output = STDERR_FILENO;
}

答案揭晓:先读 /dev/tty,不成功才读 stdin

至于 /dev/tty 是个什么东西?简单答约:当前进程的控制终端(键盘?),不管 stdin/stdout 有没有被重定向。

一些结论

输入来源 能否获取到终端用户的输入 举例
os.Stdin ❌ 不一定,因为可能被重定向 被 SSH 主进程用作数据通道以交换数据
/dev/tty ✅ 一定连接到终端/键盘输入 不管标准输入是否被重定向,都能读取

其它

#SSH

标签:ssh

文章评论 0 发表评论 登出
    还没有用户发表过评论,我要发表评论
    编辑评论