#!/usr/local/bin/perl
#------------------------------------------------------------------------------
# Name     : massadmin
# Language : perl v5
# OS       : UNIX
# Author   : Gilles Darold, gilles@darold.net
# Copyright: Copyright (c) 2002-2010 : Gilles DAROLD - All rights reserved -
# License  : GPL v3
# Usage    : See documentation.
#------------------------------------------------------------------------------
use vars qw{$VERSION $AUTHOR $COPYRIGHT};
use strict;

use Getopt::Std;
use POSIX;
use IO::Pty;
use Net::Telnet;
use Net::Ping;
use Proc::Queue size => 15, ':all';
use Fcntl ':flock';
use Expect;

$VERSION = '2.3';
$AUTHOR = 'Gilles Darold';
$COPYRIGHT = 'Copyright (c) 2002-2010 Gilles Darold - All rights reserved';


####
# method used to fork as many child as wanted
##
sub spawn {
        my $coderef = shift;

        unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') {
                print "usage: spawn CODEREF";
                exit 0;
        }

        my $pid;
        if (!defined($pid = fork)) {
                print STDERR "Error: cannot fork: $!\n";
                return;
        } elsif ($pid) {
                return; # the parent
        }
        # the child -- go spawn
        $< = $>;
        $( = $); # suid progs only

        exit &$coderef();
}

sub wait_child
{
	1 while wait != -1;
	$SIG{INT} = \&wait_child;
	$SIG{TERM} = \&wait_child;
}
$SIG{INT} = \&wait_child;
$SIG{TERM} = \&wait_child;

my $TELNET_PROMPT = '/(.*[\$\%#>]|TERM =.*) $/';
my $FTP_PROMPT = '/ftp>|221 Goodbye/';
my $LOG_PROMPT = '/(REMOTE HOST IDEN.*|Permission denied.*|\(yes\/no\)\?\s*|ogin:\s*|assword:\s*?)$/';
my $SSH_PROMPT = '/(Permission denied, please try again.|.*[\$\%#>] |assword: )$/';
my $SFTP_PROMPT = '/(Permission denied, please try again.|password: |sftp> )$/';


my $TMPDIR = '/var/tmp';
my $TMPPERM = 0600;
my $DEBUG = 0;
my $QUEUE_SIZE = 15;
my @PROTOCOLS = ('FTP','TELNET','RSH','SSH','SCP','SFTP');
my $TIMEOUT = 60;
my $FTP_PROG = `which ftp`;
my $RSH_PROG = `which rsh`;
my $SSH_PROG = `which ssh`;
my $SCP_PROG = `which scp`;
my $SFTP_PROG = `which sftp`;
chomp($FTP_PROG);
chomp($RSH_PROG);
chomp($SSH_PROG);
chomp($SCP_PROG);
chomp($SFTP_PROG);
my $CHDIR  = '';
my @HOSTS  = ();
my @LOGIN  = ();
my @PASSWD = ();
my @CMDS = ();

$| = 1;

my %opts = ();
getopts('c:l:o:h:e:q:r:p:t:d', \%opts); # -c config_file | -e command -l server_list | -h hostname,hostname -o out_log_file -q 15 -r repository -p login_list -t timeout

if ( (!$opts{e} && !$opts{c}) || (!$opts{h} && !$opts{l}) ) {
	&usage;
}

if ( (!$opts{e} && !-f "$opts{c}") || (!$opts{h} &&  !-f "$opts{l}") ) {
	&usage;
}

if (exists $opts{d}) {
	$DEBUG = 1;
}

if (exists $opts{q} && (int($opts{q}) > 0) ) {
	$QUEUE_SIZE = int($opts{q});
	# changing limit to $QUEUE_SIZE concurrent processes
	Proc::Queue::size($QUEUE_SIZE);
}

if ($opts{t}) {
	$opts{t} =~ s/[^\d]+//g;
	$TIMEOUT = $opts{t} if ($opts{t});
	print "Timeout set to $TIMEOUT\n" if ($DEBUG);
}

