#!/usr/bin/perl -w
# usage: remote [options] hosts ...
#  type password(s)
# -c commands - specify the commands to execute (on remote hosts)
# -f file - specify a file containing the commands to execute
# -t timeout - specify a timeout in seconds after which it will give up (default is never)
# -C connect_timeout - specify a connection timeout in seconds after which it will give up (default is 20sec)
# -T - don't use a "template" for the root password(s)
# -E - don't automatically add an "exit" command at the end
# -p prefix - prefix for log files (default '')
# -d - debug mode, print all expect guff (still executes it too)
# -H - "no history" mode!!!
# -R - don't need root access, so don't do an "su"
# -u - specify user for initial ssh login (default "dev")
# -F n - max number of children to fork (default 40) - actually creates more
#	than one process for each of these (expect, ssh)
# -s - synchronize - try to send the messages to all machines at the same time,
#	simply sleeps for two seconds at the moment
# -m - "multiple root passwords" - in this case, the "hosts" arguments must be
#	in the form "host pass_no host pass_no ...", where pass_no is a
#	ordinal number starting from 0 - it will prompt for each password in turn

use strict;
use Getopt::Std;
use IO::File;
use vars qw/$opt_c $opt_f $opt_t $opt_T $opt_E $opt_p $opt_d $opt_H $opt_R $opt_u $opt_F $opt_s $opt_m $opt_C/;

$SIG{PIPE} = sub { print STDERR "WARNING: SIGPIPE!\n"; };

getopts('c:f:t:Ep:dHRu:F:smTC');

$opt_F ||= 40;

my @hosts;
my $n_passwds = 1;
my %rootpasswd_no;
if ($opt_m) {
	while (@ARGV) {
		my ($host, $rootpasswd_no) = splice @ARGV, 0, 2;
		push @hosts, $host;
		$rootpasswd_no{$host} = $rootpasswd_no;
		$n_passwds = $rootpasswd_no+1 if $rootpasswd_no+1 > $n_passwds;
	}
	use Data::Dumper;
} else {
	@hosts = @ARGV; @ARGV = ();
	for (@hosts) {
		$rootpasswd_no{$_} = 0;
	}
}

my $user = $opt_u || 'dev';
my $history = $opt_H ? qq{send "unset HISTFILE\\r"} : "";
my $timeout = defined $opt_t ? $opt_t : -1;
my $connect_timeout = defined $opt_C ? $opt_C : 20;
my $prefix = defined $opt_p ? "$opt_p" : "";
-t STDIN && system "stty -echo" and die;
print STDERR "$user password: ";
chomp(my $userpasswd = <STDIN>);
$userpasswd =~ s/\$/\\\$/g;  # this escaping is not comprehensive - need for " also, at least - repeated below
print "\n";
my @rootpasswdtempl;
unless ($opt_R) {
for (0..$n_passwds-1) {
print STDERR "root password ($_): ";
chomp($rootpasswdtempl[$_] = <STDIN>);
print "\n";
}
}
-t STDIN && system "stty echo" and die;
my $commands = $opt_c || $opt_f && do{local $/; IO::File->new($opt_f, "r")->getline} || do {local $/; <>};
$commands =~ s/\\/\\\\/g;
$commands =~ s/\$/\\\$/g;
$commands =~ s/\[/\\\[/g;
$commands =~ s/\]/\\\]/g;
$commands =~ s/\"/\\"/g;
$commands .= "\nexit\n" unless $opt_E;
$commands =~ s/^(.+)$/send "$1\\r"/gm;

while (@hosts) {
	do_batch(splice @hosts, 0, $opt_F);
}

print STDERR "complete.\n";

exit 0;

sub do_batch {
my @hosts = @_;
my @children;
for my $host (@hosts) {
	my $become_root = "";
	unless ($opt_R) {
		my $rootpasswd;
		$_ = $host; $rootpasswd = $rootpasswdtempl[$rootpasswd_no{$host}];
		$rootpasswd = eval $rootpasswd unless $opt_T;
		$rootpasswd =~ s/\$/\\\$/g;
		$become_root = <<End;
send "exec su\\r";
expect {
	timeout { exit 1 }
	"Password:"
}
send "$rootpasswd\\r"
expect {
	timeout { exit 1 }
	eof { exit 1 }
	-re "\\\\\\\$.*" {
		send "exit\\r"
		expect eof
		exit 1
	}
	-re "#.*"
}
$history
End
	}
	my $expect = <<End;
set timeout $connect_timeout
spawn ssh -l $user $host
expect {
	timeout { exit 1 }
	eof { exit 1 }
	-re "\\\\?.*" {
		send "yes\\r"
		expect {
			timeout { exit 1 }
			eof { exit 1 }
			"password: "
		}
	}
	"password: "
}
send "$userpasswd\\r";
expect {
	timeout { exit 1 }
	eof { exit 1 }
	"password: " { exit 1 }
	-re "\\\\\\\$.*"
}
$history
$become_root
set timeout $timeout
End
print $expect if $opt_d;
print STDERR "$host";
my $fh = IO::File->new("|expect > $prefix$host");
$fh->autoflush;
print STDERR ".";
push @children, $fh;
print $fh $expect;
print STDERR ".\n";
}
sleep 2 if $opt_s;
for my $fh (@children) {
	print "sending commands\n";
	print $fh "$commands\nexpect eof\n";
}
for my $fh (@children) {
	close $fh;
}

}
