#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate log;
extern crate thrussh_libsodium as sodium;
#[macro_use]
extern crate thiserror;
pub use cryptovec::CryptoVec;
mod auth;
mod cipher;
mod compression;
mod kex;
mod key;
mod msg;
mod negotiation;
mod ssh_read;
mod sshbuffer;
pub use negotiation::{Named, Preferred};
mod pty;
pub use pty::Pty;
macro_rules! push_packet {
( $buffer:expr, $x:expr ) => {{
use byteorder::{BigEndian, ByteOrder};
let i0 = $buffer.len();
$buffer.extend(b"\0\0\0\0");
let x = $x;
let i1 = $buffer.len();
use std::ops::DerefMut;
let buf = $buffer.deref_mut();
BigEndian::write_u32(&mut buf[i0..], (i1 - i0 - 4) as u32);
x
}};
}
type Sha256Hash = generic_array::GenericArray<u8, <sha2::Sha256 as digest::FixedOutputDirty>::OutputSize>;
mod session;
pub mod server;
pub mod client;
#[derive(Debug, Error)]
pub enum Error {
#[error("Could not read key")]
CouldNotReadKey,
#[error("Key exchange init failed")]
KexInit,
#[error("No common key exchange algorithm")]
NoCommonKexAlgo,
#[error("No common key algorithm")]
NoCommonKeyAlgo,
#[error("No common key cipher")]
NoCommonCipher,
#[error("No common compression algorithm")]
NoCommonCompression,
#[error("invalid SSH version string")]
Version,
#[error("Key exchange failed")]
Kex,
#[error("Wrong packet authentication code")]
PacketAuth,
#[error("Inconsistent state of the protocol")]
Inconsistent,
#[error("Not yet authenticated")]
NotAuthenticated,
#[error("Index out of bounds")]
IndexOutOfBounds,
#[error("Unknown server key")]
UnknownKey,
#[error("Wrong server signature")]
WrongServerSig,
#[error("Channel not open")]
WrongChannel,
#[error("Disconnected")]
Disconnect,
#[error("No home directory when saving host key")]
NoHomeDir,
#[error("Key changed, line {}", line)]
KeyChanged { line: usize },
#[error("Connection closed by the remote side")]
HUP,
#[error("Connection timeout")]
ConnectionTimeout,
#[error("No authentication method")]
NoAuthMethod,
#[error("Channel send error")]
SendError,
#[error("Pending buffer limit reached")]
Pending,
#[error(transparent)]
Keys(#[from] thrussh_keys::Error),
#[error(transparent)]
IO(#[from] std::io::Error),
#[error(transparent)]
Utf8(#[from] std::str::Utf8Error),
#[error(transparent)]
Compress(#[from] flate2::CompressError),
#[error(transparent)]
Decompress(#[from] flate2::DecompressError),
#[error(transparent)]
Join(#[from] tokio::task::JoinError),
#[error(transparent)]
#[cfg(feature = "openssl")]
Openssl(#[from] openssl::error::ErrorStack),
#[error(transparent)]
Elapsed(#[from] tokio::time::error::Elapsed),
}
#[derive(Debug, Error)]
#[error("Could not reach the event loop")]
pub struct SendError {}
pub trait FromFinished<T>: futures::Future<Output = Result<T, Error>> {
fn finished(t: T) -> Self;
}
impl<T> FromFinished<T> for futures::future::Ready<Result<T, Error>> {
fn finished(t: T) -> Self {
futures::future::ready(Ok(t))
}
}
impl<T: 'static> FromFinished<T> for Box<dyn futures::Future<Output = Result<T, Error>> + Unpin> {
fn finished(t: T) -> Self {
Box::new(futures::future::ready(Ok(t)))
}
}
#[derive(Debug, Clone)]
pub struct Limits {
pub rekey_write_limit: usize,
pub rekey_read_limit: usize,
pub rekey_time_limit: std::time::Duration,
}
impl Limits {
pub fn new(write_limit: usize, read_limit: usize, time_limit: std::time::Duration) -> Limits {
assert!(write_limit <= 1 << 30 && read_limit <= 1 << 30);
Limits {
rekey_write_limit: write_limit,
rekey_read_limit: read_limit,
rekey_time_limit: time_limit,
}
}
}
impl Default for Limits {
fn default() -> Self {
Limits {
rekey_write_limit: 1 << 30, rekey_read_limit: 1 << 30, rekey_time_limit: std::time::Duration::from_secs(3600),
}
}
}
pub use auth::{AgentAuthError, MethodSet, Signer};
#[allow(missing_docs)] #[derive(Debug)]
pub enum Disconnect {
HostNotAllowedToConnect = 1,
ProtocolError = 2,
KeyExchangeFailed = 3,
#[doc(hidden)]
Reserved = 4,
MACError = 5,
CompressionError = 6,
ServiceNotAvailable = 7,
ProtocolVersionNotSupported = 8,
HostKeyNotVerifiable = 9,
ConnectionLost = 10,
ByApplication = 11,
TooManyConnections = 12,
AuthCancelledByUser = 13,
NoMoreAuthMethodsAvailable = 14,
IllegalUserName = 15,
}
#[allow(missing_docs)]
#[derive(Debug, Clone)]
pub enum Sig {
ABRT,
ALRM,
FPE,
HUP,
ILL,
INT,
KILL,
PIPE,
QUIT,
SEGV,
TERM,
USR1,
Custom(String),
}
impl Sig {
fn name(&self) -> &str {
match *self {
Sig::ABRT => "ABRT",
Sig::ALRM => "ALRM",
Sig::FPE => "FPE",
Sig::HUP => "HUP",
Sig::ILL => "ILL",
Sig::INT => "INT",
Sig::KILL => "KILL",
Sig::PIPE => "PIPE",
Sig::QUIT => "QUIT",
Sig::SEGV => "SEGV",
Sig::TERM => "TERM",
Sig::USR1 => "USR1",
Sig::Custom(ref c) => c,
}
}
fn from_name(name: &[u8]) -> Result<Sig, Error> {
match name {
b"ABRT" => Ok(Sig::ABRT),
b"ALRM" => Ok(Sig::ALRM),
b"FPE" => Ok(Sig::FPE),
b"HUP" => Ok(Sig::HUP),
b"ILL" => Ok(Sig::ILL),
b"INT" => Ok(Sig::INT),
b"KILL" => Ok(Sig::KILL),
b"PIPE" => Ok(Sig::PIPE),
b"QUIT" => Ok(Sig::QUIT),
b"SEGV" => Ok(Sig::SEGV),
b"TERM" => Ok(Sig::TERM),
b"USR1" => Ok(Sig::USR1),
x => Ok(Sig::Custom(std::str::from_utf8(x)?.to_string())),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[allow(missing_docs)]
pub enum ChannelOpenFailure {
AdministrativelyProhibited = 1,
ConnectFailed = 2,
UnknownChannelType = 3,
ResourceShortage = 4,
}
impl ChannelOpenFailure {
fn from_u32(x: u32) -> Option<ChannelOpenFailure> {
match x {
1 => Some(ChannelOpenFailure::AdministrativelyProhibited),
2 => Some(ChannelOpenFailure::ConnectFailed),
3 => Some(ChannelOpenFailure::UnknownChannelType),
4 => Some(ChannelOpenFailure::ResourceShortage),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ChannelId(u32);
#[derive(Debug)]
pub(crate) struct Channel {
recipient_channel: u32,
sender_channel: ChannelId,
recipient_window_size: u32,
sender_window_size: u32,
recipient_maximum_packet_size: u32,
sender_maximum_packet_size: u32,
pub confirmed: bool,
wants_reply: bool,
pending_data: std::collections::VecDeque<(CryptoVec, Option<u32>, usize)>,
}
#[derive(Debug)]
pub enum ChannelMsg {
Data {
data: CryptoVec,
},
ExtendedData {
data: CryptoVec,
ext: u32,
},
Eof,
Close,
XonXoff {
client_can_do: bool,
},
ExitStatus {
exit_status: u32,
},
ExitSignal {
signal_name: Sig,
core_dumped: bool,
error_message: String,
lang_tag: String,
},
WindowAdjusted {
new_size: u32,
},
Success,
}
#[cfg(test)]
mod test_compress {
use super::server::{Auth, Server as _, Session};
use super::*;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[tokio::test]
async fn compress_local_test() {
let _ = env_logger::try_init();
let client_key = thrussh_keys::key::KeyPair::generate_ed25519().unwrap();
let client_pubkey = Arc::new(client_key.clone_public_key());
let mut config = server::Config::default();
config.preferred = Preferred::COMPRESSED;
config.connection_timeout = None; config.auth_rejection_time = std::time::Duration::from_secs(3);
config
.keys
.push(thrussh_keys::key::KeyPair::generate_ed25519().unwrap());
let config = Arc::new(config);
let mut sh = Server {
client_pubkey,
clients: Arc::new(Mutex::new(HashMap::new())),
id: 0,
};
let socket = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = socket.local_addr().unwrap();
tokio::spawn(async move {
let (socket, _) = socket.accept().await.unwrap();
let server = sh.new(socket.peer_addr().ok());
server::run_stream(config, socket, server).await.unwrap();
});
let mut config = client::Config::default();
config.preferred = Preferred::COMPRESSED;
let config = Arc::new(config);
dbg!(&addr);
let mut session = client::connect(config, addr, Client {}).await.unwrap();
let authenticated = session
.authenticate_publickey(std::env::var("USER").unwrap(), Arc::new(client_key))
.await
.unwrap();
assert!(authenticated);
let mut channel = session.channel_open_session().await.unwrap();
let data = &b"Hello, world!"[..];
channel.data(data).await.unwrap();
let msg = channel.wait().await.unwrap();
match msg {
ChannelMsg::Data { data: msg_data } => {
assert_eq!(*data, *msg_data)
}
msg => panic!("Unexpected message {:?}", msg),
}
}
#[derive(Clone)]
struct Server {
client_pubkey: Arc<thrussh_keys::key::PublicKey>,
clients: Arc<Mutex<HashMap<(usize, ChannelId), super::server::Handle>>>,
id: usize,
}
impl server::Server for Server {
type Handler = Self;
fn new(&mut self, _: Option<std::net::SocketAddr>) -> Self {
let s = self.clone();
self.id += 1;
s
}
}
impl server::Handler for Server {
type Error = super::Error;
type FutureAuth = futures::future::Ready<Result<(Self, server::Auth), Self::Error>>;
type FutureUnit = futures::future::Ready<Result<(Self, Session), Self::Error>>;
type FutureBool = futures::future::Ready<Result<(Self, Session, bool), Self::Error>>;
fn finished_auth(self, auth: Auth) -> Self::FutureAuth {
futures::future::ready(Ok((self, auth)))
}
fn finished_bool(self, b: bool, s: Session) -> Self::FutureBool {
futures::future::ready(Ok((self, s, b)))
}
fn finished(self, s: Session) -> Self::FutureUnit {
futures::future::ready(Ok((self, s)))
}
fn channel_open_session(self, channel: ChannelId, session: Session) -> Self::FutureUnit {
{
let mut clients = self.clients.lock().unwrap();
clients.insert((self.id, channel), session.handle());
}
self.finished(session)
}
fn auth_publickey(self, _: &str, _: &thrussh_keys::key::PublicKey) -> Self::FutureAuth {
debug!("auth_publickey");
self.finished_auth(server::Auth::Accept)
}
fn data(self, channel: ChannelId, data: &[u8], mut session: Session) -> Self::FutureUnit {
debug!("server data = {:?}", std::str::from_utf8(data));
session.data(channel, CryptoVec::from_slice(data));
self.finished(session)
}
}
struct Client {}
impl client::Handler for Client {
type Error = super::Error;
type FutureUnit = futures::future::Ready<Result<(Self, client::Session), Self::Error>>;
type FutureBool = futures::future::Ready<Result<(Self, bool), Self::Error>>;
fn finished_bool(self, b: bool) -> Self::FutureBool {
futures::future::ready(Ok((self, b)))
}
fn finished(self, session: client::Session) -> Self::FutureUnit {
futures::future::ready(Ok((self, session)))
}
fn check_server_key(
self,
server_public_key: &thrussh_keys::key::PublicKey,
) -> Self::FutureBool {
println!("check_server_key: {:?}", server_public_key);
self.finished_bool(true)
}
}
}