1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//! High-level library for asynchronous SSH connections.
//!
//! This crate presents a higher-level asynchronous interface for issuing commands over SSH
//! connections. It's built on top of [thrussh](https://pijul.org/thrussh/), a pure-Rust
//! implementation of the SSH protocol.
//!
//! At its core, the crate provides the notion of an SSH [`Session`], which can have zero or more
//! [`Channel`]s. Each [`Channel`] executes some user-defined command on the remote machine, and
//! implements [`AsyncRead`](https://docs.rs/tokio-io/0.1/tokio_io/trait.AsyncRead.html) and
//! (eventually) [`AsyncWrite`](https://docs.rs/tokio-io/0.1/tokio_io/trait.AsyncWrite.html) to
//! allow reading from and writing to the remote process respectively. For those unfamiliar with
//! asynchronous I/O, you'll likely want to start with the [functions in
//! `tokio-io::io`](https://docs.rs/tokio-io/0.1/tokio_io/io/index.html#functions).
//!
//! The code is currently in a pre-alpha stage, with only a subset of the core features
//! implemented, and with fairly gaping API holes like `thrussh` types being exposed all over
//! the place or error types not being nice to work with.
//!
//! # Examples
//!
//! ```no_run
//! # extern crate tokio_core;
//! # extern crate async_ssh;
//! # extern crate thrussh_keys;
//! # extern crate thrussh;
//! # extern crate tokio_io;
//! # extern crate futures;
//! # use async_ssh::*;
//! # use futures::Future;
//! # fn main() {
//! let key = thrussh_keys::load_secret_key("/path/to/key", None).unwrap();
//!
//! let mut core = tokio_core::reactor::Core::new().unwrap();
//! let handle = core.handle();
//! let ls_out = tokio_core::net::TcpStream::connect(&"127.0.0.1:22".parse().unwrap(), &handle)
//!     .map_err(thrussh::Error::IO)
//!     .map_err(thrussh::HandlerError::Error)
//!     .and_then(|c| Session::new(c, &handle))
//!     .and_then(|session| session.authenticate_key("username", key))
//!     .and_then(|mut session| session.open_exec("ls -la"));
//!
//! let channel = core.run(ls_out).unwrap();
//! let (channel, data) = core.run(tokio_io::io::read_to_end(channel, Vec::new())).unwrap();
//! let status = core.run(channel.exit_status()).unwrap();
//!
//! println!("{}", ::std::str::from_utf8(&data[..]).unwrap());
//! println!("exited with: {}", status);
//! # }
//! ```
#![deny(missing_docs)]

extern crate futures;
extern crate thrussh;
extern crate thrussh_keys;
extern crate tokio_core;
extern crate tokio_io;

use tokio_io::{AsyncRead, AsyncWrite};
use std::rc::Rc;
use std::cell::RefCell;
use futures::Future;

mod session;
mod channel;

pub use channel::{Channel, ChannelOpenFuture, ExitStatusFuture};
pub use session::{NewSession, Session};

struct Connection<S: AsyncRead + AsyncWrite> {
    c: thrussh::client::Connection<S, session::state::Ref>,
    task: Option<futures::task::Task>,
}

struct SharableConnection<S: AsyncRead + AsyncWrite>(Rc<RefCell<Connection<S>>>);
impl<S> Clone for SharableConnection<S>
where
    S: AsyncRead + AsyncWrite,
{
    fn clone(&self) -> Self {
        SharableConnection(self.0.clone())
    }
}

impl<S: AsyncRead + AsyncWrite + thrussh::Tcp> Future for SharableConnection<S> {
    type Item = ();
    type Error = ();

    fn poll(&mut self) -> futures::Poll<Self::Item, Self::Error> {
        // NOTE: SessionStateRef as Handler cannot use Rc<RefMut<C<S>>>
        let mut c = self.0.borrow_mut();
        c.task = Some(futures::task::current());
        match c.c.poll() {
            Ok(r) => Ok(r),
            Err(e) => {
                let state = self.0.borrow();
                let state = state.c.handler();
                let mut state = state.borrow_mut();
                state.errored_with = Some(e);
                Err(())
            }
        }
    }
}