Net-SSH-Any

 view release on metacpan or  search on metacpan

lib/Net/SSH/Any/Backend/Net_SSH2.pm  view on Meta::CPAN

do {
    local ($@, $SIG{__DIE__});
    for my $c (keys %C) {
        if (defined (my $v = eval "Net::SSH2::LIBSSH2_$c()")) {
            $C{$c} = $v;
        }
    }
};

sub __copy_error {
    my ($any, $code) = @_;
    my $ssh2 = $any->{be_ssh2}
        or die "internal error: __copy_error called, but there is no ssh2 object";
    my ($error, $error_name, $error_msg) = $ssh2->error;
    $error or die "internal error: __copy_error called, but there is no error";
    $error_msg ||= $error_name;

    if ($error == $C{ERROR_EAGAIN}) {
        # most libssh2 functions can't recover from an EAGAIN error
        # leaving the connection in a broken state. We catch that case
        # marking the connection as lost. Note that the functions that
        # can recover, return LIBSSH2_ERROR_EAGAIN but leave the
        # internal error field as 0
        $any->_set_error(SSHA_CONNECTION_ERROR,
                         "connection lost: internal libssh2 error, unhandled EAGAIN, $error_msg");
    }
    else {
        $any->_set_error($code || SSHA_CHANNEL_ERROR, $error_msg);
    }
    return;
}

