自动获取SSH密码并登录

给别人打广告

tusooa 的脚本,主要用 Bash 和 Perl 写的,用了类似 GoboLinux 的组织方式,大家可以去看看。围观地址:[[https://github.com/tusooa/tusooa]] 。

正经事

一直以来都是厚脸皮蹭着朋友的 vps ,在家里连得上,不过其他有些地方没法用,所以还是要备些免费 ssh 帐号,否则万一不能用了就……
tusooa 的脚本中了解到一个提供免费 ssh 帐号的服务器,服务器会定期更新密码。从这个网页可以获取密码:[[http://vastars.info/free-ssh-source]]。

正如标题所说,我们要做的就是写一个脚本,自动获取密码并登录。

Expect 脚本

要自动登录 ssh ,由于没法使用 AuthorizedKeysFile ,不难想到用 Expect 来自动
输入密码。服务器、帐号、密码这几项,可以用 w3m -dump 之后用 shell utilities 比如 sed 处理。不过既然用了 Expect ,大可以把这些都写到 Expect 脚本里去。

#!/bin/sh                                             (ref:shebang)
# -*- tcl -*- \
exec tclsh "$0" "$@"
package require Expect

spawn w3m -dump http://vastars.info/free-ssh-source   (ref:w3m)
expect -re "\r\n服务器:(\[0-9\\.]+)" {
    set server $expect_out(1,string)
    exp_continue
} -re "\r\n帐号:(\[\\w\\.]+)" {
    set user $expect_out(1,string)
    exp_continue
} -re "\r\n密码:(\[\\w\\.]+)" {
    set password $expect_out(1,string)
}

send_user "服务器: $server\n帐号: $user\n密码: $password\n"

spawn ssh -nND 7777 $user@$server                     (ref:ssh)
expect "(yes/no)" {
    send "yes\r"
    exp_continue
} "assword" {
    send "$password\r"
}

if {[fork]} exit                                      (ref:fork)
disconnect
set timeout -1
expect

下面我们来分析这个程序

[[(shebang)]] 这一段

这是 Expect 脚本常用的写法。因为通常无法预料 tclsh
会装在哪个目录,而我们几乎可以断定 sh 会在 /bin/sh ,所以可以设法让 sh 找到 tclsh

第二行做了两件事情,首先是让 Emacs 和 Vim 都能把这个文件
认作 Tcl 脚本( Expect 脚本是一种 Tcl 脚本);
对于行尾的反斜杠, tclsh 会把下一行 exec tclsh 当作注释而 sh
则不会。

第四行是让 tclsh 加载 Expect 模块。

注意,这个脚本没有用到命令行选项,所以把 shebang 改为 #!/usr/bin/env tclsh
更简单。

[[(w3m)]] 这一段

这一段用 expect 分析 w3m -dump 抓到的网页,求出服务器、帐号等信息。
expect 的三个模式都差不多,这里就看“服务器”那个模式。

首先用 -re 标志表示模式是个正则表达式,正则表达式是回车换行后的“服务器:”,后面跟任意数字或点。这里之所以用 \r\n 是门学问,你或许看到了,很多 Expect
脚本都是用的 \r\n ,原因是 Expect 操作了一个伪终端。程序实际上只输出了 \n ,但通常情况下,终端的 opost 和 onlcr 标志都是开启的,输出到终端时,会把 \n
转换成 \r\n 。而当程序的输出重定向到文件时,就不会被转换,这就是为什么重定向到文件
的话不会出现 \r 。 expect 在分析文本前, w3m 的输出已经被终端给转换了,所以我们要用 \r\n 。

$expect_out(1,string) 代表第一个捕获括号得到的字符串。我们可以期待
([0-9\.]+) 能捕获 /IP地址/ ,结果存放在变量 server 中。这里其实还涉及到
Tcl 的正则表达式流派问题,很难讲清楚,需要注意的是 Tcl 的双引号中 []
有特殊含义,一般需要把第一个 [ 用反斜杠转义。

[[(ssh)]] 这一段

几个标志的说明:

  • -N ,我们 ssh 后不需要执行命令
  • -n , ssh 既然不需要执行命令,那也不需要 stdin
  • -D ,在本地 7777 端口开设 SOCKS 5 代理服务器

exp_continue命令类似于 C 的continue,它会重新执行当前expect

[[(fork)]] 这一段

我们的 tclsh 进程实际上占着终端,我们要设法把它转到后台去。

一般 fork 是不会失败的,如果 fork 返回非0代表父进程,否则代表子进程。我们让 tclsh 父进程退出,注意到 tclsh 父进程的父进程是 shell ,它注意到 tclsh 父进程退出后就会显示提示符,让我们继续输入命令。

另一方面, tclsh 子进程用 disconnect 命令脱离控制终端(就是 shell 的控制终端)。之后它把 timeout 设置为 -1 即不会超时,用 expect 命令等待 ssh 进程退出。这里也有个注意点:如果 tclsh 先于 spawn 出来的进程退出的话,它会杀死那些 spawn
出来的进程。所以我们不能让 tclsh 退出,要让它等待 spawn 出来的 ssh 进程先退出。