3 =head1 NAME $RCSfile: Rexec.pm,v $
5 Execute commands remotely and returning the output and status of the
6 remotely executed command.
13 Andrew DeFaria <Andrew@ClearSCM.com>
21 Mon Oct 9 18:28:28 CDT 2006
25 $Date: 2012/04/07 00:39:48 $
37 my $remote = new Rexec (host => $host);
40 print "Connected using " . $remote->{protocol} . " protocol\n";
43 @lines = $remote->execute ($cmd);
44 $status = $remote->status;
45 print "$cmd status: $status\n";
48 print "$_\n" foreach ($remote->execute ("cat /etc/passwd"));
50 print "Unable to connect to $username\@$host\n";
55 This module provides an object oriented interface to executing remote
56 commands on Linux/Unix system (or potentially well configured Windows
57 machines with Cygwin installed). Upon object creation a connection is
58 attempted to the specified host in a cascaded fashion. First ssh is
59 attempted, then rsh/rlogin and finally telnet. This clearly favors
60 secure methods over those less secure ones. If username or password is
61 prompted for, and if they are supplied, then they are used, otherwise
62 the attempted connection is considered failed.
64 Once connected the caller can use the exec method to execute commands
65 on the remote host. Upon object destruction the connection is
66 shutdown. Output from the remotely executed command is returned
67 through the exec method and also avaiable view the lines
68 method. Remote status is available via the status method. This means
69 you can now more reliably obtain the status of the command executed
70 remotely instead of just the status of the ssh/rsh command itself.
72 Note: Currently no attempt has been made to differentiate output
73 written to stdout and stderr.
75 As Expect is used to drive the remote session particular attention
76 should be defining a regex to locate the prompt. The standard prompt
77 regex (if not specified by the caller at object creation) is
79 qr'[#>:$](\s*|\e.+)$'.
81 This covers most default and common prompts.
83 =head1 Handling Timeouts
85 The tricky thing when dealing with remote execution is attempting to
86 determine if the remote machine has finished, stopped responding or
87 otherwise crashed. It's more of an art than a science! The best one
88 can do it send the command along and wait for a response. But how long
89 to wait is the question. If your wait is too short then you run the
90 risk of timing out before the remote command is finished. If you wait
91 too long then you can be possibly waiting for something that will not
92 be happening because the remote machine is either down or did not
93 behave in a manner that you expected it to.
95 To a large extent this module attempts to mitigate these issues on the
96 principal that remote command execution is pretty well known. You log
97 in and get a prompt. Issue a command and get another prompt. If the
98 prompts are well known and easily determinable things go smoothly.
99 However what happens if you execute a command remotely that will take
100 30 minutes to finish?
102 This module has two timeout values. The first is login timeout. It's
103 assumed that logins should happen fairly quickly. The default timeout
104 for logins is 5 seconds.
106 Command timeouts are set by default to 30 seconds. Most commands will
107 finish before then. If you expect a command to take much longer then
108 you can set an alternate timeout period.
110 You can achieve longer timeouts in several ways. To give a longer
111 login timeout specify your timeout to the new call. To give a longer
112 exec timeout either pass a longer timeout to exec or set it view
113 setTimeout. The current exec timeout is returned by getTimeout.
117 The following routines are exported:
131 our $VERSION = '1.0';
133 # This is the "normal" definition of a prompt. However what's normal?
134 # For example, my prompt it typically the machine name followed by a
135 # colon. But even that appears in error messages such as <host>: not
136 # found and will be mistaken for a prompt. No real good way to handle
137 # this so we define a standard prompt here and allow the caller to
138 # override that. But overriding it is tricky and left as an exercise
141 # Here we have a number of the common prompt characters [#>:%$]
142 # followed by zero or more spaces and end of line.
143 our $DEFAULT_PROMPT = qr'[#>:%$](\s*|\e.+)$';
145 my $default_login_timeout = 5;
146 my $default_exec_timeout = 30;
148 my $debug = $ENV{DEBUG} || 0;
166 my $logfile = "/tmp/rexex_debug.log";
168 open my $file, '>>', $logfile or die "Unable to open $logfile for writing - $!";
170 print $file "DEBUG: $msg\n";
178 my ($logged_in, $timedout, $password_attempts) = 0;
180 $self->{protocol} = 'ssh';
182 my $user = $self->{username} ? "$self->{username}\@" : '';
184 my $remote = Expect->new ("ssh $self->{opts} $user$self->{host}");
186 return unless $remote;
188 $remote->log_user ($debug);
193 # If password is prompted for, and if one has been specified, then
195 [ qr "[P|p]assword: $",
197 # If we already supplied the password then it must not have
198 # worked so this protocol is no good.
199 return if $password_attempts;
203 # If we're being prompted for password and there is no
204 # password to supply then there is nothing much we can do but
205 # return undef since we can't get in with this protocol
206 return unless $self->{password};
208 $exp->send ("$self->{password}\n") if $self->{password};
209 $password_attempts++;
215 # Discard lines that begin with "ssh:" (like "ssh: <host>: not
223 # If we find a prompt then everything's good
230 # Of course we may time out...
239 # It's always hard to find the prompt. So let's make a distintive one
240 $self->{prompt} = '@@@';
241 $self->{handle} = $remote;
243 # OK this is real tricky. If we call execute with a command of PS1=@@@
244 # and we've changed our prompt to '@@@' then we'll see the '@@@' in the
245 # PS1=@@@ statement as the prompt! That'll screw us up so we instead say
246 # PS1=\@\@\@. The shell then removes the extra backslashes for us and sets
247 # the prompt to just "@@@" for us. We catch that as our prompt and now we
248 # have a unique prompt that we can easily recognize.
249 if ($self->{shellstyle} eq 'sh') {
250 $self->execute ('PS1=\@\@\@');
252 $self->execute ('set prompt=\@\@\@');
255 $self->{handle}->flush;
257 } elsif ($timedout) {
258 carp "WARNING: $self->{host} is not responding to $self->{protocol} protocol";
262 carp "WARNING: Unable to connect to $self->{host} using $self->{protocol} protocol";
270 my ($logged_in, $timedout, $password_attempts) = 0;
272 $self->{protocol} = "rlogin";
274 my $user = $self->{username} ? "-l $self->{username}" : "";
276 my $remote = Expect->new ("rsh $user $self->{host}");
278 return unless $remote;
280 $remote->log_user ($debug);
285 # If password is prompted for, and if one has been specified, then
287 [ qr "[P|p]assword: $",
289 # If we already supplied the password then it must not have
290 # worked so this protocol is no good.
291 return if $password_attempts;
295 # If we're being prompted for password and there is no
296 # password to supply then there is nothing much we can do but
297 # return undef since we can't get in with this protocol
298 return unless $self->{password};
300 $exp->send ("$self->{password}\n");
301 $password_attempts++;
307 # HACK! rlogin may return "<host>: unknown host" which clashes
308 # with some prompts (OK it clashes with my prompt...)
315 # If we find a prompt then everything's good
322 # Of course we may time out...
331 # It's always hard to find the prompt. So let's make a distintive one
332 $self->{prompt} = '@@@';
333 $self->{handle} = $remote;
335 # OK this is real tricky. If we call execute with a command of PS1=@@@
336 # and we've changed our prompt to '@@@' then we'll see the '@@@' in the
337 # PS1=@@@ statement as the prompt! That'll screw us up so we instead say
338 # PS1=\@\@\@. The shell then removes the extra backslashes for us and sets
339 # the prompt to just "@@@" for us. We catch that as our prompt and now we
340 # have a unique prompt that we can easily recognize.
341 if ($self->{shellstyle} eq 'sh') {
342 $self->execute ('PS1=\@\@\@');
344 $self->execute ('set prompt=\@\@\@');
348 } elsif ($timedout) {
349 carp "WARNING: $self->{host} is not responding to $self->{protocol} protocol";
353 carp "WARNING: Unable to connect to $self->{host} using $self->{protocol} protocol";
361 my ($logged_in, $timedout, $password_attempts) = 0;
363 $self->{protocol} = "telnet";
365 my $remote = Expect->new ("telnet $self->{host}");
367 return unless $remote;
369 $remote->log_user ($debug);
374 # If login is prompted for, and if what has been specified, then
380 # If we're being prompted for username and there is no
381 # username to supply then there is nothing much we can do but
382 # return undef since we can't get in with this protocol
383 return unless $self->{username};
385 $exp->send ("$self->{username}\n");
390 # If password is prompted for, and if one has been specified, then
392 [ qr "[P|p]assword: $",
394 # If we already supplied the password then it must not have
395 # worked so this protocol is no good.
396 return if $password_attempts;
400 # If we're being prompted for password and there is no
401 # password to supply then there is nothing much we can do but
402 # return undef since we can't get in with this protocol
403 return unless $self->{password};
405 $exp->send ("$self->{password}\n");
406 $password_attempts++;
412 # HACK! rlogin may return "<host>: Unknown host" which clashes
413 # with some prompts (OK it clashes with my prompt...)
420 # If we find a prompt then everything's good
427 # Of course we may time out...
436 # It's always hard to find the prompt. So let's make a distintive one
437 $self->{prompt} = '@@@';
438 $self->{handle} = $remote;
440 # OK this is real tricky. If we call execute with a command of PS1=@@@
441 # and we've changed our prompt to '@@@' then we'll see the '@@@' in the
442 # PS1=@@@ statement as the prompt! That'll screw us up so we instead say
443 # PS1=\@\@\@. The shell then removes the extra backslashes for us and sets
444 # the prompt to just "@@@" for us. We catch that as our prompt and now we
445 # have a unique prompt that we can easily recognize.
446 if ($self->{shellstyle} eq 'sh') {
447 $self->execute ('PS1=\@\@\@');
449 $self->execute ('set prompt=\@\@\@');
453 } elsif ($timedout) {
454 carp "WARNING: $self->{host} is not responding to $self->{protocol} protocol";
458 carp "WARNING: Unable to connect to $self->{host} using $self->{protocol} protocol";
470 Performs a login on the remote host. Normally this is done during
471 construction but this method allows you to login, say again, as maybe
476 =for html <blockquote>
484 =for html </blockquote>
488 =for html <blockquote>
496 =for html </blockquote>
500 # Close any prior opened sessions
501 $self->logoff if ($self->{handle});
503 # Check to see if this machines is known in DNS. If not then the chance is
504 # good that we will not be able to log in
505 return unless gethostbyname $self->{host};
509 if ($self->{protocol}) {
510 if ($self->{protocol} eq "ssh") {
512 } elsif ($self->{protocol} eq "rsh" or $self->{protocol} eq "rlogin") {
513 return $self->rlogin;
514 } elsif ($self->{protocol} eq "telnet") {
515 return $self->telnet;
517 croak "ERROR: Invalid protocol $self->{protocol} specified", 1;
520 return $remote if $remote = $self->ssh;
521 return $remote if $remote = $self->rlogin;
522 return $self->telnet;
535 Performs a logout on the remote host. Normally handled in the
536 destructor but you could call logout to logout if you wish.
540 =for html <blockquote>
548 =for html </blockquote>
552 =for html <blockquote>
560 =for html </blockquote>
564 $self->{handle}->soft_close;
566 undef $self->{handle};
567 undef $self->{status};
568 undef $self->{lines};
580 This method instantiates a new Rexec object. Currently only hash style
581 parameter passing is supported.
585 =for html <blockquote>
589 =item host => <host>:
591 Specifies the host to connect to. Default: localhost
593 =item username => <username>
595 Specifies the username to use if prompted. Default: No username specified.
597 =item password => <password>
599 Specifies the password to use if prompted. Default: No password
600 specified. Note passwords must be in cleartext at this
601 time. Specifying them makes you insecure!
603 =item prompt => <prompt regex>
605 Specifies a regex describing how to identify a prompt. Default:
606 qr'[#>:$](\s*|\e.+)$'
608 =item protocol => <ssh|rsh|rlogin|telnet>
610 Specifies the protocol to use when connecting. Default: Try them all
613 =item opts => <options>
615 Additional options for protocol (e.g. -X for ssh and X forwarding)
617 =item verbose => <0|1>
619 If true then status messages are echoed to stdout. Default: 0.
623 =for html </blockquote>
627 =for html <blockquote>
635 =for html </blockquote>
643 $self->{host} = $parms{host} ? $parms{host} : 'localhost';
644 $self->{username} = $parms{username};
645 $self->{password} = $parms{password};
646 $self->{prompt} = $parms{prompt} ? $parms{prompt} : $DEFAULT_PROMPT;
647 $self->{protocol} = $parms{protocol};
648 $self->{verbose} = $parms{verbose};
649 $self->{shellstyle} = $parms{shellstyle} ? $parms{shellstyle} : 'sh';
650 $self->{opts} = $parms{opts} ? $parms{opts} : '';
651 $self->{timeout} = $parms{timeout} ? $parms{timeout} : $default_login_timeout;
653 if ($self->{shellstyle} ne 'sh' and $self->{shellstyle} ne 'csh') {
654 croak 'ERROR: Unknown shell style specified. Must be one of "sh" or "csh"',
657 bless ($self, $class);
660 $self->{handle} = $self->login;
662 # Set timeout to $default_exec_timeout
663 $self->{timeout} = $default_exec_timeout;
665 return $self->{handle} ? $self : undef;
669 my ($self, $cmd, $timeout) = @_;
673 =head3 exec ($cmd, $timeout)
675 This method executes a command on the remote host returning an array
676 of lines that the command produced, if any. Status of the command is
677 stored in the object and accessible via the status method.
681 =for html <blockquote>
687 Command to execute remotely
691 Set timeout for this execution. If timeout is 0 then wait forever. If
692 you wish to interrupt this then set up a signal handler.
696 =for html </blockquote>
700 =for html <blockquote>
706 An array of lines from STDOUT of the command. If STDERR is also wanted
707 then add STDERR redirection to $cmd. Exit status is not returned by
708 retained in the object. Use status method to retrieve it.
712 =for html </blockquote>
716 # If timeout is specified for this exec then use it - otherwise
717 # use the object's defined timeout.
718 $timeout = $timeout ? $timeout : $self->{timeout};
720 # If timeout is set to 0 then the user wants an indefinite
721 # timeout. But Expect wants it to be undefined. So undef it if
722 # it's 0. Note this means we do not support Expect's "check it
723 # only one time" option.
724 undef $timeout if $timeout == 0;
726 # If timeout is < 0 then the user wants to run the command in the
727 # background and return. We still need to wait as we still may
728 # timeout so change $timeout to the $default_exec_timeout in this
729 # case and add a "&" to the command if it's not already there.
730 # because the user has added a & to the command to run it in the
731 if ($timeout && $timeout < 0) {
732 $timeout = $default_exec_timeout;
733 $cmd .= "&" if $cmd !~ /&$/;
736 # Set status to -2 indicating nothing happened! We should never
737 # return -2 (unless a command manages to set $? to -2!)
738 $self->{status} = -2;
740 # Empty lines of any previous command output
743 # Hopefully we will not see the following in the output string
744 my $errno_str = "ReXeCerRoNO=";
745 my $start_str = "StaRT";
749 # If cmd ends in a & then it makes no sense to compose a compound
750 # command. The original command will be in the background and thus
751 # we should not attempt to get a status - there will be none.
753 $compound_cmd = "echo $start_str; $cmd; echo $errno_str";
754 $compound_cmd .= $self->{shellstyle} eq "sh" ? "\$?" : "\$status";
756 $compound_cmd = $cmd;
759 $self->{handle}->send ("$compound_cmd\n");
761 $self->{handle}->expect (
766 $self->{status} = -1;
780 my $before = $exp->before;
781 my $after = $exp->after;
783 if ($after =~ /(\d+)/) {
784 $self->{status} = $1;
787 my @output = split /\n/, $before;
790 chop @output if $output[0] =~ /\r$/;
794 last if /$errno_str=/;
805 print 'Hit prompt!' if $debug;
810 $self->{lines} = \@lines;
816 my ($self, $timeout) = @_;
822 Aborts the current command by sending a Control-C (assumed to be the
823 interrupt character).
827 =for html <blockquote>
835 =for html </blockquote>
839 =for html <blockquote>
845 1 if abort was successful (we got a command prompt back) or 0 if it
850 =for html </blockquote>
854 # If timeout is specified for this exec then use it - otherwise
855 # use the object's defined timeout.
856 $timeout = $timeout ? $timeout : $self->{timeout};
858 # If timeout is set to 0 then the user wants an indefinite
859 # timeout. But Expect wants it to be undefined. So undef it if
860 # it's 0. Note this means we do not support Expect's "check it
861 # only one time" option.
862 undef $timeout if $timeout == 0;
864 # Set status to -2 indicating nothing happened! We should never
865 # return -2 (unless a command manages to set $? to -2!)
866 $self->{status} = -2;
868 $self->{handle}->send ("\cC");
870 $self->{handle}->expect (
875 $self->{status} = -1;
881 print "Hit prompt!" if $debug;
886 return $self->{status};
896 Returns the status of the last command executed remotely.
900 =for html <blockquote>
908 =for html </blockquote>
912 =for html <blockquote>
918 Last status from exec.
922 =for html </blockquote>
926 return $self->{status};
936 Returns the shellstyle
940 =for html <blockquote>
948 =for html </blockquote>
952 =for html <blockquote>
958 sh: Bourne or csh: for csh style shells
962 =for html </blockquote>
966 return $self->{shellstyle};
976 Returns the lines array from the last command called by exec.
980 =for html <blockquote>
988 =for html </blockquote>
992 =for html <blockquote>
998 An array of lines from the last call to exec.
1002 =for html </blockquote>
1006 return @{$self->{lines}};
1009 sub print_lines () {
1016 Essentially prints the lines array to stdout
1020 =for html <blockquote>
1028 =for html </blockquote>
1032 =for html <blockquote>
1040 =for html </blockquote>
1044 print "$_\n" foreach ($self->lines);
1056 Returns the host from the object.
1060 =for html <blockquote>
1068 =for html </blockquote>
1072 =for html <blockquote>
1080 =for html </blockquote>
1084 return $self->{host};
1090 $self->{handle}->hard_close
1101 Returns the timeout from the object.
1105 =for html <blockquote>
1113 =for html </blockquote>
1117 =for html <blockquote>
1125 =for html </blockquote>
1129 return $self->{timeout} ? $self->{timeout} : $default_login_timeout;
1132 sub setTimeout ($) {
1133 my ($self, $timeout) = @_;
1137 =head3 setTimeout ($timeout)
1139 Sets the timeout value for subsequent execution.
1143 =for html <blockquote>
1149 New timeout value to set
1153 =for html </blockquote>
1157 =for html <blockquote>
1167 =for html </blockquote>
1171 my $oldTimeout = $self->getTimeout;
1172 $self->{timeout} = $timeout;
1183 If verbose is turned on then connections or failure to connect will be
1188 <host> is not responding to <protocol>
1189 Connected to <host> using <protocol> protocol
1190 Unable to connect to <host> using <protocol> protocol
1194 Specifying cleartext passwords is not recommended for obvious security concerns.
1196 =head1 CONFIGURATION AND ENVIRONMENT
1198 Configuration files and environment variables.
1210 =for html <a href="http://search.cpan.org/~rgiersig/Expect-1.21/Expect.pod">Expect</a>
1212 =head3 ClearSCM Perl Modules
1214 =for html <p><a href="/php/scm_man.php?file=lib/Display.pm">Display</a></p>
1216 =head1 INCOMPATABILITIES
1220 =head1 BUGS AND LIMITATIONS
1222 There are no known bugs in this module.
1224 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
1226 =head1 LICENSE AND COPYRIGHT
1228 This Perl Module is freely available; you can redistribute it and/or
1229 modify it under the terms of the GNU General Public License as
1230 published by the Free Software Foundation; either version 2 of the
1231 License, or (at your option) any later version.
1233 This Perl Module is distributed in the hope that it will be useful,
1234 but WITHOUT ANY WARRANTY; without even the implied warranty of
1235 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1236 General Public License (L<http://www.gnu.org/copyleft/gpl.html>) for more
1239 You should have received a copy of the GNU General Public License
1240 along with this Perl Module; if not, write to the Free Software Foundation,
1241 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.