well, tests pass now
This commit is contained in:
164
src/client.rs
Normal file
164
src/client.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
use crate::config::{ClientCommand, ClientConfiguration};
|
||||
use crate::crypto::{
|
||||
CompressionAlgorithm, EncryptionAlgorithm, HostKeyAlgorithm, KeyExchangeAlgorithm, MacAlgorithm,
|
||||
};
|
||||
use crate::network::host::Host;
|
||||
use crate::ssh;
|
||||
use crate::OperationalError;
|
||||
use error_stack::{report, Report, ResultExt};
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
pub async fn hush(base_config: ClientConfiguration) -> error_stack::Result<(), OperationalError> {
|
||||
match base_config.command() {
|
||||
ClientCommand::ListMacAlgorithms => print_options(MacAlgorithm::allowed()),
|
||||
|
||||
ClientCommand::ListHostKeyAlgorithms => print_options(HostKeyAlgorithm::allowed()),
|
||||
|
||||
ClientCommand::ListEncryptionAlgorithms => print_options(EncryptionAlgorithm::allowed()),
|
||||
|
||||
ClientCommand::ListKeyExchangeAlgorithms => print_options(KeyExchangeAlgorithm::allowed()),
|
||||
|
||||
ClientCommand::ListCompressionAlgorithms => print_options(CompressionAlgorithm::allowed()),
|
||||
|
||||
ClientCommand::Connect { target } => connect(&base_config, target).await,
|
||||
}
|
||||
}
|
||||
|
||||
struct Target {
|
||||
username: String,
|
||||
host: Host,
|
||||
port: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TargetParseError {
|
||||
#[error("Invalid port number '{port_string}': {error}")]
|
||||
InvalidPort {
|
||||
port_string: String,
|
||||
error: std::num::ParseIntError,
|
||||
},
|
||||
#[error("Invalid hostname '{hostname}': {error}")]
|
||||
InvalidHostname {
|
||||
hostname: String,
|
||||
error: crate::network::host::HostParseError,
|
||||
},
|
||||
}
|
||||
|
||||
impl FromStr for Target {
|
||||
type Err = Report<TargetParseError>;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (username, host_and_port) = match s.split_once('@') {
|
||||
None => (whoami::username(), s),
|
||||
Some((username, rest)) => (username.to_string(), rest),
|
||||
};
|
||||
|
||||
let (port, host) = match host_and_port.rsplit_once(':') {
|
||||
None => (22, host_and_port),
|
||||
Some((host, portstr)) => match u16::from_str(portstr) {
|
||||
Ok(port) => (port, host),
|
||||
Err(error) => {
|
||||
return Err(report!(TargetParseError::InvalidPort {
|
||||
port_string: portstr.to_string(),
|
||||
error,
|
||||
})
|
||||
.attach_printable(format!("from target string {:?}", s)))
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let host = Host::from_str(host)
|
||||
.map_err(|error| {
|
||||
report!(TargetParseError::InvalidHostname {
|
||||
hostname: host.to_string(),
|
||||
error,
|
||||
})
|
||||
})
|
||||
.attach_printable(format!("from target string {:?}", s))?;
|
||||
|
||||
Ok(Target {
|
||||
username,
|
||||
host,
|
||||
port,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect(
|
||||
base_config: &ClientConfiguration,
|
||||
target: &str,
|
||||
) -> error_stack::Result<(), OperationalError> {
|
||||
let resolver = base_config.resolver();
|
||||
let target = Target::from_str(target)
|
||||
.change_context(OperationalError::UnableToParseHostAddress)
|
||||
.attach_printable_lazy(|| format!("target address '{}'", target))?;
|
||||
let mut stream = target
|
||||
.host
|
||||
.connect(resolver, 22)
|
||||
.await
|
||||
.change_context(OperationalError::Connection)?;
|
||||
|
||||
let _preamble = ssh::Preamble::read(&mut stream)
|
||||
.await
|
||||
.change_context(OperationalError::Connection)?;
|
||||
|
||||
// if !commentary.is_empty() {
|
||||
// tracing::debug!(?commentary, "Server sent commentary.");
|
||||
// }
|
||||
// if !pre_message.is_empty() {
|
||||
// for line in pre_message.lines() {
|
||||
// tracing::debug!(?line, "Server sent prefix line.");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let my_info = format!(
|
||||
// "SSH-2.0-{}_{}\r\n",
|
||||
// env!("CARGO_PKG_NAME"),
|
||||
// env!("CARGO_PKG_VERSION")
|
||||
// );
|
||||
// connection
|
||||
// .write_all(my_info.as_bytes())
|
||||
// .await
|
||||
// .map_err(OperationalError::WriteBanner)?;
|
||||
//
|
||||
// assert_eq!(4096, read_buffer.len());
|
||||
// read_buffer.fill(0);
|
||||
//
|
||||
// let mut stream = SshChannel::new(connection);
|
||||
// let mut rng = rand::thread_rng();
|
||||
//
|
||||
// let packet = stream
|
||||
// .read()
|
||||
// .await
|
||||
// .map_err(|error| OperationalError::Read {
|
||||
// context: "Initial key exchange read",
|
||||
// error,
|
||||
// })?
|
||||
// .expect("has initial packet");
|
||||
// let message_type = SshMessageID::from(packet.buffer[0]);
|
||||
// tracing::debug!(size=packet.buffer.len(), %message_type, "Initial buffer received.");
|
||||
// let keyx = SshKeyExchange::try_from(packet)?;
|
||||
// println!("{:?}", keyx);
|
||||
//
|
||||
// let client_config = config.settings_for("");
|
||||
// let my_kex = SshKeyExchange::new(&mut rng, client_config)?;
|
||||
// stream
|
||||
// .write(my_kex.into())
|
||||
// .await
|
||||
// .map_err(|error| OperationalError::Write {
|
||||
// context: "initial negotiation message",
|
||||
// error,
|
||||
// })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_options<T: Display>(items: &[T]) -> error_stack::Result<(), OperationalError> {
|
||||
for item in items.iter() {
|
||||
println!("{}", item);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user