if ($DEBUG) {
	print STDERR "Reading list of hosts from file $opts{l}.\n" if ($opts{l});
	print STDERR "Reading list of hosts from command line: $opts{h}\n" if ($opts{h});
	print STDERR "Reading list of commands from file $opts{c}.\n" if ($opts{c});
	print STDERR "Reading list of commands from command line: $opts{e}.\n" if ($opts{e});
	print STDERR "Dumping result to file $opts{o}.\n" if ($opts{o});
}

# Extract the login/password list from command line
if ($opts{p}) {
	$opts{p} =~ s/\\,/NOSPLIT/g;
	foreach my $line (split(/,/, $opts{p})) {
		chomp($line);
		$line =~ s/NOSPLIT/,/g;
		$line =~ s/\\:/NOSPLIT/g;
		my ($log, $pass) = split(/:/, $line);
		$pass =~ s/NOSPLIT/:/g;
		push(@LOGIN, $log);
		push(@PASSWD, $pass);
	}
}

# Get hosts list from command line
if ($opts{h}) {
	$opts{h} =~ s/\\,/NOSPLIT/g;
	my @ips = split(/,/, $opts{h});
	foreach my $i (@ips) {
		$i =~ s/NOSPLIT/,/g;
		$i =~ /^([^:]+)/;
		my $p = Net::Ping->new();
		if ($p->ping($1, 5)) {
			push(@HOSTS, $i);
		} else {
			print "Host $1 is not reachable, skip this host.\n";
		}
		$p->close();
	}
}

# Extract the list of hosts to process
if ($opts{l}) {
	open (IN, "$opts{l}") or die "Error: can't open file $opts{l}. $!\n";
	while (my $line = <IN>) {
		chomp($line);
		# Skip empty line or comment line
		next if ( !$line || ($line =~ /^[\s\t]*#/) );
		# Get global set of login, character ':' in login must be escaped
		if ($line =~ s/^login://) {
			$line =~ s/\\:/NOSPLIT/g;
			push(@LOGIN, split(/:/, $line));
			map { s/NOSPLIT/:/g; } @LOGIN;
		# Get global set of password, character ':' in password must be escaped
		} elsif ($line =~ s/^password://) {
			$line =~ s/\\:/NOSPLIT/g;
			push(@PASSWD, split(/:/, $line));
			map { s/NOSPLIT/:/g; } @PASSWD;
		} elsif ($line =~ s/^chdir:(.*)//) {
			if (-d $1) {
				$CHDIR = $1;
			} else {
				die "ERROR: chdir $1 doesn't exists.\n";
			}
		} else {
			# Check if the server is reachable
			$line =~ /^([^:]+)/;
			my $p = Net::Ping->new();
			if ($p->ping($1, 5)) {
				push(@HOSTS, $line);
			} else {
				print "Host $1 is not reachable, skip this host.\n";
			}
			$p->close();
		}
	}
	close(IN);
}

# Extract the list of command from command line
if ($opts{e}) {
	$opts{e} =~ s/\\,/NOSPLIT/g;
	foreach my $line (split(/,/, $opts{e})) {
		chomp($line);
		# Skip empty line or comment line
		next if ( !$line || ($line =~ /^[\s\t]*#/) );
		$line =~ s/NOSPLIT/,/g;
		push(@CMDS, $line);
	}
	close(IN);
}

# Extract the list of command to execute from file
if ($opts{c}) {
	open (IN, "$opts{c}") or die "Error: can't open file $opts{c}. $!\n";
	while (my $line = <IN>) {
		chomp($line);
		# Skip empty line or comment line
		next if ( !$line || ($line =~ /^[\s\t]*#/) );
		push(@CMDS, $line);
	}
	close(IN);
}

local(*LOGF) = undef;
if ($opts{o}) {
	unless( open(LOGF, ">$opts{o}") ) {
		print STDERR "Error: can't open file $opts{o}, $!\n";
		exit 0;
	}
} else {
	unless( open(LOGF, ">&1") ) {
		print STDERR "Error: can't write to STDOUT, $!\n";
		exit 0;
	}
}
# Override running directory from command line
$CHDIR = $opts{r} if ($opts{r} && -d $opts{r});
chdir($CHDIR) if ($CHDIR);

# Ok now for each host fork a child to run the commands
foreach my $host ( @HOSTS ) {
	spawn sub {
		&process_host($host);
	};
}

1 while wait != -1;
close(LOGF);

exit 0;



####
# Method used to process all command for a given host
##
sub process_host
{
	my ($hostconf) = @_;

	my @MSG = '';

	# Remove line ending comment;
	$hostconf =~ s/[\s\t]+#.*//;

	# Extract the authentication part
	$hostconf =~ s/\\:/NOSPLIT/g;
	my ($host, $login, $passwd) = split(/:/, $hostconf);
	push(@MSG, "Begin processing host $host\n");
	if ($passwd) {
		$passwd =~ s/NOSPLIT/:/g;
	}

	my $conn = '';
	foreach my $cmd ( @CMDS ) {

		# Remove comment at end of line
		$cmd =~ s/[\s\t]+#.*//;

		push(@MSG, "\n\tParsing command $cmd\n") if ($DEBUG);
		$cmd =~ s/\\:/NOSPLIT/g;
		my @line = split(/:/, $cmd);
		map { s/NOSPLIT/:/g; } @line;

		# Extract the protocol part
		my $proto = shift @line;
		if (!grep(/^$proto$/i, @PROTOCOLS)) {
			push(@MSG, "Error: bad protocol $proto, protocol must be one of these values: @PROTOCOLS\n");
			exit 0;
		}

		# Extract the authentication part
		my $authen = shift @line;
		if ( $authen ) {
			# overwrite authent given from configuration file
			$authen =~ s/\\;/NOSPLIT/g;
			my ($user, $pwd) = split(/;/, $authen);
			$pwd =~ s/NOSPLIT/;/g;
			$passwd = $pwd;
			$login  = $user;
		}
		# Replace any internal parameter
		map { s/\%SRVEXE_HOST/$host/g; } @line;

		# Proceed following protcols
		if ( uc($proto) eq 'FTP' ) {
			my @res = &exec_ftp($host, $login, $passwd, @line);
			last if ( $#res < 0 );
			push(@MSG, @res);
		} elsif ( uc($proto) eq 'TELNET' ) {
			push(@MSG, &exec_telnet($host, $login, $passwd, @line));
		} elsif ( uc($proto) eq 'RSH' ) {
			push(@MSG, &exec_rsh($host, @line));
		} elsif ( uc($proto) eq 'SSH' ) {
			push(@MSG, &exec_ssh($host, $login, $passwd, @line));
		} elsif ( uc($proto) eq 'SCP' ) {
			push(@MSG, &exec_scp($host, $login, $passwd, @line));
		} elsif ( uc($proto) eq 'SFTP' ) {
			push(@MSG, &exec_sftp($host, $login, $passwd, @line));
		}
	}

	# Log information
	&lock();
	print LOGF "@MSG\n";
	&unlock();
}


####
# Show command line usage
##
sub usage
{
	print "Usage:\n",
	"$0 -c config_file -l server_list [-o output.log]\n",
	"\t-c config_file: commands list file\n",
	"\t-e comman_list: coma separated list of commands\n",
	"\t-l server_list: servers list file\n",
	"\t-h host_list  : coma separated list of servers\n",
	"\t-d            : debug mode\n",
	"\t-o output_file: output result to a file. Default is stdout.\n",
	"\t-q nb_process : changing limit to nb_process concurrent processes. Default: 15.\n",
	"\t-r dir        : change running directory to dir.\n",
	"\t-p log_list   : coma separated list of login:password pair to use.\n\n",
	"\t-t seconds    : set timeout for all connections. Default 60 seconds.\n";
	exit 0;
}


####
# Execute a FTP command
##
sub exec_ftp
{
	my ($srv, $username, $phrase, @toexec) = @_;

	my @return = ();
	my @ogin = ();
	my @pass  = ();
	push(@ogin, $username, @LOGIN);
	push(@pass, $phrase, @PASSWD);
	# Try each given login/password, begining with those defined by host
	for (my $i = 0; $i <= $#ogin; $i++) {
		next if (!$ogin[$i]);
		# Open a pseudo tty to the ftp program
		my $ftp = do_cmd($FTP_PROG, $srv);
		if ( !$ftp ) {
			die "Error: can't connect via FTP to $srv, $!\n";
		}
		# Then open a shell on this ftp instance
		my $shell = Net::Telnet->new(-fhopen => $ftp, -timeout => $TIMEOUT, -output_record_separator => "\r");
		$shell->binmode(1);
		if ($DEBUG) {
			$shell->input_log("$TMPDIR/massadmin_in-$srv");
			$shell->output_log("$TMPDIR/massadmin_out-$srv");
			chmod($TMPPERM, "$TMPDIR/massadmin_in-$srv", "$TMPDIR/massadmin_out-$srv");
		}
		$shell->errmode('die');
		# Send Authentication
		if ($shell->waitfor('/Name/')) {
			$shell->print("$ogin[$i]");
		}
		if ($shell->waitfor('/Password:/')) {
			$shell->print("$pass[$i]");
		}
		my ($prematch, $match) = $shell->waitfor($FTP_PROMPT);
		if ($prematch =~ /Login failed/) {
			push(@return, "\t530 Login $ogin[$i] failed\n");
			warn "Error: can't authenticate via FTP to host $srv (login: $ogin[$i], password: ...)\n";
			$shell->close();
			$ftp->close();
			undef $ftp;
			next;
		}

		foreach my $command ( @toexec ) {
			if ( $command =~ s/\|[\s\t]*$// ) {
				push(@return, "\tExecuting command $command\n") if ($DEBUG);
				push(@return, $shell->cmd(String => $command, Prompt => $FTP_PROMPT));
			} else {
				push(@return, "\tExecuting command $command\n") if ($DEBUG);
				$shell->print($command);
				$shell->waitfor($FTP_PROMPT);
			}
		}
		$shell->close();
		last;
	}
	map { s/
//gs; } @return;

	@return;
}


####
# Execute a TELNET command
##
sub exec_telnet
{
	my ($srv, $username, $phrase, @toexec) = @_;

	my @return = ();
	my @ogin = ();
	my @pass  = ();
	push(@ogin, $username, @LOGIN);
	push(@pass, $phrase, @PASSWD);
	# Try each given login/password, begining with those defined by host
	for (my $i = 0; $i <= $#ogin; $i++) {	
		next if (!$ogin[$i]);
		# Then open a shell on this server
		my $shell = Net::Telnet->new(-host => $srv, -timeout => $TIMEOUT, -errmode => 'return', -output_record_separator => "\r");
		if ( !$shell ) {
			die "Error: can't connect via Telnet to $srv, $!\n";
		}
		$shell->binmode(1);
		if ($DEBUG) {
			$shell->input_log("$TMPDIR/massadmin_in-$srv");
			$shell->output_log("$TMPDIR/massadmin_out-$srv");
			chmod($TMPPERM, "$TMPDIR/massadmin_in-$srv", "$TMPDIR/massadmin_out-$srv");
		}
		$shell->errmode('return');
		

		# Send Authentication
		$shell->prompt($TELNET_PROMPT);
		if (!$shell->login(Name => $ogin[$i], Password => $pass[$i])) {
			warn "Error: can't authenticate via TELNET to host $srv (login: $ogin[$i], password: ...)\n";
			$shell->close();
			next; # Try an other login/password
		}
		my @none = $shell->cmd(String => 'xterm');
		@none = $shell->cmd(String => 'export TERM=xterm');
		$shell->buffer_empty();

		foreach my $command ( @toexec ) {
			if ( $command =~ s/\|[\s\t]*$// ) {
				push(@return, "\tExecuting command $command\n") if ($DEBUG);
				push(@return, $shell->cmd(String => $command));
			} else {
				# Check if this is a su command
				if ($command =~ /^su\s+.*$/) {
					my ($su, $minus, $user, $password) = split(/\s+/, $command);
					if ( ($minus ne '-') && $user) {
						$password = $user;
						$user = $minus;
						$minus = '';
					} elsif ($minus ne '-') {
						$password = $minus;
						$minus = '';
					}
					if (!$password) {
						$password = $user;
						$user = '';
					}
					push(@return, "\tExecuting su command: $su $minus $user\n") if ($DEBUG);
					$shell->print("$su $minus $user");
					$shell->waitfor('/Password:\s*$/');
					@none = $shell->cmd(String => "$password");
					@none = $shell->cmd(String => 'export TERM=xterm');
					@none = ();
					$shell->buffer_empty();
				} else {
					push(@return, "\tExecuting command $command\n") if ($DEBUG);
					$shell->cmd(String => $command);
				}
			}
		}
		$shell->close();
		last;
	}
	map { s/
//gs; } @return;

	@return;
}


####
# Method used to do FTP over TELNET by opening a Pty
##
sub do_cmd
{
	my ($cmd,@args) = @_;

	my $pty = IO::Pty->new or die "Error: can't make Pty, $!";
	defined (my $child = fork) or die "Can't fork: $!";
	return ($child, $pty) if $child;

	setsid();
	my $tty = $pty->slave;
	$pty->make_slave_controlling_terminal();
	close $pty;

	STDIN->fdopen($tty,"r")      or die "STDIN: $!";
	STDOUT->fdopen($tty,"w")     or die "STDOUT: $!";
	STDERR->fdopen(\*STDOUT,"w") or die "STDERR: $!";
	close $tty;
	$| = 1;
	exec $cmd,@args;
	die "Couldn't exec: $!";
}


#### Method: lock
# Lock a file when writing to it. The file handle LOGF is global
##
sub lock
{
        flock(LOGF,LOCK_EX);
        # and, in case someone appended while we were waiting...
        seek(LOGF, 0, 1);
}


#### Method: unlock
# Unlock a file. The file handle LOGF is global
##    
sub unlock      
{               
        flock(LOGF,LOCK_UN);
}


####
# Execute a RSH command
##
sub exec_rsh
{
	my ($srv, @toexec) = @_;

	if ( !-x "$RSH_PROG" ) {
		die "Error: can't execute $RSH_PROG, no such file.\n";
	}
	# Then execute directly the command
	my @return = ();
	foreach my $command ( @toexec ) {
		if ( $command =~ s/\|[\s\t]*$// ) {
			push(@return, "\tExecuting command $command\n") if ($DEBUG);
			push(@return, `$RSH_PROG $srv $command 2>&1`);
		} else {
			push(@return, "\tExecuting command $command\n") if ($DEBUG);
			`$RSH_PROG $srv $command 2>&1`;
		}
	}
	map { s/
//gs; } @return;

	@return;

}


####
# Execute SSH command
##
sub exec_ssh
{
	my ($srv, $username, $phrase, @toexec) = @_;

	my @return = ();
	my @ogin = ();
	my @pass  = ();
	push(@ogin, $username, @LOGIN);
	push(@pass, $phrase, @PASSWD);
	# Try each given login/password, begining with those defined by host
	for (my $i = 0; $i <= $#ogin; $i++) {
		next if (!$ogin[$i]);
		my ($chld, $ssh) = do_cmd("$SSH_PROG", "-l", "$ogin[$i]", $srv);
		if ( !$ssh ) {
			die "Error: can't connect via SSH to $srv, $!\n";
		}
		# Then open a shell on this ftp instance
		my $shell = Net::Telnet->new(-fhopen => $ssh, -timeout => $TIMEOUT, -output_record_separator => "\r");
		$shell->binmode(1);
		if ($DEBUG) {
			$shell->input_log("$TMPDIR/massadmin_in-$srv");
			$shell->output_log("$TMPDIR/massadmin_out-$srv");
			chmod($TMPPERM, "$TMPDIR/massadmin_in-$srv", "$TMPDIR/massadmin_out-$srv");
		}
		$shell->errmode('return');
		my ($prematch, $match) = $shell->waitfor($LOG_PROMPT);
		if ($match =~ /REMOTE HOST IDEN/) {
			warn "Error: Please fix ~/.ssh/know_hosts for host $srv\n";
			$shell->close();
			$ssh->close();
			undef $ssh;
			return;
		}
		if ($match =~ /\(yes\/no\)\?\s*/) {
			$shell->print("yes");
			($prematch, $match) = $shell->waitfor($LOG_PROMPT);
		}
		# Send Authentication
		if ($match =~ /ogin:\s*/) {
			$shell->print("$ogin[$i]");
			($prematch, $match) = $shell->waitfor($LOG_PROMPT);
		}
		if ($match =~ /assword:\s*/) {
			$shell->print("$pass[$i]");
		}
		($prematch, $match) = $shell->waitfor($SSH_PROMPT);
		if ($match =~ /(Permission denied|assword: $)/) {
			warn "Error: can't authenticate via SSH to host $srv (login: $ogin[$i], password: ...)\n";
			$shell->close();
			$ssh->close();
			undef $ssh;
			next;
		}
		foreach my $command ( @toexec ) {
			if ( $command =~ s/\|[\s\t]*$// ) {
				push(@return, "\tExecuting command $command\n") if ($DEBUG);
				push(@return, $shell->cmd(String => $command, Prompt => $SSH_PROMPT));
			} else {
				# Check if this is a su command
				if ($command =~ /^su\s+.*$/) {
					my ($su, $minus, $user, $password) = split(/\s+/, $command);
					if ( ($minus ne '-') && $user) {
						$password = $user;
						$user = $minus;
						$minus = '';
					} elsif ($minus ne '-') {
						$password = $minus;
						$minus = '';
					}
					if (!$password) {
						$password = $user;
						$user = '';
					}
					push(@return, "\tExecuting su command: $su $minus $user\n") if ($DEBUG);
					$shell->print("$su $minus $user");
					$shell->waitfor('/Password:\s*$/');
					my @none = $shell->cmd(String => "$password");
					@none = $shell->cmd(String => 'export TERM=xterm');
					@none = ();
					$shell->buffer_empty();
				} else {
					push(@return, "\tExecuting command $command\n") if ($DEBUG);
					$shell->print($command);
					$shell->waitfor($SSH_PROMPT) if ($command ne 'exit');
				}
			}
		}
		$shell->close();
		$ssh->close();
		undef $ssh;
		last;
	}
	map { s/
//gs; } @return;

	@return;
}

####
# Execute SCP command.
##
sub exec_scp
{
	my ($srv, $username, $phrase, $put, $dest, $get) = @_;

	my @return = ();
	my @ogin = ();
	my @pass  = ();
	push(@ogin, $username, @LOGIN);
	push(@pass, $phrase, @PASSWD);
	$dest = $put || $get if (!$dest);
	die "ERROR: SCP syntax !\n" if (!$dest);

	# Try each given login/password, begining with those defined by host
	for (my $i = 0; $i <= $#ogin; $i++) {
		next if (!$ogin[$i]);
		my $scp = Expect->new;
		my $scp_string = "$SCP_PROG -r -q $put $ogin[$i]\@$srv:$dest";
		$scp_string = "$SCP_PROG -r -q $ogin[$i]\@$srv:$dest $get" if (!$put);
		push(@return, "\tExecuting command $scp_string\n") if ($DEBUG);
		$scp = Expect->spawn($scp_string);
		die "Couldn't start SCP program: $!\n" unless ($scp);
		$scp->log_stdout(0);
		while ( $scp->expect(2 ,-re=>'[Yy]es\/[Nn]o') ) {
			$scp->send("yes\n");
		}
		unless ( $scp->expect(2,-re=>'[Pp]assword.*?:|[Pp]assphrase.*?:') ) {
			my $err = $scp->before() || $scp->match();
			warn "Error: can't authenticate via SCP to host $srv (login: $ogin[$i], password: ...)\n";
			$scp->hard_close();
			next;
		}
		$scp->send("$pass[$i]\n");
		my $eof = 0;
		my $authfailure = 0;
		my $error = ($scp->expect($TIMEOUT,
			[qr/[Pp]ass.*/ => sub{
				
				warn "Error: can't authenticate via SCP to host $srv (login: $ogin[$i], password: ...)\n";
				$authfailure = 1;
				}
			],
			[qr/[Pp]ermission denied, please try again/ => sub{
				warn "Error: can't authenticate via SCP to host $srv (login: $ogin[$i], password: ...)\n";
				$authfailure = 1;
				}
			],
			[qr/\w+.*/ => sub{
				my $err = $scp->match() || $scp->before();
				die("Error: last line returned was: $err\n");
				}
			],
			['eof' => sub{ $eof = 1 } ],
		))[1];
		if ($authfailure) {
			$scp->hard_close();
			next;
		}
		if ($error && !($eof && $error =~ m/^(2|3)/o)) {
			$scp->hard_close();
			last;
		}
		push(@return, "\tDone...\n") if ($DEBUG);
		$scp->hard_close();
	}
	map { s/
//gs; } @return;

	@return;
}

####
# Execute SFTP command
##
sub exec_sftp
{
	my ($srv, $username, $phrase, @toexec) = @_;

	my @return = ();
	my @ogin = ();
	my @pass  = ();
	push(@ogin, $username, @LOGIN);
	push(@pass, $phrase, @PASSWD);
	# Try each given login/password, begining with those defined by host
	for (my $i = 0; $i <= $#ogin; $i++) {
		next if (!$ogin[$i]);
		my $ssh = do_cmd("$SFTP_PROG", "$ogin[$i]\@$srv");
		if ( !$ssh ) {
			die "Error: can't connect via SFTP to $srv, $!\n";
		}
		# Then open a shell on this ftp instance
		my $shell = Net::Telnet->new(-fhopen => $ssh, -timeout => $TIMEOUT, -output_record_separator => "\r");
		$shell->binmode(1);
		if ($DEBUG) {
			$shell->input_log("$TMPDIR/massadmin_in-$srv");
			$shell->output_log("$TMPDIR/massadmin_out-$srv");
			chmod($TMPPERM, "$TMPDIR/massadmin_in-$srv", "$TMPDIR/massadmin_out-$srv");
		}
		$shell->errmode('return');
		my ($prematch, $match) = $shell->waitfor($LOG_PROMPT);
		if ($match =~ /REMOTE HOST IDEN/) {
			warn "Error: Please fix ~/.ssh/know_hosts for host $srv\n";
			$shell->close();
			$ssh->close();
			undef $ssh;
			return;
		}
		if ($match =~ /\(yes\/no\)\?\s*/) {
			$shell->print("yes");
			($prematch, $match) = $shell->waitfor($LOG_PROMPT);
		}
		# Send Authentication
		if ($match =~ /ogin:\s*/) {
			$shell->print("$ogin[$i]");
			($prematch, $match) = $shell->waitfor($LOG_PROMPT);
		}
		if ($match =~ /assword:\s*/) {
			$shell->print("$pass[$i]");
		}
		($prematch, $match) = $shell->waitfor($SFTP_PROMPT);
		if ($match =~ /(Permission denied|assword: $)/) {
			warn "Error: can't authenticate via SFTP to host $srv (login: $ogin[$i], password: ...)\n";
			$shell->close();
			$ssh->close();
			undef $ssh;
			next;
		}
		foreach my $command ( @toexec ) {
			if ( $command =~ s/\|[\s\t]*$// ) {
				push(@return, "\tExecuting command $command\n") if ($DEBUG);
				push(@return, $shell->cmd(String => $command, Prompt => $SSH_PROMPT));
			} else {
				push(@return, "\tExecuting command $command\n") if ($DEBUG);
				$shell->print($command);
				$shell->waitfor($SFTP_PROMPT);
			}
		}
		$shell->close();
		$ssh->close();
		undef $ssh;
		last;
	}
	map { s/
//gs; } @return;

	@return;
}

