#!/usr/bin/perl # # multi-rsh version 1.2 # Copyright © 2004,2005 Thomas A. Fine # Freely redistribute for any purpose, in whole or part, provided this # copyright/license statement is preserved. # # online docs at http://hea-www.harvard.edu/~fine/Tech/multi-rsh.html # # email to my last name at head.cfa.harvard.edu with questions and # comments. # #Version 1.0 - 4/26/04 - Initial Version #Version 1.1 - 5/26/05 - Bug Fix: non-existent host translated to localhost #Version 1.2 - 12/29/06 - Bug Fix: typo at line 90, wrong var in chop # if ($> != 0) { print "I have to run as root!\n"; exit(1); } $|=1; use Socket; @killlist=(HUP,INT,TERM,KILL); $SIG{'INT'}=report; if ($#ARGV < 1) { &usage; exit(1); } $numconns=20; $timeout=15; $user=(getpwuid($>))[0]; while ($ARGV[0] =~ /^-/) { $opt=shift(@ARGV); if ($opt eq "-p") { $numconns=shift(@ARGV); } elsif ($opt eq "-t") { $timeout=shift(@ARGV); } elsif ($opt eq "-u") { #only allow this if the real user is root. if ($< == 0) { $user=shift(@ARGV); } else { print "the -u option requires root priveleges\n"; exit(1); } } elsif ($opt eq "-d") { $debug=1; } elsif ($opt eq "-l") { $inputlist=shift(@ARGV); } else { print "ERROR: unknown option \"$opt\"\n"; &usage; exit(1); } } if (length($inputlist)) { open(LIST,$inputlist); while() { chop; push(@hostlist,$_); } close(LIST); if ($#hostlist == -1) { print "ERROR: empty host list\n"; exit(1); } $command=join(" ",@ARGV); } else { $command=shift(@ARGV); @hostlist=@ARGV; #expand a single-arg list into a real list if ($#hostlist == 0) { @tmp=split(' ',$hostlist[0]); if ($#tmp>0) { @hostlist=@tmp; } } } $myhost=`hostname`; chop($myhost); # make initial connections for ($contact=0, $i=0; $i<$numconns && $i<=$#hostlist; ++$i, ++$contact) { ($handle[$i],$pid[$i])=&myrshinit($hostlist[$contact],$user,$command); $hostslot[$i]=$hostlist[$contact]; $when[$i]=time; $killnum[$i]=0; ++$numhits; } # select loop while ($numhits) { $rin=""; #set up mask bits $num=0; for ($i=0; $i<$numconns; ++$i) { next if (!defined($handle[$i])); vec($rin,fileno($handle[$i]),1)=1; ++$num; } #print "$num filehandles currently open!\n"; ($nfound,$tym)=select($rout=$rin, undef, undef, 1); #check each bit in mask for ($i=0; $i<$numconns; ++$i) { next if (!defined($handle[$i])); if (vec($rout,fileno($handle[$i]),1)) { if (eof($handle[$i])) { close($handle[$i]); --$numhits; if ($contact <= $#hostlist) { ($handle[$i],$pid[$i])=&myrshinit($hostlist[$contact],$user,$command); $hostslot[$i]=$hostlist[$contact]; $when[$i]=time; $killnum[$i]=0; ++$contact; ++$numhits; #print "reopened $i\n"; } else { $handle[$i]=undef; #print "finshed $i\n"; } } else { #$junk=<$handle[$i]>; #$input{$hostslot[$i]}.=$junk; #print "$hostslot[$i]: $junk"; if (! read($handle[$i],$buf,8192)) { print "read ERROR: $!\n"; } $buf =~ s/\n/\n$hostslot[$i]: /g; $buf =~ s/$hostslot[$i]: $//g; print "$hostslot[$i]: $buf"; #don't timeout if it's already answered $when[$i]=0; } } elsif ($when[$i] && (time-$when[$i] > $timeout)) { if ($debug) { print STDERR "timing out $hostslot[$i]\n"; } if (! kill(0,$pid[$i])) { print "$hostslot[$i]: dead\n"; } elsif ($killnum[$i]>$#killlist) { print "$hostslot[$i]: multi-rsh timeout exceeded, no response to kills\n"; } else { print "$hostslot[$i]: multi-rsh timeout exceeded, attempting kill $killlist[$killnum[$i]] $pid[$i]\n"; kill($killlist[$killnum[$i]],$pid[$i]); ++$killnum[$i]; } } } } # # END OF MAIN # sub rshinit { local($host,$user,$command)=($_[0],$_[1],$_[2]); local *FH; # DON'T use -n as it causes rsh sockets to exit non-cleanly and temporarily # blocks that port -- this is bad because this script can conceivably tie # up all free secure ports. #$procid=open(FH,"rsh -l $user $host $command 2>&1 |"); if (! ($procid=open(FH,"-|")) ) { setpgrp(0,0); close(STDERR); open(STDERR,">&STDOUT"); exec("/usr/ucb/rsh", "-l", $user, $host, $command); } if ($debug) { print "$host contacted\n"; } return (*FH,$procid); } sub myrshinit { local($host,$user,$command)=($_[0],$_[1],$_[2]); local($proto,$iaddr,$there); local *FH; if (! ($procid=open(FH, "-|")) ) { $rshport=514; setpgrp(0,0); #close(STDERR); #open(STDERR, ">&STDOUT"); select(STDOUT); $|=1; $myaddr = inet_aton($myhost); $iaddr = inet_aton($host); if (length($iaddr) == 0) { print "ERROR: Host not found\n"; exit(1); } $there = sockaddr_in($rshport, $iaddr); $proto = getprotobyname("tcp"); if (!socket(S,PF_INET,SOCK_STREAM,$proto)) { print STDERR "socket: $!\n"; exit(1); } # we don't want this - leads to addr in use errors on connect # if local and remote host/ports are all the same! # if (!setsockopt(S, SOL_SOCKET, SO_REUSEADDR, pack("l", 1))) { # print STDERR "setsockopt: $!\n"; exit(1); # } $done=0; $myport=1023; while(!$done) { $here = sockaddr_in($myport, $myaddr); if (bind(S, $here)) { #print "using local port $myport\n"; $done=1; } if (--$myport == 0) { print "$host- ERROR: Can't get a port\n"; exit(1); } } if (!connect(S,$there)) { print STDERR "$host- connect: $!\n"; exit(1); } select(S); $|=1; select(STDOUT); #send error port (zero means send stderr on same channel as stdout) #print S "$errport"; print S "0\0$user\0$user\0$command\0"; ##source user #print S "$user\0"; ##destination user #print S "$user\0"; ##command #print S "$command\0"; #rsh verifies the complete connection by returning a null character #But we don't want to eat that here; we want to let the main process #handle it, so that it can have separate timeouts for the connection #and the program. Well, yeah, but none of that is implemented yet. read(S,$buf,1); while(read(S,$buf,8192)) { print $buf; } exit(0); } if ($debug) { print "$host contacted\n"; } return (*FH,$procid); } sub usage { print "Usage: multi-rsh [-p n] [-t n] [-u user] command host [host ...]\n"; print " multi-rsh [-p n] [-t n] [-u user] -l inputlist command\n"; print " In the first form, command must be a single argument\n"; print " In the second form, command can be multiple args\n"; print " The second form reads a list of hosts from inputfile\n"; print " (- may be used to indicate standard input)\n"; print " -p sets number of parallel connections (default 20)\n"; print " -t sets timeout in seconds before giving up (default 15)\n"; print " -u sets the remote user (defaults to effective uid)\n"; } sub report { local($i); for ($i=0; $i<$numconns; ++$i) { printf("%2d: %20s %d\n", $i, $hostslot[$i], $when[$i]?time-$when[$i]:-1); } }