由于安全策略的限制,公司所有的linux服务器都设置了sshd_config中的PermitRootLogin no来禁止root用户直接登陆,但同时又由于安全需要,需要定期修改root密码,于是问题来了。在root无法登陆的情况下只能由普通用户登陆后su - 到root然后执行密码修改。于是我想到了如下方案:

   for i in `cat server_list`

       do ssh user1@$i "su -c 'echo \"newpasswd\" |passwd root --stdin'"

   done

但运行提示standard in must be a tty, 通过搜索发现su 所需要标准输入的无法通过管道来获取,必须通过tty及终端。我们有数百台服务器需要这样的操作,每台手工登陆然后切换用户反复输入密码显然不可取。于是我想到了expect,网络上的关于expect的文档相当很少,而且由于这里需要输入两次密码(一次普通用户密码,一次跳转到目标服务器后输入的root用户密码),我尝试了多种expect脚本似乎都很难实现,expect会由于当前shell环境跳转到目标服务器而陷入等待,而设置的timeout值是在当前服务器上似乎无法生效。于是我想到了perl的expect模块,脚本如下。

使用方法:for i in `cat server_list`;do ./expect_login.pl -h $i;done


#!/usr/bin/perl -w
# Author: Ken Zhang @2013-12-29

# Filename: expect_login.pl
use Getopt::Std;
use vars qw/%opt/;
use Expect;

sub init(){
       my $opt_string="u:h:p:";
       getopts("$opt_string",\%opt) or usage();
       #usage() if @ARGV==0;
       }
sub usage(){
       print STDERR << "EOF"
       usage: $0 [-u username] [-p password] [-h hostname]
       example: $0 -u root -p password -h localhost.localdomain
EOF
       }
sub connect(){
       my $password = 'ken.zhang@2014';
       my $username = 'ken.zhang';
       my $hostname = 'localhost';
       my $port='22';
       $password = $opt{p} if $opt{p};
       $username = $opt{u} if $opt{u};
       $hostname = $opt{h} if $opt{h};
       my $rootpass='RootP@aaA';   # default password for all servers.
       $newrootpass='new_non-prod_password' if $hostname =~ /^b/i;    #new root password for non-prod servers
       $newrootpass='new_prod_root_password' if $hostname =~ /^a|prd/i; #new root password for prod servers, server name start with 'a' or contains 'prd'
       my $cmd="ssh";
       my @params=($hostname,'-l'.$username,'-p'.$port);
       $ENV{TERM} = "xterm";
       my $exp = Expect->new;
       $exp = Expect->spawn($cmd,@params);
       $exp->log_file("output$$.log","w");
# enable debug information if uncomment below
      #$exp->exp_internal(1);
# disable output to stdout if uncomment below. all outputs will go to logfile "output$$.log"
#       $exp->log_stdout(1);
       $exp->expect(5,
       [qr/password/i => sub {my $self=shift; $self->send("$password\n");} ],
       [qr/\(yes\/no\)\?/ , sub {my $self=shift; $self->send("yes\n");exp_continue} ]
               );
       $exp->expect(5,'$');
       $exp->send("su -c \"echo $newrootpass|passwd root --stdin\"\n");
       $exp->expect(5,'[Pp]assword:');
       $exp->send("$rootpass\n");
       $exp->send("exit\n") if($exp->expect(5,'$'));
       }

&init();
&connect();