well, tests pass now

This commit is contained in:
2024-10-14 17:33:24 -07:00
parent 795c528754
commit a813b65535
81 changed files with 15233 additions and 6 deletions

164
src/client.rs Normal file
View 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(())
}