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; use tracing::Instrument; 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; fn from_str(s: &str) -> Result { 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 stream_span = tracing::debug_span!( "client connection", server = %stream.peer_addr().map(|x| x.to_string()).unwrap_or_else(|_| "".to_string()), client = %stream.local_addr().map(|x| x.to_string()).unwrap_or_else(|_| "".to_string()), ); let their_preamble = ssh::Preamble::read(&mut stream) .instrument(stream_span.clone()) .await .change_context(OperationalError::Connection)?; let our_preamble = ssh::Preamble::default() .write(&mut stream) .instrument(stream_span) .await .change_context(OperationalError::Connection)?; if !their_preamble.preamble.is_empty() { for line in their_preamble.preamble.lines() { tracing::info!("server: {}", line); } } tracing::info!( software = ?their_preamble.software_name, version = ?their_preamble.software_version, commentary = ?their_preamble.commentary, "received server preamble" ); let mut stream = ssh::SshChannel::new(stream); let their_initial = stream .read() .await .attach_printable_lazy(stream_error_info) .change_context(OperationalError::KeyExchange)? .ok_or_else(); // 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(items: &[T]) -> error_stack::Result<(), OperationalError> { for item in items.iter() { println!("{}", item); } Ok(()) }