diff --git a/.rustfmt.toml b/.rustfmt.toml index 54026e1c857138129f19e5f4bb2a4a4a54600da0..f5ac0a7aa84e62645621e109ef41d98573828635 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,7 +1,8 @@ max_width = 85 use_small_heuristics = "Max" -# fn_args_layout = "Compressed" edition = "2021" -# unstable + +## perhaps use once stabilised + # trailing_semicolon = false # overflow_delimited_expr = true diff --git a/async/examples/con1.rs b/async/examples/con1.rs index 673e673b17c2a903299f4ba75b3732cb4ae8c1f9..e903f9ca3360fc3e78f506768a8df1ddc1516ee1 100644 --- a/async/examples/con1.rs +++ b/async/examples/con1.rs @@ -145,15 +145,20 @@ async fn run(args: &Args) -> Result<()> { // Connect to a peer let mut stream = TcpStream::connect((args.host.as_str(), args.port)).await?; + let mut rxbuf = vec![0; 3000]; + let mut txbuf = vec![0; 3000]; + let mut cli = SSHClient::new(&mut rxbuf, &mut txbuf)?; + // app is a Behaviour - let mut app = door_async::CmdlineClient::new(args.username.as_ref().unwrap()); + let mut app = door_async::CmdlineClient::new( + args.username.as_ref().unwrap(), + cmd, + wantpty, + ); for i in &args.identityfile { app.add_authkey(read_key(&i).with_context(|| format!("loading key {i}"))?); } - let mut rxbuf = vec![0; 3000]; - let mut txbuf = vec![0; 3000]; - let mut cli = SSHClient::new(&mut rxbuf, &mut txbuf)?; let mut s = cli.socket(); @@ -164,6 +169,8 @@ async fn run(args: &Args) -> Result<()> { loop { cli.progress(&mut app).await.context("progress loop")?; + app.progress(&mut cli).await?; + // match ev { // Some(Event::CliAuthed) => { // let mut raw_pty_guard = None; diff --git a/async/examples/serv1.rs b/async/examples/serv1.rs index 031e822eb6a65b4a174baf53484b2f757527a254..663b1bd82a0127bcd73201cd0b0dcf2111e67bf1 100644 --- a/async/examples/serv1.rs +++ b/async/examples/serv1.rs @@ -115,9 +115,8 @@ impl DemoServer { } } -#[async_trait] -impl AsyncServBehaviour for DemoServer { - async fn hostkeys(&mut self) -> BhResult<&[SignKey]> { +impl ServBehaviour for DemoServer { + fn hostkeys(&mut self) -> BhResult<&[SignKey]> { Ok(&self.keys) } @@ -130,7 +129,7 @@ impl AsyncServBehaviour for DemoServer { false } - async fn auth_password(&mut self, user: &str, password: &str) -> bool { + fn auth_password(&mut self, user: &str, password: &str) -> bool { user == "matt" && password == "pw" } @@ -168,10 +167,7 @@ fn run_session<'a, R: Send>(args: &'a Args, scope: &'a moro::Scope<'a, '_, R>, m scope.spawn(async { loop { - let ev = serv.progress(&mut app, |ev| { - trace!("progress event {ev:?}"); - Ok(Some(())) - }).await.context("progress loop")?; + let ev = serv.progress(&mut app).await.context("progress loop")?; } #[allow(unreachable_code)] Ok::<_, anyhow::Error>(()) diff --git a/async/src/async_door.rs b/async/src/async_door.rs index 4306c51100c879841b5fa15d6c3bb1af8c59c2f2..c1231e9566b11bc1563c0ac6661c0fbd1cadb30f 100644 --- a/async/src/async_door.rs +++ b/async/src/async_door.rs @@ -131,7 +131,6 @@ pub(crate) fn poll_lock<'a>(inner: Arc<Mutex<Inner<'a>>>, cx: &mut Context<'_>, Poll::Ready(_) => None, Poll::Pending => Some(g), }; - trace!("poll_lock returned {:?}", p); p } diff --git a/async/src/cmdline_client.rs b/async/src/cmdline_client.rs index a1e40ceb5f56153049eea022ac118eaf03c50249..4564e72b2bbd9f9a6f5f8e0c3f8edd20fc4ec024 100644 --- a/async/src/cmdline_client.rs +++ b/async/src/cmdline_client.rs @@ -12,32 +12,85 @@ use std::collections::VecDeque; use async_trait::async_trait; +use crate::*; +use crate::{raw_pty, RawPtyGuard, SSHClient, ChanInOut, ChanExtIn}; + +enum CmdlineState<'a> { + PreAuth, + Authed, + // TODO split sending the channel open and the request strings + _ChanOpen, + _ChanReq, + Ready { io: ChanInOut<'a>, extin: Option<ChanExtIn<'a>> }, +} + /// Command line interface SSH client behaviour -pub struct CmdlineClient { - auth_done: bool, +pub struct CmdlineClient<'a> { + state: CmdlineState<'a>, main_ch: Option<u32>, + pty_guard: Option<RawPtyGuard>, + authkeys: VecDeque<SignKey>, username: String, + cmd: Option<String>, + want_pty: bool, } -impl CmdlineClient { - pub fn new(username: &impl AsRef<str>) -> Self { +impl<'a> CmdlineClient<'a> { + pub fn new(username: impl AsRef<str>, cmd: Option<impl AsRef<str>>, want_pty: bool) -> Self { CmdlineClient { - auth_done: false, + state: CmdlineState::PreAuth, main_ch: None, + pty_guard: None, + authkeys: VecDeque::new(), username: username.as_ref().into(), + // TODO: shorthand for this? + cmd: cmd.map(|c| c.as_ref().into()), + want_pty, + } + } + + pub async fn progress(&mut self, cli: &mut SSHClient<'a>) -> Result<()> { + match self.state { + CmdlineState::Authed => { + info!("Opening a new session channel"); + self.open_session(cli).await?; + } + | CmdlineState::PreAuth => (), + | CmdlineState::Ready {..} => (), + _ => todo!(), } + Ok(()) + } pub fn add_authkey(&mut self, k: SignKey) { self.authkeys.push_back(k) } + + async fn open_session(&mut self, cli: &mut SSHClient<'a>) -> Result<()> { + debug_assert!(matches!(self.state, CmdlineState::Authed)); + + // TODO expect + // self.pty_guard = Some(raw_pty().expect("raw pty")); + + let cmd = self.cmd.as_ref().map(|s| s.as_str()); + let (io, extin) = if self.want_pty { + let io = cli.open_session_pty(cmd).await?; + (io, None) + } else { + let (io, extin) = cli.open_session_nopty(cmd).await?; + (io, Some(extin)) + }; + self.state = CmdlineState::Ready { io, extin }; + Ok(()) + } } // #[async_trait(?Send)] #[async_trait] -impl door::CliBehaviour for CmdlineClient { +impl door::CliBehaviour for CmdlineClient<'_> { fn username(&mut self) -> BhResult<door::ResponseString> { door::ResponseString::from_str(&self.username).map_err(|_| BhError::Fail) } @@ -69,66 +122,12 @@ impl door::CliBehaviour for CmdlineClient { } fn authenticated(&mut self) { - info!("Authentication succeeded"); - self.auth_done = true; + match self.state { + CmdlineState::PreAuth => { + info!("Authentication succeeded"); + self.state = CmdlineState::Authed; + } + _ => warn!("Unexpected auth response") + } } - // } - - // impl door::BlockCliBehaviour for CmdlineClient { - // fn chan_handler<'f>(&mut self, resp: &mut RespPackets, chan_msg: ChanMsg<'f>) -> Result<()> { - // if Some(chan_msg.num) != self.main_ch { - // return Err(Error::SSHProtoError) - // } - - // match chan_msg.msg { - // ChanMsgDetails::Data(buf) => { - // let _ = std::io::stdout().write_all(buf); - // }, - // ChanMsgDetails::ExtData{..} => { - // } - // ChanMsgDetails::Req{..} => { - // } - // _ => {} - // } - // Ok(()) - // } - - // fn progress(&mut self, runner: &mut Runner) -> Result<()> { - // if self.auth_done { - // if self.main_ch.is_none() { - // let ch = runner.open_client_session(Some("cowsay it works"), false)?; - // self.main_ch = Some(ch); - // } - // } - // Ok(()) - // } - - // fn username(&mut self) -> BhResult<door::ResponseString> { - // // TODO unwrap - // let mut p = door::ResponseString::new(); - // p.push_str("matt").unwrap(); - // Ok(p) - // } - - // fn valid_hostkey(&mut self, key: &door::PubKey) -> BhResult<bool> { - // trace!("valid_hostkey for {key:?}"); - // Ok(true) - // } - - // fn auth_password(&mut self, pwbuf: &mut door::ResponseString) -> BhResult<bool> { - // let pw = rpassword::prompt_password("password: ").map_err(|e| { - // warn!("read_password failed {e:}"); - // BhError::Fail - // })?; - // if pwbuf.push_str(&pw).is_err() { - // Err(BhError::Fail) - // } else { - // Ok(true) - // } - // } - - // fn authenticated(&mut self) { - // info!("Authentication succeeded"); - // self.auth_done = true; - // } } diff --git a/async/src/lib.rs b/async/src/lib.rs index f802913b7b91609cf1aed93f89065a722ea51855..bda500e0e4d4e6db9a8e53a08f658ea78275f54b 100644 --- a/async/src/lib.rs +++ b/async/src/lib.rs @@ -11,10 +11,11 @@ pub use async_door::AsyncDoor; pub use client::SSHClient; pub use server::SSHServer; pub use cmdline_client::CmdlineClient; +pub use async_channel::{ChanInOut, ChanExtIn, ChanExtOut}; #[cfg(unix)] mod fdio; #[cfg(unix)] pub use fdio::{stdin, stdout, stderr}; -pub use pty::raw_pty; +pub use pty::{raw_pty, RawPtyGuard}; diff --git a/async/src/pty.rs b/async/src/pty.rs index 10a3fa2ca4293308b4533d95d47b976637af64a4..b1004fffa5264b31f0d7f860574d735772434092 100644 --- a/async/src/pty.rs +++ b/async/src/pty.rs @@ -32,7 +32,7 @@ pub fn current_pty() -> Result<Pty, IoError> { let mut term = heapless::String::<MAX_TERM>::new(); let t = std::env::var("TERM").unwrap_or(DEFAULT_TERM.into()); // XXX error - term.push_str(&t).expect("TERM fits buffer"); + term.push_str(&t).expect("$TERM is too long"); let wc = win_size()?; diff --git a/sshproto/src/behaviour.rs b/sshproto/src/behaviour.rs index 75f67af23292a6b54f209c5ba905f83f525cf6c9..71e580b926ba8d69bbad8f2bb18237f20b8e275f 100644 --- a/sshproto/src/behaviour.rs +++ b/sshproto/src/behaviour.rs @@ -43,7 +43,7 @@ pub enum Behaviour<'a> { } impl<'a> Behaviour<'a> { - // TODO take and store a value not a reference + // TODO: make these From<> pub fn new_client(b: &'a mut (dyn CliBehaviour + Send)) -> Self { Self::Client(b) } @@ -164,20 +164,25 @@ pub trait ServBehaviour: Sync+Send { fn hostkeys(&mut self) -> BhResult<&[sign::SignKey]>; // TODO: or return a slice of enums - fn have_auth_password(&self, user: &str) -> bool; - fn have_auth_pubkey(&self, user: &str) -> bool; + fn have_auth_password(&self, username: TextString) -> bool; + fn have_auth_pubkey(&self, username: TextString) -> bool; + #[allow(unused)] + /// Return true to allow the user to log in with no authentication + fn auth_unchallenged(&mut self, username: TextString) -> bool { + false + } #[allow(unused)] // TODO: change password - fn auth_password(&mut self, user: &str, password: &str) -> bool { + fn auth_password(&mut self, username: TextString, password: TextString) -> bool { false } /// Returns true if the pubkey can be used to log in. /// TODO: allow returning pubkey restriction options #[allow(unused)] - fn auth_pubkey(&mut self, user: &str, pubkey: &sign::SignKey) -> bool { + fn auth_pubkey(&mut self, username: TextString, pubkey: &PubKey) -> bool { false } diff --git a/sshproto/src/client.rs b/sshproto/src/client.rs index 2b17b8f6bf1506cf3c7b9677266ba526fbe455a5..8226692ef166bb42f908a29ef39e0e43fc410cb7 100644 --- a/sshproto/src/client.rs +++ b/sshproto/src/client.rs @@ -6,14 +6,14 @@ use { use snafu::prelude::*; -use crate::{*, packets::ChannelOpen}; -use packets::{Packet, PubKey, ParseContext}; -use traffic::TrafSend; -use sshnames::*; -use cliauth::CliAuth; -use sign::SignKey; +use crate::{packets::ChannelOpen, *}; use behaviour::CliBehaviour; +use cliauth::CliAuth; use heapless::String; +use packets::{Packet, ParseContext, PubKey}; +use sign::SignKey; +use sshnames::*; +use traffic::TrafSend; pub(crate) struct Client { pub auth: CliAuth, @@ -21,24 +21,27 @@ pub(crate) struct Client { impl Client { pub fn new() -> Self { - Client { - auth: CliAuth::new(), - } + Client { auth: CliAuth::new() } } // pub fn check_hostkey(hostkey: ) - pub(crate) fn auth_success(&mut self, + pub(crate) fn auth_success( + &mut self, parse_ctx: &mut ParseContext, s: &mut TrafSend, - b: &mut dyn CliBehaviour) -> Result<()> { - + b: &mut dyn CliBehaviour, + ) -> Result<()> { parse_ctx.cli_auth_type = None; s.send(packets::ServiceRequest { name: SSH_SERVICE_CONNECTION })?; self.auth.success(b) } - pub(crate) fn banner(&mut self, banner: &packets::UserauthBanner<'_>, b: &mut dyn CliBehaviour) { + pub(crate) fn banner( + &mut self, + banner: &packets::UserauthBanner<'_>, + b: &mut dyn CliBehaviour, + ) { b.show_banner(banner.message, banner.lang) } } diff --git a/sshproto/src/config.rs b/sshproto/src/config.rs index 03ff3bbc56c4475a88e70a7c096821fad03aa684..3c61839813401dac405d8925f81ba0ae1e9f6830 100644 --- a/sshproto/src/config.rs +++ b/sshproto/src/config.rs @@ -13,5 +13,5 @@ pub const MAX_EXEC: usize = 200; // Unsure if this is specified somewhere pub const MAX_TERM: usize = 32; -pub const DEFAULT_TERM: &str = "vt220"; +pub const DEFAULT_TERM: &str = "xterm"; diff --git a/sshproto/src/conn.rs b/sshproto/src/conn.rs index b11396f0b67f8fccc3ea43916506da9da9db22f8..e646783e961479c7e080740f350a37a40f8e7cde 100644 --- a/sshproto/src/conn.rs +++ b/sshproto/src/conn.rs @@ -103,8 +103,10 @@ impl<'a> Conn<'a> { Self::new(ClientServer::Client(client::Client::new())) } - pub fn new_server() -> Result<Self> { - Self::new(ClientServer::Server(server::Server::new())) + pub fn new_server( + b: &mut dyn ServBehaviour, + ) -> Result<Self> { + Self::new(ClientServer::Server(server::Server::new(b))) } fn new(cliserv: ClientServer) -> Result<Self, Error> { @@ -267,9 +269,13 @@ impl<'a> Conn<'a> { _ => return Err(Error::PacketWrong), } } - Packet::ServiceRequest(_p) => { - // TODO: this is server only - todo!("service request"); + Packet::ServiceRequest(p) => { + if let ClientServer::Server(serv) = &mut self.cliserv { + serv.service_request(&p, s)?; + } else { + debug!("Server sent a service request"); + return Err(Error::SSHProtoError) + } } Packet::ServiceAccept(p) => { // Don't need to do anything, if a request failed the server disconnects @@ -288,12 +294,15 @@ impl<'a> Conn<'a> { // TODO: SSH2_DISCONNECT_BY_APPLICATION is normal, sent by openssh client. info!("Received disconnect: {:?}", p.desc); } - Packet::UserauthRequest(_p) => { - // TODO: this is server only - todo!("userauth request"); + Packet::UserauthRequest(p) => { + if let ClientServer::Server(serv) = &mut self.cliserv { + serv.auth.request(p, s, b.server()?)?; + } else { + debug!("Server sent an auth request"); + return Err(Error::SSHProtoError) + } } Packet::UserauthFailure(p) => { - // TODO: client only if let ClientServer::Client(cli) = &mut self.cliserv { cli.auth.failure(&p, &mut self.parse_ctx, s, b.client()?).await?; } else { @@ -302,7 +311,6 @@ impl<'a> Conn<'a> { } } Packet::UserauthSuccess(_) => { - // TODO: client only if let ClientServer::Client(cli) = &mut self.cliserv { if matches!(self.state, ConnState::PreAuth) { self.state = ConnState::Authed; diff --git a/sshproto/src/error.rs b/sshproto/src/error.rs index bfbb14b2affd80ef11ae51fcd7579cd1dbd017d8..e519c3e6a419140586af16d5f2d5011af5bf43e1 100644 --- a/sshproto/src/error.rs +++ b/sshproto/src/error.rs @@ -38,10 +38,6 @@ pub enum Error { /// Signature is incorrect BadSig, - // TODO: should this just be badsig? as long as we catch UnknownMethod first - /// Signature doesn't match key type - SigMismatch, - /// Error in received SSH protocol. Will disconnect. SSHProtoError, @@ -148,7 +144,7 @@ impl Error { /// Like [`bug()`] but with a message /// The message can be used instead of a code comment, is logged at `debug` level. #[cold] - pub fn bug_args(args: Arguments) -> Error { + pub fn bug_fmt(args: Arguments) -> Error { // Easier to track the source of errors in development, // but release builds shouldn't panic. if cfg!(debug_assertions) { @@ -172,12 +168,12 @@ impl Error { #[cold] /// TODO: is the generic `T` going to make it bloat? pub fn bug_msg<T>(msg: &str) -> Result<T, Error> { - Err(Self::bug_args(format_args!("{}", msg))) + Err(Self::bug_fmt(format_args!("{}", msg))) } #[cold] pub fn bug_err_msg(msg: &str) -> Error { - Self::bug_args(format_args!("{}", msg)) + Self::bug_fmt(format_args!("{}", msg)) } } @@ -209,7 +205,7 @@ impl<T, E> TrapBug<T> for Result<T, E> { if let Ok(i) = self { Ok(i) } else { - Err(Error::bug_args(args)) + Err(Error::bug_fmt(args)) } } } @@ -228,7 +224,7 @@ impl<T> TrapBug<T> for Option<T> { if let Some(i) = self { Ok(i) } else { - Err(Error::bug_args(args)) + Err(Error::bug_fmt(args)) } } } diff --git a/sshproto/src/kex.rs b/sshproto/src/kex.rs index aceeb22764b5cfece37e45f6fa800dd53489fe3b..6e6f65d7393afcce75d65baae61acb4f178bb640 100644 --- a/sshproto/src/kex.rs +++ b/sshproto/src/kex.rs @@ -233,7 +233,7 @@ impl Kex { packets::KexInit { cookie: self.our_cookie, kex: (&conf.kexs).into(), - hostkey: (&conf.hostsig).into(), + hostsig: (&conf.hostsig).into(), cipher_c2s: (&conf.ciphers).into(), cipher_s2c: (&conf.ciphers).into(), mac_c2s: (&conf.macs).into(), @@ -311,14 +311,14 @@ impl Kex { }; let hostsig_method = p - .hostkey + .hostsig .first_match(is_client, &conf.hostsig)? .ok_or(Error::AlgoNoMatch { algo: "hostkey" })?; let hostsig = SigType::from_name(hostsig_method)?; let goodguess_hostkey = if kexguess2 { - p.hostkey.first() == hostsig_method + p.hostsig.first() == hostsig_method } else { - p.hostkey.first() == conf.hostsig.first() + p.hostsig.first() == conf.hostsig.first() }; // Switch between client/server tx/rx @@ -434,6 +434,7 @@ impl SharedSecret { } }; + // TODO: error message on signature failure. algos.hostsig.verify(&p.k_s.0, kex_out.h.as_ref(), &p.sig.0)?; debug!("Hostkey signature is valid"); if matches!(b.valid_hostkey(&p.k_s.0), Ok(true)) { @@ -448,12 +449,12 @@ impl SharedSecret { mut kex: Kex, p: &packets::KexDHInit, sess_id: &Option<SessId>, s: &mut TrafSend, b: &mut dyn ServBehaviour, ) -> Result<KexOutput> { - // let mut algos = kex.algos.take().trap()?; let mut algos = kex.algos.trap()?; + // hostkeys list must contain the signature type + let hostkey = b.hostkeys()?.iter().find(|k| k.can_sign(algos.hostsig)).trap()?; + let mut kex_hash = kex.kex_hash.take().trap()?; - // TODO - let fake_hostkey = PubKey::Ed25519(packets::Ed25519PubKey{ key: BinString(&[]) }); - kex_hash.prefinish(&fake_hostkey, p.q_c.0, algos.kex.pubkey())?; + kex_hash.prefinish(&hostkey.pubkey(), p.q_c.0, algos.kex.pubkey())?; let (kex_out, kex_pub) = match algos.kex { SharedSecret::KexCurve25519(_) => { let kex_out = KexCurve25519::secret(&mut algos, p.q_c.0, kex_hash, sess_id)?; @@ -461,18 +462,16 @@ impl SharedSecret { } }; - Self::send_kexdhreply(&kex_out, kex_pub, algos.hostsig, s, b)?; + Self::send_kexdhreply(&kex_out, kex_pub, hostkey, s)?; Ok(kex_out) } // server only - pub fn send_kexdhreply(ko: &KexOutput, kex_pub: &[u8], sig_type: SigType, s: &mut TrafSend, b: &mut dyn ServBehaviour) -> Result<()> { + pub fn send_kexdhreply(ko: &KexOutput, kex_pub: &[u8], hostkey: &SignKey, s: &mut TrafSend) -> Result<()> { let q_s = BinString(kex_pub); - // hostkeys list must contain the signature type - let key = b.hostkeys()?.iter().find(|k| k.can_sign(&sig_type)).trap()?; - let k_s = Blob(key.pubkey()); - let sig = key.sign(&ko.h.as_slice(), None)?; + let k_s = Blob(hostkey.pubkey()); + let sig = hostkey.sign(&ko.h.as_slice(), None)?; let sig: Signature = (&sig).into(); let sig = Blob(sig); s.send(packets::KexDHReply { k_s, q_s, sig }) @@ -573,7 +572,6 @@ mod tests { use crate::*; use crate::doorlog::init_test_log; - use super::SSH_NAME_CURVE25519; #[test] @@ -631,11 +629,11 @@ mod tests { Ok(self.keys.as_slice()) } - fn have_auth_pubkey(&self, userame: &str) -> bool { + fn have_auth_pubkey(&self, userame: TextString) -> bool { false } - fn have_auth_password(&self, userame: &str) -> bool { + fn have_auth_password(&self, userame: TextString) -> bool { false } diff --git a/sshproto/src/lib.rs b/sshproto/src/lib.rs index 9dac0a9a675c42f8a2e3b28a6992dd63be1d6b7b..b77e37a6eedcc4bc3544004b515b23e6b2559f6d 100644 --- a/sshproto/src/lib.rs +++ b/sshproto/src/lib.rs @@ -44,7 +44,9 @@ pub mod sshwire; pub mod config; // Application API -pub use behaviour::{Behaviour, ServBehaviour, CliBehaviour, BhError, BhResult, ResponseString}; +pub use behaviour::{Behaviour, ServBehaviour, CliBehaviour, + BhError, BhResult, ResponseString}; +pub use sshwire::{TextString}; pub use runner::Runner; pub use sign::SignKey; diff --git a/sshproto/src/packets.rs b/sshproto/src/packets.rs index 1b3292f4dcc87a7291f3711901b52ee287c91265..e4bd7c84dde37a7c5dfe92660c8d49f4ef54713a 100644 --- a/sshproto/src/packets.rs +++ b/sshproto/src/packets.rs @@ -32,7 +32,11 @@ use sshwire::{SSHEncodeEnum, SSHDecodeEnum}; pub struct KexInit<'a> { pub cookie: [u8; 16], pub kex: NameList<'a>, - pub hostkey: NameList<'a>, // is actually a signature type, not a key type + /// A list of signature algorithms + /// + /// RFC4253 refers to this as the host key algorithms, but actually they + /// are signature algorithms. + pub hostsig: NameList<'a>, pub cipher_c2s: NameList<'a>, pub cipher_s2c: NameList<'a>, pub mac_c2s: NameList<'a>, diff --git a/sshproto/src/runner.rs b/sshproto/src/runner.rs index a1954d2eec92ca69a5ce509b174bc49e88562d41..3c2675250cdeab6d9f920008c71bf3d4ebe046f2 100644 --- a/sshproto/src/runner.rs +++ b/sshproto/src/runner.rs @@ -64,8 +64,10 @@ impl<'a> Runner<'a> { pub fn new_server( inbuf: &'a mut [u8], outbuf: &'a mut [u8], + // TODO: can probably get rid of b argument here (and in callees) + b: &mut dyn ServBehaviour, ) -> Result<Runner<'a>, Error> { - let conn = Conn::new_server()?; + let conn = Conn::new_server(b)?; let runner = Runner { conn, traf_in: TrafIn::new(inbuf), @@ -110,7 +112,6 @@ impl<'a> Runner<'a> { if let Some(d) = d.0 { // incoming channel data, we haven't finished with payload trace!("handle_payload chan input"); - self.traf_in.handled_payload()?; self.traf_in.set_channel_input(d)?; } else { // other packets have been completed @@ -118,6 +119,7 @@ impl<'a> Runner<'a> { self.traf_in.done_payload()?; } } + self.conn.progress(&mut s, behaviour).await?; self.wake(); diff --git a/sshproto/src/servauth.rs b/sshproto/src/servauth.rs index 364658dbe636c4d746a75bb4b8957bba27cedd70..325660c3165fd70100cf5f92445e353e8c629c6d 100644 --- a/sshproto/src/servauth.rs +++ b/sshproto/src/servauth.rs @@ -1,11 +1,132 @@ +#[allow(unused_imports)] +use { + crate::error::{Error, Result, TrapBug}, + log::{debug, error, info, log, trace, warn}, +}; + +use heapless::Vec; + +use crate::sshnames::*; use crate::*; +use packets::{AuthMethod, Userauth60, UserauthPkOk}; +use traffic::TrafSend; pub(crate) struct ServAuth { + pub authed: bool, } +// for auth_inner() +enum AuthResp { + // success + Success, + // failed, send a response + Failure, + // failure, a response has already been send + FailNoReply, +} impl ServAuth { - pub fn new() -> Self { - Self {} + pub fn new(b: &mut dyn ServBehaviour) -> Self { + Self { authed: false } + } + + /// Returns `true` if auth succeeds + pub fn request( + &self, + p: packets::UserauthRequest, + s: &mut TrafSend, + b: &mut dyn ServBehaviour, + ) -> Result<bool> { + let r = self.auth_inner(p, s, b)?; + + match r { + AuthResp::Success => { + s.send(packets::UserauthSuccess {})?; + Ok(true) + } + AuthResp::Failure => { + let mut n: Vec<&str, NUM_AUTHMETHOD> = Vec::new(); + let methods = self.avail_methods(&mut n); + let methods = (&methods).into(); + + s.send(packets::UserauthFailure { methods, partial: false })?; + Ok(false) + } + AuthResp::FailNoReply => Ok(false), + } + } + + pub fn auth_inner( + &self, + p: packets::UserauthRequest, + s: &mut TrafSend, + b: &mut dyn ServBehaviour, + ) -> Result<AuthResp> { + // even allows "none" auth + if b.auth_unchallenged(p.username) { + return Ok(AuthResp::Success); + } + + let success = match p.method { + AuthMethod::Password(m) => b.auth_password(p.username, m.password), + AuthMethod::PubKey(m) => { + let allowed_key = b.auth_pubkey(p.username, &m.pubkey.0); + if allowed_key { + if m.sig.is_none() { + s.send(Userauth60::PkOk(UserauthPkOk { + algo: m.sig_algo, + key: m.pubkey, + }))?; + return Ok(AuthResp::FailNoReply); + } else { + self.verify_pubkey(&m) + } + } else { + false + } + } + AuthMethod::None => { + // nothing to do + false + } + AuthMethod::Unknown(u) => { + debug!("Request for unknown auth method {}", u); + false + } + }; + + if success { + Ok(AuthResp::Success) + } else { + Ok(AuthResp::Failure) + } + } + + /// Returns `true` on successful signature verifcation. `false` on bad signature. + fn verify_pubkey(&self, m: &packets::MethodPubKey) -> bool { + let sig = match m.sig.as_ref() { + Some(s) => &s.0, + None => return false, + }; + + let sig_type = match sig.sig_type() { + Ok(t) => t, + Err(_) => return false, + }; + + false + // XXX + // sig_type.verify(&m.pubkey.0, sess_id.as) + + // m.pubkey. + } + + fn avail_methods<'f>( + &self, + buf: &'f mut Vec<&str, NUM_AUTHMETHOD>, + ) -> namelist::LocalNames<'f> { + buf.clear(); + // for + buf.as_slice().into() } } diff --git a/sshproto/src/server.rs b/sshproto/src/server.rs index a5077872ee1596b3975d812c8e752dbae71ebcde..cc1bfa403a9f9ebaabe623ae301eaa2e2bdd8a2d 100644 --- a/sshproto/src/server.rs +++ b/sshproto/src/server.rs @@ -1,14 +1,37 @@ +#[allow(unused_imports)] +use { + crate::error::{Error, Result, TrapBug}, + log::{debug, error, info, log, trace, warn}, +}; + use crate::*; +use crate::packets::{ServiceAccept, ServiceRequest}; use crate::servauth::ServAuth; +use crate::sshnames::{SSH_SERVICE_CONNECTION, SSH_SERVICE_USERAUTH}; +use traffic::TrafSend; pub(crate) struct Server { - auth: ServAuth, + pub(crate) auth: ServAuth, } impl Server { - pub fn new() -> Self { - Server { - auth: ServAuth::new(), + pub fn new( + b: &mut dyn ServBehaviour, + ) -> Self { + Server { auth: ServAuth::new(b) } + } + + pub fn service_request(&self, p: &ServiceRequest, s: &mut TrafSend) -> Result<()> { + let success = match p.name { + SSH_SERVICE_USERAUTH => true, + SSH_SERVICE_CONNECTION => self.auth.authed, + _ => false, + }; + if success { + s.send(ServiceAccept { name: p.name }) + } else { + warn!("Received unexpected service request '{}'", p.name); + Err(Error::SSHProtoError) } } } diff --git a/sshproto/src/sign.rs b/sshproto/src/sign.rs index 7ec846c9962492089a5e00dc34aaf84d79d99622..d15689320a7a335b277c1294f1f8cf5923404c37 100644 --- a/sshproto/src/sign.rs +++ b/sshproto/src/sign.rs @@ -45,11 +45,12 @@ impl SigType { } } + /// Returns `Ok(())` on success pub fn verify( &self, pubkey: &PubKey, message: &[u8], sig: &Signature) -> Result<()> { // Check that the signature type is known - let sig_type = sig.sig_type()?; + let sig_type = sig.sig_type().map_err(|_| Error::BadSig)?; // `self` is the expected signature type from kex/auth packet // This would also get caught by SignatureMismatch below @@ -92,7 +93,7 @@ impl SigType { sig.algorithm_name(), pubkey.algorithm_name(), ); - Err(Error::SigMismatch) + Err(Error::BadSig) } } } @@ -138,7 +139,7 @@ impl SignKey { } /// Returns whether this `SignKey` can create a given signature type - pub(crate) fn can_sign(&self, sig_type: &SigType) -> bool { + pub(crate) fn can_sign(&self, sig_type: SigType) -> bool { match self { SignKey::Ed25519(_) => matches!(sig_type, SigType::Ed25519), } diff --git a/sshproto/src/sshnames.rs b/sshproto/src/sshnames.rs index 97b179b5e8a6485ad8683cb7ba9376295c97c387..5a73a735b3b3e7cdb2f09b33d0e6bf72cca92c7d 100644 --- a/sshproto/src/sshnames.rs +++ b/sshproto/src/sshnames.rs @@ -50,6 +50,7 @@ pub const SSH_AUTHMETHOD_PASSWORD: &str = "password"; pub const SSH_AUTHMETHOD_PUBLICKEY: &str = "publickey"; /// [RFC4256](https://tools.ietf.org/html/rfc4256) pub const SSH_AUTHMETHOD_INTERACTIVE: &str = "keyboard-interactive"; +pub(crate) const NUM_AUTHMETHOD: usize = 3; /// [RFC4254](https://tools.ietf.org/html/rfc4254) pub const SSH_EXTENDED_DATA_STDERR: u32 = 1; diff --git a/sshproto/src/traffic.rs b/sshproto/src/traffic.rs index 0fae0f52dd18abf5fd03a56c4d42a023f1b8004c..110a17ec325f5d7fe21e027a7cbf075b6a6f3a71 100644 --- a/sshproto/src/traffic.rs +++ b/sshproto/src/traffic.rs @@ -75,9 +75,6 @@ enum RxState { ReadComplete { len: usize }, /// Decrypted complete input payload InPayload { len: usize, seq: u32 }, - /// Decrypted complete input payload. It has been dispatched by handle_payload(), - /// remains "borrowed" for use by a progress() Event. - BorrowPayload { len: usize }, /// Decrypted incoming channel data InChannelData { /// channel number @@ -104,7 +101,6 @@ impl<'a> TrafIn<'a> { | RxState::Read { .. } => true, RxState::ReadComplete { .. } | RxState::InPayload { .. } - | RxState::BorrowPayload { .. } | RxState::InChannelData { .. } => false, } @@ -130,38 +126,10 @@ impl<'a> TrafIn<'a> { Ok(inlen) } - pub(crate) fn payload_reborrow(&mut self) -> Option<&[u8]> { - match self.state { - | RxState::InPayload { len, .. } - | RxState::BorrowPayload { len, .. } - => { - let payload = &self.buf[SSH_PAYLOAD_START..SSH_PAYLOAD_START + len]; - Some(payload) - } - _ => None, - } - } - - /// Called when `payload()` has been handled once, can still be - /// `payload_reborrow()`ed later. - pub(crate) fn handled_payload(&mut self) -> Result<(), Error> { - match self.state { - | RxState::InPayload { len, .. } - | RxState::BorrowPayload { len } - => { - self.state = RxState::BorrowPayload { len }; - Ok(()) - } - _ => Err(Error::bug()) - } - } - /// Called when `payload()` and `payload_reborrow()` are complete. pub(crate) fn done_payload(&mut self) -> Result<(), Error> { match self.state { - | RxState::InPayload { .. } - | RxState::BorrowPayload { .. } - => { + RxState::InPayload { .. } => { self.state = RxState::Idle; Ok(()) } @@ -257,14 +225,14 @@ impl<'a> TrafIn<'a> { pub fn set_channel_input(&mut self, di: channel::DataIn) -> Result<()> { trace!("traf chan input state {:?}", self.state); match self.state { - RxState::Idle => { + RxState::InPayload { .. } => { let idx = SSH_PAYLOAD_START + di.offset; self.state = RxState::InChannelData { chan: di.num, ext: di.ext, idx, len: idx + di.len }; // error!("set input {:?}", self.state); - trace!("all buf {:?}", self.buf[..32].hex_dump()); - trace!("set chan input offset {} idx {} {:?}", - di.offset, idx, - self.buf[idx..idx + di.len].hex_dump()); + // trace!("all buf {:?}", self.buf[..32].hex_dump()); + // trace!("set chan input offset {} idx {} {:?}", + // di.offset, idx, + // self.buf[idx..idx + di.len].hex_dump()); Ok(()) } _ => Err(Error::bug()),