File Coverage

blib/lib/Net/OpenSSH.pm
Criterion Covered Total %
statement 498 1395 35.7
branch 230 1076 21.3
condition 80 433 18.4
subroutine 46 123 37.4
pod 45 49 91.8
total 899 3076 29.2


line stmt bran cond sub pod time code
1             package Net::OpenSSH;
2              
3             our $VERSION = '0.82';
4              
5 5     5   240398 use strict;
  5         14  
  5         120  
6 5     5   20 use warnings;
  5         9  
  5         244  
7              
8             our $debug ||= 0;
9             our $debug_fh ||= \*STDERR;
10              
11             our $FACTORY;
12              
13 5     5   26 use Carp qw(carp croak);
  5         6  
  5         237  
14 5     5   1920 use POSIX qw(:sys_wait_h);
  5         27990  
  5         20  
15 5     5   6843 use Socket;
  5         2914  
  5         1914  
16 5     5   30 use File::Spec;
  5         6  
  5         115  
17 5     5   23 use Cwd ();
  5         10  
  5         54  
18 5     5   15 use Scalar::Util ();
  5         5  
  5         90  
19 5     5   1906 use Errno ();
  5         5392  
  5         120  
20 5     5   1797 use Net::OpenSSH::Constants qw(:error :_state);
  5         10  
  5         639  
21 5     5   1757 use Net::OpenSSH::ModuleLoader;
  5         14  
  5         193  
22 5     5   1594 use Net::OpenSSH::ShellQuoter;
  5         10  
  5         124  
23 5     5   25 use Digest::MD5;
  5         6  
  5         808  
24              
25             my $thread_generation = 0;
26              
27 0     0   0 sub CLONE { $thread_generation++ };
28              
29             sub _debug {
30 0     0   0 local ($!, $@);
31 0 0       0 print {$debug_fh} '# ', (map { defined($_) ? $_ : '' } @_), "\n"
  0         0  
  0         0  
32             }
33              
34             sub _debug_dump {
35 0     0   0 local ($!, $@);
36 0         0 require Data::Dumper;
37 0         0 local $Data::Dumper::Terse = 1;
38 0         0 local $Data::Dumper::Indent = 0;
39 0         0 my $head = shift;
40 0         0 _debug("$head: ", Data::Dumper::Dumper(@_));
41             }
42              
43             sub _hexdump {
44 5     5   30 no warnings qw(uninitialized);
  5         9  
  5         26493  
45 0     0   0 my $data = shift;
46 0         0 while ($data =~ /(.{1,32})/smg) {
47 0         0 my $line=$1;
48 0         0 my @c= (( map { sprintf "%02x",$_ } unpack('C*', $line)),
  0         0  
49             ((" ") x 32))[0..31];
50 0 0       0 $line=~s/(.)/ my $c=$1; unpack("c",$c)>=32 ? $c : '.' /egms;
  0         0  
  0         0  
51 0         0 print {$debug_fh} "#> ", join(" ", @c, '|', $line), "\n";
  0         0  
52             }
53             }
54              
55             {
56             my %good;
57              
58             sub _sub_options {
59 85     85   107 my $sub = shift;
60 85         95 $good{__PACKAGE__ . "::$sub"} = { map { $_ => 1 } @_ };
  1330         1789  
61             }
62              
63             sub _croak_bad_options (\%) {
64 6     6   15 my $opts = shift;
65 6 50       18 if (%$opts) {
66 0         0 my $sub = (caller 1)[3];
67 0         0 my $good = $good{$sub};
68 0 0       0 my @keys = grep defined($opts->{$_}), ( $good ? grep !$good->{$_}, keys %$opts : keys %$opts);
69 0 0       0 if (@keys) {
70 0         0 croak "Invalid or bad combination of options ('" . CORE::join("', '", @keys) . "')";
71             }
72             }
73             }
74             }
75              
76             sub _croak_scalar_context {
77 0     0   0 my ($sub, $wantarray) = (caller 1)[3, 5];
78 0 0       0 unless ($wantarray) {
79 0         0 $sub =~ s/^.*:://;
80 0         0 croak "method '$sub' called in scalar context";
81             }
82             }
83              
84             sub _tcroak {
85 0 0   0   0 if (${^TAINT} > 0) {
86 0         0 push @_, " while running with -T switch";
87 0         0 goto &croak;
88             }
89 0 0       0 if (${^TAINT} < 0) {
90 0         0 push @_, " while running with -t switch";
91 0         0 goto &carp;
92             }
93             }
94              
95             sub _catch_tainted_args {
96 0     0   0 my $i;
97 0         0 for (@_) {
98 0 0       0 next unless $i++;
99 0 0       0 if (Scalar::Util::tainted($_)) {
    0          
100 0         0 my (undef, undef, undef, $subn) = caller 1;
101 0 0       0 my $msg = ( $subn =~ /::([a-z]\w*)$/
102             ? "Insecure argument '$_' on '$1' method call"
103             : "Insecure argument '$_' on method call" );
104 0         0 _tcroak($msg);
105             }
106             elsif (ref($_) eq 'HASH') {
107 0         0 for (grep Scalar::Util::tainted($_), values %$_) {
108 0         0 my (undef, undef, undef, $subn) = caller 1;
109 0 0       0 my $msg = ( $subn =~ /::([a-z]\w*)$/
110             ? "Insecure argument on '$1' method call"
111             : "Insecure argument on method call" );
112 0         0 _tcroak($msg);
113             }
114             }
115             }
116             }
117              
118             sub _set_error {
119 3     3   10 my $self = shift;
120 3   100     53 my $code = shift || 0;
121 3         26 my @extra = grep defined, @_;
122             my $err = $self->{_error} = ( $code
123 3 50       14 ? Scalar::Util::dualvar($code, join(': ', @{$self->{_error_prefix}},
  1 100       14  
124             (@extra ? @extra : "Unknown error $code")))
125             : 0 );
126 3 50 33     14 $debug and $debug & 1 and _debug "set_error($code - $err)";
127 3         9 return $err
128             }
129              
130             my $check_eval_re = do {
131             my $path = quotemeta $INC{"Net/OpenSSH.pm"};
132             qr/at $path line \d+.$/
133             };
134              
135             sub _check_eval_ok {
136 0     0   0 my ($self, $code) = @_;
137 0 0       0 if ($@) {
138 0         0 my $err = $@;
139 0         0 $err =~ s/$check_eval_re//;
140 0         0 $self->_set_error($code, $err);
141 0         0 return;
142             }
143             1
144 0         0 }
145              
146             sub _or_set_error {
147 0     0   0 my $self = shift;
148 0 0       0 $self->{_error} or $self->_set_error(@_);
149             }
150              
151 33   100 33   120 sub _first_defined { defined && return $_ for @_; return }
  9         21  
