Advertisement
shirotenshi

mass.pl

Oct 8th, 2016
3,844
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 25.17 KB | None | 0 0
  1. #!/usr/bin/perl
  2. # This program is free software, released under the Artistic license.
  3. # Scott Cruzen <sic@lerp.com>
  4.  
  5. # Requires (probably works with older versions of most of these):
  6. # Expect-1.10
  7. # IO-Tty-0.04
  8. # IO-Stty-.02
  9. # TermReadKey-2.14
  10. # Parse-RecDescent-1.94
  11.  
  12. use strict;
  13. use Expect;
  14. use Getopt::Long;
  15. use Term::ReadKey;
  16. use POSIX qw(strftime);
  17. use Parse::RecDescent;
  18. use Text::ParseWords qw(shellwords);
  19.  
  20. use Data::Dumper;
  21.  
  22. $| = 1;
  23.  
  24. push @INC, ".";
  25. push @INC, "/usr/local/etc";
  26.  
  27. eval { require "names.pl"; };
  28.  
  29. warn $@ if $@;
  30.  
  31. my $usage = << "END";
  32. mass.pl version 1.14
  33. $0 --script script --machines machine1,...,machineN
  34. --name namedlist --send file --retrieve file --ssh [1 or 2]
  35. --noroot --sshpass --sshphrase --su --sudo --user username --bd --noping
  36. --ssh [1 or 2] --pingonly
  37.  
  38. general opts:
  39. --script script\t\tspecify the name of the script to run on the targets
  40. --machines machines\toperate on this comma separated list of machines
  41. --name namedlist\tuse the named list of machines from names.pl
  42. --send files\t\tsend this comma separated list of files
  43. --retrieve files\tscp these files back after executing the script
  44. --numchild n\t\tnumber of processes to fork, default min of 10 or number of targets
  45. --verbose\t\tshow slightly more output
  46. --debug\t\t\tshow more debugging output
  47.  
  48. auth opts:
  49. --noroot\t\tdon't try to use su to become root
  50. --sshpass\t\task for password to authenticate to ssh with
  51. --sshphrase\t\task for passphrase to authenticate to ssh with
  52. --su\t\t\tbecome root via su instead of sudo
  53. --sudo\t\t\tbecome root via sudo instead of su
  54. --user username\t\tspecify the user we should scp and ssh as
  55. --bd\t\t\tbackdoor, equal to --noroot --user root --sshpass
  56.  
  57. misc opts:
  58. --ssh option\t\tspecify ssh options (version etc)
  59. --retonly\t\tonly retrieve files, no script
  60. --pingonly\t\tonly ping hosts
  61. --nocleanup\t\tdon't delete sent files
  62. --dry\t\t\tlike --pingonly --noping
  63. --groups\t\tonly display host groups
  64. --export\t\tlike dry but output export statement for bash, to be used via eval
  65. --help\t\t\tthis.
  66. END
  67.  
  68. my (@send, @retrieve, @machines, @noclean);
  69. my (@failed, @passed, @fatal);
  70. my ($name, $user, $script, $passwd, $noping, $noroot, $nocleanup, $retonly);
  71. my ($sshopts, $usesu, $dry, $groups, $export, $domain);
  72. my ($sshpass, $bd, $sshphrase, $pingonly, $shbang, $prog);
  73. my ($retrievedir, $reportsdir, $do, $verbose, $debug, $help);
  74. my $usesudo = 1;
  75. my $retryct = 3;
  76. my $numchild = 10;
  77.  
  78. #######################################################################
  79. # process command line options
  80.  
  81. my @args = @ARGV;
  82.  
  83. GetOptions( "script=s" => \$script, "machines=s" => \@machines,
  84. "noroot" => \$noroot, "sudo" => \$usesudo, "su" => \$usesu,
  85. "name=s" => \$name, "sshpass" => \$sshpass, "user=s" => \$user,
  86. "send:s" => \@send, "retrieve:s" => \@retrieve, "bd" => \$bd,
  87. "ssh:s" => \$sshopts, "noping" => \$noping, "sshphrase" => \$sshphrase,
  88. "retonly" => \$retonly, "pingonly" => \$pingonly, "nocleanup" => \$nocleanup,
  89. "dry" => \$dry, "groups" => \$groups, "export" => \$export,
  90. "domain=s" => \$domain, "numchild:s" => \$numchild, "do" => \$do,
  91. "verbose+" => \$verbose, "debug" => \$debug, "help" => \$help,
  92. "retryct=s" => \$retryct );
  93.  
  94. my @scriptargs = @ARGV;
  95.  
  96. if ($ENV{MASS_HOME} && -d $ENV{MASS_HOME}) {
  97. $reportsdir = "$ENV{MASS_HOME}/reports";
  98. $retrievedir = "$ENV{MASS_HOME}/retrieve";
  99. } else {
  100. $reportsdir = "reports";
  101. $retrievedir = "retrieve";
  102. }
  103.  
  104.  
  105. my $grammar = <<'EOF';
  106. { no strict "refs";
  107. my @stack;
  108. sub process {
  109. my ($A, $B, $op) = @_;
  110. my $return;
  111. my (%count, %seen);
  112. my (@isect, @diff, @union, @aonly);
  113.  
  114. foreach my $e (@$A) { $count{$e}++ }
  115. foreach my $e (@$B) { $count{$e}++; $seen{$e}++ }
  116.  
  117. if ($op eq '+' || $op eq ',') {
  118. foreach my $e (keys %count) {
  119. push(@union, $e);
  120. }
  121. $return = \@union;
  122. }
  123. if ($op eq '/') {
  124. foreach my $e (@$A) {
  125. push(@aonly, $e) unless exists $seen{$e};
  126. }
  127. $return = \@aonly;
  128. }
  129. if ($op eq '%') {
  130. foreach my $e (keys %count) {
  131. push @{ $count{$e} == 2 ? \@isect : \@diff }, $e;
  132. }
  133. $return = \@diff;
  134. }
  135. if ($op eq '*') {
  136. foreach my $e (keys %count) {
  137. push @{ $count{$e} == 2 ? \@isect : \@diff }, $e;
  138. }
  139. $return = \@isect;
  140. }
  141. return $return;
  142. }
  143. }
  144. startrule: expression /^\Z/ { $return = $item{expression}; }
  145. expression:
  146. '(' expression ')' exptail {
  147. $return = $item{expression};
  148. my $elem;
  149. while ($elem = pop @stack) {
  150. $return = process($return,$elem->{a},$elem->{op});
  151. }
  152. 1;
  153. } |
  154. identifier exptail {
  155. $return = $item{identifier};
  156. my $elem;
  157. while ($elem = pop @stack) {
  158. $return = process($return,$elem->{a},$elem->{op});
  159. }
  160. 1;
  161. }
  162. exptail:
  163. operator '(' expression ')' exptail {
  164. my $A = $item{expression};
  165. my $op = $item{operator};
  166.  
  167. push @stack, {op => $op, a => $A };
  168. } |
  169. operator identifier exptail {
  170. my $A = $item{identifier};
  171. my $op = $item{operator};
  172.  
  173. push @stack, {op => $op, a => $A };
  174. } |
  175. {1}
  176. identifier: idre {
  177. my $id = $item{idre};
  178. $id =~ s/\\-/-/g;
  179. $id =~ s/'//g;
  180. if ($id =~ s/^@//) {
  181. $return = [$id];
  182. } else {
  183. my $tmp = $Names::names{$id};
  184. if (@$tmp) {
  185. $return = [@$tmp];
  186. } else {
  187. $return = [$id];
  188. }
  189. }
  190. }
  191. idre: /('\@?[a-z][\w-\.]*')|(\@?[a-z](\.|\w|-)*)/i
  192. operator: /[,%*+\/]/
  193. EOF
  194.  
  195. if ($dry or $export) {
  196. $pingonly = 1;
  197. $noping = 1;
  198. }
  199.  
  200. die $usage unless $pingonly || $script || ($retonly && @retrieve);
  201. die $usage if $help;
  202.  
  203. sub parsefile {
  204. my ($script) = @_;
  205. local *FILE;
  206. open FILE, $script;
  207. $shbang = <FILE>;
  208. die "$script doesn't look like a shell script" if ($shbang !~ /^#!/);
  209. if ($shbang =~ /^#!\s*(\/.*)$/) {
  210. $prog = $1;
  211. } else {
  212. die "failed to extract program name from $shbang\n";
  213. }
  214.  
  215. # scan the script for any '# send:' or '# retrieve:" lines
  216. while (<FILE>) {
  217. /^\# name:\s+(.*)/ && do { die "multiple name expressions!" if $name; $name = $1; };
  218. /^\# send:\s+(.*)/ && do { push @send, shellwords($1); };
  219. /^\# noclean:\s+(.*)/ && do { push @noclean, shellwords($1); };
  220. /^\# retrieve:\s+(.*)/ && do { push @retrieve, shellwords($1); };
  221. /^\# include:\s+(.*)/ && do {
  222. my ($include) = shellwords($1);
  223. push @send, $include;
  224. parsefile($include);
  225. };
  226. }
  227. }
  228.  
  229. # check that $script starts with #!
  230. unless ($pingonly or $retonly or $do) {
  231. parsefile($script);
  232. }
  233.  
  234. if ($name) {
  235. $::RD_HINT = 1;
  236. my $p = new Parse::RecDescent($grammar) or die "Compile error\n";
  237. my $ret = $p->startrule(\$name);
  238. die "Syntax error in name expression\n" unless defined $ret;
  239. @machines = sort(@$ret,@machines);
  240. }
  241.  
  242. if ($groups) {
  243. for (sort(keys %Names::names)) {
  244. chomp();
  245. print "$_, " if $_ and scalar($Names::names{$_}) > 0;
  246. }
  247. print "\n";
  248. exit();
  249. }
  250.  
  251. die $usage unless @machines;
  252.  
  253. unless ($user) { $user = $ENV{LOGNAME}; }
  254.  
  255. push @send, $script unless $do;
  256.  
  257. #$script =~ s/.*\///;
  258.  
  259. if ($bd) {
  260. $noroot = $sshpass = $bd;
  261. $user = "root";
  262. }
  263.  
  264. if ($usesu) {
  265. $usesudo = 0;
  266. }
  267.  
  268. @send = map(quotemeta, split(/,/,join(',',@send)));
  269. @noclean = map(quotemeta, split(/,/,join(',',@noclean)));
  270. @retrieve = map(quotemeta, split(/,/,join(',',@retrieve)));
  271. @machines = split(/,/,join(',',@machines));
  272.  
  273. @machines = map { "$_.$domain" } @machines if $domain;
  274.  
  275. sub unesc {
  276. return [shellwords($_)]->[0];
  277. }
  278.  
  279. my %fixmodes = ();
  280.  
  281. for (@send, @noclean) {
  282. my $fn = unesc($_);
  283. die "Couldn't find $fn\n" unless -f $fn;
  284. my (undef,undef,$mode) = stat $fn;
  285. chmod 0644, $fn;
  286. #print "chmod 0644, $fn\n";
  287. $fixmodes{$fn} = $mode & 07777;
  288. }
  289.  
  290. #######################################################################
  291. # get the root/sudo password
  292. unless ($pingonly) {
  293. $passwd = getpass($usesudo ? "sudo password: " : "su password: ") unless $noroot;
  294. $sshpass = getpass("ssh password: ") if $sshpass;
  295. $sshphrase = getpass("ssh passphrase: ") if $sshphrase;
  296. } elsif ($dry) {
  297. $script = "dry";
  298. } else {
  299. $script = "pingonly";
  300. }
  301.  
  302. if ($export) {
  303. print "declare -a masstargets; masstargets=(".(join ' ',@machines).")\n";
  304. exit 0;
  305. }
  306.  
  307. #######################################################################
  308. # iterate over the specified machines
  309.  
  310. if (scalar @machines < $numchild) {
  311. $numchild = scalar @machines;
  312. }
  313.  
  314. ####
  315. # Setup auto-reaping of dead children
  316.  
  317. my $waitedpid;
  318.  
  319. sub REAPER {
  320. $waitedpid = wait;
  321. # print STDERR "reaped $waitedpid\n";
  322. }
  323.  
  324. ####
  325. # Make child processes
  326.  
  327. my %pipes;
  328.  
  329. use POSIX;
  330.  
  331. print "trying to run $script @scriptargs on ",scalar @machines, " machines\n";
  332. printnice(@machines);
  333.  
  334. if ($dry) {
  335. exit 0;
  336. }
  337.  
  338. for (my $childid = 0; $childid < $numchild; $childid++) {
  339. # print STDERR "forking: $childid $numchild\n";
  340. my ($outrd, $outwr, $inrd, $inwr, $ctlrd, $ctlwr);
  341.  
  342. # out is for child stdout and stderr, it's unfiltered lines of text from the child script
  343. pipe $outrd, $outwr;
  344.  
  345. # in is for parent -> child control, to send each host to the child process
  346. pipe $inrd, $inwr;
  347.  
  348. # ctl is for child -> parent status reporting
  349. pipe $ctlrd, $ctlwr;
  350.  
  351. my $pid = fork;
  352.  
  353. if (!defined $pid) {
  354. die "can't fork: $!\n";
  355. } elsif ($pid) {
  356. # parent
  357. close $inrd;
  358. close $outwr;
  359. close $ctlwr;
  360.  
  361. $pipes{$pid} = { outrd => $outrd, inwr => $inwr, ctlrd => $ctlrd };
  362. } else {
  363. for (keys(%pipes)) {
  364. close $pipes{$_}{outrd};
  365. close $pipes{$_}{inwr};
  366. close $pipes{$_}{ctlrd};
  367. }
  368.  
  369. close $outrd;
  370. close $inwr;
  371. close $ctlrd;
  372.  
  373. dup2(fileno $outwr, 1); # stdout
  374. dup2(fileno $outwr, 2); # stderr
  375.  
  376. my ($buf, $len);
  377.  
  378. while (($len = sysread($inrd, $buf, 4096)) > 0) {
  379. for my $line (split /\n/, $buf) {
  380. my $host;
  381. for ($line) {
  382. /host: (.*)$/ && do {
  383. $host = $1;
  384. syswrite $ctlwr, process($host);
  385. };
  386. }
  387. }
  388. }
  389. sleep 1;
  390. exit;
  391. }
  392. }
  393.  
  394. $SIG{CHLD} = \&REAPER;
  395.  
  396. my %inprogress = map { +"$_" => { attempts => 0 } } @machines;
  397.  
  398. sub done {
  399. my ($targets) = @_;
  400. my @machines = keys %$targets;
  401. grep { ! ($targets->{$_}{done} || $targets->{$_}{fatal} || ($targets->{$_}{attempts} >= $retryct)) } @machines;
  402. }
  403.  
  404. sub nexttarget {
  405. my ($targets, $pid) = @_;
  406. for (keys(%$targets)) {
  407. my $p = $targets->{$_};
  408. if (!$p->{pid} && !$p->{fatal} && !$p->{done} && $p->{attempts} < $retryct) {
  409. $p->{pid} = $pid;
  410. $p->{attempts}++;
  411. print STDERR "next host: $_\n" if ($verbose > 1);
  412. return $_;
  413. }
  414. }
  415. return undef;
  416. }
  417.  
  418. sub updateresult {
  419. my ($targets, $pid, $result) = @_;
  420. for my $host (keys(%$targets)) {
  421. my $p = $targets->{$host};
  422. if ($p->{pid} == $pid) {
  423. delete $p->{pid};
  424. for ($result) {
  425. /^warning:/i && do {
  426. $p->{warning} = $result;
  427. return $host;
  428. };
  429. /^fatal:/i && do {
  430. $p->{fatal} = $result;
  431. return $host;
  432. };
  433. $p->{done} = $result;
  434. return $host;
  435. }
  436. }
  437. }
  438. undef;
  439. }
  440.  
  441. sub cleanout {
  442. my ($host, $buf) = @_;
  443. my $capture;
  444. for (split /\n/, $buf) {
  445. /^script done/ && do { $capture = 0; };
  446. print "$host: $_\n" if $capture or ($verbose > 2);
  447. /echo script\\ done/ && do { $capture = 1; };
  448. }
  449. }
  450.  
  451. ####
  452. # Select on the child process' pipes
  453.  
  454. my ($rin, $rout, $win, $wout);
  455.  
  456. for (keys(%pipes)) {
  457. vec($rin, fileno($pipes{$_}{outrd}), 1) = 1;
  458. vec($rin, fileno($pipes{$_}{ctlrd}), 1) = 1;
  459.  
  460. vec($win, fileno($pipes{$_}{inwr}), 1) = 1;
  461. }
  462.  
  463. my %output = ();
  464. my $nready;
  465.  
  466. my @remaining = done(\%inprogress);
  467. my $count = scalar @remaining;
  468.  
  469. while (scalar (@remaining = done(\%inprogress)) > 0) {
  470. if ($verbose > 1 && scalar @remaining != $count) {
  471. my $left;
  472. if ($count < 5) {
  473. $left = join ',', @remaining;
  474. }
  475. print STDERR "remaining: $count $left\n";
  476. $count = scalar @remaining;
  477. }
  478.  
  479. $nready = select($rout=$rin, $wout=$win, undef, 1);
  480. #print STDERR "select: $nready\n";
  481. next if $nready <= 0;
  482.  
  483. LUP: for my $pid (keys(%pipes)) {
  484. if (vec($wout, fileno($pipes{$pid}{inwr}), 1) == 1) {
  485. # print STDERR "writable\n";
  486.  
  487. if (!$pipes{$pid}{host}) {
  488. vec($win, fileno($pipes{$pid}{inwr}), 1) = 0;
  489.  
  490. my $host = nexttarget(\%inprogress, $pid);
  491. my $fh = $pipes{$pid}{inwr};
  492. if (!$host) {
  493. #print STDERR "close $pid fh\n";
  494. close($fh);
  495. } else {
  496. $pipes{$pid}{host} = $host;
  497. syswrite($fh, "host: $host\n");
  498. }
  499. }
  500. }
  501.  
  502. my $host = $pipes{$pid}{host};
  503.  
  504. if (vec($rout, fileno($pipes{$pid}{ctlrd}), 1)) {
  505. my $fh = $pipes{$pid}{ctlrd};
  506. my ($buf, $len);
  507.  
  508. if (($len = sysread($fh, $buf, 4096)) == 0) {
  509. #print STDERR "parent failed to read from ctrl pipe for $pid\n";
  510. #print STDERR Dumper $pipes{$pid};
  511. vec($rin, fileno($pipes{$pid}{ctlrd}), 1) = 0;
  512. } else {
  513. #print STDERR "result: $buf\n";
  514. my $finished = updateresult(\%inprogress, $pid, $buf);
  515. # print "finished: $finished\n";
  516. if (exists $output{$pid} && exists $output{$pid}{$host}) {
  517. cleanout($host, join '', @{$output{$pid}{$host}});
  518. }
  519.  
  520. # clear host, so that this child can work on the next target
  521. delete $pipes{$pid}{host};
  522.  
  523. # add the input write pipe back to the watched write fds
  524. vec($win, fileno($pipes{$pid}{inwr}), 1) = 1;
  525. }
  526. }
  527.  
  528. if (vec($rout, fileno($pipes{$pid}{outrd}), 1)) {
  529. my $fh = $pipes{$pid}{outrd};
  530. my ($buf, $len);
  531.  
  532. if (($len = sysread($fh, $buf, 4096)) == 0) {
  533. vec($rin, fileno($pipes{$pid}{outrd}), 1) = 0;
  534. } else {
  535. print "output: $buf\n" if $debug;
  536. push @{$output{$pid}{$host}}, $buf;
  537. }
  538. }
  539. }
  540. }
  541.  
  542. #print STDERR "select done: $nready\n";
  543.  
  544. my %byhost;
  545.  
  546. for my $pid (keys(%output)) {
  547. for my $host (keys(%{$output{$pid}})) {
  548. my $buf = join '', @{$output{$pid}{$host}};
  549. $byhost{$host} = $buf;
  550. }
  551. }
  552.  
  553. #for my $host (sort(keys(%byhost))) {
  554. # my $buf = $byhost{$host};
  555. ## print map { "$host: $_\n" } split /\n/, $buf;
  556. ## print "$host:\n$buf\n\n";
  557. ## print map { "$host: $_\n" }
  558. # cleanout($host, $buf);
  559. #}
  560.  
  561. sub process {
  562. my ($host) = @_;
  563. # print "host: $host\n";
  564. my $ret = eval { dostuff($host, $script, \@scriptargs, $user, [@send], [@noclean], [@retrieve]); };
  565. $@ ? $@ : $ret;
  566. }
  567.  
  568. # zero the root password in memory (pointless)
  569. $passwd =~ s/./z/g;
  570. $sshpass =~ s/./z/g;
  571. $sshphrase =~ s/./z/g;
  572.  
  573. for (keys(%inprogress)) {
  574. if ($inprogress{$_}{warning}) {
  575. push @failed, $_;
  576. }
  577. if ($inprogress{$_}{fatal}) {
  578. push @fatal, $_;
  579. }
  580. if ($inprogress{$_}{done}) {
  581. push @passed, $_;
  582. }
  583. }
  584.  
  585. print scalar @passed, " passed ";
  586. # really don't like this message with large numbers of hosts
  587. if ($verbose > 1) {
  588. printnice(@passed)
  589. } else {
  590. print "\n";
  591. }
  592.  
  593. print scalar @failed, " failed: ";
  594. printnice(@failed);
  595.  
  596. print scalar @fatal, " fatal errors: ";
  597. printnice(@fatal);
  598.  
  599. # Save the report to a file
  600. mkdir("$reportsdir", 0755) unless -d "$reportsdir";
  601. open FILE, ">>$reportsdir/$script";
  602.  
  603. my $ofh = select(FILE);
  604.  
  605. print scalar localtime(),"\n";
  606. print "args: ", join(' ', @args), "\n";
  607.  
  608. print scalar @passed, " passed: ";
  609. printnice(@passed);
  610.  
  611. print scalar @failed, " failed: ";
  612. printnice(@failed);
  613.  
  614. print scalar @fatal, " fatal errors: ";
  615. printnice(@fatal);
  616. print "\n";
  617.  
  618. select($ofh);
  619.  
  620. for my $fn (keys(%fixmodes)) {
  621. chmod $fixmodes{$fn}, $fn;
  622. #printf "chmod %o, %s\n", $fixmodes{$fn}, $fn;
  623. }
  624.  
  625. exit (@failed + @fatal);
  626.  
  627. #######################################################################
  628. # subroutines
  629.  
  630. # dostuff:
  631. # ping a machine, if there's a response, scp the script specified on
  632. # the command line to the machine (and any --send files), then ssh in,
  633. # su to root, execute the script, close the ssh connection, then scp any
  634. # files specified in --retrieve back and store them in retrieve/$name
  635. #
  636. # returns undef on success or an error string
  637. sub dostuff {
  638. my ($name, $script, $args, $user, $send, $noclean, $retrieve) = @_;
  639. my $file;
  640.  
  641. # ping is suid (usually), so by using system we avoid having
  642. # to run mass.pl as root
  643. if (!$noping && system("ping -n -c 1 -W 1 $name >&2")) {
  644. die "Warning: $name doesn't respond to ping\n";
  645. }
  646.  
  647. if ($pingonly) {
  648. return;
  649. }
  650.  
  651. if ($retonly) {
  652. goto retrieveonly;
  653. }
  654. #$Expect::Exp_Internal = 1;
  655.  
  656. my @t;
  657. if (scalar @$noclean + scalar @$send > 0) {
  658. push @t, "scp";
  659. push @t, "-o $sshopts" if $sshopts;
  660. push @t, map(unesc,@$noclean), map(unesc,@$send), "$user\@$name:";
  661.  
  662. my $scp = Expect->spawn(@t) ||
  663. die "Warning: failed to start scp on $name\n";
  664.  
  665. $scp->expect(undef,
  666. [ 'Are you sure you want to continue ',
  667. sub {
  668. print $scp "yes\r"; exp_continue;
  669. },
  670. ],
  671. [ 'Enter passphrase ',
  672. sub {
  673. $scp->log_group(undef);
  674. $scp->log_stdout(undef);
  675. print $scp "$sshphrase\r";
  676. $scp->expect(0, '');
  677. $scp->clear_accum();
  678. $scp->log_stdout(1);
  679. $scp->log_group(1);
  680. exp_continue;
  681. }
  682. ],
  683. [ qr/password/i,
  684. sub {
  685. $scp->log_group(undef);
  686. $scp->log_stdout(undef);
  687. print $scp "$sshpass\r";
  688. $scp->expect(0, '');
  689. $scp->clear_accum();
  690. $scp->log_stdout(1);
  691. $scp->log_group(1);
  692.  
  693. exp_continue;
  694. }
  695. ],
  696. [ 'WARNING: REMOTE HOST',
  697. sub {
  698. die "Fatal: scp failed on $name\n";
  699. }
  700. ],
  701. [ 'Permission denied',
  702. sub {
  703. die "Warning: scp failed on $name\n";
  704. }
  705. ],
  706. [ 'Connection closed by remote host',
  707. sub {
  708. die "Warning: scp failed on $name\n";
  709. }
  710. ],
  711. [ 'lost connection',
  712. sub {
  713. die "Warning: scp failed on $name\n";
  714. }
  715. ]
  716. );
  717.  
  718. $scp->hard_close();
  719. }
  720.  
  721. @t = qw(ssh -x);
  722. push @t, "-o $sshopts" if $sshopts;
  723. push @t, "-t", "-l", $user, $name, 'PS1=$\ ', "sh";
  724.  
  725. my $ssh = Expect->spawn(@t) ||
  726. die "Warning: ssh failed on $name\n";
  727.  
  728. #$Expect::Exp_Internal = 1;
  729.  
  730. $ssh->expect(120,
  731. [ 'Are you sure you want to continue ',
  732. sub {
  733. print $ssh "yes\r"; exp_continue;
  734. },
  735. ],
  736. [ 'Permission denied, please try again.',
  737. sub {
  738. die "Warning: ssh failed on $name\n";
  739. }
  740. ],
  741. [ 'Connection closed by remote host',
  742. sub {
  743. die "Warning: ssh failed on $name\n";
  744. }
  745. ],
  746. [ 'lost connection',
  747. sub {
  748. die "Warning: ssh failed on $name\n";
  749. }
  750. ],
  751. [ 'Error reading response length from authentication socket.',
  752. sub {
  753. die "Warning: ssh failed on $name\n";
  754. }
  755. ],
  756. [ 'Enter passphrase ',
  757. sub {
  758. $ssh->log_group(undef);
  759. $ssh->log_stdout(undef);
  760. print $ssh "$sshphrase\r";
  761. $ssh->expect(0, '');
  762. $ssh->clear_accum();
  763. $ssh->log_stdout(1);
  764. $ssh->log_group(1);
  765. exp_continue;
  766. }
  767. ],
  768. [ qr/password/i,
  769. sub {
  770. $ssh->log_group(undef);
  771. $ssh->log_stdout(undef);
  772. print $ssh "$sshpass\r";
  773. $ssh->expect(0, '');
  774. $ssh->clear_accum();
  775. $ssh->log_stdout(1);
  776. $ssh->log_group(1);
  777. exp_continue;
  778. }
  779. ],
  780. [ '[#$] $',
  781. # at this point we're logged in
  782. sub {
  783. my $fqdn;
  784. unless ($name =~ /^\d+\.\d+.\d+\.\d+$/) {
  785. $fqdn = `host $name | head -1`;
  786. $fqdn = $name unless $? >> 8;
  787. $fqdn =~ s/^(\S*) has.*/$1/;
  788. chomp($fqdn);
  789. print "\n\nrunning: HOSTNAME=$fqdn; export HOSTNAME;\n\n";
  790. print $ssh "HOSTNAME=$fqdn; export HOSTNAME;";
  791. }
  792. print $ssh "PS1='\$ '; PATH=/usr/local/bin:".
  793. "/bin:/usr/bin:/usr/sbin:/usr/local/sbin:".
  794. "/sbin;unset HISTFILE;export PS1;export PATH\r";
  795.  
  796. beroot($ssh, $name) unless $noroot;
  797.  
  798. my $argstr = join ' ', @$args;
  799. print $ssh "$prog $script $argstr && ".
  800. "echo script\\ done || ".
  801. "echo script\\ failed\r";
  802.  
  803. $ssh->clear_accum();
  804. }
  805. ],
  806. [ 'timeout',
  807. sub {
  808. $ssh->hard_close();
  809. die "Warning: No shell prompt on $name\n";
  810. }
  811. ]
  812. );
  813.  
  814. my $err;
  815.  
  816. #$Expect::Exp_Internal = 1;
  817. $ssh->expect(undef,
  818. [ 'script done' ],
  819. [ 'script failed',
  820. sub {
  821. print "failed!\n";
  822. $err = "Fatal: Script $script failed on $name\n";
  823. }
  824. ]
  825. );
  826.  
  827. # cleanup
  828. unless ($nocleanup) {
  829. my @rm = map { s/.*\///; $_; } @$send;
  830. print $ssh "/bin/rm -f @rm && echo rem\\oved\r";
  831.  
  832. $ssh->expect(undef,
  833. [ "removed" ]
  834. );
  835. }
  836.  
  837. $ssh->hard_close();
  838.  
  839. die $err if $err;
  840.  
  841. print "\n\n";
  842.  
  843. retrieveonly:
  844. if (@$retrieve) {
  845. mkdir("$retrievedir", 0755) unless -d "$retrievedir";
  846. mkdir("$retrievedir/$name", 0755) unless -d "$retrievedir/$name";
  847.  
  848. my @t;
  849. push @t, "scp";
  850. push @t, "-o $sshopts" if $sshopts;
  851. push @t, "$user\@$name:@$retrieve";
  852. push @t, "$retrievedir/$name";
  853.  
  854. #print Dumper @t;
  855.  
  856. my $scp = Expect->spawn(@t) ||
  857. die "Warning: scp failed on $name\n";
  858.  
  859. $scp->expect(undef,
  860. [ 'Are you sure you want to continue ',
  861. sub {
  862. print $scp "yes\r"; exp_continue;
  863. },
  864. ],
  865. [ 'Enter passphrase ',
  866. sub {
  867. $scp->log_group(undef);
  868. $scp->log_stdout(undef);
  869. print $scp "$sshphrase\r";
  870. $scp->expect(0, '');
  871. $scp->clear_accum();
  872. $scp->log_stdout(1);
  873. $scp->log_group(1);
  874. exp_continue;
  875. }
  876. ],
  877. [ qr/password/i,
  878. sub {
  879. $scp->log_group(undef);
  880. $scp->log_stdout(undef);
  881. print $scp "$sshpass\r";
  882. $scp->expect(0, '');
  883. $scp->clear_accum();
  884. $scp->log_stdout(1);
  885. $scp->log_group(1);
  886.  
  887. exp_continue;
  888. }
  889. ],
  890. [ 'Permission denied, please try again.',
  891. sub {
  892. die "Warning: scp failed on $name\n";
  893. }
  894. ],
  895. [ 'Connection closed by remote host',
  896. sub {
  897. die "Warning: scp failed on $name\n";
  898. }
  899. ],
  900. [ 'lost connection',
  901. sub {
  902. die "Warning: scp failed on $name\n";
  903. }
  904. ]
  905. );
  906.  
  907. $scp->hard_close();
  908. }
  909.  
  910. 1;
  911. }
  912.  
  913. sub beroot {
  914. my ($ssh, $name) = @_;
  915.  
  916. #$Expect::Exp_Internal = 1;
  917.  
  918. $ssh->clear_accum();
  919.  
  920. if ($usesudo) {
  921. print $ssh "sudo -K ; if [ -x /usr/bin/pfexec ]; then /usr/bin/pfexec sudo sh; else sudo sh; fi\r";
  922. # print $ssh "sudo -K ; sudo sh\r";
  923. } else {
  924. print $ssh "su root -c sh\r";
  925. }
  926.  
  927. # 250 ms sleep
  928. select(undef,undef,undef,0.25);
  929.  
  930. $ssh->expect(undef,
  931. [ 'assword',
  932. sub {
  933. print "sending sudo password\n";
  934. print $ssh "$passwd\r";
  935. }
  936. ],
  937. [ '[$#] $',
  938. sub {
  939. print $ssh "\r";
  940. }
  941. ]
  942. );
  943.  
  944. $ssh->expect(undef,
  945. [ 'not found',
  946. sub {
  947. $ssh->hard_close();
  948. die("Fatal: ".($usesudo ? "sudo" : "su").
  949. " not found on $name\n");
  950. }
  951. ],
  952. [ 'timeout',
  953. sub {
  954. $ssh->hard_close();
  955. die "Fatal: No root prompt on $name\n";
  956. }
  957. ],
  958. [ 'Sorry|incorrect password|Password:',
  959. sub {
  960. $ssh->hard_close();
  961. die "Fatal: No root prompt on $name (bad password)\n";
  962. }
  963. ],
  964. [ '[$#] $',
  965. sub {
  966. print $ssh "unset HISTFILE; PATH=/usr/local/bin:/bin".
  967. ":/usr/bin:/usr/sbin:/sbin:/usr/local/sbin ; ".
  968. "export PATH\r".
  969. "if [ `id|cut -d ' ' -f 1` = 'uid=0(root)' ];".
  970. " then PS1='# '; fi\r";
  971. }
  972. ]
  973. );
  974. #$Expect::Exp_Internal = 0;
  975. }
  976.  
  977. # use format to wrap text
  978. # takes an array as input and prints it comma separated
  979. # for example: n123, n456, n789
  980. sub printnice {
  981. my $list;
  982.  
  983. format Something =
  984. ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ~~
  985. $list
  986. .
  987.  
  988. # format line break characters
  989. local $: = ',';
  990. # input record separator
  991. local $/ = '';
  992. # format name
  993. local $~ = 'Something';
  994.  
  995. $list = join(',',sort(@_));
  996. if ($list) {
  997. write;
  998. } else {
  999. print "\n";
  1000. }
  1001. }
  1002.  
  1003. sub getpass {
  1004. my ($prompt) = @_;
  1005.  
  1006. print STDERR $prompt;
  1007.  
  1008. ReadMode 2; # Turn off echo
  1009.  
  1010. my $pass = <STDIN>;
  1011. chomp $pass;
  1012.  
  1013. ReadMode 0; # Reset tty mode before exiting
  1014.  
  1015. print STDERR "\n";
  1016.  
  1017. return $pass;
  1018. }
  1019.  
  1020. # $Id: mass.pl,v 3.23 2013/07/27 17:09:14 sic Exp $
  1021. # vim:sw=2:ts=2:softtabstop=2:expandtab
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement