Telnet 和 SSH 协议是 Internet 远程登录服务的标准协议和主要方式,它们为用户提供了在本地机器上完成远程机器上工作的能力。用户使用 Telnet 或者 SSH 软件连接远程服务器,在 Telnet 或者 SSH 软件中输入的命令就会在服务器上运行。Telnet 协议本质上是不安全的,因为它使用明文传送数据、用户账号和口令,很容易受到中间人***方式的***;而 SSH 协议则是比较可靠、专为远程登录会话和其他网络服务提供安全性的协议。通过 SSH 可以对所有传输的数据进行加密,也能够防止 DNS 欺骗和 IP 欺骗。本文主要针对 SSH 协议进行阐述,使用的 SSH 软件是 OpenSSH,它是开放源代码的免费的 SSH 替代软件包。
      用户使用 Telnet 或者 SSH 登录不同的操作系统,会得到不同的返回信息。用户可以编写程序根据运程登录的返回信息进行判断和处理,从而实现远程服务器系统的自动化登录。本文将先介绍实现远程自动化登录用到的技术,再详细介绍如何实现 Unix/Linux 系统间远程登录自动化。
    远程系统自动化登录机制简介
    所谓自动化远程登录,是指在用户不干预的情况下,不需要手动输入密码,就能登录到远程系统。目前远程登录工具有两种安全认证方式。
    基于用户口令的安全认证
    当使用登录账户登录时,根据提示输入口令,SSH 就会用安全密码认证协议,将加密传送给 SSHD 服务器。认证成功后,就可以登录到 SSHD 服务器。
    针对这种安全认证方式,可以让程序自动输入用户名和密码,实现自动化登录。目前可以用 expect, C 或 Perl 来实现。如果采用 C, 需要很熟悉 TCP/IP 协议,实现起来比较复杂;如果用 expect, 由于 expect 是基于 Tcl 的,需要熟悉 Tcl 语法;如果用 perl 实现,需要采用 perl 的 expect.pm 这个包。但需要用户输入口令,因此这种认证方式存在着安全隐患。
    基于 SSH key 交换的安全认证方式
    用户需要先在 SSH 客户端为登录账户创建一对密匙:私钥(private key)和公钥 (public key),然后把公钥传送到要登录的 SSHD 服务器上。当用户使用 SSH 客户端登录 SSHD 服务器时,SSH 客户端就会向 SSHD 服务器发出用登录帐户的密钥进行安全验证的请求;SSHD 服务器收到请求,先在登录帐号的主目录下寻找对应的公钥,并与客户端发送过来的公钥进行对比;如果两个密钥一致,SSHD 服务器会用公钥加密“质询”(challenge),发送给 SSH 客户端;SSH 客户端收到“质询”之后用客户端的私钥解密,再把它发送给 SSHD 服务器。这样就完成了安全认证的整个过程。
    使用基于 SSH key 交换的认证方式,用户只要将在 SSH 客户端生成的公钥复制到远程的 SSHD 服务器。当通过 SSH 客户端登录 SSHD 服务器时,用户不需要输入密码,就可以自动登录到远程 SSHD 服务器。这种方式不仅简便,而且避免了用户名和密码的泄露,比第一种方式要安全。
    使用 Expect 的自动化登录
    Expect 的基础知识
    Expect 是由 Don Libes 基于 Tcl 语言开发的,并被广泛应用于交互式操作和自动化测试的场景之中,它尤其适用于需要对多台服务器执行相同操作的环境中,可以大幅度得提高系统管理人员的工作效率。目前,大部分 Unix/Linux 系统安装有 expect. 万一系统中没有,可以从 http://expect.nist.gov/ 下载相应的包安装。
    Expect 作为基于 Tcl 的高级语言,增加了一些特殊的语法。传统意义上的 Expect 是以 Tcl 扩展包的形式出现的,任何 Tcl 语言编写的应用程序都可以加载 Expect 功能;此外,Expect 已经以模块的方式移植到了 Perl 和 Python 语言中,因此用户同样可以在 Perl 和 Python 脚本中利用 Expect 强大的交互功能。
    Send,expect 和 spwan 是 Expect 语言最基本的命令。其中,send 命令会发送字符串给指定进程(process); expect 命令会等待接受该进程返回的结果并且会根据返回的字符串来决定下一步的操作;而 spwan 命令可以发起一个进程的运行。
    send 命令接收一个字符串做为参数并发送给指定的进程;从
    send “Hello world”
    这行代码中,send 会送出字符串“Hello world”( 不带引号 )。如果 Expect 早已经开始与某一个程序进行交互,那么这个字符串将被发送给该程序;而在通常情况下,这个字符串会被送到标准输出设备。
    expect 命令则等待一个响应,通常是来自于 Expect 正在与之交互的进程,或者来自于标准输入设备;它会等待一个指定的字符串或者满足给定的正则表达式的任何字符串。我们可以创建一个名为 response.exp 的文件,来看 Expect 是如何处理的,其内容如下:
    #!expect – f
    expect “hi\n”
    send “hello there\n”
    然后在 shell 下面运行 ”expect response.exp”,它会等待来自标准输入设备的响应,直到用户输入 hi 并回车,它才会发送”hello there”到标准输出设备,并回车。然后结束 expect 脚本的运行。但是,如果用户没有输入 hi 并回车,那么 expect 会继续等待”hi\n”;输入其他的字符并不会影响到 expect 的工作。通常情况下,expect 会一直等会输入,直到最终超时退出。此外, expect 还支持使用正则表达式来预防 expect 匹配到未预想到的输入数据。
    spawn 命令会调用另一个程序。它的第一个参数是要启动程序的名字;剩余的参数则会被传递给该程序做为参数。比如
    spawn ftp linux.ibm.com">ftp.linux.ibm.com
    命令会衍生出一个 ftp 进程,并且将 ftp.linux.ibm.com 做为参数传递给这个 ftp 进程。
    用户通过 spawn,send 和 expect 这三个基本命令,就可以编写一段 Expect 程序来实现自动化工作。
    Expect 脚本实现
    本节将利用基于用户口令的安全认证方式,并使用 Expect 来实现 SSHD 服务器的自动化登录过程,并在登录的会话中实现命令在 SSHD 服务器端的执行。本文使用的具体实验环境如下:用户使用的 SSH 客户端机器:操作系统均为 RHELS5.3, IP 地址为 192.168.0.3, Expect 版本为 version 5.43.0;远程的 SSHD 服务器:操作系统均为 RHELS5.3,IP 地址为 192.168.0.4,用户名 / 密码为 root/123456。
    清单1.登录 SSHD 服务器的自动化脚本
    #!/usr/bin/expect
    # 设置超时时间为 60 秒
    set timeout  60
    # 设置要登录的主机 IP 地址
    set host 192.168.0.4
    # 设置以什么名字的用户登录
    set name root
    # 设置用户名的登录密码
    set password 123456
    #spawn 一个 ssh 登录进程
    spawn  ssh $host -l $name
    # 等待响应,第一次登录往往会提示是否永久保存 RSA 到本机的 know hosts 列表中;等到回答后,在提示输出密码;之后就直接提示输入密码
    expect {
    "(yes/no)?" {
    send "yes\n"
    expect "assword:"
    send "$pasword\n"
    }
    "assword:" {
    send "$password\n"
    }
    }
    expect "#"
    # 下面测试是否登录到 $host
    send "uname\n"
    expect "Linux"
    send_user  "Now you can do some operation on this terminal\n"
    # 这里使用了 interact 命令,使执行完程序后,用户可以在 $host 终端进行交互操作。
    Interact
   如果要运行该脚本,可以参考如下的操作,假设 expect 脚本的文件名为 t1.expect。另外,在运行该脚本之前,需要将 t1.expect 文件设置成可执行的模式 ;
    清单2.运行自动化登录脚本的操作步骤
    [root@redhat ~]chmod a+x t1.expect
    [root@redhat ~]./t1.expect
    spawn ssh 192.168.0.4 -l root
    root@192.168.0.4's password:
    Last login: Fri Jun 12 15:36:01 2009 from 192.168.0.3
    Red Hat Enterprise Linux Server release 5.1 (Tikanga)
    [root@c96m3h4ms01 ~]# uname
    Linux
    Now you can do some operation on this terminal
    [root@c96m3h4ms01 ~]#
    基于 SSH 交换 Key 自动化登录
    SSH 证书简介
    SSH 证书使用一对密钥 : 私钥(private key)和公钥 (public key)。公钥(public key)对数据进行加密而且只能用于加密,私钥(private key)只能对所匹配的公钥(public key)加密过的数据进行解密。私钥(private key)只保存你独有的一些秘密信息。SSH 客户端用其向 SSHD 服务器证明自已的身份。公钥是公开的,可以随便将其放入 SSHD 服务器上自已的帐号中 , 在认证时,进行私钥和公钥协商,如果匹配,那么身份就得以证明,认证就成功。
    目前所有的 OpenSSH 版本都应该既能使用 RSA 密钥又能使用 DSA 密钥。RSA 密钥和 DSA 密钥的生成命令和使用方法相同,本文仅介绍 RSA。
    如果采用 SSH 密钥认证的方式实现自动化登录,用户可以参考下面的章节。
    生成密钥对
    ssh-keygen 程序生成的 RSA 密钥的文件名默认为 id_rsa 和 id_rsa.pub,用户也可以将其更改为别的名称。下面”清单 3. 生成密钥对”的操作过程将采用系统的默认值。
    清单3. 生成密钥对
    [root@redhat ~]# ssh-keygen -t rsa
    Generating public/private rsa key pair.
    Enter file in which to save the key (/root/.ssh/id_rsa):
    Created directory '/root/.ssh'.
    Enter passphrase (empty for no passphrase):      <--- 可以不输入密码
    Enter same passphrase again:   <--- 可以不输入密码
    Your identification has been saved in /root/.ssh/id_rsa.
    Your public key has been saved in /root/.ssh/id_rsa.pub.
    The key fingerprint is:
    4b:70:20:de:89:92:a9:fe:21:a4:9b:7c:6b:65:ae:e0 root@redhat
    [root@redhat ~]# ls -al .ssh
    total 20
    drwx------  2 root root 4096 May 10 02:51 .
    drwxr-x--- 11 root root 4096 May 10 02:51 ..
    -rw-------  1 root root 1675 May 10 02:51 id_rsa
    -rw-r--r--  1 root root  397 May 10 02:51 id_rsa.pub
    如果 ssh 目录不存在,程序为自动创建本地 SSH 目录 ~/.ssh,并将所生成的密钥分成两个文件存储,私钥为 id_rsa,公钥为 id_rsa.pub。
    配置自动化登录
    在配置自动化登录的过程中,账户对应的公钥需要被添加到 SSHD 服务器端的配置文件。在 3.0 版本的 OPENSSH 中,用户需要修改的文件为 authorized_keys,早于 3.0 的版本则使用 authorized_keys2 文件。将在客户端生成的 id_rsa.pub 文件内容加入到 authorized_keys 或者 authorized_keys2 文件中即可完成配置工作。
    为安全起见,要确保 $HOME/.ssh 目录的安全,只有所有者才有 权写入。如果远程用户的 SSH 配置文件的权限设置不当,服务器可能会拒绝进行认证。
    下面是配置自动化登录的具体过程,其场景如下:
    Openssh 为 OpenSSH_4.3p2SSH,将以 root 用户登录到 SSHD 服务器(192.168.0.4)上,因此公钥的内容将存放在 root/.ssh/authorized_keys。
    清单4. 终端操作记录
    [root@redhat ~]# scp /root/.ssh/id_rsa.pub root@192.168.0.4:/tmp
    [root@redhat .ssh]# ssh 192.168.0.4
    root@192.168.0.4's password: ******
    [root@server ~]# cat /tmp/id_rsa.pub >> /root/.ssh/authorized_keys
    如果用户在生成 rsa 和 id_rsa.pub 的时候没有输入密码,可以直接使用“ssh root@192.168.0.4”登录,而不用输入密码。
    而如果用户在生成 rsa 和 id_rsa.pub 的时候输入了密码,需要进行以下两步操作:
    启用 ssh-agent 认证代理 ,
    清单5. 终端操作记录
    [root@redhat ~]# ssh-agent $SHELL
    (2) 使用 ssh-add, 装入私钥,并输入生成 rsa 和 id_rsa.pub 时输入的密码
    清单6. 终端操作记录
    [root@redhat ~]# ssh-add
    Enter passphrase for /root/.ssh/id_rsa:
    Identity added: /root/.ssh/id_rsa (/root/.ssh/id_rsa)
    Identity added: /root/.ssh/id_dsa (/root/.ssh/id_dsa)
    [root@redhat ~]# ssh 192.168.0.4
    Last login: Sat May 16 11:37:39 2009 from redhat
    把私钥保存在内存中,为认证提供服务,之后以 root 用户 ssh 登录时,就不用重复输入密码。其生命周期为 ssh-agent 启动的那个 shell,当用户退出该 shell 时,需要重新执行 ssh-agent 和 ssh-add。
    只要密钥配置好,以后登录就是自动化了。因此本部分登录过程不需要用脚本来实现,实现了前期拷贝 SSH key 到远程服务器,并添加到相应的位置,并测试自动登录是否成功。
   脚本实现配置自动化登录
    本节的脚本实现基于 SSH key 交换的安全认证方式,并利用 Expect 来实现自动化登录。本节所实现的脚本可以运行在以下的实验环境中:用户使用的 SSH 客户端机器:操作系统均为 RHELS5.3, IP 地址为 192.168.0.3, Expect 版本为 version 5.43.0;远程的 SSHD 服务器:操作系统均为 RHELS5.3,IP 地址为 192.168.0.4,用户名 / 密码为 root/123456。
    Expect 脚本的内容如下:
    清单7.基于 SSH key 交换的自动化登录脚本
    #!/usr/bin/expect
    # 判断输入的参数是否为 3 个,如果不为 3 个,就打印错误信息,退出该程序。
    if { $argc != 3 } {
    puts stderr "Usage: test1 host-address username host-password\n"
    exit 1
    }
    # 设置超时时间为 60 秒
    set timeout  60
    # 将命令行输入的第一个参数作为将要登录的 SSHD 服务器
    set host [lindex $argv 0]
    # 第二个参数是用户名,赋值给 name, 之后用 $name 格式来使用
    set name [lindex $argv 1]
    # 第三个参数是以 $name 登录 $host 的口令
    set password [lindex $argv 2]
    ##set timeout  60
    ##set password "cluster"
    ##set name “root”
    ##set host "192.168.0.4"
    #root 用户的 rsa key 放在 /root/.ssh 中,其他用户则放在 /home/$name/.ssh
    if { $name == "root"} {
    spawn scp /$name/.ssh/id_rsa.pub $name@$host:/tmp
    } else {
    spawn scp /home/$name/.ssh/id_rsa.pub $name@$host:/tmp
    }
    # 等待上个命令的响应
    expect {
    "(yes/no)?" {
    send "yes\n"
    expect "assword:"
    send "$pasword\n"
    }
    "assword:" {
    send "$password\n"
    }
    }
    # 输入密码后,拷贝成功,出现 100% 字符串,作为预期响应
    expect "100%"
    # 调用 ssh 以 $name 用户名登录到 $host 上
    spawn  ssh $host – l$name
    # 期待提示出入密码的响应
    expect "assword:"
    # 接收密码
    send "$password\n"
    expect ":~#"
    # 将刚刚拷贝的 rsa key 添加到用户的 home 目录下的 ./ssh/authorized_keys
    if { $name == "root"} {
    send "cat /tmp/id_rsa.pub >> /root/.ssh/authorized_keys\n"
    } else {
    send "cat /tmp/id_rsa.pub >> /home/$name/.ssh/authorized_keys\n"
    }
    expect ":~#"
    # 操作成功后,退回 SSH 客户端机器
    send "exit\n"
    expect "#"
    # 下面将测试能否自动登录,不用输入密码
    spawn  ssh $host – l$name
    expect  {
    "Welcome" {
    send_user "Auto login the server successfully!"
    }
    "assword:" {
    send_user "failed to login the server!"
    }
    }
    send "ls\n"
    expect ":~#"
    # 退出 $host
    send "exit\n"
    # 程序结束
    expect eof
    清单 7 基于 SSH key 交换的自动化登录脚本注释掉了主机名,用户名和密码,因此用户在运行脚本时需要手工输入主机名,用户名和密码。一旦 SSH Key 的交换完成,用户就可以直接运行 “ssh host-address – l username”实现自动化登录,而不再需要输入用户名密码。