152              
153             my $obfuscate = sub {
154             # just for the casual observer...
155             my $txt = shift;
156             $txt =~ s/(.)/chr(ord($1) ^ 47)/ges
157             if defined $txt;
158             $txt;
159             };
160              
161             my $deobfuscate = $obfuscate;
162              
163             # regexp from Regexp::IPv6
164             my $IPv6_re = qr((?-xism::(?::[0-9a-fA-F]{1,4}){0,5}(?:(?::[0-9a-fA-F]{1,4}){1,2}|:(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})))|[0-9a-fA-F]{1,4}:(?:[0-9a-fA-F]{1,4}:(?:[0-9a-fA-F]{1,4}:(?:[0-9a-fA-F]{1,4}:(?:[0-9a-fA-F]{1,4}:(?:[0-9a-fA-F]{1,4}:(?:[0-9a-fA-F]{1,4}:(?:[0-9a-fA-F]{1,4}|:)|(?::(?:[0-9a-fA-F]{1,4})?|(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))))|:(?:(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))|[0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4})?|))|(?::(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))|:[0-9a-fA-F]{1,4}(?::(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))|(?::[0-9a-fA-F]{1,4}){0,2})|:))|(?:(?::[0-9a-fA-F]{1,4}){0,2}(?::(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))|(?::[0-9a-fA-F]{1,4}){1,2})|:))|(?:(?::[0-9a-fA-F]{1,4}){0,3}(?::(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))|(?::[0-9a-fA-F]{1,4}){1,2})|:))|(?:(?::[0-9a-fA-F]{1,4}){0,4}(?::(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))|(?::[0-9a-fA-F]{1,4}){1,2})|:))));
165              
166             sub parse_connection_opts {
167 22     22 0 5030 my ($class, $opts) = @_;
168 22         30 my ($user, $passwd, $ipv6, $host, $port, $host_squared);
169              
170 22         49 my $target = delete $opts->{host};
171 22 50       356 defined $target or croak "mandatory host argument missing";
172              
173 22 50       4371 ($user, $passwd, $ipv6, $host, $port) =
174             $target =~ m{^
175             \s* # space
176             (?:
177             ([^:]+) # username
178             (?::(.*))? # : password
179             \@ # @
180             )?
181             (?: # host
182             ( # IPv6...
183             \[$IPv6_re(?:\%[^\[\]]*)\] # [IPv6]
184             | # or
185             $IPv6_re # IPv6
186             )
187             | # or
188             ([^\[\]\@:]+) # hostname / ipv4
189             )
190             (?::([^\@:]+))? # port
191             \s* # space
192             $}ix
193             or croak "bad host/target '$target' specification";
194              
195 22 100       155 if (defined $ipv6) {
196 1         6 ($host) = $ipv6 =~ /^\[?(.*?)\]?$/;
197 1         4 $host_squared = "[$host]";
198             }
199             else {
200 21         42 $host_squared = $host;
201             }
202              
203 22 100       44 $user = delete $opts->{user} unless defined $user;
204 22 50       45 $port = delete $opts->{port} unless defined $port;
205 22 100       49 $passwd = delete $opts->{passwd} unless defined $passwd;
206 22 100       39 $passwd = delete $opts->{password} unless defined $passwd;
207              
208 22 100       64 wantarray and return ($host, $port, $user, $passwd, $host_squared);
209              
210 19         57 my %r = ( user => $user,
211             password => $passwd,
212             host => $host,
213             host_squared => $host_squared,
214             port => $port );
215 19 100       35 $r{ipv6} = 1 if defined $ipv6;
216 19         49 return \%r;
217             }
218              
219             my $sizeof_sun_path = ($^O eq 'linux' ? 108 :
220             $^O =~ /bsd/i ? 104 :
221             $^O eq 'hpux' ? 92 : undef);
222              
223             sub new {
224 3 50   3 1 3220443 ${^TAINT} and &_catch_tainted_args;
225              
226 3         33 my $class = shift;
227 3 50       27 @_ & 1 and unshift @_, 'host';
228              
229 3 50       33 return $FACTORY->($class, @_) if defined $FACTORY;
230              
231 3         42 my %opts = @_;
232              
233 3         18 my $external_master = delete $opts{external_master};
234             # reuse_master is an obsolete alias:
235 3 50       36 $external_master = delete $opts{reuse_master} unless defined $external_master;
236              
237 3 50 33     42 if (not defined $opts{host} and defined $external_master) {
238 0         0 $opts{host} = '0.0.0.0';
239             }
240              
241 3         30 my ($host, $port, $user, $passwd, $host_squared) = $class->parse_connection_opts(\%opts);
242              
243 3         12 my ($passphrase, $key_path, $login_handler);
244 3 50       12 unless (defined $passwd) {
245 3         6 $key_path = delete $opts{key_path};
246 3         15 $passwd = delete $opts{passphrase};
247 3 50       21 if (defined $passwd) {
248 0         0 $passphrase = 1;
249             }
250             else {
251 3         9 $login_handler = delete $opts{login_handler};
252             }
253             }
254              
255 3         6 my $ssh_version = delete $opts{ssh_version};
256 3         6 my $batch_mode = delete $opts{batch_mode};
257 3         6 my $ctl_path = delete $opts{ctl_path};
258 3         6 my $ctl_dir = delete $opts{ctl_dir};
259 3         6 my $proxy_command = delete $opts{proxy_command};
260 3 50       9 my $gateway = delete $opts{gateway} unless defined $proxy_command;
261 3         36 my $ssh_cmd = _first_defined delete $opts{ssh_cmd}, 'ssh';
262 3         12 my $rsync_cmd = _first_defined delete $opts{rsync_cmd}, 'rsync';
263 3         18 my $scp_cmd = delete $opts{scp_cmd};
264 3         12 my $sshfs_cmd = _first_defined delete $opts{sshfs_cmd}, 'sshfs';
265             my $sftp_server_cmd = _first_defined delete $opts{sftp_server_cmd},
266 3         9 '/usr/lib/openssh/sftp-server';
267 3         6 my $timeout = delete $opts{timeout};
268 3         6 my $kill_ssh_on_timeout = delete $opts{kill_ssh_on_timeout};
269 3         9 my $strict_mode = _first_defined delete $opts{strict_mode}, 1;
270 3         9 my $connect = _first_defined delete $opts{connect}, 1;
271 3         6 my $async = delete $opts{async};
272 3         9 my $remote_shell = _first_defined delete $opts{remote_shell}, 'POSIX';
273 3         6 my $expand_vars = delete $opts{expand_vars};
274 3         9 my $vars = _first_defined delete $opts{vars}, {};
275 3         6 my $default_encoding = delete $opts{default_encoding};
276             my $default_stream_encoding =
277 3         9 _first_defined delete $opts{default_stream_encoding}, $default_encoding;
278             my $default_argument_encoding =
279 3         15 _first_defined delete $opts{default_argument_encoding}, $default_encoding;
280 3         6 my $forward_agent = delete $opts{forward_agent};
281 3 50 33     15 $forward_agent and $passphrase and
282             croak "agent forwarding can not be used when a passphrase has also been given";
283 3         6 my $forward_X11 = delete $opts{forward_X11};
284 3         6 my $passwd_prompt = delete $opts{password_prompt};
285 3         6 my $master_pty_force = delete $opts{master_pty_force};
286 3 50       9 $passwd_prompt = delete $opts{passwd_prompt} unless defined $passwd_prompt;
287              
288 3         6 my ($master_opts, @master_opts,
289             $master_stdout_fh, $master_stderr_fh,
290             $master_stdout_discard, $master_stderr_discard,
291             $master_setpgrp);
292 3 50       6 unless ($external_master) {
293             ($master_stdout_fh = delete $opts{master_stdout_fh} or
294 3 50       9 $master_stdout_discard = delete $opts{master_stdout_discard});
295              
296             ($master_stderr_fh = delete $opts{master_stderr_fh} or
297 3 50       9 $master_stderr_discard = delete $opts{master_stderr_discard});
298              
299 3         6 $master_opts = delete $opts{master_opts};
300 3 50       15 if (defined $master_opts) {
301 3 50       42 if (ref $master_opts) {
302 3         15 @master_opts = @$master_opts;
303             }
304             else {
305 0 0       0 carp "'master_opts' argument looks like if it should be splited first"
306             if $master_opts =~ /^-\w\s+\S/;
307 0         0 @master_opts = $master_opts;
308             }
309             }
310 3         6 $master_setpgrp = delete $opts{master_setpgrp};
311              
312             # when a password/passphrase is given, calling setpgrp is
313             # useless because the process runs attached to a different tty
314 3 50 33     39 undef $master_setpgrp if $login_handler or defined $passwd;
315             }
316              
317 3         9 my $default_ssh_opts = delete $opts{default_ssh_opts};
318 3 0 33     12 carp "'default_ssh_opts' argument looks like if it should be splited first"
      33        
319             if defined $default_ssh_opts and not ref $default_ssh_opts and $default_ssh_opts =~ /^-\w\s+\S/;
320              
321 3         6 my ($default_stdout_fh, $default_stderr_fh, $default_stdin_fh,
322             $default_stdout_file, $default_stderr_file, $default_stdin_file,
323             $default_stdout_discard, $default_stderr_discard, $default_stdin_discard);
324              
325             $default_stdout_file = (delete $opts{default_stdout_discard}
326             ? '/dev/null'
327 3 50       9 : delete $opts{default_stdout_file});
328             $default_stdout_fh = delete $opts{default_stdout_fh}
329 3 50       9 unless defined $default_stdout_file;
330              
331             $default_stderr_file = (delete $opts{default_stderr_discard}
332             ? '/dev/null'
333 3 50       9 : delete $opts{default_stderr_file});
334             $default_stderr_fh = delete $opts{default_stderr_fh}
335 3 50       6 unless defined $default_stderr_file;
336              
337             $default_stdin_file = (delete $opts{default_stdin_discard}
338             ? '/dev/null'
339 3 50       9 : delete $opts{default_stdin_file});
340             $default_stdin_fh = delete $opts{default_stdin_fh}
341 3 50       6 unless defined $default_stdin_file;
342              
343 3         27 _croak_bad_options %opts;
344              
345 3         3 my @ssh_opts;
346             # TODO: are those options really requiered or just do they eat on
347             # the command line limited length?
348 3 50       9 push @ssh_opts, -l => $user if defined $user;
349 3 50       9 push @ssh_opts, -p => $port if defined $port;
350              
351 3         21 my $home = do {
352 3         42 local ($@, $SIG{__DIE__});
353 3         9 eval { Cwd::realpath((getpwuid $>)[7]) }
  3         396  
354             };
355              
356 3 50       21 if (${^TAINT}) {
357 0         0 ($home) = $home =~ /^(.*)$/;
358 0 0       0 Scalar::Util::tainted($ENV{PATH}) and
359             _tcroak('Insecure $ENV{PATH}');
360             }
361              
362 3         42 my $self = { _error => 0,
363             _error_prefix => [],
364             _perl_pid => $$,
365             _thread_generation => $thread_generation,
366             _ssh_version => $ssh_version,
367             _ssh_cmd => $ssh_cmd,
368             _scp_cmd => $scp_cmd,
369             _rsync_cmd => $rsync_cmd,
370             _sshfs_cmd => $sshfs_cmd,
371             _sftp_server_cmd => $sftp_server_cmd,
372             _pid => undef,
373             _host => $host,
374             _host_squared => $host_squared,
375             _user => $user,
376             _port => $port,
377             _passwd => $obfuscate->($passwd),
378             _passwd_prompt => $passwd_prompt,
379             _passphrase => $passphrase,
380             _key_path => $key_path,
381             _login_handler => $login_handler,
382             _timeout => $timeout,
383             _proxy_command => $proxy_command,
384             _gateway_args => $gateway,
385             _kill_ssh_on_timeout => $kill_ssh_on_timeout,
386             _batch_mode => $batch_mode,
387             _home => $home,
388             _forward_agent => $forward_agent,
389             _forward_X11 => $forward_X11,
390             _external_master => $external_master,
391             _default_ssh_opts => $default_ssh_opts,
392             _default_stdin_fh => $default_stdin_fh,
393             _default_stdout_fh => $default_stdout_fh,
394             _default_stderr_fh => $default_stderr_fh,
395             _master_stdout_fh => $master_stdout_fh,
396             _master_stderr_fh => $master_stderr_fh,
397             _master_stdout_discard => $master_stdout_discard,
398             _master_stderr_discard => $master_stderr_discard,
399             _master_setpgrp => $master_setpgrp,
400             _master_pty_force => $master_pty_force,
401             _remote_shell => $remote_shell,
402             _default_stream_encoding => $default_stream_encoding,
403             _default_argument_encoding => $default_argument_encoding,
404             _expand_vars => $expand_vars,
405             _vars => $vars,
406             _master_state => _STATE_START,
407             };
408 3         9 bless $self, $class;
409              
410 3         39 $self->_detect_ssh_version;
411              
412             # default file handles are opened so late in order to have the
413             # $self object to report errors
414 2 50       22 $self->{_default_stdout_fh} = $self->_open_file('>', $default_stdout_file)
415             if defined $default_stdout_file;
416 2 50       10 $self->{_default_stderr_fh} = $self->_open_file('>', $default_stderr_file)
417             if defined $default_stderr_file;
418 2 50       8 $self->{_default_stdin_fh} = $self->_open_file('<', $default_stdin_file)
419             if defined $default_stdin_file;
420              
421 2 50       28 if ($self->{_error} == OSSH_SLAVE_PIPE_FAILED) {
422 0         0 $self->_master_fail($async, "Unable to create default slave stream", $self->{_error});
423 0         0 return $self;
424             }
425              
426 2         96 $self->{_ssh_opts} = [$self->_expand_vars(@ssh_opts)];
427 2         24 $self->{_master_opts} = [$self->_expand_vars(@master_opts)];
428              
429 2         20 $ctl_path = $self->_expand_vars($ctl_path);
430 2         8 $ctl_dir = $self->_expand_vars($ctl_dir);
431              
432 2 50       10 if (defined $ctl_path) {
433 0 0       0 if ($external_master) {
434 0 0       0 unless (-S $ctl_path) {
435 0         0 $self->_master_fail($async, "ctl_path $ctl_path does not point to a socket");
436 0         0 return $self;
437             }
438             }
439             else {
440 0 0       0 if (-e $ctl_path) {
441 0         0 $self->_master_fail($async, "unable to use ctl_path $ctl_path, a file object already exists there");
442 0         0 return $self;
443             }
444             }
445             }
446             else {
447 2 50       8 $external_master and croak "external_master is set but ctl_path is not defined";
448              
449 2 50       8 unless (defined $ctl_dir) {
450 2 50       8 unless (defined $self->{_home}) {
451 0         0 $self->_master_fail($async, "unable to determine home directory for uid $>");
452 0         0 return $self;
453             }
454              
455 2         114 $ctl_dir = File::Spec->catdir($self->{_home}, ".libnet-openssh-perl");
456             }
457              
458 2         240 mkdir $ctl_dir, 0700;
459 2 50       102 unless (-d $ctl_dir) {
460 0         0 $self->_master_fail($async, "unable to create ctl_dir $ctl_dir");
461 0         0 return $self;
462             }
463              
464 2         48 my $target = join('-', grep defined, $user, $host, $port);
465              
466 2         12 for (1..10) {
467 2         106 my $ctl_file = Digest::MD5::md5_hex(sprintf "%s-%d-%d-%d", $target, $$, time, rand 1e6);
468 2         140 $ctl_path = File::Spec->join($ctl_dir, $ctl_file);
469 2 50       82 last unless -e $ctl_path
470             }
471 2 50       52 if (-e $ctl_path) {
472 0         0 $self->_master_fail($async, "unable to find unused name for ctl_path inside ctl_dir $ctl_dir");
473 0         0 return $self;
474             }
475             }
476              
477 2 50 33     20 if (defined $sizeof_sun_path and length $ctl_path > $sizeof_sun_path) {
478 0         0 $self->_master_fail($async, "ctl_path $ctl_path is too long (max permissible size for $^O is $sizeof_sun_path)");
479 0         0 return $self;
480             }
481              
482 2         214 $ctl_dir = File::Spec->catpath((File::Spec->splitpath($ctl_path))[0,1], "");
483 2 50 33     14 $debug and $debug & 2 and _debug "ctl_path: $ctl_path, ctl_dir: $ctl_dir";
484              
485 2 50 33     10 if ($strict_mode and !$self->_is_secure_path($ctl_dir)) {
486 0         0 $self->_master_fail($async, "ctl_dir $ctl_dir is not secure");
487 0         0 return $self;
488             }
489              
490 2         8 $self->{_ctl_path} = $ctl_path;
491              
492 2 50       56 $self->_master_wait($async) if $connect;
493              
494 1         47 $self;
495             }
496              
497 0     0 1 0 sub get_user { shift->{_user} }
498 0     0 1 0 sub get_host { shift->{_host} }
499 0     0 1 0 sub get_port { shift->{_port} }
500 0     0 1 0 sub get_master_pid { shift->{_pid} }
501 0     0 1 0 sub get_ctl_path { shift->{_ctl_path} }
502 0     0 1 0 sub get_expand_vars { shift->{_expand_vars} }
503              
504 0     0 1 0 sub get_master_pty_log { shift->{_master_pty_log} }
505              
506             sub set_expand_vars {
507 0     0 1 0 my $self = shift;
508 0 0       0 $self->{_expand_vars} = (shift(@_) ? 1 : 0);
509             }
510              
511             sub set_var {
512 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
513 0         0 my $self = shift;
514 0         0 my $k = shift;
515 0 0       0 $k =~ /^(?:USER|HOST|PORT)$/
516             and croak "internal variable %$k% can not be set";
517 0         0 $self->{_vars}{$k} = shift;
518             }
519              
520             sub get_var {
521 0     0 1 0 my ($self, $k) = @_;
522             my $v = ( $k =~ /^(?:USER|HOST|PORT)$/
523             ? $self->{lc "_$k"}
524 0 0       0 : $self->{_vars}{$k} );
525 0 0       0 (defined $v ? $v : '');
526             }
527              
528             sub _expand_vars {
529 17     17   109 my ($self, @str) = @_;
530 17 50 33     136 if (ref $self and $self->{_expand_vars}) {
531 0         0 for (@str) {
532 0 0       0 s{%(\w*)%}{length ($1) ? $self->get_var($1) : '%'}ge
  0 0       0  
533             if defined $_;
534             }
535             }
536 17 100       107 wantarray ? @str : $str[0]
537             }
538              
539 1     1 1 39 sub error { shift->{_error} }
540              
541             sub die_on_error {
542 0     0 0 0 my $ssh = shift;
543 0 0       0 $ssh->{_error} and croak(@_ ? "@_: $ssh->{_error}" : $ssh->{_error});
    0          
544             }
545              
546              
547             sub _is_secure_path {
548 0     0   0 my ($self, $path) = @_;
549 0         0 my @parts = File::Spec->splitdir(Cwd::realpath($path));
550 0         0 my $home = $self->{_home};
551 0         0 for my $last (reverse 0..$#parts) {
552 0         0 my $dir = File::Spec->catdir(@parts[0..$last]);
553 0 0       0 unless (-d $dir) {
554 0 0 0     0 $debug and $debug & 2 and _debug "$dir is not a directory";
555 0         0 return undef;
556             }
557 0         0 my ($mode, $uid) = (stat $dir)[2, 4];
558 0 0 0     0 $debug and $debug & 2 and _debug "_is_secure_path(dir: $dir, file mode: $mode, file uid: $uid, euid: $>";
559 0 0 0     0 return undef unless(($uid == $> or $uid == 0 ) and (($mode & 022) == 0 or ($mode & 01000)));
      0        
      0        
560 0 0 0     0 return 1 if (defined $home and $home eq $dir);
561             }
562 0         0 return 1;
563             }
564              
565             sub _detect_ssh_version {
566 3     3   9 my $self = shift;
567 3 50       42 if (defined $self->{_ssh_version}) {
568 0 0 0     0 $debug and $debug & 4 and _debug "ssh version given as $self->{_ssh_version}";
569             }
570             else {
571             my (undef, $out, undef, $pid) = $self->open_ex({_cmd => 'raw',
572             _no_master_required => 1,
573             stdout_pipe => 1,
574             stdin_discard => 1,
575             stderr_to_stdout => 1 },
576 3         90 $self->{_ssh_cmd}, '-V');
577 2         130 my ($txt) = $self->_io3($out, undef, undef, undef, 10, 'bytes');
578 2         14 local $self->{_kill_ssh_on_timeout} = 1;
579 2         38 $self->_waitpid($pid, 10);
580 2 50       54 if (my ($full, $num) = $txt =~ /^OpenSSH_((\d+\.\d+)\S*)/mi) {
581 2 50 33     10 $debug and $debug & 4 and _debug "OpenSSH version is $full";
582 2         38 $self->{_ssh_version} = $num;
583             }
584             else {
585 0         0 $self->{_ssh_version} = 0;
586 0 0 0     0 $debug and $debug & 4 and _debug "unable to determine version, '$self->{_ssh_cmd} -V', output:\n$txt"
587             }
588             }
589             }
590              
591             sub _make_ssh_call {
592 2     2   20 my $self = shift;
593 2 50       4 my @before = @{shift || []};
  2         14  
594             my @args = ($self->{_ssh_cmd}, @before,
595             -S => $self->{_ctl_path},
596 2         48 @{$self->{_ssh_opts}}, $self->{_host},
597 2 50       6 '--',
598             (@_ ? "@_" : ()));
599 2 50 33     10 $debug and $debug & 8 and _debug_dump 'call args' => \@args;
600 2         14 @args;
601             }
602              
603             sub _scp_cmd {
604 0     0   0 my $self = shift;
605 0   0     0 $self->{_scp_cmd} ||= do {
606 0         0 my $scp = $self->{_ssh_cmd};
607 0 0       0 $scp =~ s/ssh$/scp/i or croak "scp command name not set";
608 0         0 $scp;
609             }
610             }
611              
612             sub _make_scp_call {
613 0     0   0 my $self = shift;
614 0 0       0 my @before = @{shift || []};
  0         0  
615             my @args = ($self->_scp_cmd, @before,
616             -o => "ControlPath=$self->{_ctl_path}",
617             -S => $self->{_ssh_cmd},
618 0 0       0 (defined $self->{_port} ? (-P => $self->{_port}) : ()),
619             '--', @_);
620              
621 0 0 0     0 $debug and $debug & 8 and _debug_dump 'scp call args' => \@args;
622 0         0 @args;
623             }
624              
625             sub _rsync_quote {
626 0     0   0 my ($self, @args) = @_;
627 0         0 for (@args) {
628 0 0       0 if (/['"\s]/) {
629 0         0 s/"/""/g;
630 0         0 $_ = qq|"$_"|;
631             }
632 0         0 s/%/%%/;
633             }
634 0 0       0 wantarray ? @args : join(' ', @args);
635             }
636              
637             sub _make_rsync_call {
638 0     0   0 my $self = shift;
639 0         0 my $before = shift;
640             my @transport = ($self->{_ssh_cmd}, @$before,
641 0         0 -S => $self->{_ctl_path});
642 0         0 my $transport = $self->_rsync_quote(@transport);
643             my @args = ( $self->{_rsync_cmd},
644 0         0 -e => $transport,
645             @_);
646              
647 0 0 0     0 $debug and $debug & 8 and _debug_dump 'rsync call args' => \@args;
648 0         0 @args;
649             }
650              
651             sub _make_W_option {
652 0     0   0 my $self = shift;
653 0 0       0 if (@_ == 1) {
654 0         0 my $path = shift;
655 0 0       0 $path = "./$path" unless $path =~ m|/|;
656 0         0 $path =~ s/([\\:])/\\$1/g;
657 0         0 return "-W$path";
658             }
659 0 0       0 if (@_ == 2) {
660 0         0 return "-W" . join(':', @_);
661             }
662 0         0 croak "bad number of arguments for creating a tunnel"
663             }
664              
665             sub _make_tunnel_call {
666 0     0   0 my $self = shift;
667 0 0       0 my @before = @{shift||[]};
  0         0  
668 0         0 push @before, $self->_make_W_option(@_);
669 0         0 my @args = $self->_make_ssh_call(\@before);
670 0 0 0     0 $debug and $debug & 8 and _debug_dump 'tunnel call args' => \@args;
671 0         0 @args;
672             }
673              
674             sub master_exited {
675 0     0 1 0 my $self = shift;
676 0         0 $self->_master_gone(1)
677             }
678              
679             sub _master_gone {
680 0     0   0 my $self = shift;
681 0         0 my $async = shift;
682 0         0 delete $self->{_pid};
683 0 0       0 $self->_master_fail($async, (@_ ? @_ : "master process exited unexpectedly"));
684             }
685              
686             my @kill_signal = qw(0 0 TERM TERM TERM KILL);
687              
688             sub __has_sigchld_handle {
689 2     2   22 my $h = $SIG{CHLD};
690 2 50 33     114 defined $h and $h ne 'IGNORE' and $h ne 'DEFAULT'
691             }
692              
693             sub _master_kill {
694 0     0   0 my ($self, $async) = @_;
695              
696 0 0       0 if (my $pid = $self->_my_master_pid) {
697 0 0 0     0 $debug and $debug & 32 and _debug '_master_kill: ', $pid;
698              
699 0         0 my $now = time;
700 0   0     0 my $start = $self->{_master_kill_start} ||= $now;
701 0   0     0 $self->{_master_kill_last} ||= $now;
702 0   0     0 $self->{_master_kill_count} ||= 0;
703              
704 0 0 0 0   0 local $SIG{CHLD} = sub {} unless $async or __has_sigchld_handle;
705 0         0 while (1) {
706 0 0       0 if ($self->{_master_kill_last} < $now) {
707 0         0 $self->{_master_kill_last} = $now;
708 0         0 my $sig = $kill_signal[$self->{_master_kill_count}++];
709 0 0       0 $sig = 'KILL' unless defined $sig;
710 0 0 0     0 $debug and $debug & 32 and _debug "killing master $$ with signal $sig";
711 0         0 kill $sig, $pid;
712             }
713 0         0 my $deceased = waitpid($pid, WNOHANG);
714 0 0 0     0 $debug and $debug & 32 and _debug "waitpid(master: $pid) => pid: $deceased, rc: $!";
715 0 0 0     0 last if $deceased == $pid or ($deceased < 0 and $! == Errno::ECHILD());
      0        
716 0 0       0 if ($self->{_master_kill_count} > 20) {
717             # FIXME: remove the hard-coded 20 retries?
718 0 0 0     0 $debug and $debug & 32 and _debug "unable to kill SSH master process, giving up";
719 0         0 last;
720             }
721 0 0       0 return if $async;
722 0         0 select(undef, undef, undef, 0.2);
723 0         0 $now = time;
724             }
725             }
726             else {
727             $debug and $debug & 32 and _debug("not killing master SSH (", $self->{_pid}, ") started from " .
728             "process ", $self->{_perl_pid}, "/", $self->{_thread_generation},
729 0 0 0     0 ", current ", $$, "/", $thread_generation, ")");
730             }
731 0         0 $self->_master_gone($async);
732             }
733              
734             sub disconnect {
735 0     0 1 0 my ($self, $async) = @_;
736 0 0       0 @_ <= 2 or croak 'Usage: $self->disconnect([$async])';
737 0         0 $self->_disconnect($async, 1);
738             }
739              
740             sub disown_master {
741 0     0 1 0 my $self = shift;
742 0 0       0 if (my $pid = $self->_my_master_pid) {
743 0 0       0 if ($self->wait_for_master) {
744 0         0 $self->{_external_master} = 1;
745 0         0 return $pid;
746             }
747             }
748 0         0 undef;
749             }
750              
751             sub restart {
752 0     0 1 0 my ($self, $async) = @_;
753 0 0       0 $self->{_external_master} and croak "Can restart SSH connection when using external master";
754              
755             # user is responsible for calling us in STATE_GONE in async mode
756 0 0       0 $self->_disconnect($async, 1) unless $async;
757              
758 0 0       0 if ($self->{_master_state} != _STATE_GONE) {
759 0 0       0 croak "restart method called in wrong state (terminate the connection first!)" if $async;
760 0         0 return $self->_master_fail($async, "Unable to restart SSH session from state $self->{_master_state}")
761             }
762              
763             # These slots should be deleted when exiting the KILLING state but
764             # I like keeping them around for throubleshoting purposes.
765 0         0 delete $self->{_master_kill_start};
766 0         0 delete $self->{_master_kill_last};
767 0         0 delete $self->{_master_kill_count};
768 0         0 $self->_master_jump_state(_STATE_START, $async);
769             }
770              
771             sub _my_master_pid {
772 4     4   11 my $self = shift;
773 4 50       16 unless ($self->{_external_master}) {
774 4         13 my $pid = $self->{_pid};
775             return $pid if
776 4 50 66     89 $pid and $self->{_perl_pid} == $$ and $self->{_thread_generation} == $thread_generation;
      66        
777             }
778             ()
779 3         30 }
780              
781             sub _disconnect {
782 1     1   3 my ($self, $async, $send_ctl) = @_;
783 1 50       314 return if $self->{_master_state} == _STATE_GONE;
784              
785 0 0 0     0 if (!$async and
      0        
      0        
786             $self->{_master_state} == _STATE_RUNNING and
787             ($send_ctl or $self->_my_master_pid)) {
788             # we have successfully created the master connection so we
789             # can send control commands:
790 0 0 0     0 $debug and $debug & 32 and _debug("sending exit control to master");
791 0         0 $self->_master_ctl('exit');
792             }
793 0         0 $self->_master_fail($async, 'aborted')
794             }
795              
796             sub _check_is_system_fh {
797 6     6   12 my ($name, $fh) = @_;
798 6 100       27 my $fn = fileno(defined $fh ? $fh : $name);
799 6 50 33     39 defined $fn and $fn >= 0 and return;
800 0         0 croak "child process $name is not a real system file handle";
801             }
802              
803             sub _master_redirect {
804 2     2   16 my $self = shift;
805 2         20 my $uname = uc shift;
806 2         28 my $name = lc $uname;
807              
808 5     5   40 no strict 'refs';
  5         6  
  5         50350  
809 2 50       24 if ($self->{"_master_${name}_discard"}) {
810 0         0 open *$uname, '>>', '/dev/null';
811             }
812             else {
813 2         18 my $fh = $self->{"_master_${name}_fh"};
814 2 50       22 $fh = $self->{"_default_${name}_fh"} unless defined $fh;
815 2 50       17 if (defined $fh) {
816 0         0 _check_is_system_fh $uname => $fh;
817 0 0       0 if (fileno $fh != fileno *$uname) {
818 0 0       0 open *$uname, '>>&', $fh or POSIX::_exit(255);
819             }
820             }
821             }
822             }
823              
824             sub _waitpid {
825 2     2   10 my ($self, $pid, $timeout) = @_;
826 2         38 $? = 0;
827 2 50       16 if ($pid) {
828 2 50       8 $timeout = $self->{_timeout} unless defined $timeout;
829              
830 2         6 my $time_limit;
831 2 50 33     16 if (defined $timeout and $self->{_kill_ssh_on_timeout}) {
832 2 50       10 $timeout = 0 if $self->{_error} == OSSH_SLAVE_TIMEOUT;
833 2         6 $time_limit = time + $timeout;
834             }
835 2 50   0   28 local $SIG{CHLD} = sub {} unless __has_sigchld_handle;
836 2         10 while (1) {
837 2         4 my $deceased;
838 2 50       8 if (defined $time_limit) {
839 2         4 while (1) {
840             # TODO: we assume that all OSs return 0 when the
841             # process is still running, that may be wrong!
842 2 50       100 $deceased = waitpid($pid, WNOHANG) and last;
843 0         0 my $remaining = $time_limit - time;
844 0 0       0 if ($remaining <= 0) {
845 0 0 0     0 $debug and $debug & 16 and _debug "killing SSH slave, pid: $pid";
846 0         0 kill TERM => $pid;
847 0         0 $self->_or_set_error(OSSH_SLAVE_TIMEOUT, "ssh slave failed", "timed out");
848             }
849             # There is a race condition here. We try to
850             # minimize it keeping the waitpid and the select
851             # together and limiting the sleep time to 1s:
852 0 0       0 my $sleep = ($remaining < 0.1 ? 0.1 : 1);
853 0 0 0     0 $debug and $debug & 16 and
854             _debug "waiting for slave, timeout: $timeout, remaining: $remaining, sleep: $sleep";
855 0 0       0 $deceased = waitpid($pid, WNOHANG) and last;
856 0         0 select(undef, undef, undef, $sleep);
857             }
858             }
859             else {
860 0         0 $deceased = waitpid($pid, 0);
861             }
862 2 50 33     12 $debug and $debug & 16 and _debug "_waitpid($pid) => pid: $deceased, rc: $?, err: $!";
863 2 50       8 if ($deceased == $pid) {
    0          
    0          
864 2 50       10 if ($?) {
865 0         0 my $signal = ($? & 255);
866 0         0 my $errstr = "child exited with code " . ($? >> 8);
867 0 0       0 $errstr .= ", signal $signal" if $signal;
868 0         0 $self->_or_set_error(OSSH_SLAVE_CMD_FAILED, $errstr);
869 0         0 return undef;
870             }
871 2         36 return 1;
872             }
873             elsif ($deceased < 0) {
874             # at this point $deceased < 0 and so, $! has a valid error value.
875 0 0       0 next if $! == Errno::EINTR();
876 0 0       0 if ($! == Errno::ECHILD()) {
877 0         0 $self->_or_set_error(OSSH_SLAVE_FAILED, "child process $pid does not exist", $!);
878             return undef
879 0         0 }
880 0         0 warn "Internal error: unexpected error (".($!+0).": $!) from waitpid($pid) = $deceased. Report it, please!";
881             }
882             elsif ($deceased > 0) {
883 0         0 warn "Internal error: spurious process $deceased exited"
884             }
885              
886             # wait a bit before trying again
887 0         0 select(undef, undef, undef, 0.1);
888             }
889             }
890             else {
891 0         0 $self->_or_set_error(OSSH_SLAVE_FAILED, "spawning of new process failed");
892 0         0 return undef;
893             }
894             }
895              
896             sub check_master {
897 0     0 1 0 my $self = shift;
898 0 0       0 @_ and croak 'Usage: $ssh->check_master()';
899 0         0 $self->_master_check(0);
900             }
901              
902             sub wait_for_master {
903 0     0 1 0 my ($self, $async) = @_;
904 0 0       0 @_ <= 2 or croak 'Usage: $ssh->wait_for_master([$async])';
905             $self->{_error} = 0
906 0 0       0 unless $self->{_error} == OSSH_MASTER_FAILED;
907 0         0 $self->_master_wait($async);
908             }
909              
910             sub _master_start {
911 2     2   8 my ($self, $async) = @_;
912 2         32 $self->_set_error;
913              
914 2   50     14 my $timeout = int((($self->{_timeout} || 90) + 2)/3);
915 2         12 my $ssh_flags= '-2MN';
916 2 0       24 $ssh_flags .= ($self->{_forward_agent} ? 'A' : 'a') if defined $self->{_forward_agent};
    50          
917 2 50       12 $ssh_flags .= ($self->{_forward_X11} ? 'X' : 'x');
918 2         50 my @master_opts = (@{$self->{_master_opts}},
919             -o => "ServerAliveInterval=$timeout",
920 2 50       6 ($self->{_ssh_version} >= 5.6 ? (-o => "ControlPersist=no") : ()),
921             $ssh_flags);
922              
923 2         8 my ($mpty, $use_pty, $pref_auths);
924             $use_pty = 1 if ( $self->{_master_pty_force} or
925 2 50 33     26 defined $self->{_login_handler} );
926 2 50       12 if (defined $self->{_passwd}) {
    50          
927 0         0 $use_pty = 1;
928             $pref_auths = ($self->{_passphrase}
929 0 0       0 ? 'publickey'
930             : 'keyboard-interactive,password');
931 0         0 push @master_opts, -o => 'NumberOfPasswordPrompts=1';
932             }
933             elsif ($self->{_batch_mode}) {
934 0         0 push @master_opts, -o => 'BatchMode=yes';
935             }
936              
937 2 50       8 if (defined $self->{_key_path}) {
938 0         0 $pref_auths = 'publickey';
939 0         0 push @master_opts, -i => $self->{_key_path};
940             }
941              
942 2         20 my $proxy_command = $self->{_proxy_command};
943              
944 2         10 my $gateway;
945 2 50       10 if (my $gateway_args = $self->{_gateway_args}) {
946 0 0       0 if (ref $gateway_args eq 'HASH') {
947 0         0 _load_module('Net::OpenSSH::Gateway');
948 0         0 my $errors;
949 0 0       0 unless ($gateway = Net::OpenSSH::Gateway->find_gateway(errors => $errors,
950             host => $self->{_host}, port => $self->{_port},
951             %$gateway_args)) {
952 0         0 return $self->_master_fail($async, 'Unable to build gateway object', join(', ', @$errors));
953             }
954             }
955             else {
956 0         0 $gateway = $gateway_args
957             }
958 0         0 $self->{_gateway} = $gateway;
959 0 0       0 $gateway->before_ssh_connect or
960             return $self->_master_fail($async, 'Gateway setup failed', join(', ', $gateway->errors));
961 0         0 $proxy_command = $gateway->proxy_command;
962             }
963              
964 2 50       8 if (defined $proxy_command) {
965 0         0 push @master_opts, -o => "ProxyCommand=$proxy_command";
966             }
967              
968 2 50       8 if ($use_pty) {
969 0         0 _load_module('IO::Pty');
970 0         0 $self->{_mpty} = $mpty = IO::Pty->new;
971             }
972              
973 2 50       24 push @master_opts, -o => "PreferredAuthentications=$pref_auths"
974             if defined $pref_auths;
975              
976 2         12 my @call = $self->_make_ssh_call(\@master_opts);
977              
978 2         1960 my $pid = fork;
979 2 100       150 unless ($pid) {
980 1 50       38 defined $pid
981             or return $self->_master_fail($async, "unable to fork ssh master: $!");
982              
983 1 50 33     45 if ($debug and $debug & 512) {
984 0         0 require Net::OpenSSH::OSTracer;
985 0         0 Net::OpenSSH::OSTracer->trace;
986             }
987              
988 1 50       16 $mpty->make_slave_controlling_terminal if $mpty;
989              
990 1         52 $self->_master_redirect('STDOUT');
991 1         16 $self->_master_redirect('STDERR');
992              
993 1 50       6 delete $ENV{SSH_ASKPASS} if defined $self->{_passwd};
994 1 50       6 delete $ENV{SSH_AUTH_SOCK} if defined $self->{_passphrase};
995              
996 1 50       6 setpgrp if $self->{_master_setpgrp};
997              
998 1         48 local $SIG{__DIE__};
999 1         23 eval { exec @call };
  1         0  
1000 0         0 POSIX::_exit(255);
1001             }
1002 1         24 $self->{_pid} = $pid;
1003 1         145 1;
1004             }
1005              
1006             sub _master_check {
1007 0     0   0 my ($self, $async) = @_;
1008 0         0 my $error;
1009 0 0       0 if ($async) {
1010 0 0       0 if (-S $self->{_ctl_path}) {
1011 0         0 delete $self->{_master_pty_log};
1012 0         0 return 1
1013             }
1014 0         0 $error = "master SSH connection broken";
1015             }
1016             else {
1017 0         0 my $out = $self->_master_ctl('check');
1018 0         0 $error = $self->{_error};
1019 0 0       0 unless ($error) {
1020 0         0 my $pid = $self->{_pid};
1021 0 0       0 if ($out =~ /pid=(\d+)/) {
1022 0 0 0     0 if (!$pid or $1 == $pid) {
1023 0         0 delete $self->{_master_pty_log};
1024 0         0 return 1;
1025             }
1026 0         0 $error = "bad ssh master at $self->{_ctl_path} socket owned by pid $1 (pid $pid expected)";
1027             }
1028             else {
1029 0 0       0 $error = ($out =~ /illegal option/i
1030             ? 'OpenSSH 4.1 or later required'
1031             : 'unknown error');
1032             }
1033             }
1034             }
1035 0         0 $self->_master_fail($async, $error);
1036             }
1037              
1038             sub _master_fail {
1039 1     1   4 my $self = shift;
1040 1         2 my $async = shift;
1041 1 50       12 if ($self->{_error} != OSSH_MASTER_FAILED) {
1042 1         15 $self->_set_error(OSSH_MASTER_FAILED, @_);
1043             }
1044 1 50       23 $self->_master_jump_state($self->{_pid} ? _STATE_KILLING : _STATE_GONE, $async);
1045             }
1046              
1047             sub _master_jump_state {
1048 2     2   16 my ($self, $state, $async) = @_;
1049 2 50 33     33 $debug and $debug & 4 and _debug "master state jumping from $self->{_master_state} to $state";
1050 2 0 33     38 if ($state == $self->{_master_state} and
      33        
1051             $state != _STATE_KILLING and
1052             $state != _STATE_GONE) {
1053 0         0 croak "internal error: state jump to itself ($state)!";
1054             }
1055 2         18 $self->{_master_state} = $state;
1056 2         48 return $self->_master_wait($async);
1057             }
1058              
1059             sub _master_wait {
1060 4     4   18 my ($self, $async) = @_;
1061              
1062 4         26 my $pid = $self->_my_master_pid;
1063 4 100       14 if ($pid) {
1064 1         10 my $deceased = waitpid($pid, WNOHANG);
1065 1 50 33     32 if ($deceased == $pid or ($deceased < 0 and $! == Errno::ECHILD())) {
      33        
1066 0 0 0     0 $debug and $debug & 4 and _debug "master $pid exited, rc:", $?,", err: ",$!;
1067 0         0 return $self->_master_gone($async);
1068             }
1069             }
1070              
1071 4 50       16 if ($self->{_master_state} == _STATE_RUNNING) {
1072 0 0       0 return 1 if -S $self->{_ctl_path};
1073 0         0 return $self->_master_fail($async, "master SSH connection broken");
1074             }
1075              
1076 4 50       13 if ($self->{_master_state} == _STATE_KILLING) {
1077 0 0 0     0 $debug and $debug & 4 and _debug "killing master";
1078 0         0 return $self->_master_kill($async);
1079             }
1080              
1081 4 100       15 if ($self->{_master_state} == _STATE_START) {
1082 2 50       8 if ($self->{_external_master}) {
1083 0   0     0 return ($self->_master_jump_state(_STATE_RUNNING, $async) and
1084             $self->_master_check($async))
1085             }
1086              
1087 2 50       20 $self->_master_start($async) or return;
1088 1 50       28 if ($self->{_mpty}) {
1089 0         0 $self->{_wfm_bout} = '';
1090 0         0 $self->{_master_pty_log} = '';
1091 0 0 0     0 if (defined $self->{_passwd} or $self->{_login_handler}) {
1092 0         0 return $self->_master_jump_state(_STATE_LOGIN, $async);
1093             }
1094             }
1095 1         50 return $self->_master_jump_state(_STATE_AWAITING_MUX, $async);
1096             }
1097              
1098 2 100       16 if ($self->{_master_state} == _STATE_GONE) {
1099 1 50       3 if (my $mpty = delete $self->{_mpty}) {
1100 0         0 close($mpty)
1101             }
1102 1         10 return 0;
1103             }
1104 1 50       4 if ($self->{_master_state} == _STATE_STOPPED) {
1105 0         0 return 0;
1106             }
1107              
1108             # At this point we are either in state AWAITIN_MUX or LOGIN
1109              
1110 1         3 local $self->{_error_prefix} = [@{$self->{_error_prefix}},
  1         21  
1111             "unable to establish master SSH connection"];
1112              
1113 1 50       13 $pid or return $self->_master_gone($async,
1114             "perl process was forked or threaded before SSH connection had been established");
1115              
1116 1         18 my $old_tcpgrp;
1117 1 0 33     7 if ($self->{_master_setpgrp} and not $async and
      33        
      0        
1118             not $self->{_batch_mode} and not $self->{_external_master}) {
1119 0         0 $old_tcpgrp = POSIX::tcgetpgrp(0);
1120 0 0       0 if ($old_tcpgrp > 0) {
1121             # let the master process ask for passwords at the TTY
1122 0         0 POSIX::tcsetpgrp(0, $pid);
1123             }
1124             else {
1125 0         0 undef $old_tcpgrp;
1126             }
1127             }
1128              
1129 1         3 my $mpty = $self->{_mpty};
1130 1         1 my $fnopty;
1131 1         16 my $rv = '';
1132 1 0 0     4 if ($mpty and
      33        
1133             ( $self->{_master_state} == _STATE_LOGIN or
1134             $self->{_master_state} == _STATE_AWAITING_MUX )) {
1135 0         0 $fnopty = fileno $mpty;
1136 0         0 vec($rv, $fnopty, 1) = 1
1137             }
1138              
1139 1         12 my $timeout = $self->{_timeout};
1140 1 50       23 my $dt = ($async ? 0 : 0.02);
1141 1         4 my $start_time = time;
1142 1         3 my $error;
1143              
1144             # Loop until the mux socket appears or something goes wrong:
1145 1         1 while (1) {
1146 15 50       219 $dt *= 1.10 if $dt < 0.2; # adaptative delay
1147 15 50       410 if (-e $self->{_ctl_path}) {
1148 0 0 0     0 $debug and $debug & 4 and _debug "file object found at $self->{_ctl_path}";
1149 0         0 last;
1150             }
1151 15 50 33     57 $debug and $debug & 4 and _debug "file object not yet found at $self->{_ctl_path}, state:", $self->{_master_state};
1152              
1153 15 50 33     134 if (defined $timeout and (time - $start_time) > $timeout) {
1154 0         0 $error = "login timeout";
1155 0         0 last;
1156             }
1157              
1158 15         174 my $deceased = waitpid($pid, WNOHANG);
1159 15 100 33     129 if ($deceased == $pid or ($deceased < 0 and $! == Errno::ECHILD())) {
      66        
1160 1         28 $error = "master process exited unexpectedly";
1161             $error = "bad pass" . ($self->{_passphrase} ? 'phrase' : 'word') . " or $error"
1162 1 0       9 if defined $self->{_passwd};
    50          
1163 1         11 delete $self->{_pid};
1164 1         4 last;
1165             }
1166              
1167 14 50 33     88 if ($self->{_login_handler} and $self->{_master_state} == _STATE_LOGIN) {
1168 0         0 local ($@, $SIG{__DIE__});
1169 0 0       0 if (eval { $self->{_login_handler}->($self, $mpty, \$self->{_wfm_bout}) }) {
  0         0  
1170 0         0 $self->{_master_state} = _STATE_AWAITING_MUX;
1171 0         0 next;
1172             }
1173 0 0       0 if ($@) {
1174 0         0 $error = "custom login handler failed: $@";
1175 0         0 last;
1176             }
1177             # fallback
1178             }
1179             else {
1180             # we keep reading from mpty even after leaving state
1181             # STATE_LOGIN in order to search for additional password
1182             # prompts.
1183 14         39 my $rv1 = $rv;
1184 14         617470 my $n = select($rv1, undef, undef, $dt);
1185 14 50       162 if ($n > 0) {
1186 0 0       0 vec($rv1, $fnopty, 1) or die "internal error";
1187 0         0 my $read = sysread($mpty, $self->{_wfm_bout}, 4096, length $self->{_wfm_bout});
1188 0 0       0 if ($read) {
1189 0         0 $self->{_master_pty_log} .= substr($self->{_wfm_bout}, -$read);
1190 0 0       0 if ((my $remove = length($self->{_master_pty_log}) - 4096) > 0) {
1191 0         0 substr($self->{_master_pty_log}, 0, $remove) = ''
1192             }
1193              
1194 0 0       0 if ($self->{_wfm_bout} =~ /The authenticity of host.*can't be established/si) {
1195 0         0 $error = "the authenticity of the target host can't be established; the remote host " .
1196             "public key is probably not present in the '~/.ssh/known_hosts' file";
1197 0         0 last;
1198             }
1199              
1200 0 0       0 if ($self->{_wfm_bout} =~ /WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED/si) {
1201 0         0 $error = "the authenticity of the target host can't be established; the remote host " .
1202             "public key doesn't match the one stored locally";
1203 0         0 last;
1204             }
1205              
1206 0         0 my $passwd_prompt = _first_defined $self->{_passwd_prompt}, qr/[:?]/;
1207 0 0       0 $passwd_prompt = quotemeta $passwd_prompt unless ref $passwd_prompt;
1208              
1209 0 0 0     0 if ($self->{_master_state} == _STATE_LOGIN) {
    0          
1210 0 0       0 if ($self->{_wfm_bout} =~ /^(.*$passwd_prompt)/s) {
1211 0 0 0     0 $debug and $debug & 4 and _debug "passwd/passphrase requested ($1)";
1212 0         0 print $mpty $deobfuscate->($self->{_passwd}) . "\n";
1213 0         0 $self->{_wfm_bout} = ''; # reset
1214 0         0 $self->{_master_state} = _STATE_AWAITING_MUX;
1215             }
1216             }
1217             elsif (length($passwd_prompt) and $self->{_wfm_bout} =~ /^(.*$passwd_prompt)\s*$/s) {
1218 0 0 0     0 $debug and $debug & 4 and _debug "passwd/passphrase requested again ($1)";
1219 0         0 $error = "password authentication failed";
1220 0         0 last;
1221             }
1222 0         0 next; # skip delay
1223             }
1224             }
1225             }
1226 14 50       65 return if $async;
1227 14         617470 select(undef, undef, undef, $dt);
1228             }
1229              
1230 1 50       8 if (defined $old_tcpgrp) {
1231 0 0 0     0 $debug and $debug & 4 and
1232             _debug("ssh pid: $pid, pgrp: ", getpgrp($pid),
1233             ", \$\$: ", $$,
1234             ", tcpgrp: ", POSIX::tcgetpgrp(0),
1235             ", old_tcppgrp: ", $old_tcpgrp);
1236 0         0 local $SIG{TTOU} = 'IGNORE';
1237 0         0 POSIX::tcsetpgrp(0, $old_tcpgrp);
1238             }
1239              
1240 1 50       4 if ($error) {
1241 1         21 return $self->_master_fail($async, $error);
1242             }
1243              
1244 0 0       0 $self->_master_jump_state(_STATE_RUNNING, $async)
1245             and $self->_master_check($async);
1246             }
1247              
1248             sub _master_ctl {
1249 0     0   0 my $self = shift;
1250 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
1251 0         0 my $cmd = shift;
1252              
1253 0         0 local $?;
1254 0         0 local $self->{_error_prefix} = [@{$self->{_error_prefix}},
  0         0  
1255             "control command failed"];
1256 0         0 $self->capture({ %opts,
1257             encoding => 'bytes', # don't let the encoding
1258             # stuff get in the way
1259             stdin_discard => 1, tty => 0,
1260             stderr_to_stdout => 1, ssh_opts => [-O => $cmd]});
1261             }
1262              
1263             sub stop {
1264 0     0 0 0 my ($self, $timeout) = @_;
1265 0         0 my $pid = $self->{_pid};
1266 0         0 local $self->{_kill_ssh_on_timeout} = 1;
1267 0         0 $self->_master_ctl({timeout => $timeout}, 'stop');
1268 0 0       0 unless ($self->{_error}) {
1269 0         0 $self->_set_error(OSSH_MASTER_FAILED, "master stopped");
1270 0         0 $self->_master_jump_state(_STATE_STOPPED, 1);
1271             }
1272             }
1273              
1274             sub _make_pipe {
1275 3     3   9 my $self = shift;
1276 3         6 my ($r, $w);
1277 3 50       111 if (pipe $r, $w) {
1278 3         18 my $old = select;
1279 3         12 select $r; $ |= 1;
  3         24  
1280 3         27 select $w; $ |= 1;
  3         9  
1281 3         9 select $old;
1282 3         18 return ($r, $w);
1283             }
1284 0         0 $self->_set_error(OSSH_SLAVE_PIPE_FAILED, "unable to create pipe: $!");
1285 0         0 return;
1286             }
1287              
1288             sub _remote_quoter {
1289 3     3   9 my ($self, $remote_shell) = @_;
1290 3 50 33     54 if (ref $self and (!defined $remote_shell or $remote_shell eq $self->{_remote_shell})) {
      33        
1291 3   33     78 return $self->{remote_quoter} ||= Net::OpenSSH::ShellQuoter->quoter($self->{_remote_shell});
1292             }
1293 0         0 Net::OpenSSH::ShellQuoter->quoter($remote_shell);
1294             }
1295              
1296             sub _quote_args {
1297 3     3   6 my $self = shift;
1298 3         6 my $opts = shift;
1299 3 50       9 ref $opts eq 'HASH' or die "internal error";
1300 3         6 my $quote = delete $opts->{quote_args};
1301 3         6 my $quote_extended = delete $opts->{quote_args_extended};
1302 3         6 my $glob_quoting = delete $opts->{glob_quoting};
1303 3 50       9 $quote = (@_ > 1) unless defined $quote;
1304              
1305 3 50       9 if ($quote) {
1306 3         6 my $remote_shell = delete $opts->{remote_shell};
1307 3         12 my $quoter = $self->_remote_quoter($remote_shell);
1308 3 50       27 my $quote_method = ($glob_quoting ? 'quote_glob' : 'quote');
1309             # foo => $quoter
1310             # \foo => $quoter_glob
1311             # \\foo => no quoting at all and disable extended quoting as it is not safe
1312 3         12 my @quoted;
1313 3         9 for (@_) {
1314 6 50       15 if (ref $_) {
1315 0 0 0     0 if (ref $_ eq 'SCALAR') {
    0          
1316 0         0 push @quoted, $quoter->quote_glob($self->_expand_vars($$_));
1317             }
1318             elsif (ref $_ eq 'REF' and ref $$_ eq 'SCALAR') {
1319 0         0 push @quoted, $self->_expand_vars($$$_);
1320 0         0 undef $quote_extended;
1321             }
1322             else {
1323 0         0 croak "invalid reference in remote command argument list"
1324             }
1325             }
1326             else {
1327 6         24 push @quoted, $quoter->$quote_method($self->_expand_vars($_));
1328             }
1329             }
1330              
1331 3 50       9 if ($quote_extended) {
1332 0         0 my @fragments;
1333 0 0 0     0 if ( $opts->{stdout_discard} and
      0        
1334             ( $opts->{stderr_discard} or $opts->{stderr_to_stdout} ) ) {
1335 0         0 @fragments = ('stdout_and_stderr_discard');
1336 0 0       0 push @fragments, 'stdin_discard' if $opts->{stdin_discard};
1337             }
1338             else {
1339 0         0 @fragments = grep $opts->{$_}, qw(stdin_discard stdout_discard
1340             stderr_discard stderr_to_stdout);
1341             }
1342 0         0 push @quoted, $quoter->shell_fragments(@fragments);
1343             }
1344 3 50       18 wantarray ? @quoted : join(" ", @quoted);
1345             }
1346             else {
1347 0 0       0 croak "reference found in argument list when argument quoting is disabled"
1348             if (grep ref, @_);
1349              
1350 0         0 my @args = $self->_expand_vars(@_);
1351 0 0       0 wantarray ? @args : join(" ", @args);
1352             }
1353             }
1354              
1355             sub shell_quote {
1356 0     0 1 0 shift->_quote_args({quote_args => 1}, @_);
1357             }
1358              
1359             sub shell_quote_glob {
1360 0     0 1 0 shift->_quote_args({quote_args => 1, glob_quoting => 1}, @_);
1361             }
1362              
1363 5 0   5   63 sub _array_or_scalar_to_list { map { defined($_) ? (ref $_ eq 'ARRAY' ? @$_ : $_ ) : () } @_ }
  5 50       23  
1364              
1365             sub make_remote_command {
1366 0     0 1 0 my $self = shift;
1367 0 0       0 $self->wait_for_master or return;
1368 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
1369 0         0 my @ssh_opts = _array_or_scalar_to_list delete $opts{ssh_opts};
1370 0         0 my $tty = delete $opts{tty};
1371 0         0 my $ssh_flags = '';
1372 0 0       0 $ssh_flags .= ($tty ? 'qtt' : 'T') if defined $tty;
    0          
1373 0 0       0 if ($self->{_forward_agent}) {
1374 0 0       0 my $forward_always = (($self->{_forward_agent} eq 'always') ? 1 : undef);
1375 0         0 my $forward_agent = _first_defined(delete($opts{forward_agent}), $forward_always);
1376 0 0       0 $ssh_flags .= ($forward_agent ? 'A' : 'a') if defined $forward_agent;
    0          
1377             }
1378 0 0       0 if ($self->{_forward_X11}) {
1379 0         0 my $forward_X11 = delete $opts{forward_X11};
1380 0 0       0 $ssh_flags .= ($forward_X11 ? 'X' : 'x');
1381             }
1382 0         0 my $tunnel = delete $opts{tunnel};
1383 0         0 my (@args);
1384 0 0       0 if ($tunnel) {
1385 0         0 push @ssh_opts, $self->_make_W_option(@_);
1386             }
1387             else {
1388 0         0 my $subsystem = delete $opts{subsystem};
1389 0 0       0 if ($subsystem) {
1390 0         0 push @ssh_opts, '-s';
1391 0 0       0 @_ == 1 or croak "wrong number of arguments for subsystem command";
1392             }
1393 0         0 @args = $self->_quote_args(\%opts, @_);
1394             }
1395 0         0 _croak_bad_options %opts;
1396              
1397 0 0       0 push @ssh_opts, "-$ssh_flags" if length $ssh_flags;
1398 0         0 my @call = $self->_make_ssh_call(\@ssh_opts, @args);
1399 0 0       0 if (wantarray) {
1400 0 0 0     0 $debug and $debug & 16 and _debug_dump make_remote_command => \@call;
1401 0         0 return @call;
1402             }
1403             else {
1404 0         0 my $call = join ' ', $self->shell_quote(@call);
1405 0 0 0     0 $debug and $debug & 16 and _debug_dump 'make_remote_command (quoted)' => $call;
1406 0         0 return $call
1407             }
1408             }
1409              
1410             sub _open_file {
1411 0     0   0 my ($self, $default_mode, $name_or_args) = @_;
1412 0 0       0 my ($mode, @args) = (ref $name_or_args
1413             ? @$name_or_args
1414             : ($default_mode, $name_or_args));
1415 0         0 @args = $self->_expand_vars(@args);
1416 0 0       0 if (open my $fh, $mode, @args) {
1417 0         0 return $fh;
1418             }
1419             else {
1420 0         0 $self->_set_error(OSSH_SLAVE_PIPE_FAILED,
1421             "Unable to open file '$args[0]': $!");
1422 0         0 return undef;
1423             }
1424             }
1425              
1426             sub _fileno_dup_over {
1427 3     3   17 my ($good_fn, $fh) = @_;
1428 3 100       15 if (defined $fh) {
1429 2         7 my $fn = fileno $fh;
1430 2         16 for (1..5) {
1431 2 50       10 $fn >= $good_fn and return $fn;
1432 0         0 $fn = POSIX::dup($fn);
1433             }
1434 0         0 POSIX::_exit(255);
1435             }
1436 1         11 undef;
1437             }
1438              
1439             sub _exec_dpipe {
1440 0     0   0 my ($self, $cmd, $io, $err) = @_;
1441 0         0 my $io_fd = _fileno_dup_over(3 => $io);
1442 0         0 my $err_fd = _fileno_dup_over(3 => $err);
1443 0         0 POSIX::dup2($io_fd, 0);
1444 0         0 POSIX::dup2($io_fd, 1);
1445 0 0       0 POSIX::dup2($err_fd, 2) if defined $err_fd;
1446 0 0       0 if (ref $cmd) {
1447 0         0 exec @$cmd;
1448             }
1449             else {
1450 0         0 exec $cmd;
1451             }
1452             }
1453              
1454             sub _delete_stream_encoding {
1455 0     0   0 my ($self, $opts) = @_;
1456             _first_defined(delete $opts->{stream_encoding},
1457             $opts->{encoding},
1458 0         0 $self->{_default_stream_encoding});
1459             }
1460              
1461             sub _delete_argument_encoding {
1462 3     3   9 my ($self, $opts) = @_;
1463             _first_defined(delete $opts->{argument_encoding},
1464             delete $opts->{encoding},
1465 3         9 $self->{_default_argument_encoding});
1466             }
1467              
1468             sub open_ex {
1469 3 50   3 1 15 ${^TAINT} and &_catch_tainted_args;
1470 3         6 my $self = shift;
1471 3 50       12 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  3         15  
1472 3 50       15 unless (delete $opts{_no_master_required}) {
1473 0 0       0 $self->wait_for_master or return;
1474             }
1475              
1476 3         18 my $ssh_flags = '';
1477 3         9 my $tunnel = delete $opts{tunnel};
1478 3         6 my ($cmd, $close_slave_pty, @args);
1479 3 50       12 if ($tunnel) {
1480 0         0 @args = @_;
1481             }
1482             else {
1483 3         15 my $argument_encoding = $self->_delete_argument_encoding(\%opts);
1484 3         6 my $tty = delete $opts{tty};
1485 3 0       9 $ssh_flags .= ($tty ? 'qtt' : 'T') if defined $tty;
    50          
1486              
1487 3   50     12 $cmd = delete $opts{_cmd} || 'ssh';
1488             $opts{quote_args_extended} = 1
1489 3 50 33     33 if (not defined $opts{quote_args_extended} and $cmd eq 'ssh');
1490 3         42 @args = $self->_quote_args(\%opts, @_);
1491 3 50       39 $self->_encode_args($argument_encoding, @args) or return;
1492             }
1493              
1494 3         18 my ($stdinout_socket, $stdinout_dpipe_make_parent);
1495 3         12 my $stdinout_dpipe = delete $opts{stdinout_dpipe};
1496 3 50       12 if ($stdinout_dpipe) {
1497 0         0 $stdinout_dpipe_make_parent = delete $opts{stdinout_dpipe_make_parent};
1498 0         0 $stdinout_socket = 1;
1499             }
1500             else {
1501 3         6 $stdinout_socket = delete $opts{stdinout_socket};
1502             }
1503              
1504 3         9 my ($stdin_discard, $stdin_pipe, $stdin_fh, $stdin_file, $stdin_pty,
1505             $stdout_discard, $stdout_pipe, $stdout_fh, $stdout_file, $stdout_pty,
1506             $stderr_discard, $stderr_pipe, $stderr_fh, $stderr_file, $stderr_to_stdout);
1507 3 50       12 unless ($stdinout_socket) {
1508 3 0 33     12 unless ($stdin_discard = delete $opts{stdin_discard} or
      33        
      0        
1509             $stdin_pipe = delete $opts{stdin_pipe} or
1510             $stdin_fh = delete $opts{stdin_fh} or
1511             $stdin_file = delete $opts{stdin_file}) {
1512 0 0       0 unless ($tunnel) {
1513 0 0       0 if ($stdin_pty = delete $opts{stdin_pty}) {
1514 0         0 $close_slave_pty = _first_defined delete $opts{close_slave_pty}, 1;
1515             }
1516             }
1517             }
1518              
1519             ( $stdout_discard = delete $opts{stdout_discard} or
1520             $stdout_pipe = delete $opts{stdout_pipe} or
1521             $stdout_fh = delete $opts{stdout_fh} or
1522             $stdout_file = delete $opts{stdout_file} or
1523 3 0 0     36 (not $tunnel and $stdout_pty = delete $opts{stdout_pty}) );
      33        
      33        
      33        
1524              
1525 3 50 33     12 $stdout_pty and !$stdin_pty
1526             and croak "option stdout_pty requires stdin_pty set";
1527             }
1528              
1529             ( $stderr_discard = delete $opts{stderr_discard} or
1530             $stderr_pipe = delete $opts{stderr_pipe} or
1531             $stderr_fh = delete $opts{stderr_fh} or
1532             $stderr_to_stdout = delete $opts{stderr_to_stdout} or
1533 3 50 33     45 $stderr_file = delete $opts{stderr_file} );
      33        
      33        
1534              
1535 3         6 my $ssh_opts = delete $opts{ssh_opts};
1536 3 50       9 $ssh_opts = $self->{_default_ssh_opts} unless defined $ssh_opts;
1537 3         15 my @ssh_opts = $self->_expand_vars(_array_or_scalar_to_list $ssh_opts);
1538 3 50       9 if ($self->{_forward_agent}) {
1539 0 0       0 my $forward_always = (($self->{_forward_agent} eq 'always') ? 1 : undef);
1540 0         0 my $forward_agent = _first_defined(delete($opts{forward_agent}), $forward_always);
1541 0 0       0 $ssh_flags .= ($forward_agent ? 'A' : 'a') if defined $forward_agent;
    0          
1542             }
1543 3 50       9 if ($self->{_forward_X11}) {
1544 0         0 my $forward_X11 = delete $opts{forward_X11};
1545 0 0       0 $ssh_flags .= ($forward_X11 ? 'X' : 'x');
1546             }
1547 3 50       9 if (delete $opts{subsystem}) {
1548 0         0 $ssh_flags .= 's';
1549             }
1550              
1551 3         6 my $setpgrp = delete $opts{setpgrp};
1552 3 50       6 undef $setpgrp if defined $stdin_pty;
1553              
1554 3         12 _croak_bad_options %opts;
1555              
1556 3 50       6 if (defined $stdin_file) {
1557 0 0       0 $stdin_fh = $self->_open_file('<', $stdin_file) or return
1558             }
1559 3 50       12 if (defined $stdout_file) {
1560 0 0       0 $stdout_fh = $self->_open_file('>', $stdout_file) or return
1561             }
1562 3 50       9 if (defined $stderr_file) {
1563 0 0       0 $stderr_fh = $self->_open_file('>', $stderr_file) or return
1564             }
1565              
1566 3         18 my ($rin, $win, $rout, $wout, $rerr, $werr);
1567              
1568 3 50       12 if ($stdinout_socket) {
1569 0 0       0 unless(socketpair $rin, $win, AF_UNIX, SOCK_STREAM, PF_UNSPEC) {
1570 0         0 $self->_set_error(OSSH_SLAVE_PIPE_FAILED, "socketpair failed: $!");
1571 0         0 return;
1572             }
1573 0         0 $wout = $rin;
1574             }
1575             else {
1576 3 50       33 if ($stdin_pipe) {
    50          
    50          
1577 0 0       0 ($rin, $win) = $self->_make_pipe or return;
1578             }
1579             elsif ($stdin_pty) {
1580 0         0 _load_module('IO::Pty');
1581 0         0 $win = IO::Pty->new;
1582 0 0       0 unless ($win) {
1583 0         0 $self->_set_error(OSSH_SLAVE_PIPE_FAILED, "unable to allocate pseudo-tty: $!");
1584 0         0 return;
1585             }
1586 0         0 $rin = $win->slave;
1587             }
1588             elsif (defined $stdin_fh) {
1589 0         0 $rin = $stdin_fh;
1590             }
1591             else {
1592             $rin = $self->{_default_stdin_fh}
1593 3         12 }
1594 3         24 _check_is_system_fh STDIN => $rin;
1595              
1596 3 50       9 if ($stdout_pipe) {
    0          
    0          
1597 3 50       27 ($rout, $wout) = $self->_make_pipe or return;
1598             }
1599             elsif ($stdout_pty) {
1600 0         0 $wout = $rin;
1601             }
1602             elsif (defined $stdout_fh) {
1603 0         0 $wout = $stdout_fh;
1604             }
1605             else {
1606 0         0 $wout = $self->{_default_stdout_fh};
1607             }
1608 3         12 _check_is_system_fh STDOUT => $wout;
1609             }
1610              
1611 3 50       12 unless ($stderr_to_stdout) {
1612 0 0       0 if ($stderr_pipe) {
    0          
1613 0 0       0 ($rerr, $werr) = $self->_make_pipe or return;
1614             }
1615             elsif (defined $stderr_fh) {
1616 0         0 $werr = $stderr_fh;
1617             }
1618             else {
1619 0         0 $werr = $self->{_default_stderr_fh};
1620             }
1621 0         0 _check_is_system_fh STDERR => $werr;
1622             }
1623              
1624 3 50       9 push @ssh_opts, "-$ssh_flags" if length $ssh_flags;
1625              
1626 3 50       33 my @call = ( $tunnel ? $self->_make_tunnel_call(\@ssh_opts, @args) :
    50          
    50          
    50          
    50          
1627             $cmd eq 'ssh' ? $self->_make_ssh_call(\@ssh_opts, @args) :
1628             $cmd eq 'scp' ? $self->_make_scp_call(\@ssh_opts, @args) :
1629             $cmd eq 'rsync' ? $self->_make_rsync_call(\@ssh_opts, @args) :
1630             $cmd eq 'raw' ? @args :
1631             die "Internal error: bad _cmd protocol" );
1632              
1633 3 50 33     12 $debug and $debug & 16 and _debug_dump open_ex => \@call;
1634              
1635 3         1591 my $pid = fork;
1636 3 100       192 unless ($pid) {
1637 1 50       23 unless (defined $pid) {
1638 0         0 $self->_set_error(OSSH_SLAVE_FAILED,
1639             "unable to fork new ssh slave: $!");
1640 0         0 return;
1641             }
1642              
1643 1 50       20 setpgrp if $setpgrp;
1644              
1645 1 50 33     138 $stdin_discard and (open $rin, '<', '/dev/null' or POSIX::_exit(255));
1646 1 50 0     15 $stdout_discard and (open $wout, '>', '/dev/null' or POSIX::_exit(255));
1647 1 50 0     10 $stderr_discard and (open $werr, '>', '/dev/null' or POSIX::_exit(255));
1648              
1649 1 50       14 if ($stdinout_dpipe) {
1650 0         0 my $pid1 = fork;
1651 0 0       0 defined $pid1 or POSIX::_exit(255);
1652              
1653 0 0 0     0 unless ($pid1 xor $stdinout_dpipe_make_parent) {
1654 0         0 eval { $self->_exec_dpipe($stdinout_dpipe, $win, $werr) };
  0         0  
1655 0         0 POSIX::_exit(255);
1656             }
1657             }
1658              
1659 1         28 my $rin_fd = _fileno_dup_over(0 => $rin);
1660 1         4 my $wout_fd = _fileno_dup_over(1 => $wout);
1661 1         6 my $werr_fd = _fileno_dup_over(2 => $werr);
1662              
1663 1 50       7 if (defined $rin_fd) {
1664 1 50       4 $win->make_slave_controlling_terminal if $stdin_pty;
1665 1 50 33     47 $rin_fd == 0 or POSIX::dup2($rin_fd, 0) or POSIX::_exit(255);
1666             }
1667 1 50       4 if (defined $wout_fd) {
1668 1 50 33     21 $wout_fd == 1 or POSIX::dup2($wout_fd, 1) or POSIX::_exit(255);
1669             }
1670 1 50       8 if (defined $werr_fd) {
    50          
1671 0 0 0     0 $werr_fd == 2 or POSIX::dup2($werr_fd, 2) or POSIX::_exit(255);
1672             }
1673             elsif ($stderr_to_stdout) {
1674 1 50       8 POSIX::dup2(1, 2) or POSIX::_exit(255);
1675             }
1676 1         8 do { exec @call };
  1         0  
1677 0         0 POSIX::_exit(255);
1678             }
1679 2 50       34 $win->close_slave() if $close_slave_pty;
1680 2 50       30 undef $win if defined $stdinout_dpipe;
1681 2 50       274 wantarray ? ($win, $rout, $rerr, $pid) : $pid;
1682             }
1683              
1684             sub pipe_in {
1685 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
1686 0         0 my $self = shift;
1687 0 0       0 $self->wait_for_master or return;
1688 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
1689 0         0 my $argument_encoding = $self->_delete_argument_encoding(\%opts);
1690 0         0 my @args = $self->_quote_args(\%opts, @_);
1691 0         0 _croak_bad_options %opts;
1692              
1693 0 0       0 $self->_encode_args($argument_encoding, @args) or return;
1694 0         0 my @call = $self->_make_ssh_call([], @args);
1695 0 0 0     0 $debug and $debug & 16 and _debug_dump pipe_in => @call;
1696 0         0 my $pid = open my $rin, '|-', @call;
1697 0 0       0 unless ($pid) {
1698 0         0 $self->_set_error(OSSH_SLAVE_FAILED,
1699             "unable to fork new ssh slave: $!");
1700 0         0 return;
1701             }
1702 0 0       0 wantarray ? ($rin, $pid) : $rin;
1703             }
1704              
1705             sub pipe_out {
1706 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
1707 0         0 my $self = shift;
1708 0 0       0 $self->wait_for_master or return;
1709 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
1710 0         0 my $argument_encoding = $self->_delete_argument_encoding(\%opts);
1711 0         0 my @args = $self->_quote_args(\%opts, @_);
1712 0         0 _croak_bad_options %opts;
1713              
1714 0 0       0 $self->_encode_args($argument_encoding, @args) or return;
1715 0         0 my @call = $self->_make_ssh_call([], @args);
1716 0 0 0     0 $debug and $debug & 16 and _debug_dump pipe_out => @call;
1717 0         0 my $pid = open my $rout, '-|', @call;
1718 0 0       0 unless ($pid) {
1719 0         0 $self->_set_error(OSSH_SLAVE_FAILED,
1720             "unable to fork new ssh slave: $!");
1721 0         0 return;
1722             }
1723 0 0       0 wantarray ? ($rout, $pid) : $rout;
1724             }
1725              
1726             sub _find_encoding {
1727 5     5   12 my ($self, $encoding, $data) = @_;
1728 5 50 66     53 if (defined $encoding and $encoding ne 'bytes') {
1729 0         0 _load_module('Encode');
1730 0         0 my $enc = Encode::find_encoding($encoding);
1731 0 0       0 unless (defined $enc) {
1732 0         0 $self->_set_error(OSSH_ENCODING_ERROR, "bad encoding '$encoding'");
1733             return
1734 0         0 }
1735 0         0 return $enc
1736             }
1737             return undef
1738 5         12 }
1739              
1740             sub _encode {
1741 0     0   0 my $self = shift;
1742 0         0 my $enc = shift;
1743 0 0 0     0 if (defined $enc and @_) {
1744 0         0 local ($@, $SIG{__DIE__});
1745 0         0 eval {
1746 0         0 for (@_) {
1747 0 0       0 defined or next;
1748 0         0 $_ = $enc->encode($_, Encode::FB_CROAK());
1749             }
1750             };
1751 0 0       0 $self->_check_eval_ok(OSSH_ENCODING_ERROR) or return undef;
1752             }
1753 0         0 1;
1754             }
1755              
1756             sub _encode_args {
1757 3 50   3   18 if (@_ > 2) {
1758 3         6 my $self = shift;
1759 3         6 my $encoding = shift;
1760              
1761 3         15 my $enc = $self->_find_encoding($encoding);
1762 3 50       9 if ($enc) {
1763 0         0 local $self->{_error_prefix} = [@{$self->{_error_prefix}}, "argument encoding failed"];
  0         0  
1764 0         0 $self->_encode($enc, @_);
1765             }
1766 3         12 return !$self->{_error};
1767             }
1768 0         0 1;
1769             }
1770              
1771             sub _decode {
1772 0     0   0 my $self = shift;
1773 0         0 my $enc = shift;
1774 0         0 local ($@, $SIG{__DIE__});
1775 0         0 eval {
1776 0         0 for (@_) {
1777 0 0       0 defined or next;
1778 0         0 $_ = $enc->decode($_, Encode::FB_CROAK());
1779             }
1780             };
1781 0         0 $self->_check_eval_ok(OSSH_ENCODING_ERROR);
1782             }
1783              
1784             my @retriable = (Errno::EINTR(), Errno::EAGAIN());
1785             push @retriable, Errno::EWOULDBLOCK() if Errno::EWOULDBLOCK() != Errno::EAGAIN();
1786              
1787             sub _io3 {
1788 2     2   28 my ($self, $out, $err, $in, $stdin_data, $timeout, $encoding, $keep_in_open) = @_;
1789             # $self->wait_for_master or return;
1790 2         22 my @data = _array_or_scalar_to_list $stdin_data;
1791 2         32 my ($cout, $cerr, $cin) = (defined($out), defined($err), defined($in));
1792 2 50       40 $timeout = $self->{_timeout} unless defined $timeout;
1793              
1794 2 0       28 my $has_input = grep { defined and length } @data;
  0         0  
1795 2 50 33     58 if ($cin and !$has_input) {
    50 33        
1796 0 0       0 close $in unless $keep_in_open;
1797 0         0 undef $cin;
1798             }
1799             elsif (!$cin and $has_input) {
1800 0         0 croak "remote input channel is not defined but data is available for sending"
1801             }
1802              
1803 2         14 my $enc = $self->_find_encoding($encoding);
1804 2 50 33     8 if ($enc and @data) {
1805 0         0 local $self->{_error_prefix} = [@{$self->{_error_prefix}}, "stdin data encoding failed"];
  0         0  
1806 0 0       0 $self->_encode($enc, @data) if $has_input;
1807 0 0       0 return if $self->{_error};
1808             }
1809              
1810 2         14 my $bout = '';
1811 2         6 my $berr = '';
1812 2         4 my ($fnoout, $fnoerr, $fnoin);
1813 2         108 local $SIG{PIPE} = 'IGNORE';
1814              
1815 2   66     24 MLOOP: while ($cout or $cerr or $cin) {
      66        
1816 2 50 0     18 $debug and $debug & 64 and _debug "io3 mloop, cin: " . ($cin || 0) .
      0        
      0        
      33        
1817             ", cout: " . ($cout || 0) . ", cerr: " . ($cerr || 0);
1818 2         36 my ($rv, $wv);
1819              
1820 2 50 33     18 if ($cout or $cerr) {
1821 2         16 $rv = '';
1822 2 50       10 if ($cout) {
1823 2         10 $fnoout = fileno $out;
1824 2         26 vec($rv, $fnoout, 1) = 1;
1825             }
1826 2 50       10 if ($cerr) {
1827 0         0 $fnoerr = fileno $err;
1828 0         0 vec($rv, $fnoerr, 1) = 1
1829             }
1830             }
1831              
1832 2 50       4 if ($cin) {
1833 0         0 $fnoin = fileno $in;
1834 0         0 $wv = '';
1835 0         0 vec($wv, $fnoin, 1) = 1;
1836             }
1837              
1838 2         4 my $recalc_vecs;
1839 2         6 FAST: until ($recalc_vecs) {
1840 4 50 0     28 $debug and $debug & 64 and
      0        
      0        
      33        
1841             _debug "io3 fast, cin: " . ($cin || 0) .
1842             ", cout: " . ($cout || 0) . ", cerr: " . ($cerr || 0);
1843 4         12 my ($rv1, $wv1) = ($rv, $wv);
1844 4         2241122 my $n = select ($rv1, $wv1, undef, $timeout);
1845 4 50       66 if ($n > 0) {
1846 4 50 33     418 if ($cout and vec($rv1, $fnoout, 1)) {
1847 4         14 my $offset = length $bout;
1848 4         60 my $read = sysread($out, $bout, 20480, $offset);
1849 4 50 33     26 if ($debug and $debug & 64) {
1850 0         0 _debug "stdout, bytes read: ", $read, " at offset $offset";
1851 0 0 0     0 $read and $debug & 128 and _hexdump substr $bout, $offset;
1852             }
1853 4 100 66     132 unless ($read or grep $! == $_, @retriable) {
1854 2         44 close $out;
1855 2         8 undef $cout;
1856 2         6 $recalc_vecs = 1;
1857             }
1858             }
1859 4 50 33     28 if ($cerr and vec($rv1, $fnoerr, 1)) {
1860 0         0 my $read = sysread($err, $berr, 20480, length($berr));
1861 0 0 0     0 $debug and $debug & 64 and _debug "stderr, bytes read: ", $read;
1862 0 0 0     0 unless ($read or grep $! == $_, @retriable) {
1863 0         0 close $err;
1864 0         0 undef $cerr;
1865 0         0 $recalc_vecs = 1;
1866             }
1867             }
1868 4 50 33     90 if ($cin and vec($wv1, $fnoin, 1)) {
1869 0         0 my $written = syswrite($in, $data[0], 20480);
1870 0 0 0     0 if ($debug and $debug & 64) {
1871 0         0 _debug "stdin, bytes written: ", $written;
1872 0 0 0     0 $written and $debug & 128 and _hexdump substr $data[0], 0, $written;
1873             }
1874 0 0       0 if ($written) {
    0          
1875 0         0 substr($data[0], 0, $written, '');
1876 0         0 while (@data) {
1877             next FAST
1878 0 0 0     0 if (defined $data[0] and length $data[0]);
1879 0         0 shift @data;
1880             }
1881             # fallback when stdin queue is exhausted
1882             }
1883             elsif (grep $! == $_, @retriable) {
1884 0         0 next FAST;
1885             }
1886 0 0       0 close $in unless $keep_in_open;
1887 0         0 undef $cin;
1888 0         0 $recalc_vecs = 1;
1889             }
1890             }
1891             else {
1892 0 0 0     0 next if $n < 0 and grep $! == $_, @retriable;
1893 0         0 $self->_set_error(OSSH_SLAVE_TIMEOUT, 'ssh slave failed', 'timed out');
1894 0         0 last MLOOP;
1895             }
1896             }
1897             }
1898 2 50       10 close $out if $cout;
1899 2 50       22 close $err if $cerr;
1900 2 50 33     10 close $in if $cin and not $keep_in_open;
1901              
1902 2 50       6 if ($enc) {
1903 0         0 local $self->{_error_prefix} = [@{$self->{_error_prefix}}, 'output decoding failed'];
  0         0  
1904 0 0       0 unless ($self->_decode($enc, $bout, $berr)) {
1905 0         0 undef $bout;
1906 0         0 undef $berr;
1907             }
1908             }
1909 2 50 33     10 $debug and $debug & 64 and _debug "leaving _io3()";
1910 2         50 return ($bout, $berr);
1911             }
1912              
1913              
1914              
1915             _sub_options spawn => qw(stderr_to_stdout stdin_discard stdin_fh stdin_file stdout_discard stdout_fh
1916             stdout_file stderr_discard stderr_fh stderr_file stdinout_dpipe
1917             stdinout_dpipe_make_parent quote_args quote_args_extended remote_shell
1918             glob_quoting tty ssh_opts tunnel encoding argument_encoding forward_agent
1919             forward_X11 setpgrp subsystem);
1920             sub spawn {
1921 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
1922 0         0 my $self = shift;
1923 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
1924 0         0 _croak_bad_options %opts;
1925              
1926 0         0 return scalar $self->open_ex(\%opts, @_);
1927             }
1928              
1929             _sub_options open2 => qw(stderr_to_stdout stderr_discard stderr_fh stderr_file quote_args quote_args_extended
1930             remote_shell glob_quoting tty ssh_opts tunnel encoding argument_encoding forward_agent
1931             forward_X11 setpgrp subsystem);
1932             sub open2 {
1933 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
1934 0         0 my $self = shift;
1935 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
1936 0         0 _croak_bad_options %opts;
1937 0         0 _croak_scalar_context;
1938              
1939 0 0       0 my ($in, $out, undef, $pid) =
1940             $self->open_ex({ stdout_pipe => 1,
1941             stdin_pipe => 1,
1942             %opts }, @_) or return ();
1943 0         0 return ($in, $out, $pid);
1944             }
1945              
1946             _sub_options open2pty => qw(stderr_to_stdout stderr_discard stderr_fh stderr_file
1947             quote_args quote_args_extended remote_shell glob_quoting tty
1948             close_slave_pty ssh_opts encoding argument_encoding forward_agent
1949             forward_X11 setpgrp subsystem);
1950             sub open2pty {
1951 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
1952 0         0 my $self = shift;
1953 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
1954 0         0 _croak_bad_options %opts;
1955              
1956 0 0       0 my ($pty, undef, undef, $pid) =
1957             $self->open_ex({ stdout_pty => 1,
1958             stdin_pty => 1,
1959             tty => 1,
1960             %opts }, @_) or return ();
1961 0 0       0 wantarray ? ($pty, $pid) : $pty;
1962             }
1963              
1964             _sub_options open2socket => qw(stderr_to_stdout stderr_discard stderr_fh stderr_file
1965             quote_args quote_args_extended remote_shell glob_quoting tty
1966             ssh_opts tunnel encoding argument_encoding forward_agent
1967             forward_X11 setpgrp subsystem);
1968             sub open2socket {
1969 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
1970 0         0 my $self = shift;
1971 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
1972 0         0 _croak_bad_options %opts;
1973              
1974 0 0       0 my ($socket, undef, undef, $pid) =
1975             $self->open_ex({ stdinout_socket => 1,
1976             %opts }, @_) or return ();
1977 0 0       0 wantarray ? ($socket, $pid) : $socket;
1978             }
1979              
1980             _sub_options open3 => qw(quote_args quote_args_extended remote_shell glob_quoting tty ssh_opts
1981             encoding argument_encoding forward_agent forward_X11 setpgrp subsystem);
1982             sub open3 {
1983 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
1984 0         0 my $self = shift;
1985 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
1986 0         0 _croak_bad_options %opts;
1987 0         0 _croak_scalar_context;
1988              
1989 0 0       0 my ($in, $out, $err, $pid) =
1990             $self->open_ex({ stdout_pipe => 1,
1991             stdin_pipe => 1,
1992             stderr_pipe => 1,
1993             %opts },
1994             @_) or return ();
1995 0         0 return ($in, $out, $err, $pid);
1996             }
1997              
1998             _sub_options open3pty => qw(quote_args quote_args_extended remote_shell glob_quoting tty close_slave_pty ssh_opts
1999             encoding argument_encoding forward_agent forward_X11 setpgrp subsystem);
2000             sub open3pty {
2001 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
2002 0         0 my $self = shift;
2003 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
2004 0         0 _croak_bad_options %opts;
2005 0         0 _croak_scalar_context;
2006              
2007 0 0       0 my ($pty, undef, $err, $pid) =
2008             $self->open_ex({ stdout_pty => 1,
2009             stdin_pty => 1,
2010             tty => 1,
2011             stderr_pipe => 1,
2012             %opts },
2013             @_) or return ();
2014 0         0 return ($pty, $err, $pid);
2015             }
2016              
2017             _sub_options open3socket => qw(quote_args quote_args_extended remote_shell glob_quoting tty ssh_opts encoding
2018             argument_encoding forward_agent forward_X11 setpgrp subsystem);
2019             sub open3socket {
2020 0 0   0 0 0 ${^TAINT} and &_catch_tainted_args;
2021 0         0 my $self = shift;
2022 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
2023 0         0 _croak_bad_options %opts;
2024 0         0 _croak_scalar_context;
2025              
2026 0 0       0 my ($socket, undef, $err, $pid) =
2027             $self->open_ex({ stdinout_socket => 1,
2028             stderr_pipe => 1,
2029             %opts }, @_) or return ();
2030 0         0 return ($socket, $err, $pid);
2031             }
2032              
2033             _sub_options system => qw(stdout_discard stdout_fh stdin_discard stdout_file stdin_fh stdin_file
2034             quote_args quote_args_extended remote_shell glob_quoting
2035             stderr_to_stdout stderr_discard stderr_fh stderr_file
2036             stdinout_dpipe stdinout_dpipe_make_parent tty ssh_opts tunnel encoding
2037             argument_encoding forward_agent forward_X11 setpgrp subsystem);
2038             sub system {
2039 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
2040 0         0 my $self = shift;
2041 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
2042 0         0 my $stdin_data = delete $opts{stdin_data};
2043 0         0 my $timeout = delete $opts{timeout};
2044 0         0 my $async = delete $opts{async};
2045 0 0       0 my $stdin_keep_open = ($async ? undef : delete $opts{stdin_keep_open});
2046              
2047 0         0 _croak_bad_options %opts;
2048              
2049 0 0 0     0 $stdin_data = '' if $stdin_keep_open and not defined $stdin_data;
2050              
2051 0         0 my $stream_encoding;
2052 0 0       0 if (defined $stdin_data) {
2053 0         0 $opts{stdin_pipe} = 1;
2054 0         0 $stream_encoding = $self->_delete_stream_encoding(\%opts);
2055             }
2056              
2057 0         0 local $SIG{INT} = 'IGNORE';
2058 0         0 local $SIG{QUIT} = 'IGNORE';
2059 0         0 local $SIG{CHLD};
2060              
2061 0 0       0 my ($in, undef, undef, $pid) = $self->open_ex(\%opts, @_) or return undef;
2062              
2063 0 0       0 $self->_io3(undef, undef, $in, $stdin_data,
2064             $timeout, $stream_encoding, $stdin_keep_open) if defined $stdin_data;
2065 0 0       0 return $pid if $async;
2066 0         0 $self->_waitpid($pid, $timeout);
2067             }
2068              
2069             _sub_options test => qw(stdout_discard stdout_fh stdin_discard stdout_file stdin_fh stdin_file
2070             quote_args quote_args_extended remote_shell glob_quoting stderr_to_stdout
2071             stderr_discard stderr_fh stderr_file stdinout_dpipe
2072             stdinout_dpipe_make_parent tty ssh_opts timeout stdin_data stdin_keep_open
2073             encoding stream_encoding argument_encoding forward_agent forward_X11
2074             setpgrp subsystem);
2075              
2076             sub test {
2077 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
2078 0         0 my $self = shift;
2079 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
2080 0 0       0 $opts{stdout_discard} = 1 unless grep defined($opts{$_}), qw(stdout_discard stdout_fh
2081             stdout_file stdinout_dpipe);
2082 0 0       0 $opts{stderr_discard} = 1 unless grep defined($opts{$_}), qw(stderr_discard stderr_fh
2083             stderr_file stderr_to_stdout);
2084 0         0 _croak_bad_options %opts;
2085              
2086 0         0 $self->system(\%opts, @_);
2087 0         0 my $error = $self->{_error};
2088 0 0       0 unless ($error) {
2089 0         0 return 1;
2090             }
2091 0 0       0 if ($error == OSSH_SLAVE_CMD_FAILED) {
2092 0         0 $self->_set_error(0);
2093 0         0 return 0;
2094             }
2095 0         0 return undef;
2096             }
2097              
2098             _sub_options capture => qw(stderr_to_stdout stderr_discard stderr_fh stderr_file stdin_discard
2099             stdin_fh stdin_file quote_args quote_args_extended remote_shell
2100             glob_quoting tty ssh_opts tunnel encoding argument_encoding forward_agent
2101             forward_X11 setpgrp subsystem);
2102             sub capture {
2103 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
2104 0         0 my $self = shift;
2105 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
2106 0         0 my $stdin_data = delete $opts{stdin_data};
2107 0         0 my $stdin_keep_open = delete $opts{stdin_keep_open};
2108 0         0 my $timeout = delete $opts{timeout};
2109 0         0 _croak_bad_options %opts;
2110              
2111 0 0 0     0 $stdin_data = '' if $stdin_keep_open and not defined $stdin_data;
2112              
2113 0         0 my $stream_encoding = $self->_delete_stream_encoding(\%opts);
2114 0         0 $opts{stdout_pipe} = 1;
2115 0 0       0 $opts{stdin_pipe} = 1 if defined $stdin_data;
2116              
2117 0         0 local $SIG{INT} = 'IGNORE';
2118 0         0 local $SIG{QUIT} = 'IGNORE';
2119 0         0 local $SIG{CHLD};
2120              
2121 0 0       0 my ($in, $out, undef, $pid) = $self->open_ex(\%opts, @_) or return ();
2122 0         0 my ($output) = $self->_io3($out, undef, $in, $stdin_data,
2123             $timeout, $stream_encoding, $stdin_keep_open);
2124 0         0 $self->_waitpid($pid, $timeout);
2125 0 0       0 if (wantarray) {
2126 0         0 my $pattern = quotemeta $/;
2127 0         0 return split /(?<=$pattern)/, $output;
2128             }
2129             $output
2130 0         0 }
2131              
2132             _sub_options capture2 => qw(stdin_discard stdin_fh stdin_file quote_args quote_args_extended
2133             remote_shell glob_quoting tty ssh_opts encoding stream_encoding
2134             argument_encoding forward_agent forward_X11 setpgrp subsystem);
2135             sub capture2 {
2136 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
2137 0         0 my $self = shift;
2138 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
2139 0         0 my $stdin_data = delete $opts{stdin_data};
2140 0         0 my $stdin_keep_open = delete $opts{stdin_keep_open};
2141 0         0 my $timeout = delete $opts{timeout};
2142 0         0 _croak_bad_options %opts;
2143              
2144 0 0 0     0 $stdin_data = '' if $stdin_keep_open and not defined $stdin_data;
2145              
2146 0         0 my $stream_encoding = $self->_delete_stream_encoding(\%opts);
2147 0         0 $opts{stdout_pipe} = 1;
2148 0         0 $opts{stderr_pipe} = 1;
2149 0 0       0 $opts{stdin_pipe} = 1 if defined $stdin_data;
2150              
2151 0         0 local $SIG{INT} = 'IGNORE';
2152 0         0 local $SIG{QUIT} = 'IGNORE';
2153 0         0 local $SIG{CHLD};
2154              
2155 0 0       0 my ($in, $out, $err, $pid) = $self->open_ex( \%opts, @_) or return ();
2156 0         0 my @capture = $self->_io3($out, $err, $in, $stdin_data,
2157             $timeout, $stream_encoding, $stdin_keep_open);
2158 0         0 $self->_waitpid($pid, $timeout);
2159 0 0       0 wantarray ? @capture : $capture[0];
2160             }
2161              
2162             _sub_options open_tunnel => qw(ssh_opts stderr_discard stderr_fh stderr_file
2163             encoding argument_encoding forward_agent setpgrp);
2164             sub open_tunnel {
2165 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
2166 0         0 my $self = shift;
2167 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
2168 0 0       0 $opts{stderr_discard} = 1 unless grep defined $opts{$_}, qw(stderr_discard stderr_fh stderr_file);
2169 0         0 _croak_bad_options %opts;
2170 0 0       0 @_ == 2 or croak 'Usage: $ssh->open_tunnel(\%opts, $host, $port)';
2171 0         0 $opts{tunnel} = 1;
2172 0         0 $self->open2socket(\%opts, @_);
2173             }
2174              
2175             _sub_options capture_tunnel => qw(ssh_opts stderr_discard stderr_fh stderr_file stdin_discard
2176             stdin_fh stdin_file stdin_data timeout encoding stream_encoding
2177             argument_encoding forward_agent setpgrp);
2178             sub capture_tunnel {
2179 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
2180 0         0 my $self = shift;
2181 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
2182 0 0       0 $opts{stderr_discard} = 1 unless grep defined $opts{$_}, qw(stderr_discard stderr_fh stderr_file);
2183 0         0 _croak_bad_options %opts;
2184 0 0       0 @_ == 2 or croak 'Usage: $ssh->capture_tunnel(\%opts, $host, $port)';
2185 0         0 $opts{tunnel} = 1;
2186 0         0 $self->capture(\%opts, @_);
2187             }
2188              
2189             sub _calling_method {
2190 0     0   0 my $method = (caller 2)[3];
2191 0         0 $method =~ s/.*:://;
2192 0         0 $method;
2193             }
2194              
2195             sub _scp_get_args {
2196 0     0   0 my $self = shift;
2197 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
2198              
2199 0 0       0 @_ > 0 or croak
2200             'Usage: $ssh->' . _calling_method . '(\%opts, $remote_fn1, $remote_fn2, ..., $local_fn_or_dir)';
2201              
2202 0         0 my $glob = delete $opts{glob};
2203              
2204 0 0       0 my $target = (@_ > 1 ? pop @_ : '.');
2205 0 0       0 $target =~ m|^[^/]*:| and $target = "./$target";
2206              
2207 0         0 my $prefix = $self->{_host_squared};
2208 0 0       0 $prefix = "$self->{_user}\@$prefix" if defined $self->{_user};
2209              
2210 0         0 my $src = "$prefix:". join(" ", $self->_quote_args({quote_args => 1,
2211             glob_quoting => $glob},
2212             @_));
2213 0         0 ($self, \%opts, $target, $src);
2214             }
2215              
2216             sub scp_get {
2217 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
2218 0         0 my ($self, $opts, $target, @src) = _scp_get_args @_;
2219 0         0 $self->_scp($opts, @src, $target);
2220             }
2221              
2222             sub rsync_get {
2223 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
2224 0         0 my ($self, $opts, $target, @src) = _scp_get_args @_;
2225 0         0 $self->_rsync($opts, @src, $target);
2226             }
2227              
2228             sub _scp_put_args {
2229 0     0   0 my $self = shift;
2230 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
2231              
2232 0 0       0 @_ > 0 or croak
2233             'Usage: $ssh->' . _calling_method . '(\%opts, $local_fn1, $local_fn2, ..., $remote_dir_or_fn)';
2234              
2235 0         0 my $glob = delete $opts{glob};
2236 0 0 0     0 my $glob_flags = ($glob ? delete $opts{glob_flags} || 0 : undef);
2237              
2238 0         0 my $prefix = $self->{_host_squared};
2239 0 0       0 $prefix = "$self->{_user}\@$prefix" if defined $self->{_user};
2240              
2241 0         0 my $remote_shell = delete $opts{remote_shell};
2242 0 0       0 my $target = $prefix . ':' . ( @_ > 1
2243             ? $self->_quote_args({quote_args => 1, remote_shell => $remote_shell}, pop(@_))
2244             : '');
2245              
2246 0         0 my @src = @_;
2247 0 0       0 if ($glob) {
2248 0         0 require File::Glob;
2249 0         0 @src = map File::Glob::bsd_glob($_, $glob_flags), @src;
2250 0 0       0 unless (@src) {
2251 0         0 $self->_set_error(OSSH_SLAVE_FAILED,
2252             "given file name patterns did not match any file");
2253 0         0 return undef;
2254             }
2255             }
2256 0         0 $_ = "./$_" for grep m|^[^/]*:|, @src;
2257              
2258 0         0 ($self, \%opts, $target, @src);
2259             }
2260              
2261             sub scp_put {
2262 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
2263 0         0 my ($self, $opts, $target, @src) = _scp_put_args @_;
2264 0 0       0 return unless $self;
2265 0         0 $self->_scp($opts, @src, $target);
2266             }
2267              
2268             sub rsync_put {
2269 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
2270 0         0 my ($self, $opts, $target, @src) = _scp_put_args @_;
2271 0 0       0 return unless $self;
2272 0         0 $self->_rsync($opts, @src, $target);
2273             }
2274              
2275             _sub_options _scp => qw(stderr_to_stdout stderr_discard stderr_fh
2276             stderr_file stdout_discard stdout_fh
2277             stdout_file encoding argument_encoding
2278             forward_agent setpgrp);
2279             sub _scp {
2280 0     0   0 my $self = shift;
2281 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
2282 0         0 my $quiet = delete $opts{quiet};
2283 0 0       0 $quiet = 1 unless defined $quiet;
2284 0         0 my $recursive = delete $opts{recursive};
2285 0         0 my $copy_attrs = delete $opts{copy_attrs};
2286 0         0 my $bwlimit = delete $opts{bwlimit};
2287 0         0 my $async = delete $opts{async};
2288 0         0 my $ssh_opts = delete $opts{ssh_opts};
2289 0         0 my $timeout = delete $opts{timeout};
2290 0         0 my $verbose = delete $opts{verbose};
2291 0         0 _croak_bad_options %opts;
2292              
2293 0         0 my @opts;
2294 0 0       0 @opts = @$ssh_opts if $ssh_opts;
2295 0 0       0 push @opts, '-q' if $quiet;
2296 0 0       0 push @opts, '-v' if $verbose;
2297 0 0       0 push @opts, '-r' if $recursive;
2298 0 0       0 push @opts, '-p' if $copy_attrs;
2299 0 0       0 push @opts, '-l', $bwlimit if $bwlimit;
2300              
2301 0         0 local $self->{_error_prefix} = [@{$self->{_error_prefix}}, 'scp failed'];
  0         0  
2302              
2303 0         0 my $pid = $self->open_ex({ %opts,
2304             _cmd => 'scp',
2305             ssh_opts => \@opts,
2306             quote_args => 0 },
2307             @_);
2308              
2309 0 0       0 return $pid if $async;
2310 0         0 $self->_waitpid($pid, $timeout);
2311             }
2312              
2313             my %rsync_opt_with_arg = map { $_ => 1 } qw(chmod suffix backup-dir rsync-path max-delete max-size min-size partial-dir
2314             timeout modify-window temp-dir compare-dest copy-dest link-dest compress-level
2315             skip-compress filter exclude exclude-from include include-from
2316             out-format log-file log-file-format bwlimit protocol iconv checksum-seed files-from);
2317              
2318             my %rsync_opt_forbidden = map { $_ => 1 } qw(rsh address port sockopts password-file write-batch
2319             only-write-batch read-batch ipv4 ipv6 version help daemon config detach
2320             protect-args list-only);
2321              
2322             $rsync_opt_forbidden{"no-$_"} = 1 for (keys %rsync_opt_with_arg, keys %rsync_opt_forbidden);
2323              
2324             my %rsync_error = (1, 'syntax or usage error',
2325             2, 'protocol incompatibility',
2326             3, 'errors selecting input/output files, dirs',
2327             4, 'requested action not supported: an attempt was made to manipulate 64-bit files on a platform '.
2328             'that cannot support them; or an option was specified that is supported by the client and not '.
2329             'by the server.',
2330             5, 'error starting client-server protocol',
2331             6, 'daemon unable to append to log-file',
2332             10, 'error in socket I/O',
2333             11, 'error in file I/O',
2334             12, 'error in rsync protocol data stream',
2335             13, 'errors with program diagnostics',
2336             14, 'error in IPC code',
2337             20, 'received SIGUSR1 or SIGINT',
2338             21, 'some error returned by waitpid()',
2339             22, 'error allocating core memory buffers',
2340             23, 'partial transfer due to error',
2341             24, 'partial transfer due to vanished source files',
2342             25, 'the --max-delete limit stopped deletions',
2343             30, 'timeout in data send/receive',
2344             35, 'timeout waiting for daemon connection');
2345              
2346             my %rsync_opt_open_ex = map { $_ => 1 } qw(stderr_to_stdout
2347             stderr_discard stderr_fh
2348             stderr_file stdout_discard
2349             stdout_fh stdout_file encoding
2350             argument_encoding);
2351             sub _rsync {
2352 0     0   0 my $self = shift;
2353 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
2354 0         0 my $async = delete $opts{async};
2355 0         0 my $verbose = delete $opts{verbose};
2356 0         0 my $quiet = delete $opts{quiet};
2357 0         0 my $copy_attrs = delete $opts{copy_attrs};
2358 0         0 my $timeout = delete $opts{timeout};
2359 0 0 0     0 $quiet = 1 unless (defined $quiet or $verbose);
2360              
2361 0         0 my @opts;
2362 0 0       0 push @opts, '-q' if $quiet;
2363 0 0       0 push @opts, '-pt' if $copy_attrs;
2364 0 0       0 push @opts, '-' . ($verbose =~ /^\d+$/ ? 'v' x $verbose : 'v') if $verbose;
    0          
2365              
2366 0         0 my %opts_open_ex = ( _cmd => 'rsync',
2367             quote_args => 0 );
2368              
2369 0         0 for my $opt (keys %opts) {
2370 0         0 my $value = $opts{$opt};
2371 0 0       0 if (defined $value) {
2372 0 0       0 if ($rsync_opt_open_ex{$opt}) {
2373 0         0 $opts_open_ex{$opt} = $value;
2374             }
2375             else {
2376 0         0 my $opt1 = $opt;
2377 0         0 $opt1 =~ tr/_/-/;
2378 0 0       0 $rsync_opt_forbidden{$opt1} and croak "forbidden rsync option '$opt' used";
2379 0 0       0 if ($rsync_opt_with_arg{$opt1}) {
2380 0         0 push @opts, "--$opt1=$_" for _array_or_scalar_to_list($value)
2381             }
2382             else {
2383 0 0       0 $value = !$value if $opt1 =~ s/^no-//;
2384 0 0       0 push @opts, ($value ? "--$opt1" : "--no-$opt1");
2385             }
2386             }
2387             }
2388             }
2389              
2390 0         0 local $self->{_error_prefix} = [@{$self->{_error_prefix}}, 'rsync failed'];
  0         0  
2391              
2392 0         0 my $pid = $self->open_ex(\%opts_open_ex, @opts, '--', @_);
2393 0 0       0 return $pid if $async;
2394 0 0       0 $self->_waitpid($pid, $timeout) and return 1;
2395              
2396 0 0 0     0 if ($self->{_error} == OSSH_SLAVE_CMD_FAILED and $?) {
2397 0         0 my $err = ($? >> 8);
2398 0         0 my $errstr = $rsync_error{$err};
2399 0 0       0 $errstr = 'Unknown rsync error' unless defined $errstr;
2400 0         0 my $signal = $? & 255;
2401 0 0       0 my $signalstr = ($signal ? " (signal $signal)" : '');
2402 0         0 $self->_set_error(OSSH_SLAVE_CMD_FAILED,
2403             "command exited with code $err$signalstr: $errstr");
2404             }
2405             return undef
2406 0         0 }
2407              
2408             _sub_options sftp => qw(autoflush timeout argument_encoding encoding block_size queue_size autodie
2409             late_set_perm forward_agent setpgrp min_block_size read_ahead write_delay
2410             dirty_cleanup remote_has_volumes autodisconnect more);
2411              
2412             sub sftp {
2413 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
2414 0 0       0 @_ & 1 or croak 'Usage: $ssh->sftp(%sftp_opts)';
2415 0         0 _load_module('Net::SFTP::Foreign', '1.47');
2416 0         0 my ($self, %opts) = @_;
2417 0         0 my $stderr_fh = delete $opts{stderr_fh};
2418 0         0 my $stderr_discard = delete $opts{stderr_discard};
2419             my $fs_encoding = _first_defined(delete $opts{fs_encoding},
2420             $opts{argument_encoding},
2421             $opts{encoding},
2422 0         0 $self->{_default_argument_encoding});
2423 0 0 0     0 undef $fs_encoding if (defined $fs_encoding and $fs_encoding eq 'bytes');
2424 0         0 _croak_bad_options %opts;
2425 0 0       0 $opts{timeout} = $self->{_timeout} unless defined $opts{timeout};
2426 0 0       0 $self->wait_for_master or return undef;
2427 0 0       0 my ($in, $out, $pid) = $self->open2( { subsystem => 1,
2428             stderr_fh => $stderr_fh,
2429             stderr_discard => $stderr_discard },
2430             'sftp' )
2431             or return undef;
2432              
2433 0         0 my $sftp = Net::SFTP::Foreign->new(transport => [$out, $in, $pid],
2434             dirty_cleanup => 0,
2435             fs_encoding => $fs_encoding,
2436             %opts);
2437 0 0       0 if ($sftp->error) {
2438 0         0 $self->_or_set_error(OSSH_SLAVE_SFTP_FAILED, "unable to create SFTP client", $sftp->error);
2439 0         0 return undef;
2440             }
2441             $sftp
2442 0         0 }
2443              
2444             _sub_options sshfs_import => qw(stderr_discard stderr_fh stderr_file
2445             ssh_opts argument_encoding sshfs_opts setpgrp);
2446             sub sshfs_import {
2447 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
2448 0         0 my $self = shift;
2449 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
2450 0 0       0 @_ == 2 or croak 'Usage: $ssh->sshfs_import(\%opts, $remote, $local)';
2451 0         0 my ($from, $to) = @_;
2452             my @sshfs_opts = ( -o => 'slave',
2453 0         0 _array_or_scalar_to_list delete $opts{sshfs_opts} );
2454 0         0 _croak_bad_options %opts;
2455              
2456 0         0 $opts{ssh_opts} = ['-s', _array_or_scalar_to_list delete $opts{ssh_opts}];
2457 0         0 $opts{stdinout_dpipe} = [$self->{_sshfs_cmd}, "$self->{_host_squared}:$from", $to, @sshfs_opts];
2458 0         0 $opts{stdinout_dpipe_make_parent} = 1;
2459 0         0 $self->spawn(\%opts, 'sftp');
2460             }
2461              
2462             _sub_options sshfs_export => qw(stderr_discard stderr_fh stderr_file
2463             ssh_opts argument_encoding sshfs_opts setpgrp);
2464             sub sshfs_export {
2465 0 0   0 1 0 ${^TAINT} and &_catch_tainted_args;
2466 0         0 my $self = shift;
2467 0 0       0 my %opts = (ref $_[0] eq 'HASH' ? %{shift()} : ());
  0         0  
2468 0 0       0 @_ == 2 or croak 'Usage: $ssh->sshfs_export(\%opts, $local, $remote)';
2469 0         0 my ($from, $to) = @_;
2470             my @sshfs_opts = ( -o => 'slave',
2471 0         0 _array_or_scalar_to_list delete $opts{sshfs_opts} );
2472 0         0 _croak_bad_options %opts;
2473 0         0 $opts{stdinout_dpipe} = $self->{_sftp_server_cmd};
2474              
2475 0         0 my $hostname = do {
2476 0         0 local ($@, $SIG{__DIE__});
2477 0         0 eval {
2478 0         0 require Sys::Hostname;
2479 0         0 Sys::Hostname::hostname();
2480             };
2481             };
2482 0 0 0     0 $hostname = 'remote' if (not defined $hostname or
      0        
2483             not length $hostname or
2484             $hostname=~/^localhost\b/);
2485 0         0 $self->spawn(\%opts, $self->{_sshfs_cmd}, "$hostname:$from", $to, @sshfs_opts);
2486             }
2487              
2488             sub object_remote {
2489 0     0 1 0 my $self = shift;
2490 0 0       0 _load_module('Object::Remote') or return;
2491 0 0       0 _load_module('Net::OpenSSH::ObjectRemote') or return;
2492 0         0 my $connector = Net::OpenSSH::ObjectRemote->new(net_openssh => $self);
2493 0         0 $connector->connect(@_);
2494             }
2495              
2496             sub any {
2497 0     0 1 0 my $self = shift;
2498 0         0 _load_module('Net::SSH::Any');
2499             Net::SSH::Any->new($self->{_host}, user => $self->{_user}, port => $self->{_port},
2500 0         0 backend => 'Net_OpenSSH',
2501             backend_opts => { Net_OpenSSH => { instance => $self } });
2502             }
2503              
2504             sub DESTROY {
2505 1     1   1252 my $self = shift;
2506 1 50 33     11 $debug and $debug & 2 and _debug("DESTROY($self, pid: ", $self->{_pid}, ")");
2507 1         27 local ($SIG{__DIE__}, $@, $?, $!);
2508 1         5 $self->_disconnect;
2509             }
2510              
2511             1;
2512             __END__