Net-OpenSSH-Compat-0.07/0000755000175000017500000000000012571053461013666 5ustar salvasalvaNet-OpenSSH-Compat-0.07/t/0000755000175000017500000000000012571053461014131 5ustar salvasalvaNet-OpenSSH-Compat-0.07/t/Net-SSH.t0000644000175000017500000000011411640566035015475 0ustar salvasalva use Test::More tests => 1; BEGIN { use_ok('Net::OpenSSH::Compat::SSH') }; Net-OpenSSH-Compat-0.07/t/Net-OpenSSH-Compat.t0000644000175000017500000000075011551103450017533 0ustar salvasalva# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Net-OpenSSH-Compat.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use Test::More tests => 1; BEGIN { use_ok('Net::OpenSSH::Compat') }; ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. Net-OpenSSH-Compat-0.07/t/Net-SSH-Perl.t0000644000175000017500000000011511640561203016366 0ustar salvasalva use Test::More tests => 1; BEGIN { use_ok('Net::OpenSSH::Compat::Perl') }; Net-OpenSSH-Compat-0.07/README0000644000175000017500000000116512331663426014553 0ustar salvasalvaNet-OpenSSH-Compat ================== Compatibility modules for Net::OpenSSH INSTALLATION To install this module type the following: perl Makefile.PL make make test make install DEPENDENCIES This module requires these other modules and libraries: Net::OpenSSH IO::Pty (recommended) Net::SFTP::Foreign (recommended) COPYRIGHT AND LICENCE Copyright (C) 2011, 2014 by Salvador Fandino This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.1 or, at your option, any later version of Perl 5 you may have available. Net-OpenSSH-Compat-0.07/Makefile.PL0000644000175000017500000000044411622704523015640 0ustar salvasalvause ExtUtils::MakeMaker; WriteMakefile( NAME => 'Net::OpenSSH::Compat', VERSION_FROM => 'lib/Net/OpenSSH/Compat.pm', PREREQ_PM => { "Net::OpenSSH" => "0.53_03" }, AUTHOR => 'Salvador Fandino ' ); Net-OpenSSH-Compat-0.07/lib/0000755000175000017500000000000012571053461014434 5ustar salvasalvaNet-OpenSSH-Compat-0.07/lib/Net/0000755000175000017500000000000012571053461015162 5ustar salvasalvaNet-OpenSSH-Compat-0.07/lib/Net/OpenSSH/0000755000175000017500000000000012571053461016441 5ustar salvasalvaNet-OpenSSH-Compat-0.07/lib/Net/OpenSSH/Compat/0000755000175000017500000000000012571053461017664 5ustar salvasalvaNet-OpenSSH-Compat-0.07/lib/Net/OpenSSH/Compat/SSH2/0000755000175000017500000000000012571053461020403 5ustar salvasalvaNet-OpenSSH-Compat-0.07/lib/Net/OpenSSH/Compat/SSH2/Poll.pm0000644000175000017500000001040411561715107021646 0ustar salvasalvapackage Net::OpenSSH::Compat::SSH2; use strict; use warnings; use Carp; use POSIX (); use IO::Poll qw( POLLIN POLLOUT POLLERR POLLHUP POLLNVAL POLLPRI POLLRDNORM POLLWRNORM POLLRDBAND POLLWRBAND POLLNORM ); my %libssh2_cts = ( in => 0x0001, pri => 0x0002, ext => 0x0002, out => 0x0004, err => 0x0008, hup => 0x0010, session_closed => 0x0010, nval => 0x0020, ex => 0x0040, channel_closed => 0x0080, listener_closed => 0x0080 ); my %poll_mask = ( in => POLLIN, out => POLLOUT, pri => POLLPRI, err => POLLERR, hup => POLLHUP); my %poll_event_in = ( in => 1, out => 1, pri => 1); sub _poll { my ($self, $timeout, $e) = @_; my $poll = IO::Poll->new; my @err; my @is_channel; my @channel; my @channel_pid; my @session_pid; my %pids; for my $ix (0..$#$e) { my $hash = $e->[$ix]; my $fh = $hash->{handle}; my $channel; if (UNIVERSAL::isa("Net::OpenSSH::Compat::SSH2::Channel", $fh)) { $is_channel[$ix] = 1; $channel = $channel[$ix] = $fh; next unless $fh->_state eq 'exec'; $channel_pid[$ix] = $fh->_hash->{pid}; $session_pid[$ix] = $fh->_parent->{ssh}->get_master_pid; } my $events = $hash->{events}; unless (ref $events) { my @a; while (my ($k, $v) = each %libssh2_cts) { push @a, $k if (($events & $v) == $v); } $events = \@a; } for my $event (@$events) { if ($poll_event_in{$event}) { $poll->mask($fh, $poll_mask{$event}); } elsif ($event eq 'ext') { if ($channel) { my $err = $err[$ix] = $channel->_hash->{err}; $err and $poll->mask($err, POLLIN); } } else { # croak "unsupported event $event" } } } $pids{$_} = undef for (grep defined, @channel_pid, @session_pid); { my $sigchl; local $SIG{CHLD} = sub { $sigchl = 1 }; $timeout = 0 if _check_pids(\%pids); return -1 if ($poll->poll($timeout) < 0 and !$sigchl) } _check_pids(\%pids); my $r = 0; for my $ix (0..$#$e) { my $hash = $e->[$ix]; my $handle = $hash->{handle}; my $selected = $poll->events($handle); my @revents; while (my ($event, $mask) = each %poll_mask) { if (($selected & $mask) == $mask) { push @revents, $event; } } if ($is_channel[$ix]) { my $err = $err[$ix]; if ($err and (($poll->events($err) & POLLIN) == POLLIN)) { push @revents, 'ext'; } if (my $cp = $channel_pid[$ix]) { $handle->_slave_exited($pids{$cp}) if defined $pids{$cp}; } unless ($handle->_hash->{pid}) { push @revents, 'channel_closed'; } if (my $sp = $session_pid[$ix]) { $handle->_master_exited($pids{$sp}) if defined $pids{$sp}; } if ($handle->_parent->_state == 'failed') { push @revents, 'session_closed'; } } my $v = 0; if (@revents) { $r++; for my $k (@revents) { $v |= $libssh2_cts{$k}; } } $hash->{revents} = {value => $v, map { $_ => 1} @revents } } return $r; } sub _check_pids { my $pids = shift; my $ok; while (my ($k, $v) = each %$pids) { next if defined $v; if (waitpid($k, POSIX::WNOHANG()) == $k) { $v = $?; } } } 1; Net-OpenSSH-Compat-0.07/lib/Net/OpenSSH/Compat/SSH2/Constants.pm0000644000175000017500000005260111551335322022715 0ustar salvasalva use strict; use warnings; use Carp; our %EXPORT_TAGS; ### CONSTANTS BELOW!!! (this is a marker) # # The constant definitions are automatically extracted from the real # Net::SSH2 module and inserted here by the gen_constants_ssh2.pl # script. # # Do not touch by hand!!! # sub LIBSSH2_CALLBACK_DEBUG () { 1 } sub LIBSSH2_CALLBACK_DISCONNECT () { 2 } sub LIBSSH2_CALLBACK_IGNORE () { 0 } sub LIBSSH2_CALLBACK_MACERROR () { 3 } sub LIBSSH2_CALLBACK_X11 () { 4 } sub LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE () { 1 } sub LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE () { 2 } sub LIBSSH2_CHANNEL_EXTENDED_DATA_NORMAL () { 0 } sub LIBSSH2_ERROR_ALLOC () { -6 } sub LIBSSH2_ERROR_BANNER_NONE () { -2 } sub LIBSSH2_ERROR_BANNER_SEND () { -3 } sub LIBSSH2_ERROR_CHANNEL_CLOSED () { -26 } sub LIBSSH2_ERROR_CHANNEL_EOF_SENT () { -27 } sub LIBSSH2_ERROR_CHANNEL_FAILURE () { -21 } sub LIBSSH2_ERROR_CHANNEL_OUTOFORDER () { -20 } sub LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED () { -25 } sub LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED () { -22 } sub LIBSSH2_ERROR_CHANNEL_UNKNOWN () { -23 } sub LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED () { -24 } sub LIBSSH2_ERROR_DECRYPT () { -12 } sub LIBSSH2_ERROR_FILE () { -16 } sub LIBSSH2_ERROR_HOSTKEY_INIT () { -10 } sub LIBSSH2_ERROR_HOSTKEY_SIGN () { -11 } sub LIBSSH2_ERROR_INVAL () { -34 } sub LIBSSH2_ERROR_INVALID_MAC () { -4 } sub LIBSSH2_ERROR_INVALID_POLL_TYPE () { -35 } sub LIBSSH2_ERROR_KEX_FAILURE () { -5 } sub LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE () { -8 } sub LIBSSH2_ERROR_METHOD_NONE () { -17 } sub LIBSSH2_ERROR_METHOD_NOT_SUPPORTED () { -33 } sub LIBSSH2_ERROR_PASSWORD_EXPIRED () { -15 } sub LIBSSH2_ERROR_PROTO () { -14 } sub LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED () { -18 } sub LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED () { -19 } sub LIBSSH2_ERROR_REQUEST_DENIED () { -32 } sub LIBSSH2_ERROR_SCP_PROTOCOL () { -28 } sub LIBSSH2_ERROR_SFTP_PROTOCOL () { -31 } sub LIBSSH2_ERROR_SOCKET_DISCONNECT () { -13 } sub LIBSSH2_ERROR_SOCKET_NONE () { -1 } sub LIBSSH2_ERROR_SOCKET_SEND () { -7 } sub LIBSSH2_ERROR_SOCKET_TIMEOUT () { -30 } sub LIBSSH2_ERROR_TIMEOUT () { -9 } sub LIBSSH2_ERROR_ZLIB () { -29 } sub LIBSSH2_HOSTKEY_HASH_MD5 () { 1 } sub LIBSSH2_HOSTKEY_HASH_SHA1 () { 2 } sub LIBSSH2_METHOD_COMP_CS () { 6 } sub LIBSSH2_METHOD_COMP_SC () { 7 } sub LIBSSH2_METHOD_CRYPT_CS () { 2 } sub LIBSSH2_METHOD_CRYPT_SC () { 3 } sub LIBSSH2_METHOD_HOSTKEY () { 1 } sub LIBSSH2_METHOD_KEX () { 0 } sub LIBSSH2_METHOD_LANG_CS () { 8 } sub LIBSSH2_METHOD_LANG_SC () { 9 } sub LIBSSH2_METHOD_MAC_CS () { 4 } sub LIBSSH2_METHOD_MAC_SC () { 5 } sub LIBSSH2_FX_BAD_MESSAGE () { 5 } sub LIBSSH2_FX_CONNECTION_LOST () { 7 } sub LIBSSH2_FX_DIR_NOT_EMPTY () { 18 } sub LIBSSH2_FX_EOF () { 1 } sub LIBSSH2_FX_FAILURE () { 4 } sub LIBSSH2_FX_FILE_ALREADY_EXISTS () { 11 } sub LIBSSH2_FX_INVALID_FILENAME () { 20 } sub LIBSSH2_FX_INVALID_HANDLE () { 9 } sub LIBSSH2_FX_LINK_LOOP () { 21 } sub LIBSSH2_FX_LOCK_CONFlICT () { 17 } sub LIBSSH2_FX_NOT_A_DIRECTORY () { 19 } sub LIBSSH2_FX_NO_CONNECTION () { 6 } sub LIBSSH2_FX_NO_MEDIA () { 13 } sub LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM () { 14 } sub LIBSSH2_FX_NO_SUCH_FILE () { 2 } sub LIBSSH2_FX_NO_SUCH_PATH () { 10 } sub LIBSSH2_FX_OK () { 0 } sub LIBSSH2_FX_OP_UNSUPPORTED () { 8 } sub LIBSSH2_FX_PERMISSION_DENIED () { 3 } sub LIBSSH2_FX_QUOTA_EXCEEDED () { 15 } sub LIBSSH2_FX_UNKNOWN_PRINCIPLE () { 16 } sub LIBSSH2_FX_WRITE_PROTECT () { 12 } sub LIBSSH2_FXF_APPEND () { 4 } sub LIBSSH2_FXF_CREAT () { 8 } sub LIBSSH2_FXF_EXCL () { 32 } sub LIBSSH2_FXF_READ () { 1 } sub LIBSSH2_FXF_TRUNC () { 16 } sub LIBSSH2_FXF_WRITE () { 2 } sub LIBSSH2_SFTP_ATTR_ACMODTIME () { 8 } sub LIBSSH2_SFTP_ATTR_EXTENDED () { 2147483648 } sub LIBSSH2_SFTP_ATTR_PERMISSIONS () { 4 } sub LIBSSH2_SFTP_ATTR_SIZE () { 1 } sub LIBSSH2_SFTP_ATTR_UIDGID () { 2 } sub LIBSSH2_SFTP_LSTAT () { 1 } sub LIBSSH2_SFTP_OPENDIR () { 1 } sub LIBSSH2_SFTP_OPENFILE () { 0 } sub LIBSSH2_SFTP_PACKET_MAXLEN () { 40000 } sub LIBSSH2_SFTP_READLINK () { 1 } sub LIBSSH2_SFTP_REALPATH () { 2 } sub LIBSSH2_SFTP_RENAME_ATOMIC () { 2 } sub LIBSSH2_SFTP_RENAME_NATIVE () { 4 } sub LIBSSH2_SFTP_RENAME_OVERWRITE () { 1 } sub LIBSSH2_SFTP_SETSTAT () { 2 } sub LIBSSH2_SFTP_STAT () { 0 } sub LIBSSH2_SFTP_SYMLINK () { 0 } sub LIBSSH2_SFTP_TYPE_BLOCK_DEVICE () { 8 } sub LIBSSH2_SFTP_TYPE_CHAR_DEVICE () { 7 } sub LIBSSH2_SFTP_TYPE_DIRECTORY () { 2 } sub LIBSSH2_SFTP_TYPE_FIFO () { 9 } sub LIBSSH2_SFTP_TYPE_REGULAR () { 1 } sub LIBSSH2_SFTP_TYPE_SOCKET () { 6 } sub LIBSSH2_SFTP_TYPE_SPECIAL () { 4 } sub LIBSSH2_SFTP_TYPE_SYMLINK () { 3 } sub LIBSSH2_SFTP_TYPE_UNKNOWN () { 5 } sub LIBSSH2_SFTP_VERSION () { 3 } sub SSH_DISCONNECT_AUTH_CANCELLED_BY_USER () { croak 'SSH_DISCONNECT_AUTH_CANCELLED_BY_USER is not a valid Net::SSH2 macro' } sub SSH_DISCONNECT_BY_APPLICATION () { croak 'SSH_DISCONNECT_BY_APPLICATION is not a valid Net::SSH2 macro' } sub SSH_DISCONNECT_COMPRESSION_ERROR () { croak 'SSH_DISCONNECT_COMPRESSION_ERROR is not a valid Net::SSH2 macro' } sub SSH_DISCONNECT_CONNECTION_LOST () { croak 'SSH_DISCONNECT_CONNECTION_LOST is not a valid Net::SSH2 macro' } sub SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE () { croak 'SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE is not a valid Net::SSH2 macro' } sub SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT () { croak 'SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT is not a valid Net::SSH2 macro' } sub SSH_DISCONNECT_ILLEGAL_USER_NAME () { croak 'SSH_DISCONNECT_ILLEGAL_USER_NAME is not a valid Net::SSH2 macro' } sub SSH_DISCONNECT_KEY_EXCHANGE_FAILED () { croak 'SSH_DISCONNECT_KEY_EXCHANGE_FAILED is not a valid Net::SSH2 macro' } sub SSH_DISCONNECT_MAC_ERROR () { croak 'SSH_DISCONNECT_MAC_ERROR is not a valid Net::SSH2 macro' } sub SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE () { croak 'SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE is not a valid Net::SSH2 macro' } sub SSH_DISCONNECT_PROTOCOL_ERROR () { croak 'SSH_DISCONNECT_PROTOCOL_ERROR is not a valid Net::SSH2 macro' } sub SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED () { croak 'SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED is not a valid Net::SSH2 macro' } sub SSH_DISCONNECT_RESERVED () { croak 'SSH_DISCONNECT_RESERVED is not a valid Net::SSH2 macro' } sub SSH_DISCONNECT_SERVICE_NOT_AVAILABLE () { croak 'SSH_DISCONNECT_SERVICE_NOT_AVAILABLE is not a valid Net::SSH2 macro' } sub SSH_DISCONNECT_TOO_MANY_CONNECTIONS () { croak 'SSH_DISCONNECT_TOO_MANY_CONNECTIONS is not a valid Net::SSH2 macro' } %EXPORT_TAGS = ( 'fx' => [ 'LIBSSH2_FX_BAD_MESSAGE', 'LIBSSH2_FX_CONNECTION_LOST', 'LIBSSH2_FX_DIR_NOT_EMPTY', 'LIBSSH2_FX_EOF', 'LIBSSH2_FX_FAILURE', 'LIBSSH2_FX_FILE_ALREADY_EXISTS', 'LIBSSH2_FX_INVALID_FILENAME', 'LIBSSH2_FX_INVALID_HANDLE', 'LIBSSH2_FX_LINK_LOOP', 'LIBSSH2_FX_LOCK_CONFlICT', 'LIBSSH2_FX_NOT_A_DIRECTORY', 'LIBSSH2_FX_NO_CONNECTION', 'LIBSSH2_FX_NO_MEDIA', 'LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM', 'LIBSSH2_FX_NO_SUCH_FILE', 'LIBSSH2_FX_NO_SUCH_PATH', 'LIBSSH2_FX_OK', 'LIBSSH2_FX_OP_UNSUPPORTED', 'LIBSSH2_FX_PERMISSION_DENIED', 'LIBSSH2_FX_QUOTA_EXCEEDED', 'LIBSSH2_FX_UNKNOWN_PRINCIPLE', 'LIBSSH2_FX_WRITE_PROTECT' ], 'disconnect' => [ 'SSH_DISCONNECT_AUTH_CANCELLED_BY_USER', 'SSH_DISCONNECT_BY_APPLICATION', 'SSH_DISCONNECT_COMPRESSION_ERROR', 'SSH_DISCONNECT_CONNECTION_LOST', 'SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE', 'SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT', 'SSH_DISCONNECT_ILLEGAL_USER_NAME', 'SSH_DISCONNECT_KEY_EXCHANGE_FAILED', 'SSH_DISCONNECT_MAC_ERROR', 'SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE', 'SSH_DISCONNECT_PROTOCOL_ERROR', 'SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED', 'SSH_DISCONNECT_RESERVED', 'SSH_DISCONNECT_SERVICE_NOT_AVAILABLE', 'SSH_DISCONNECT_TOO_MANY_CONNECTIONS' ], 'channel' => [ 'LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE', 'LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE', 'LIBSSH2_CHANNEL_EXTENDED_DATA_NORMAL' ], 'all' => [ 'LIBSSH2_CALLBACK_DEBUG', 'LIBSSH2_CALLBACK_DISCONNECT', 'LIBSSH2_CALLBACK_IGNORE', 'LIBSSH2_CALLBACK_MACERROR', 'LIBSSH2_CALLBACK_X11', 'LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE', 'LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE', 'LIBSSH2_CHANNEL_EXTENDED_DATA_NORMAL', 'LIBSSH2_ERROR_ALLOC', 'LIBSSH2_ERROR_BANNER_NONE', 'LIBSSH2_ERROR_BANNER_SEND', 'LIBSSH2_ERROR_CHANNEL_CLOSED', 'LIBSSH2_ERROR_CHANNEL_EOF_SENT', 'LIBSSH2_ERROR_CHANNEL_FAILURE', 'LIBSSH2_ERROR_CHANNEL_OUTOFORDER', 'LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED', 'LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED', 'LIBSSH2_ERROR_CHANNEL_UNKNOWN', 'LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED', 'LIBSSH2_ERROR_DECRYPT', 'LIBSSH2_ERROR_FILE', 'LIBSSH2_ERROR_HOSTKEY_INIT', 'LIBSSH2_ERROR_HOSTKEY_SIGN', 'LIBSSH2_ERROR_INVAL', 'LIBSSH2_ERROR_INVALID_MAC', 'LIBSSH2_ERROR_INVALID_POLL_TYPE', 'LIBSSH2_ERROR_KEX_FAILURE', 'LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE', 'LIBSSH2_ERROR_METHOD_NONE', 'LIBSSH2_ERROR_METHOD_NOT_SUPPORTED', 'LIBSSH2_ERROR_PASSWORD_EXPIRED', 'LIBSSH2_ERROR_PROTO', 'LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED', 'LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED', 'LIBSSH2_ERROR_REQUEST_DENIED', 'LIBSSH2_ERROR_SCP_PROTOCOL', 'LIBSSH2_ERROR_SFTP_PROTOCOL', 'LIBSSH2_ERROR_SOCKET_DISCONNECT', 'LIBSSH2_ERROR_SOCKET_NONE', 'LIBSSH2_ERROR_SOCKET_SEND', 'LIBSSH2_ERROR_SOCKET_TIMEOUT', 'LIBSSH2_ERROR_TIMEOUT', 'LIBSSH2_ERROR_ZLIB', 'LIBSSH2_HOSTKEY_HASH_MD5', 'LIBSSH2_HOSTKEY_HASH_SHA1', 'LIBSSH2_METHOD_COMP_CS', 'LIBSSH2_METHOD_COMP_SC', 'LIBSSH2_METHOD_CRYPT_CS', 'LIBSSH2_METHOD_CRYPT_SC', 'LIBSSH2_METHOD_HOSTKEY', 'LIBSSH2_METHOD_KEX', 'LIBSSH2_METHOD_LANG_CS', 'LIBSSH2_METHOD_LANG_SC', 'LIBSSH2_METHOD_MAC_CS', 'LIBSSH2_METHOD_MAC_SC', 'LIBSSH2_FX_BAD_MESSAGE', 'LIBSSH2_FX_CONNECTION_LOST', 'LIBSSH2_FX_DIR_NOT_EMPTY', 'LIBSSH2_FX_EOF', 'LIBSSH2_FX_FAILURE', 'LIBSSH2_FX_FILE_ALREADY_EXISTS', 'LIBSSH2_FX_INVALID_FILENAME', 'LIBSSH2_FX_INVALID_HANDLE', 'LIBSSH2_FX_LINK_LOOP', 'LIBSSH2_FX_LOCK_CONFlICT', 'LIBSSH2_FX_NOT_A_DIRECTORY', 'LIBSSH2_FX_NO_CONNECTION', 'LIBSSH2_FX_NO_MEDIA', 'LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM', 'LIBSSH2_FX_NO_SUCH_FILE', 'LIBSSH2_FX_NO_SUCH_PATH', 'LIBSSH2_FX_OK', 'LIBSSH2_FX_OP_UNSUPPORTED', 'LIBSSH2_FX_PERMISSION_DENIED', 'LIBSSH2_FX_QUOTA_EXCEEDED', 'LIBSSH2_FX_UNKNOWN_PRINCIPLE', 'LIBSSH2_FX_WRITE_PROTECT', 'LIBSSH2_FXF_APPEND', 'LIBSSH2_FXF_CREAT', 'LIBSSH2_FXF_EXCL', 'LIBSSH2_FXF_READ', 'LIBSSH2_FXF_TRUNC', 'LIBSSH2_FXF_WRITE', 'LIBSSH2_SFTP_ATTR_ACMODTIME', 'LIBSSH2_SFTP_ATTR_EXTENDED', 'LIBSSH2_SFTP_ATTR_PERMISSIONS', 'LIBSSH2_SFTP_ATTR_SIZE', 'LIBSSH2_SFTP_ATTR_UIDGID', 'LIBSSH2_SFTP_LSTAT', 'LIBSSH2_SFTP_OPENDIR', 'LIBSSH2_SFTP_OPENFILE', 'LIBSSH2_SFTP_PACKET_MAXLEN', 'LIBSSH2_SFTP_READLINK', 'LIBSSH2_SFTP_REALPATH', 'LIBSSH2_SFTP_RENAME_ATOMIC', 'LIBSSH2_SFTP_RENAME_NATIVE', 'LIBSSH2_SFTP_RENAME_OVERWRITE', 'LIBSSH2_SFTP_SETSTAT', 'LIBSSH2_SFTP_STAT', 'LIBSSH2_SFTP_SYMLINK', 'LIBSSH2_SFTP_TYPE_BLOCK_DEVICE', 'LIBSSH2_SFTP_TYPE_CHAR_DEVICE', 'LIBSSH2_SFTP_TYPE_DIRECTORY', 'LIBSSH2_SFTP_TYPE_FIFO', 'LIBSSH2_SFTP_TYPE_REGULAR', 'LIBSSH2_SFTP_TYPE_SOCKET', 'LIBSSH2_SFTP_TYPE_SPECIAL', 'LIBSSH2_SFTP_TYPE_SYMLINK', 'LIBSSH2_SFTP_TYPE_UNKNOWN', 'LIBSSH2_SFTP_VERSION', 'SSH_DISCONNECT_AUTH_CANCELLED_BY_USER', 'SSH_DISCONNECT_BY_APPLICATION', 'SSH_DISCONNECT_COMPRESSION_ERROR', 'SSH_DISCONNECT_CONNECTION_LOST', 'SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE', 'SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT', 'SSH_DISCONNECT_ILLEGAL_USER_NAME', 'SSH_DISCONNECT_KEY_EXCHANGE_FAILED', 'SSH_DISCONNECT_MAC_ERROR', 'SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE', 'SSH_DISCONNECT_PROTOCOL_ERROR', 'SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED', 'SSH_DISCONNECT_RESERVED', 'SSH_DISCONNECT_SERVICE_NOT_AVAILABLE', 'SSH_DISCONNECT_TOO_MANY_CONNECTIONS' ], 'hash' => [ 'LIBSSH2_HOSTKEY_HASH_MD5', 'LIBSSH2_HOSTKEY_HASH_SHA1' ], 'fxf' => [ 'LIBSSH2_FXF_APPEND', 'LIBSSH2_FXF_CREAT', 'LIBSSH2_FXF_EXCL', 'LIBSSH2_FXF_READ', 'LIBSSH2_FXF_TRUNC', 'LIBSSH2_FXF_WRITE' ], 'callback' => [ 'LIBSSH2_CALLBACK_DEBUG', 'LIBSSH2_CALLBACK_DISCONNECT', 'LIBSSH2_CALLBACK_IGNORE', 'LIBSSH2_CALLBACK_MACERROR', 'LIBSSH2_CALLBACK_X11' ], 'error' => [ 'LIBSSH2_ERROR_ALLOC', 'LIBSSH2_ERROR_BANNER_NONE', 'LIBSSH2_ERROR_BANNER_SEND', 'LIBSSH2_ERROR_CHANNEL_CLOSED', 'LIBSSH2_ERROR_CHANNEL_EOF_SENT', 'LIBSSH2_ERROR_CHANNEL_FAILURE', 'LIBSSH2_ERROR_CHANNEL_OUTOFORDER', 'LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED', 'LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED', 'LIBSSH2_ERROR_CHANNEL_UNKNOWN', 'LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED', 'LIBSSH2_ERROR_DECRYPT', 'LIBSSH2_ERROR_FILE', 'LIBSSH2_ERROR_HOSTKEY_INIT', 'LIBSSH2_ERROR_HOSTKEY_SIGN', 'LIBSSH2_ERROR_INVAL', 'LIBSSH2_ERROR_INVALID_MAC', 'LIBSSH2_ERROR_INVALID_POLL_TYPE', 'LIBSSH2_ERROR_KEX_FAILURE', 'LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE', 'LIBSSH2_ERROR_METHOD_NONE', 'LIBSSH2_ERROR_METHOD_NOT_SUPPORTED', 'LIBSSH2_ERROR_PASSWORD_EXPIRED', 'LIBSSH2_ERROR_PROTO', 'LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED', 'LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED', 'LIBSSH2_ERROR_REQUEST_DENIED', 'LIBSSH2_ERROR_SCP_PROTOCOL', 'LIBSSH2_ERROR_SFTP_PROTOCOL', 'LIBSSH2_ERROR_SOCKET_DISCONNECT', 'LIBSSH2_ERROR_SOCKET_NONE', 'LIBSSH2_ERROR_SOCKET_SEND', 'LIBSSH2_ERROR_SOCKET_TIMEOUT', 'LIBSSH2_ERROR_TIMEOUT', 'LIBSSH2_ERROR_ZLIB' ], 'method' => [ 'LIBSSH2_METHOD_COMP_CS', 'LIBSSH2_METHOD_COMP_SC', 'LIBSSH2_METHOD_CRYPT_CS', 'LIBSSH2_METHOD_CRYPT_SC', 'LIBSSH2_METHOD_HOSTKEY', 'LIBSSH2_METHOD_KEX', 'LIBSSH2_METHOD_LANG_CS', 'LIBSSH2_METHOD_LANG_SC', 'LIBSSH2_METHOD_MAC_CS', 'LIBSSH2_METHOD_MAC_SC' ], 'sftp' => [ 'LIBSSH2_SFTP_ATTR_ACMODTIME', 'LIBSSH2_SFTP_ATTR_EXTENDED', 'LIBSSH2_SFTP_ATTR_PERMISSIONS', 'LIBSSH2_SFTP_ATTR_SIZE', 'LIBSSH2_SFTP_ATTR_UIDGID', 'LIBSSH2_SFTP_LSTAT', 'LIBSSH2_SFTP_OPENDIR', 'LIBSSH2_SFTP_OPENFILE', 'LIBSSH2_SFTP_PACKET_MAXLEN', 'LIBSSH2_SFTP_READLINK', 'LIBSSH2_SFTP_REALPATH', 'LIBSSH2_SFTP_RENAME_ATOMIC', 'LIBSSH2_SFTP_RENAME_NATIVE', 'LIBSSH2_SFTP_RENAME_OVERWRITE', 'LIBSSH2_SFTP_SETSTAT', 'LIBSSH2_SFTP_STAT', 'LIBSSH2_SFTP_SYMLINK', 'LIBSSH2_SFTP_TYPE_BLOCK_DEVICE', 'LIBSSH2_SFTP_TYPE_CHAR_DEVICE', 'LIBSSH2_SFTP_TYPE_DIRECTORY', 'LIBSSH2_SFTP_TYPE_FIFO', 'LIBSSH2_SFTP_TYPE_REGULAR', 'LIBSSH2_SFTP_TYPE_SOCKET', 'LIBSSH2_SFTP_TYPE_SPECIAL', 'LIBSSH2_SFTP_TYPE_SYMLINK', 'LIBSSH2_SFTP_TYPE_UNKNOWN', 'LIBSSH2_SFTP_VERSION' ] ); ### CONSTANTS ABOVE!!! (this is a marker) 1; Net-OpenSSH-Compat-0.07/lib/Net/OpenSSH/Compat/SSH2.pm0000644000175000017500000005060312331663407020746 0ustar salvasalvapackage Net::OpenSSH::Compat::SSH2; our $VERSION = '0.02'; use strict; use warnings; use Net::OpenSSH; use Net::OpenSSH::Constants qw(OSSH_MASTER_FAILED); use IO::Handle; use IO::Seekable; use File::Basename (); use Fcntl (); use Carp (); use Scalar::Util (); use POSIX (); require Exporter; our @ISA = qw(Exporter Net::OpenSSH::Compat::SSH2::Base); use Net::OpenSSH::Compat::SSH2::Constants; our %EXPORT_TAGS; our @EXPORT_OK = @{$EXPORT_TAGS{all}}; $EXPORT_TAGS{supplant} = []; our %DEFAULTS = (connection => [], channel => [], sftp => [], methods => []); my $supplant; sub import { my $class = shift; if (!$supplant and $class eq __PACKAGE__ and grep($_ eq ':supplant', @_)) { $supplant = 1; for my $end ('', qw(Channel SFTP Dir File)) { my $this = __PACKAGE__; my $pkg = "Net::SSH2"; my $file = "Net/SSH2"; if ($end) { $this .= "::$end"; $pkg .= "::$end"; $file .= "/$end"; } $INC{$file . '.pm'} = __FILE__; no strict 'refs'; @{"${pkg}::ISA"} = ($this); ${"${pkg}::VERSION"} = __PACKAGE__->version; } } __PACKAGE__->export_to_level(1, $class, grep $_ ne ':supplant', @_); } sub version { "1.2.6 (".__PACKAGE__."-$VERSION)" } sub new { my $class = shift; my %methods = @{$DEFAULTS{methods}}; my $cpt = { state => 'new', error => [0, "", ""], blocking => 1, channels => [], methods => \%methods, }; bless $cpt, $class; } sub _free_channels { my $cs = shift->{channels}; @$cs = grep defined, @$cs; Scalar::Util::weaken $_ for @$cs; } sub _master_exited { my $cpt = shift; my $ssh = $cpt->{ssh}; if ($ssh) { $ssh->master_exited; $cpt->_set_error(LIBSSH2_ERROR_SOCKET_DISCONNECT => $ssh->error); } } sub banner {} sub error { wantarray ? @{shift->{error}} : shift->{error}[0] } sub _set_error { my $cpt = shift; my $ssh = $cpt->{ssh}; if ($ssh and $ssh->error == OSSH_MASTER_FAILED) { $cpt->{state} = 'failed'; } $cpt->SUPER::_set_error(@_); } sub sock { undef } sub trace { } my @_auth_list = qw(publickey password); sub auth_list { wantarray ? @_auth_list : join(',', @_auth_list) } sub connect { @_ < 2 and croak "Net::SSH2::connect: not enough parameters"; my $cpt = shift; ref $_[0] and croak "accepting a handler reference for the connection is not implemented"; $cpt->{connect_args} = [@_]; $cpt->{state} = 'connected*'; } sub auth_ok { shift->{state} eq 'ok' } sub auth_password { shift->_connect(auth_password => @_) } sub auth_publickey { shift->_connect(auth_publickey => @_) } sub auth { shift->_connect(auth => @_) } my %method_default = ( HOSTKEY => 'ssh-rsa', KEX => 'diffie-hellman-group14-sha1', CRYPT => 'aes128-ctr', MAC => 'hmac-sha1', COMP => 'none', ); sub method { my $cpt = shift; my $attr = shift; $attr =~ s/_(?:SC|CS)$//; if (exists $method_default{$attr}) { if (@_) { $cpt->{methods}{$attr} = join(',', @_) if $cpt->{state} eq 'new'; return 1; } else { $cpt->{state} eq 'new' and return; my $val = $cpt->{methods}{$attr}; return (defined $val ? $val : $method_default{$attr}); } } croak "Net::SSH2::method: unknown method type: $attr"; } my %method2opt = (HOSTKEY => 'HostKeyAlgorithms', CRYPT => 'Ciphers', KEX => 'KexAlgorithms', MAC => 'MACs', ); sub _connect { my $cpt = shift; $cpt->_check_state('connected*') or return; my ($host, $port, %opts) = @{$cpt->{connect_args}}; my $defs = $DEFAULTS{connection}; my @master_opts; my @args = (($defs ? @$defs : ()), host => $host, port => $port, timeout => delete($opts{Timeout})); %opts and Carp::croak "unsupported option(s) given: ".join(", ", keys %opts); for my $method (keys %method2opt) { my $v = $cpt->{methods}{$method}; push @master_opts, -o => "$method2opt{$method}=$v" if defined $v; } my $COMP = $cpt->{methods}{COMP}; if (defined $COMP and $COMP ne 'none') { push @master_opts, '-C'; } my $auth = shift; $cpt->{auth_method} ||= $auth; $cpt->{auth_args} ||= [@_]; if ($auth eq 'auth_password') { my ($user, $passwd) = @_; push @args, user => $user, passwd => $passwd; } elsif ($auth eq 'auth_publickey') { my ($user, undef, $private, $passphrase) = @_; push @args, user => $user, key_path => $private, passphrase => $passphrase; } elsif ($auth eq 'auth') { my %opts = @_; my $rank = delete $opts{rank}; $rank = 'publickey,password' unless defined $rank; my $username = delete $opts{username}; my $password = delete $opts{password}; my $publickey = delete $opts{publickey}; my $privatekey = delete $opts{privatekey}; my $hostname = delete $opts{hostname}; %opts and Carp::croak "unsupported option(s) given: ".join(", ", keys %opts); for my $method (split /\s*,\s*/, $rank) { $cpt->{state} = 'connected*'; if ($method eq 'publickey') { if (defined $privatekey) { $cpt->_connect(auth_publickey => $username, $publickey, $privatekey) } } elsif ($method eq 'password') { if (defined $password) { $cpt->_connect(auth_password => $username, $password); } } $cpt->{state} eq 'ok' and return 1; } return; } else { Carp::croak "unsupported login method"; } push @args, master_opts => \@master_opts if @master_opts; my $ssh = Net::OpenSSH->new(@args); if ($ssh->error) { $cpt->_set_error(LIBSSH2_ERROR_SOCKET_DISCONNECT => $ssh->error); return } else { $cpt->{ssh} = $ssh; $cpt->{state} = 'ok'; return 1 } } sub tcpip { Carp::croak "method tcpip not implemented" } sub listen { Carp::croak "method listen not implemented" } sub poll { require Net::OpenSSH::Compat::SSH::Poll; goto &_poll; } sub debug {} sub blocking { my ($cpt, $blocking) = @_; if ($cpt->{blocking} xor $blocking) { $cpt->{blocking} = $blocking; $cpt->_free_channels; $_->_blocking($blocking) for @{$cpt->{channels}}; } $blocking; } sub scp_get { my ($cpt, $remote, $local) = @_; $cpt->_check_state('ok'); unless (defined $local) { $local = File::Basename::basename($remote); } my $ssh = $cpt->{ssh}; $ssh->scp_get($remote, $local); if ($ssh->error) { $cpt->_set_error(LIBSSH2_ERROR_SCP_PROTOCOL => "scp_get failed"); return } 1 } sub scp_put { my ($cpt, $local, $remote) = @_; $cpt->_check_state('ok'); unless (defined $remote) { $remote = File::Basename::basename($local); } my $ssh = $cpt->{ssh}; $ssh->scp_put($local, $remote); if ($ssh->error) { $cpt->_set_error(LIBSSH2_ERROR_SCP_PROTOCOL => "scp_get failed"); return } 1 } sub channel { my $cpt = shift; $cpt->_check_state('ok'); my $class = join('::', ref($cpt), 'Channel'); my $chan = $class->_new($cpt); push @{$cpt->{channels}}, $chan; $cpt->_free_channels; $chan; } sub sftp { my $cpt = shift; $cpt->_check_state('ok'); my $class = join('::', ref($cpt), 'SFTP'); $class->_new($cpt); } package Net::OpenSSH::Compat::SSH2::Channel; our @ISA = qw(IO::Handle Net::OpenSSH::Compat::SSH2::Base); sub _new { my ($class, $cpt) = @_; my $chan = $class->SUPER::new; *$chan = { cpt => $cpt, state => 'new', error => [0, "", ""], blocking => 1 }; return $chan; } sub _hash { *{shift @_}{HASH} } sub _parent { shift->_hash->{cpt} } sub ext_data { $_[0]->_hash->{ext_data} = $_[1] } sub setenv { my $ch = shift->_hash; my $env = $ch->{env} ||= {}; %$env = (%$env, @_); 1; } sub _exec { my $chan = shift; $chan->_check_state('new') or return; my $defs = $DEFAULTS{channel}; my %opts = ( ($defs ? @$defs : ()), (ref $_[0] ? %{shift @_} : ()), stdinout_socket => 1 ); my $ch = $chan->_hash; my $mode = $ch->{ext_data}; my $cpt = $ch->{cpt}; my $ssh = $cpt->{ssh}; $mode ||= 'normal'; if ($mode eq 'ignore') { $opts{stderr_discard} = 1; } elsif ($mode eq 'merge') { $opts{stderr_to_stdout} = 1; } else { $opts{stderr_pipe} = 1; } local %ENV = (%ENV, %{$ch->{env}}) if $ch->{env}; my ($io, undef, $err, $pid) = $ssh->open_ex(\%opts, @_); if ($ssh->error) { $chan->_set_error(LIBSSH2_ERROR_SOCKET_DISCONNECT => $ssh->error); $ch->{state} = 'failed'; return } $chan->fdopen($io, 'r+'); $chan->autoflush(1); binmode $chan; $ch->{err} = $err; $ch->{pid} = $pid; $ch->{state} = 'exec'; $chan->_blocking($ch->{cpt}{blocking}); return 1; } sub exec { shift->_exec(@_) } sub shell { shift->_exec } sub subsystem { shift->_exec({ssh_opts => ['-s']}, @_) } sub send_eof { my $chan = shift; shutdown $chan, 1 } sub close { my $chan = shift; $chan->_check_state('exec') or return; my $ch = $chan->_hash; $chan->SUPER::close; $ch->{err} and close($ch->{err}); # warn "reaping $ch->{pid}"; $chan->_exit_status(1); $ch->{state} = 'closed'; $ch->{eof} = 1; 1; } sub _master_exited { shift->_hash->{cpt}->_master_exited(@_) } sub _slave_exited { my ($chan, $rc) = @_; my $ch = $chan->_hash; delete $ch->{pid}; $ch->{exit_status} = $rc; } sub DESTROY { my $chan = shift; my $ch = $chan->_hash; $chan->close if $ch->{state} eq 'exec'; $chan->SUPER::DESTROY; } sub wait_closed { my $chan = shift; my $ch = $chan->_hash; shift->close if $ch->{state} eq 'exec'; $ch->{state} eq 'closed'; } sub _exit_status { my ($chan, $wait) = @_; my $ch = $chan->_hash; return $ch->{exit_status} if defined $ch->{exit_status}; return 0 unless defined $ch->{pid}; while (1) { my $pid = waitpid $ch->{pid}, ($wait ? 0 : POSIX::WNOHANG()); if ($pid == $ch->{pid}) { return $ch->{exit_status} = $?; } if ($pid < 0 and $! == Errno::ECHILD) { return $ch->{exit_status} = 0; } return 0 unless $wait; select(undef, undef, undef, 0.1); } } sub exit_status { shift->_exit_status(0) >> 8 } sub exit_signal { shift->_exit_status(0) & 255 } sub blocking { shift->_hash->{cpt}->blocking(@_) } sub _blocking { my ($chan, $blocking) = @_; my $ch = $chan->_hash; if (($ch->{state} eq 'exec') and ($blocking xor $ch->{blocking})) { $ch->{blocking} = $blocking; $chan->SUPER::blocking($blocking); my $err = $chan->_hash->{err}; $err->blocking($blocking) if $err; } } sub write { my ($chan, $data, $ext) = @_; $chan->_check_state('exec') or return; if ($ext) { # silently discard data sent to ext channel return length $data; } else { $chan->syswrite($data); } } sub read { my ($chan, undef, $size, $ext) = @_; $size ||= 1024; $chan->_check_state('exec') or return; my $ch = $chan->_hash; if ($ext) { my $fd = $ch->{err} or $chan->_set_error(LIBSSH2_ERROR_CHANNEL_UNKNOWN => 'no ext channel available'); return sysread($fd, $_[1], $size); } else { unless ($ch->{blocking}) { my $fno = fileno($chan); my $v = ''; vec($v, $fno, 1) = 1; select($v, undef, undef, 0); vec($v, $fno, 1) or return 0; } my $bytes = sysread($chan, $_[1], $size || 0); $ch->{eof} = 1 unless $bytes; return $bytes; } } sub eof { shift->_hash->{eof} || 0} sub flush { 0 } package Net::OpenSSH::Compat::SSH2::SFTP; sub _new { my ($class, $cpt) = @_; my $defs = $DEFAULTS{sftp}; my $sftp = $cpt->{ssh}->sftp($defs ? @$defs : ()); my $sw = { cpt => $cpt, sftp => $sftp }; bless $sw, $class; } sub error { my $sw = shift; my $status = $sw->{sftp}->status; wantarray ? ($status + 0, "$status") : $status + 0; } sub open { my ($sw, $file, $flags, $mode) = @_; my $sftp = $sw->{sftp}; my $a = Net::SFTP::Foreign::Attributes->new(); $a->set_perm(defined $mode ? $mode : 0666); my $fh = $sftp->open($file, $flags, $a); my $class = join('::', ref($sw->{cpt}), 'File'); $class->_new($sw, $fh); } sub opendir { my ($sw, $dir) = @_; my $sftp = $sw->{sftp}; my $dh = $sftp->opendir($dir); my $class = join('::', ref($sw->{cpt}), 'Dir'); $class->_new($sw, $dh); } sub unlink { my ($sw, $file) = @_; my $sftp = $sw->{sftp}; $sftp->unlink($file); } sub rename { my ($sw, $old, $new, $flags) = @_; my $sftp = $sw->{sftp}; $sftp->rename($old, $new); } sub mkdir { my ($sw, $dir, $mode) = @_; my $sftp = $sw->{sftp}; my $a = Net::SFTP::Foreign::Attributes->new; $a->set_perm(defined $mode ? $mode : 0777); $sftp->mkdir($dir, $a); } sub rmdir { my ($sw, $dir) = @_; my $sftp = $sw->{sftp}; $sftp->rmdir($dir); } my $a2e = sub { my $a = shift; ( mode => $a->perm, size => $a->size, uid => $a->uid, gid => $a->gid, atime => $a->atime, mtime => $a->mtime ); }; my $e2a = sub { my %e = @_; my $a = Net::SFTP::Foreign::Attributes->new; $a->set_perm($e{mode}) if defined $e{mode}; $a->set_size($e{size}) if defined $e{size}; $a->set_ugid($e{uid}, $e{gid}) if grep defined($e{$_}), qw(uid gid); $a->set_amtime($e{atime}, $e{mtime}) if grep defined($e{$_}), qw(atime mtime); $a; }; sub stat { my ($sw, $file) = @_; my $sftp = $sw->{sftp}; my $a = $sftp->stat($file) or return; my %e = $a2e->($a); (wantarray ? %e : \%e); } sub setstat { my ($sw, $file, %opts) = @_; my $sftp = $sw->{sftp}; my $a = $e2a->(%opts); $sftp->setstat($file, $a); } sub symlink { my ($sw, $path, $target, $type) = @_; my $sftp = $sw->{sftp}; $sftp->symlink($path, $target); } sub readlink { my ($sw, $path) = @_; my $sftp = $sw->{sftp}; $sftp->readlink($path); } sub realpath { my ($sw, $path) = @_; my $sftp = $sw->{sftp}; $sftp->realpath($path); } package Net::OpenSSH::Compat::SSH2::Dir; sub _new { my ($class, $sw, $dh) = @_; my $dw = { dh => $dh, sw => $sw }; bless $dw, $class; } sub read { my $dw = shift; my $e = $dw->{sw}->readdir($dw->{dh}) or return; my %e = ( name => $e->{filename}, $a2e->($e->{a}) ); wantarray ? %e : \%e; } package Net::OpenSSH::Compat::SSH2::File; our @ISA = qw(IO::Handle IO::Seekable); sub TIEHANDLE { return shift } sub _new { my ($class, $sw, $fh) = @_; my $fw = $class->SUPER::new(); *$fw = { sw => $sw, fh => $fh }; tie *$fw, $fw; $fw; } our $AUTOLOAD; sub AUTOLOAD { my $fw = shift; my $method = $AUTOLOAD; $method =~ s/.*:://; if ($method =~ /^[A-Z]+$/) { my $fh = *{$fw}{HASH}{fh}; $fh->$method(@_); } else { $method = "SUPER::$method"; $fw->$method(@_); } } sub stat { my $fw = shift; my $sftp = *{$fw}{HASH}{sw}{sftp}; my $fh = *{$fw}{HASH}{fh}; my $a = $sftp->fstat($fh) or return; my %e = $a2e->($a); wantarray ? %e : \%e; } sub setstat { my $fw = shift; my $sftp = *{$fw}{HASH}{sw}{sftp}; my $fh = *{$fw}{HASH}{fh}; my $a = $e2a->(@_); $sftp->fsetstat($fh, $a); } package Net::OpenSSH::Compat::SSH2::Base; sub _entry_method { my $n = 1; my $last = 'unknown'; while (1) { my $sub = (caller $n++)[3]; $sub =~ /^Net::OpenSSH::Compat::SSH2::(?:\w+::)?(\w+)$/ or last; $last = $1; } $last; } sub _hash { shift } sub _parent { undef } sub error { my $self = shift->_hash; wantarray ? @{$self->{error}} : $self->{error}[0] } sub _set_error { my ($self, $error, $msg) = @_; my $n = eval $error; my $parent = $self->_parent; $parent->_set_error(@_) if $parent; @{$self->_hash->{error}} = ($n, $error, $msg); } sub _bad_state_error { 'LIBSSH2_ERROR_SOCKET_SEND' } sub _check_state { my ($self, $expected) = @_; my $state = $self->_state; return 1 if $expected eq $state; my $method = $self->_entry_method; my $class = ref $self; $self->_set_error($self->_bad_state_error, qq($class object can't do "$method" on state $state)); return } sub _state { shift->_hash->{state} } 1; __END__ =head1 NAME Net::OpenSSH::Compat::SSH2 - Net::OpenSSH adapter for Net::SSH2 API compatibility =head1 SYNOPSIS use Net::OpenSSH::Compat::SSH2 qw(:supplant); use Net::SSH2; my $ssh2 = Net::SSH2->new; $ssh2->connect('host'); $ssh2->auth_publickey("jsmith", "/home/jsmith/.ssh/id_dsa.pub", "/home/jsmith/.ssh/id_dsa"); my $c = $ssh2->channel; $c->exec("ls"); print while <$c>; $c->close; print "exit status: ", $c->exit_status, "\n"; =head1 DESCRIPTION This module implements L API on top of L. After the module is loaded as... use Net::OpenSSH::Compat::SSH2 qw(:supplant); it will supplant the Net::SSH2 module as if it was installed on the machine and use L under the hood to handle SSH operations. Most programs using L should continue to work without any change. =head2 Setting defaults The hash C<%Net::OpenSSH::Compat::SSH2::DEFAULTS> can be used to set default values for L and other modules called under the hood and otherwise not accessible through the Net::SSH2 API. The entries currently supported are: =over =item connection => [ %opts ] Extra options passed to C constructor. Example: $Net::OpenSSH::Compat::SSH2::DEFAULTS{connection} = [ ssh_path => "/opt/SSH/bin/ssh" ]; =item channel => [ %opts ] Extra options passed to C method. =item sftp => [ %opts ] Extra options passed to C constructor. $Net::OpenSSH::Compat::SSH2::DEFAULTS{connection} = [ read_ahead => 128 * 1024, queue_size => 20 ]; =back =head1 BUGS AND SUPPORT B Besides that, there are some functionality of Net::SSH2 that can not be emulated with Net::OpenSSH. Fortunately, the missing bits are rarely used so probably you may not need them at all. Specifically, the return values from the C<$ssh2-Emethod($ATTR)> are not real but faked ones. C return value is also faked. Anyway, if your Net::SSH2 script fails, fill a bug report at the CPAN RT bugtracker (L) or just send me an e-mail with the details. Include at least: =over 4 =item 1 - The full source of the script =item 2 - A description of what happens in your machine =item 3 - What you thing it should be happening =item 4 - What happens when you use the real Net::SSH2 =item 5 - The version and name of your operating system =item 6 - The version of the OpenSSH ssh client installed on your machine (C) =item 7 - The Perl version (C) =item 8 - The versions of the Perl packages Net::OpenSSH, IO::Pty and this Net::OpenSSH::Compat. =back =head2 Git repository The source code repository is at L. =head2 My wishlist If you like this module and you're feeling generous, take a look at my Amazon Wish List: L Also consider contributing to the OpenSSH project this module builds upon: L. =head1 COPYRIGHT AND LICENSE Copyright (C) 2011, 2014 by Salvador FandiEo (sfandino@yahoo.com) This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.0 or, at your option, any later version of Perl 5 you may have available. =cut Net-OpenSSH-Compat-0.07/lib/Net/OpenSSH/Compat/Perl.pm0000644000175000017500000002055112571053315021125 0ustar salvasalvapackage Net::OpenSSH::Compat::Perl; our $VERSION = '0.07'; use strict; use warnings; use Carp (); use Net::OpenSSH; use Net::OpenSSH::Constants qw(OSSH_MASTER_FAILED OSSH_SLAVE_CMD_FAILED); require Exporter; our @ISA = qw(Exporter); my $supplant; my $session_id = 1; our %DEFAULTS = ( session => [protocol => 2], connection => [] ); sub import { my $class = shift; if (!$supplant and $class eq __PACKAGE__ and grep($_ eq ':supplant', @_)) { $supplant = 1; for my $end ('') { #, qw(Channel SFTP Dir File)) { my $this = __PACKAGE__; my $pkg = "Net::SSH::Perl"; my $file = "Net/SSH/Perl"; if ($end) { $this .= "::$end"; $pkg .= "::$end"; $file .= "/$end"; } $INC{$file . '.pm'} = __FILE__; no strict 'refs'; @{"${pkg}::ISA"} = ($this); ${"${pkg}::VERSION"} = __PACKAGE__->version; } } __PACKAGE__->export_to_level(1, $class, grep $_ ne ':supplant', @_); } sub version { "1.34 (".__PACKAGE__."-$VERSION)" } sub new { my $class = shift; my $host = shift; my $cfg = Net::OpenSSH::Compat::Perl::Config->new(@_); my $cpt = { host => $host, state => 'new', cfg => $cfg, session_id => $session_id++ }; bless $cpt, $class; } sub _entry_method { my $n = 1; my $last = 'unknown'; while (1) { my $sub = (caller $n++)[3]; $sub =~ /^Net::OpenSSH::Compat::(?:\w+::)?(\w+)$/ or last; $last = $1; } $last; } sub _check_state { my ($cpt, $expected) = @_; my $state = $cpt->{state}; return 1 if $expected eq $state; my $method = $cpt->_entry_method; my $class = ref $cpt; Carp::croak qq($class object can't do "$method" on state $state); return } sub _check_error { my $cpt = shift; my $ssh = $cpt->{ssh}; return if (!$ssh->error or $ssh->error == OSSH_SLAVE_CMD_FAILED); my $method = $cpt->_entry_method; $cpt->{state} = 'failed' if $ssh->error == OSSH_MASTER_FAILED; Carp::croak "$method failed: " . $ssh->error; } sub login { my ($cpt, $user, $password, $suppress_shell) = @_; $cpt->_check_state('new'); $cpt->{user} = $user; $cpt->{password} = '*****' if defined $password; $cpt->{suppress_shell} = $suppress_shell; my @args = (host => $cpt->{host}, @{$DEFAULTS{connection}}); push @args, user => $user if defined $user; push @args, password => $password if defined $password; my $cfg = $cpt->{cfg}; push @args, port => $cfg->{port} if defined $cfg->{port}; push @args, batch_mode => 1 unless $cfg->{interactive}; my @more; push @more, 'UsePrivilegedPort=yes' if $cfg->{privileged}; push @more, "Ciphers=$cfg->{ciphers}" if defined $cfg->{ciphers}; push @more, "Compression=$cfg->{compression}" if defined $cfg->{compression}; push @more, "CompressionLevel=$cfg->{compression_level}" if defined $cfg->{compression_level}; if ($cfg->{identity_files}) { push @more, "IdentityFile=$_" for @{$cfg->{identity_files}}; } if ($cfg->{options}) { push @more, @{$cfg->{options}}; } push @args, master_opts => [map { -o => $_ } @more]; # warn "args: @args"; my $ssh = $cpt->{ssh} = Net::OpenSSH->new(@args); if ($ssh->error) { $ssh->{state} = 'failed'; $ssh->die_on_error; } $cpt->{state} = 'connected'; } sub cmd { my ($cpt, $cmd, $stdin) = @_; $cpt->_check_state('connected'); my $ssh = $cpt->{ssh}; $stdin = '' unless defined $stdin; local $?; my ($out, $err) = $ssh->capture2({stdin_data => $stdin}, $cmd); $cpt->_check_error; return ($out, $err, ($? >> 8)); } sub shell { my $cpt = shift; $cpt->_check_state('connected'); my $ssh = $cpt->{ssh}; my $tty = $cpt->{cfg}{use_pty}; $tty = 1 unless defined $tty; $ssh->system({tty => $tty}); } sub config { shift->{cfg} } sub debug { Carp::carp("@_") if shift->{cfg}{debug} } sub session_id { shift->{session_id} } my $make_missing_methods = sub { my $pkg = caller; my $faked = $pkg; $faked =~ s/^Net::OpenSSH::Compat::/Net::SSH::/; for (@_) { my $name = $_; no strict 'refs'; *{$pkg.'::'.$name} = sub { Carp::croak("method ${faked}::$name is not implemented by $pkg, report a bug if you want it supported!"); } } }; $make_missing_methods->(qw(register_handler sock incomming_data packet_start)); package Net::OpenSSH::Compat::Perl::Config; my %option_perl2openssh = qw(protocol proto); sub new { my $class = shift; my %opts = (@{$DEFAULTS{session}}, @_); my %cfg = map { my $v = delete $opts{$_}; my $name = $option_perl2openssh{$_} || $_; defined $v ? ($name, $v) : () } qw(port protocol debug interactive privileged identity_files cipher ciphers compression compression_level use_pty options); %opts and Carp::croak "unsupported configuration option(s) given: ".join(", ", keys %opts); $cfg{proto} =~ /\b2\b/ or Carp::croak "Unsupported protocol version requested $cfg{proto}"; bless \%cfg, $class; } sub get { $_[0]->{$_[1]} } sub set { my ($cfg, $k, $v) = @_; $cfg->{$k} = $v if @_ == 3; $cfg->{$k}; } sub DESTROY {}; $make_missing_methods->(qw(read_config merge_directive AUTOLOAD)); 1; __END__ =head1 NAME Net::OpenSSH::Compat::Perl - Net::OpenSSH adapter for Net::SSH::Perl API compatibility =head1 SYNOPSIS use Net::OpenSSH::Compat::Perl qw(:supplant); use Net::SSH::Perl; my $ssh = Net::SSH::Perl->new('host'); $ssh->login($user, $passwd); my ($out, $err, $rc) = $ssh->cmd($cmd); =head1 DESCRIPTION This module implements a subset of L API on top of L. After the module is loaded as... use Net::OpenSSH::Compat::Perl qw(:supplant); ... it supplants the Net::SSH::Perl module as if it were installed on the machine using L under the hood to handle SSH operations. =head2 Setting defaults The hash C<%Net::OpenSSH::Compat::Perl::DEFAULTS> can be used to set default values for L and other modules called under the hood and otherwise not accessible through the Net::SSH::Perl API. The entries currently supported are: =over =item connection => [ %opts ] Extra options passed to C constructor. Example: $Net::OpenSSH::Compat::SSH::Perl::DEFAULTS{connection} = [ ssh_path => "/opt/SSH/bin/ssh" ]; =back =head1 BUGS AND SUPPORT B C method is not supported. Net::SSH::Perl submodules (i.e. L) are not emulated. Anyway, if your Net::SSH::Perl script fails, fill a bug report at the CPAN RT bugtracker (L) or just send me an e-mail with the details. Include at least: =over 4 =item 1 - The full source of the script =item 2 - A description of what happens in your machine =item 3 - What you thing it should be happening =item 4 - What happens when you use the real Net::SSH::Perl =item 5 - The version and name of your operating system =item 6 - The version of the OpenSSH ssh client installed on your machine (C) =item 7 - The Perl version (C) =item 8 - The versions of the Perl packages Net::OpenSSH, IO::Pty and this Net::OpenSSH::Compat. =back =head2 Git repository The source code repository is at L. =head2 My wishlist If you like this module and you're feeling generous, take a look at my Amazon Wish List: L Also consider contributing to the OpenSSH project this module builds upon: L. =head1 COPYRIGHT AND LICENSE Copyright (C) 2011, 2014, 2015 by Salvador FandiEo (sfandino@yahoo.com) This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.0 or, at your option, any later version of Perl 5 you may have available. =cut Net-OpenSSH-Compat-0.07/lib/Net/OpenSSH/Compat/SSH.pm0000644000175000017500000000706411640564516020672 0ustar salvasalvapackage Net::OpenSSH::Compat::SSH; our $VERSION = '0.06'; use strict; use warnings; use Carp (); use IPC::Open2; use IPC::Open3; use Net::OpenSSH; use Net::OpenSSH::Constants qw(OSSH_MASTER_FAILED OSSH_SLAVE_CMD_FAILED); require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(ssh ssh_cmd sshopen2 sshopen3); my $supplant; our %DEFAULTS = ( connection => [] ); sub import { my $class = shift; if (!$supplant and $class eq __PACKAGE__ and grep($_ eq ':supplant', @_)) { $supplant = 1; for my $end ('') { my $this = __PACKAGE__; my $pkg = "Net::SSH"; my $file = "Net/SSH"; if ($end) { $this .= "::$end"; $pkg .= "::$end"; $file .= "/$end"; } $INC{$file . '.pm'} = __FILE__; no strict 'refs'; @{"${pkg}::ISA"} = ($this); ${"${pkg}::VERSION"} = __PACKAGE__->version; } } __PACKAGE__->export_to_level(1, $class, grep $_ ne ':supplant', @_); } sub version { "0.09 (".__PACKAGE__."-$VERSION)" } sub ssh { my $host = shift; my $ssh = Net::OpenSSH->new($host); if ($ssh->error) { $? = (255<<8); return -1; } my @cmd = $ssh->make_remote_command({quote_args => 0}, @_); system (@cmd); } sub ssh_cmd { my ($host, $user, $command, @args, $stdin); if (ref $_[0] eq 'HASH') { my %opts = $_[0]; $host = delete $opts{host}; $user = delete $opts{user}; $command = delete $opts{command}; $stdin = delete $opts{stdin_string}; my $args = delete $opts{args}; @args = @$args if defined $args; } else { ($host, $command, @args) = @_; } $stdin = '' unless defined $stdin; my $ssh = Net::OpenSSH->new($host, user => $user); if ($ssh->error) { $? = (255<<8); die $ssh->error; } my ($out, $err) = $ssh->capture2({quote_args => 0, stdin_data => $stdin}, $command, @args); die $err if length $err; $out; } sub sshopen2 { my($host, $reader, $writer, $cmd, @args) = @_; my $ssh = Net::OpenSSH->new($host); $ssh->die_on_error; my @cmd = $ssh->make_remote_command({quote_args => 0}, $cmd, @args); open2($reader, $writer, @cmd); } sub sshopen3 { my($host, $writer, $reader, $error, $cmd, @args) = @_; my $ssh = Net::OpenSSH->new($host); $ssh->die_on_error; my @cmd = $ssh->make_remote_command({quote_args => 0}, $cmd, @args); open3($writer, $reader, $error, @cmd); } 1; __END__ =head1 NAME Net::OpenSSH::Compat::SSH - Net::OpenSSH adapter for Net::SSH API compatibility =head1 SYNOPSIS use Net::OpenSSH::ConnectionCache; # for speed bost use Net::OpenSSH::Compat qw(Net::SSH); use Net::SSH qw(ssh ssh_cmd sshopen3); my $out = ssh_cmd('username@host', $command); sshopen2('user@hostname', $reader, $writer, $command); sshopen3('user@hostname', $writer, $reader, $error, $command); =head1 DESCRIPTION This module implements L API on top of L. After the module is loaded as follows: use Net::OpenSSH::Compat 'Net::SSH'; or from the command line: $ perl -MNet::OpenSSH::Compat=Net::SSH script.pl it will supplant Net::SSH module as if it was installed on the machine and use L under the hood to handle SSH operations. Most programs using L should continue to work without any change. It can be used together with L usually for a big speed boost. =cut Net-OpenSSH-Compat-0.07/lib/Net/OpenSSH/Compat.pm0000644000175000017500000000405112571053337020224 0ustar salvasalvapackage Net::OpenSSH::Compat; our $VERSION = '0.07'; use strict; use warnings; use Carp; my %impl = ('Net::SSH2' => 'SSH2', 'Net::SSH::Perl' => 'Perl', 'Net::SSH' => 'SSH'); sub import { my $class = shift; for my $mod (@_) { my $impl = $impl{$mod}; defined $impl or croak "$mod compatibility is not available"; my $adapter = __PACKAGE__ . "::$impl"; eval "use $adapter ':supplant';"; die if $@; } 1; } 1; __END__ =head1 NAME Net::OpenSSH::Compat - Compatibility modules for Net::OpenSSH =head1 SYNOPSIS use Net::OpenSSH::Compat 'Net::SSH2'; use Net::OpenSSH::Compat 'Net::SSH::Perl'; =head1 DESCRIPTION This package contains a set of adapter modules that run on top of Net::OpenSSH providing the APIs of other SSH modules available from CPAN. Currently, there are adapters available for L and L. Adapters for L and L are planned... maybe also for L and L if somebody request them. =head1 BUGS AND SUPPORT B If you find any bug fill a report at the CPAN RT bugtracker (L) or just send me an e-mail with the details. =head2 Git repository The source code repository is at L. =head2 My wishlist If you like this module and you're feeling generous, take a look at my Amazon Wish List: L Also consider contributing to the OpenSSH project this module builds upon: L. =head1 SEE ALSO L, L, L. =head1 COPYRIGHT AND LICENSE Copyright (C) 2011, 2014, 2015 by Salvador FandiEo (sfandino@yahoo.com) This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.1 or, at your option, any later version of Perl 5 you may have available. =cut Net-OpenSSH-Compat-0.07/examples/0000755000175000017500000000000012571053461015504 5ustar salvasalvaNet-OpenSSH-Compat-0.07/examples/conditional_loading.pl0000644000175000017500000000172112331662404022037 0ustar salvasalva#!/usr/bin/perl use strict; use warnings; use Data::Dumper; my %connection_details = ( host => 'localhost', @ARGV); my $ssh = ssh_connection(%connection_details); print "ssh: ", Dumper($ssh), "\n"; exit (0); BEGIN { eval { require Net::SSH2; warn "Net::SSH2 loaded"; 1; } or eval { require Net::OpenSSH::Compat::SSH2; Net::OpenSSH::Compat::SSH2->import(':supplant'); warn "Net::SSH2 supplanted"; 1; } or die "unable to load any SSH module: $@"; Net::SSH2->import(); } sub ssh_connection { my (%connection_info) = @_; ## Connect to the ssh server my $ssh = Net::SSH2->new(); $ssh->connect($connection_info{'host'},22) or die "Unable to connect to the remote ssh server \n\n $@"; ## Login to ssh server $ssh->auth_password($connection_info{'user'},$connection_info{'pass'}) or die "Unable to login Check username and password. \n\n $@\n"; return $ssh; } Net-OpenSSH-Compat-0.07/MANIFEST0000644000175000017500000000077512571053461015030 0ustar salvasalvaChanges gen/gen_constants_ssh2.pl lib/Net/OpenSSH/Compat.pm lib/Net/OpenSSH/Compat/SSH2.pm lib/Net/OpenSSH/Compat/SSH2/Constants.pm lib/Net/OpenSSH/Compat/SSH2/Poll.pm lib/Net/OpenSSH/Compat/Perl.pm lib/Net/OpenSSH/Compat/SSH.pm Makefile.PL MANIFEST README t/Net-OpenSSH-Compat.t t/Net-SSH.t t/Net-SSH-Perl.t examples/conditional_loading.pl META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) Net-OpenSSH-Compat-0.07/Changes0000644000175000017500000000250112571053275015162 0ustar salvasalvaRevision history for Perl extension Net::OpenSSH::Compat. 0.07 Aug 31, 2015 - accept protocol argument (bug report by Bas van Sisseren) - spelling error fixes - pod fix (bug report by Florian Schlichting from Debian) - add conditional_loading example 0.06 Sep 28, 2011 - in SSH2: - honor use_pty configuration flag from shell method - in SSH: - order or $reader, $writer arguments for sshopen3 was wrong 0.05 Sep 10, 2011 - add use_ok tests for submodules - in SSH2 : - exit_status can be called from any state - add support for eof and exit_signal methods - more checks for out of order operations - solve bug accessing parent object from channel - close now sets eof - syntax error bug solved (reported by Will Hawes) 0.04 Aug 29, 2011 - add support for Net::SSH::Perl and Net::SSH - import method was broken - minor doc corrections 0.02 May 29, 2011 - add support for auth_list - add support for "Net::SSH2::method" method. - add pointer to Github repository - correct mispellings and some wording error in the docs 0.01 Tue Apr 12 19:08:56 2011 - original version; created by h2xs 1.23 with options -AXn Net::OpenSSH::Compat Net-OpenSSH-Compat-0.07/gen/0000755000175000017500000000000012571053461014437 5ustar salvasalvaNet-OpenSSH-Compat-0.07/gen/gen_constants_ssh2.pl0000644000175000017500000000156511561715107020607 0ustar salvasalva#!/usr/bin/perl use strict; use warnings; use 5.010; use Net::SSH2; use Scalar::Quote qw(quote); use Data::Dumper; while (<>) { print; last if /^###+\s+CONSTANTS\s+BELOW/; } say <Dump([\%Net::SSH2::EXPORT_TAGS], [qw(*EXPORT_TAGS)]); say ""; my $print; while (<>) { $print = 1 if /^###+\s+CONSTANTS\s+ABOVE/; print if $print; } Net-OpenSSH-Compat-0.07/META.yml0000664000175000017500000000075712571053461015152 0ustar salvasalva--- abstract: unknown author: - 'Salvador Fandino ' build_requires: ExtUtils::MakeMaker: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.04, CPAN::Meta::Converter version 2.150001' license: unknown meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Net-OpenSSH-Compat no_index: directory: - t - inc requires: Net::OpenSSH: 0.53_03 version: '0.07' Net-OpenSSH-Compat-0.07/META.json0000664000175000017500000000155312571053461015315 0ustar salvasalva{ "abstract" : "unknown", "author" : [ "Salvador Fandino " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.04, CPAN::Meta::Converter version 2.150001", "license" : [ "unknown" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "Net-OpenSSH-Compat", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Net::OpenSSH" : "0.53_03" } } }, "release_status" : "stable", "version" : "0.07" }