sub __check_host_key {
    my $any = shift;
    my $ssh2 = $any->{be_ssh2} or croak "internal error: be_ssh2 is not set";
    my $be_opts = $any->{be_opts};

    my $hostkey_method = $ssh2->can('remote_hostkey');
    unless ($hostkey_method) {
        carp "The version of Net::SSH2 installed ($Net::SSH2::VERSION) doesn't support " .
            "checking the host key against a known_hosts file. This script is exposed to ".
                "man-in-the-middle atacks!!!";
        return 1;
    }

    my ($key, $type) = $hostkey_method->($ssh2);

    my $known_hosts_path = $be_opts->{known_hosts_path};
    unless (defined $known_hosts_path) {
        my $config_dir;
        if ($windows) {
            $any->_load_module('Win32') or return;
            my $appdata = Win32::GetFolderPath(Win32::CSIDL_APPDATA());
            unless (defined $appdata) {
                $any->_set_error(SSHA_CONNECTION_ERROR, "unable to determine directory for user application data");
                return;
            }
            $config_dir = File::Spec->join($appdata, 'libnet-ssh-any-perl');
        }
        else {
            my $home = (getpwuid $>)[7];
            $home = $ENV{HOME} unless defined $home;
            unless (defined $home) {
                $any->_set_error(SSHA_CONNECTION_ERROR, "unable to determine user home directory");
                return;
            }
            $config_dir = File::Spec->join($home, '.ssh');
        }
        unless (-d $config_dir or mkdir $config_dir, 0700) {
            $any->_set_error(SSHA_CONNECTION_ERROR, "unable to create directory '$config_dir': $^E");
            return;
        }
        $known_hosts_path = File::Spec->join($config_dir, 'known_hosts');
    }

    $debug and $debug & 1024 and _debug "reading known host keys from '$known_hosts_path'";

    local ($@, $SIG{__DIE__});

    my $kh = $ssh2->known_hosts;
    my $ok = eval { $kh->readfile($known_hosts_path) };
    unless (defined $ok) {
        $debug and $debug & 1024 and _debug "unable to read known hosts file: " . $ssh2->error;
        if ($ssh2->error == $C{ERROR_FILE}) {
            if (-f $known_hosts_path) {
                $any->_set_error(SSHA_CONNECTION_ERROR, "unable to read known_hosts file at '$known_hosts_path'");
                return;
            }
            # a non-existent file is not an error, continue...
        }
        else {
            $any->_set_error(SSHA_CONNECTION_ERROR,
                             "Unable to parse known_hosts file at '$known_hosts_path': ". ($ssh2->error)[2]);
            return;
        }
    }

    if ($debug and $debug & 1024) {
        _debug "remote key is of type $type";
        _debug_hexdump("key", $key);
    }

    my $key_type = ( $C{KNOWNHOST_TYPE_PLAIN} |
                     $C{KNOWNHOST_KEYENC_RAW} |
                     (($type + 1) << $C{KNOWNHOST_KEY_SHIFT}) );

    my $check = $kh->check($be_opts->{host}, $be_opts->{port}, $key, $key_type);

    if ($check == $C{KNOWNHOST_CHECK_MATCH}) {
        $debug and $debug & 1024 and _debug("host key matched");
        return 1;
    }
    elsif ($check == $C{KNOWNHOST_CHECK_MISMATCH}) {
        $debug and $debug & 1024 and _debug("host key found but did not match");
        $any->_set_error(SSHA_CONNECTION_ERROR, "The host key for '$be_opts->{host}' has changed");
        return;
    }
    elsif ($check == $C{KNOWNHOST_CHECK_NOTFOUND}) {
        $debug and $debug & 1024 and _debug("host key not found in known_hosts");
        if ($be_opts->{strict_host_key_checking}) {
            $any->_set_error(SSHA_CONNECTION_ERROR, "the authenticity of host '$be_opts->{host}' can't be established");

lib/Net/SSH/Any/Backend/Net_SSH2.pm  view on Meta::CPAN

}

sub _validate_backend_opts {
    my ($any, %be_opts) = @_;
    my $mod_ver = do { no warnings; 0 + $Net::SSH2::VERSION };
    if ($mod_ver < 0.59) {
        $any->_set_error(SSHA_CONNECTION_ERROR,
                         "The version of Net::SSH2 available ($Net::SSH2::VERSION) is too old. ".
                         "0.59 or later required");
        return;
    }

    my @lib_ver = Net::SSH2::version();
    $debug and $debug & 1024 and _debug "libssh2 version $lib_ver[2]";
    if ($lib_ver[1] < 0x010500) {
        $any->_set_error(SSHA_CONNECTION_ERROR,
                         "Net::SSH2 was compiled against an old unsupported version of libssh2 ($lib_ver[2])");
        return;
    }

    my $ssh2 = $any->{be_ssh2} = Net::SSH2->new;
    unless ($ssh2) {
        $any->_set_error(SSHA_CONNECTION_ERROR, "Unable to create Net::SSH2 object");
        return;
    }
    $debug and $debug & 2048 and $ssh2->trace(~0); #~$C{TRACE_TRANS});

    $ssh2->timeout(1000 * ($be_opts{timeout} // $be_opts{io_timeout}));

    if ($be_opts{compress}) {
        if (defined(my $flag_method = $ssh2->can('flag'))) {
            $debug and $debug & 1024 and _debug "enabling compression";
            $flag_method->($ssh2, $C{FLAG_COMPRESS}, 1);
        }
    }

    $any->{be_opts} = \%be_opts;
    1;
}

sub _connect {
    my $any = shift;
    my $ssh2 = $any->{be_ssh2} or return;
    my $be_opts = $any->{be_opts};
    my $socket = IO::Socket::INET->new(PeerHost => $be_opts->{host},
                                       PeerPort => ($be_opts->{port} || 22),
                                       ($be_opts->{timeout} ? (Timeout => $be_opts->{timeout}) : ()));
    if ($socket) {
        $socket->sockopt(SO_LINGER, pack(SS => 0, 0));
        $socket->sockopt(SO_KEEPALIVE, 1);
    }
    unless ($socket and $ssh2->connect($socket)) {
        return $any->_set_error(SSHA_CONNECTION_ERROR, "Unable to connect to remote host");
    }
    $debug and $debug & 1024 and _debug 'COMP_SC: ' . $ssh2->method('COMP_SC') . ' COMP_CS: ' .$ssh2->method('COMP_CS');

    __check_host_key($any) or return;

    my %aa;
    $aa{username} = _first_defined($be_opts->{user},
                                   eval { (getpwuid $<)[0] },
                                   eval { getlogin() });
    $aa{password} = $be_opts->{password} if defined $be_opts->{password};
    $aa{passphrase} = $be_opts->{passphrase} if defined $be_opts->{passphrase};
    if (defined (my $private = $be_opts->{key_path})) {
        unless (-f $private) {
            $any->_set_error(SSHA_CONNECTION_ERROR, "Private key '$private' does not exist on file system");
            return;
        }
        my $public = $private.".pub";
        unless (-f $public) {
            $any->_set_error(SSHA_CONNECTION_ERROR, "Public key '$public' does not exist on file system");
            return;
        }
        $aa{privatekey} = $private;
        $aa{publickey}  = $public;
    }
    # TODO: use default user keys on ~/.ssh/id_dsa and ~/.ssh/id_rsa

    $debug and $debug & 1024 and _debug_dump "Net::SSH2 authentication args", \%aa;

    $ssh2->auth(%aa, interact => !$be_opts->{batch_mode});
    unless ($ssh2->auth_ok) {
        $any->_set_error(SSHA_CONNECTION_ERROR, "Authentication failed");
        return;
    }

    $any->{be_fileno} = fileno $ssh2->sock;
    $debug and $debug & 1024 and _debug("SSH socket file descriptor: $any->{be_fileno}");
    $any->{be_select_bm} = '';
    vec ($any->{be_select_bm}, $any->{be_fileno}, 1) = 1;
    1;
}

# those are the operations that can be safely carried on in a
# non-blocking fashion:
my %non_blocking_method = (read => 1);

sub _channel_do {
    my $any = shift;
    my $channel = shift;
    my $blocking = shift;
    my $method = shift;
    if ($any->error == SSHA_CONNECTION_ERROR) {
        $debug and $debug & 1024 and _debug "skipping $channel->$method call because connection is broken";
        return
    }
    my $ssh2 = $any->{be_ssh2};
    $blocking ||= !$non_blocking_method{$method};
    $ssh2->blocking($blocking);

    $debug and $debug & 1024 and _debug "calling $channel->$method with ", scalar(@_), " args";

    my $time_limit = time + $any->{io_timeout};
    while (1) {
        my $rc = $channel->$method(@_);
        $debug and $debug & 1024 and _debug "$channel->$method rc: ", $rc;
        return $rc if defined $rc;
        my ($error, $error_name, $error_msg) = $ssh2->error;
        # We assume Net::SSH2 masked a LIBSSH2_ERROR_EAGAIN if
        # both $rc and $ssh->error are unset



( run in 0.538 second using v1.01-cache-2.11-cpan-5735350b133 )