3 =head1 NAME $RCSfile: Rexec.pm,v $
5 Execute commands remotely and returning the output and status of the
6 remotely executed command.
14 Andrew DeFaria <Andrew@ClearSCM.com>
22 Mon Oct 9 18:28:28 CDT 2006
26 $Date: 2012/04/07 00:39:48 $
38 my $remote = new Rexec (host => $host);
41 print "Connected using " . $remote->{protocol} . " protocol\n";
44 @lines = $remote->execute ($cmd);
45 $status = $remote->status;
46 print "$cmd status: $status\n";
49 print "$_\n" foreach ($remote->execute ("cat /etc/passwd"));
51 print "Unable to connect to $username\@$host\n";
56 This module provides an object oriented interface to executing remote
57 commands on Linux/Unix system (or potentially well configured Windows
58 machines with Cygwin installed). Upon object creation a connection is
59 attempted to the specified host in a cascaded fashion. First ssh is
60 attempted, then rsh/rlogin and finally telnet. This clearly favors
61 secure methods over those less secure ones. If username or password is
62 prompted for, and if they are supplied, then they are used, otherwise
63 the attempted connection is considered failed.
65 Once connected the caller can use the exec method to execute commands
66 on the remote host. Upon object destruction the connection is
67 shutdown. Output from the remotely executed command is returned
68 through the exec method and also avaiable view the lines
69 method. Remote status is available via the status method. This means
70 you can now more reliably obtain the status of the command executed
71 remotely instead of just the status of the ssh/rsh command itself.
73 Note: Currently no attempt has been made to differentiate output
74 written to stdout and stderr.
76 As Expect is used to drive the remote session particular attention
77 should be defining a regex to locate the prompt. The standard prompt
78 regex (if not specified by the caller at object creation) is qr'[#>:$]
79 $'. This covers most default and common prompts.
81 =head1 Handling Timeouts
83 The tricky thing when dealing with remote execution is attempting to
84 determine if the remote machine has finished, stopped responding or
85 otherwise crashed. It's more of an art than a science! The best one
86 can do it send the command along and wait for a response. But how long
87 to wait is the question. If your wait is too short then you run the
88 risk of timing out before the remote command is finished. If you wait
89 too long then you can be possibly waiting for something that will not
90 be happening because the remote machine is either down or did not
91 behave in a manner that you expected it to.
93 To a large extent this module attempts to mitigate these issues on the
94 principal that remote command execution is pretty well known. You log
95 in and get a prompt. Issue a command and get another prompt. If the
96 prompts are well known and easily determinable things go
97 smoothly. However what happens if you execute a command remotely that
98 will take 30 minutes to finish?
100 This module has two timeout values. The first is login timeout. It's
101 assumed that logins should happen fairly quickly. The default timeout
102 for logins is 5 seconds.
104 Command timeouts are set by default to 30 seconds. Most commands will
105 finish before then. If you expect a command to take much longer then
106 you can set an alternate timeout period.
108 You can achieve longer timeouts in several ways. To give a longer
109 login timeout specify your timeout to the new call. To give a longer
110 exec timeout either pass a longer timeout to exec or set it view
111 setTimeout. The current exec timeout is returned by getTimeout.
115 The following routines are exported:
129 our $VERSION = '1.0';
131 # This is the "normal" definition of a prompt. However what's normal?
132 # For example, my prompt it typically the machine name followed by a
133 # colon. But even that appears in error messages such as <host>: not
134 # found and will be mistaken for a prompt. No real good way to handle
135 # this so we define a standard prompt here and allow the caller to
136 # override that. But overriding it is tricky and left as an exercise
139 # Here we have a number of the common prompt characters [#>:%$]
140 # followed by a space and end of line.
141 our $DEFAULT_PROMPT = qr'[#>:%$] $';
143 my $default_login_timeout = 5;
144 my $default_exec_timeout = 30;
146 my $debug = $ENV{DEBUG} || 0;
164 my ($logged_in, $timedout, $password_attempts) = 0;
166 $self->{protocol} = 'ssh';
168 my $user = $self->{username} ? "$self->{username}\@" : '';
170 my $remote = Expect->new ("ssh $self->{opts} $user$self->{host}");
174 $remote->log_user ($debug);
179 # If password is prompted for, and if one has been specified, then
181 [ qr "[P|p]assword: $",
183 # If we already supplied the password then it must not have
184 # worked so this protocol is no good.
185 return if $password_attempts;
189 # If we're being prompted for password and there is no
190 # password to supply then there is nothing much we can do but
191 # return undef since we can't get in with this protocol
192 return if !$self->{password};
194 $exp->send ("$self->{password}\n") if $self->{password};
195 $password_attempts++;
201 # Discard lines that begin with "ssh:" (like "ssh: <host>: not
209 # If we find a prompt then everything's good
216 # Of course we may time out...
226 } elsif ($timedout) {
227 carp "WARNING: $self->{host} is not responding to $self->{protocol} protocol";
231 carp "WARNING: Unable to connect to $self->{host} using $self->{protocol} protocol";
239 my ($logged_in, $timedout, $password_attempts) = 0;
241 $self->{protocol} = "rlogin";
243 my $user = $self->{username} ? "-l $self->{username}" : "";
245 my $remote = Expect->new ("rsh $user $self->{host}");
249 $remote->log_user ($debug);
254 # If password is prompted for, and if one has been specified, then
256 [ qr "[P|p]assword: $",
258 # If we already supplied the password then it must not have
259 # worked so this protocol is no good.
260 return if $password_attempts;
264 # If we're being prompted for password and there is no
265 # password to supply then there is nothing much we can do but
266 # return undef since we can't get in with this protocol
267 return if !$self->{password};
269 $exp->send ("$self->{password}\n");
270 $password_attempts++;
276 # HACK! rlogin may return "<host>: unknown host" which clashes
277 # with some prompts (OK it clashes with my prompt...)
284 # If we find a prompt then everything's good
291 # Of course we may time out...
301 } elsif ($timedout) {
302 carp "WARNING: $self->{host} is not responding to $self->{protocol} protocol";
306 carp "WARNING: Unable to connect to $self->{host} using $self->{protocol} protocol";
314 my ($logged_in, $timedout, $password_attempts) = 0;
316 $self->{protocol} = "telnet";
318 my $remote = Expect->new ("telnet $self->{host}");
322 $remote->log_user ($debug);
327 # If login is prompted for, and if what has been specified, then
333 # If we're being prompted for username and there is no
334 # username to supply then there is nothing much we can do but
335 # return undef since we can't get in with this protocol
336 return if !$self->{username};
338 $exp->send ("$self->{username}\n");
343 # If password is prompted for, and if one has been specified, then
345 [ qr "[P|p]assword: $",
347 # If we already supplied the password then it must not have
348 # worked so this protocol is no good.
349 return if $password_attempts;
353 # If we're being prompted for password and there is no
354 # password to supply then there is nothing much we can do but
355 # return undef since we can't get in with this protocol
356 return if !$self->{password};
358 $exp->send ("$self->{password}\n");
359 $password_attempts++;
365 # HACK! rlogin may return "<host>: Unknown host" which clashes
366 # with some prompts (OK it clashes with my prompt...)
373 # If we find a prompt then everything's good
380 # Of course we may time out...
390 } elsif ($timedout) {
391 carp "WARNING: $self->{host} is not responding to $self->{protocol} protocol";
395 carp "WARNING: Unable to connect to $self->{host} using $self->{protocol} protocol";
407 Performs a login on the remote host. Normally this is done during
408 construction but this method allows you to login, say again, as maybe
413 =for html <blockquote>
421 =for html </blockquote>
425 =for html <blockquote>
433 =for html </blockquote>
437 # Close any prior opened sessions
438 $self->logoff if ($self->{handle});
442 if ($self->{protocol}) {
443 if ($self->{protocol} eq "ssh") {
445 } elsif ($self->{protocol} eq "rsh" or $self->{protocol} eq "rlogin") {
446 return $self->rlogin;
447 } elsif ($self->{protocol} eq "telnet") {
448 return $self->telnet;
450 croak "ERROR: Invalid protocol $self->{protocol} specified", 1;
453 return $remote if $remote = $self->ssh;
454 return $remote if $remote = $self->rlogin;
455 return $self->telnet;
468 Performs a logout on the remote host. Normally handled in the
469 destructor but you could call logout to logout if you wish.
473 =for html <blockquote>
481 =for html </blockquote>
485 =for html <blockquote>
493 =for html </blockquote>
497 $self->{handle}->soft_close;
499 undef $self->{handle};
500 undef $self->{status};
501 undef $self->{lines};
513 This method instantiates a new Rexec object. Currently only hash style
514 parameter passing is supported.
518 =for html <blockquote>
522 =item host => <host>:
524 Specifies the host to connect to. Default: localhost
526 =item username => <username>
528 Specifies the username to use if prompted. Default: No username specified.
530 =item password => <password>
532 Specifies the password to use if prompted. Default: No password
533 specified. Note passwords must be in cleartext at this
534 time. Specifying them makes you insecure!
536 =item prompt => <prompt regex>
538 Specifies a regex describing how to identify a prompt. Default: qr'[#>:$] $'
540 =item protocol => <ssh|rsh|rlogin|telnet>
542 Specifies the protocol to use when connecting. Default: Try them all
545 =item opts => <options>
547 Additional options for protocol (e.g. -X for ssh and X forwarding)
549 =item verbose => <0|1>
551 If true then status messages are echoed to stdout. Default: 0.
555 =for html </blockquote>
559 =for html <blockquote>
567 =for html </blockquote>
575 $self->{host} = $parms{host} ? $parms{host} : 'localhost';
576 $self->{username} = $parms{username};
577 $self->{password} = $parms{password};
578 $self->{prompt} = $parms{prompt} ? $parms{prompt} : $DEFAULT_PROMPT;
579 $self->{protocol} = $parms{protocol};
580 $self->{verbose} = $parms{verbose};
581 $self->{shellstyle} = $parms{shellstyle} ? $parms{shellstyle} : 'sh';
582 $self->{opts} = $parms{opts} ? $parms{opts} : '';
583 $self->{timeout} = $parms{timeout} ? $parms{timeout} : $default_login_timeout;
585 if ($self->{shellstyle} ne 'sh' and $self->{shellstyle} ne 'csh') {
586 croak 'ERROR: Unknown shell style specified. Must be one of "sh" or "csh"', 1;
589 bless ($self, $class);
592 $self->{handle} = $self->login;
594 # Set timeout to $default_exec_timeout
595 $self->{timeout} = $default_exec_timeout;
597 return $self->{handle} ? $self : undef;
601 my ($self, $cmd, $timeout) = @_;
605 =head3 exec ($cmd, $timeout)
607 This method executes a command on the remote host returning an array
608 of lines that the command produced, if any. Status of the command is
609 stored in the object and accessible via the status method.
613 =for html <blockquote>
619 Command to execute remotely
623 Set timeout for this execution. If timeout is 0 then wait forever. If
624 you wish to interrupt this then set up a signal handler.
628 =for html </blockquote>
632 =for html <blockquote>
638 An array of lines from STDOUT of the command. If STDERR is also wanted
639 then add STDERR redirection to $cmd. Exit status is not returned by
640 retained in the object. Use status method to retrieve it.
644 =for html </blockquote>
648 # If timeout is specified for this exec then use it - otherwise
649 # use the object's defined timeout.
650 $timeout = $timeout ? $timeout : $self->{timeout};
652 # If timeout is set to 0 then the user wants an indefinite
653 # timeout. But Expect wants it to be undefined. So undef it if
654 # it's 0. Note this means we do not support Expect's "check it
655 # only one time" option.
656 undef $timeout if $timeout == 0;
658 # If timeout is < 0 then the user wants to run the command in the
659 # background and return. We still need to wait as we still may
660 # timeout so change $timeout to the $default_exec_timeout in this
661 # case and add a "&" to the command if it's not already there.
662 # because the user has added a & to the command to run it in the
663 if ($timeout && $timeout < 0) {
664 $timeout = $default_exec_timeout;
665 $cmd .= "&" if $cmd !~ /&$/;
668 # Set status to -2 indicating nothing happened! We should never
669 # return -2 (unless a command manages to set $? to -2!)
670 $self->{status} = -2;
672 # Empty lines of any previous command output
675 # Hopefully we will not see the following in the output string
676 my $errno_str = "ReXeCerRoNO=";
677 my $start_str = "StaRT";
681 # If cmd ends in a & then it makes no sense to compose a compound
682 # command. The original command will be in the background and thus
683 # we should not attempt to get a status - there will be none.
685 $compound_cmd = "echo $start_str; $cmd; echo $errno_str";
686 $compound_cmd .= $self->{shellstyle} eq "sh" ? "\$?" : "\$status";
688 $compound_cmd = $cmd;
691 $self->{handle}->send ("$compound_cmd\n");
693 $self->{handle}->expect (
698 $self->{status} = -1;
712 my $before = $exp->before;
713 my $after = $exp->after;
715 if ($after =~ /(\d+)/) {
716 $self->{status} = $1;
719 my @output = split /\n/, $before;
722 chop @output if $output[0] =~ /\r$/;
726 last if /$errno_str=/;
737 print 'Hit prompt!' if $debug;
742 $self->{lines} = \@lines;
748 my ($self, $timeout) = @_;
754 Aborts the current command by sending a Control-C (assumed to be the
755 interrupt character).
759 =for html <blockquote>
767 =for html </blockquote>
771 =for html <blockquote>
777 1 if abort was successful (we got a command prompt back) or 0 if it
782 =for html </blockquote>
786 # If timeout is specified for this exec then use it - otherwise
787 # use the object's defined timeout.
788 $timeout = $timeout ? $timeout : $self->{timeout};
790 # If timeout is set to 0 then the user wants an indefinite
791 # timeout. But Expect wants it to be undefined. So undef it if
792 # it's 0. Note this means we do not support Expect's "check it
793 # only one time" option.
794 undef $timeout if $timeout == 0;
796 # Set status to -2 indicating nothing happened! We should never
797 # return -2 (unless a command manages to set $? to -2!)
798 $self->{status} = -2;
800 $self->{handle}->send ("\cC");
802 $self->{handle}->expect (
807 $self->{status} = -1;
813 print "Hit prompt!" if $debug;
818 return $self->{status};
828 Returns the status of the last command executed remotely.
832 =for html <blockquote>
840 =for html </blockquote>
844 =for html <blockquote>
850 Last status from exec.
854 =for html </blockquote>
858 return $self->{status};
868 Returns the shellstyle
872 =for html <blockquote>
880 =for html </blockquote>
884 =for html <blockquote>
890 sh: Bourne or csh: for csh style shells
894 =for html </blockquote>
898 return $self->{shellstyle};
908 Returns the lines array from the last command called by exec.
912 =for html <blockquote>
920 =for html </blockquote>
924 =for html <blockquote>
930 An array of lines from the last call to exec.
934 =for html </blockquote>
938 return @{$self->{lines}};
948 Essentially prints the lines array to stdout
952 =for html <blockquote>
960 =for html </blockquote>
964 =for html <blockquote>
972 =for html </blockquote>
976 print "$_\n" foreach ($self->lines);
988 Returns the host from the object.
992 =for html <blockquote>
1000 =for html </blockquote>
1004 =for html <blockquote>
1012 =for html </blockquote>
1016 return $self->{host};
1022 $self->{handle}->hard_close
1033 Returns the timeout from the object.
1037 =for html <blockquote>
1045 =for html </blockquote>
1049 =for html <blockquote>
1057 =for html </blockquote>
1061 return $self->{timeout} ? $self->{timeout} : $default_login_timeout;
1064 sub setTimeout ($) {
1065 my ($self, $timeout) = @_;
1069 =head3 setTimeout ($timeout)
1071 Sets the timeout value for subsequent execution.
1075 =for html <blockquote>
1081 New timeout value to set
1085 =for html </blockquote>
1089 =for html <blockquote>
1099 =for html </blockquote>
1103 my $oldTimeout = $self->getTimeout;
1104 $self->{timeout} = $timeout;
1115 If verbose is turned on then connections or failure to connect will be
1120 <host> is not responding to <protocol>
1121 Connected to <host> using <protocol> protocol
1122 Unable to connect to <host> using <protocol> protocol
1126 Specifying cleartext passwords is not recommended for obvious security concerns.
1128 =head1 CONFIGURATION AND ENVIRONMENT
1130 Configuration files and environment variables.
1142 =for html <a href="http://search.cpan.org/~rgiersig/Expect-1.21/Expect.pod">Expect</a><b
1144 =head3 ClearSCM Perl Modules
1146 =for html <p><a href="/php/cvs_man.php?file=lib/Display.pm">Display</a></p>
1148 =head1 INCOMPATABILITIES
1152 =head1 BUGS AND LIMITATIONS
1154 There are no known bugs in this module.
1156 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
1158 =head1 LICENSE AND COPYRIGHT
1160 This Perl Module is freely available; you can redistribute it and/or
1161 modify it under the terms of the GNU General Public License as
1162 published by the Free Software Foundation; either version 2 of the
1163 License, or (at your option) any later version.
1165 This Perl Module is distributed in the hope that it will be useful,
1166 but WITHOUT ANY WARRANTY; without even the implied warranty of
1167 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1168 General Public License (L<http://www.gnu.org/copyleft/gpl.html>) for more
1171 You should have received a copy of the GNU General Public License
1172 along with this Perl Module; if not, write to the Free Software Foundation,
1173 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.