diff --git a/Cargo.lock b/Cargo.lock index 9fea71108121bde744bc5a0aadec57de20031ff8..eabcf14a84ca727623983621240ff11966700f7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,7 +352,6 @@ dependencies = [ "nix", "pretty-hex", "rpassword", - "signal-hook-tokio", "simplelog", "snafu", "tokio", @@ -1225,16 +1224,6 @@ dependencies = [ "digest 0.10.3", ] -[[package]] -name = "signal-hook" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" -dependencies = [ - "libc", - "signal-hook-registry", -] - [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1244,17 +1233,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signal-hook-tokio" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213241f76fb1e37e27de3b6aa1b068a2c333233b59cca6634f634b80a27ecf1e" -dependencies = [ - "libc", - "signal-hook", - "tokio", -] - [[package]] name = "signature" version = "1.5.0" diff --git a/async/Cargo.toml b/async/Cargo.toml index cbe0cdd7221c3ae692b9a758396af89f8a1a9608..eb07034dfd65766a7a2797f7330c55394f936430 100644 --- a/async/Cargo.toml +++ b/async/Cargo.toml @@ -29,8 +29,6 @@ moro = "0.4" libc = "0.2" nix = "0.24" -signal-hook-tokio = "0.3" - heapless = "0.7.10" # TODO diff --git a/async/src/async_channel.rs b/async/src/async_channel.rs index 952ec94f799f56be3bebb0fa2837b3b89373c3fc..0adc4c773bb59817153980dc885d21337cd27da0 100644 --- a/async/src/async_channel.rs +++ b/async/src/async_channel.rs @@ -17,6 +17,30 @@ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use crate::*; use async_door::{Inner, poll_lock}; +pub struct Channel<'a> { + chan: u32, + door: AsyncDoor<'a>, +} + +impl<'a> Channel<'a> { + /// Should be called by a SSH client when the local terminal changes size + /// (`SIGWINCH` is received). Only applicable to client session + /// channels with a pty. + pub async fn term_window_change(&self) { + let wc = match pty::win_size() { + Ok(wc) => wc, + Err(e) => { + warn!("Failed getting window size: {e}"); + return; + } + }; + + // TODO: also need to wait for spare output buffer + self.door.inner.lock().await + .runner.term_window_change(self.chan, wc); + } +} + pub struct ChanInOut<'a> { chan: u32, door: AsyncDoor<'a>, diff --git a/async/src/pty.rs b/async/src/pty.rs index 9bb12b275f31df79793c5f684fc0325a892df6ee..ec2f413a9aca2567d6eb6f6f7f440c140d6653e5 100644 --- a/async/src/pty.rs +++ b/async/src/pty.rs @@ -8,29 +8,43 @@ use libc::{ioctl, winsize, termios, tcgetattr, tcsetattr }; use door_sshproto as door; use door::{Behaviour, AsyncCliBehaviour, Runner, Result, Pty}; use door::config::*; +use door::packets::WinChange; +/// Returns the size of the current terminal +pub fn win_size() -> Result<WinChange, IoError> { + let mut ws = winsize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0 }; + let r = unsafe { ioctl(libc::STDIN_FILENO, libc::TIOCGWINSZ, &mut ws) }; + if r != 0 { + return Err(IoError::last_os_error()) + } + Ok(WinChange { + rows: ws.ws_row as u32, + cols: ws.ws_col as u32, + width: ws.ws_xpixel as u32, + height: ws.ws_ypixel as u32, + }) +} + +/// Returns a `Pty` describing the current terminal. 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"); - let mut ws = winsize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0 }; - let r = unsafe { ioctl(libc::STDIN_FILENO, libc::TIOCGWINSZ, &mut ws) }; - if r != 0 { - return Err(IoError::last_os_error()) - } + let wc = win_size()?; - info!("rows {} cols {}", ws.ws_row, ws.ws_col); + // TODO modes + let modes = heapless::Vec::new(); Ok(Pty { term, - rows: ws.ws_row as u32, - cols: ws.ws_col as u32, - width: ws.ws_xpixel as u32, - height: ws.ws_ypixel as u32, - modes: heapless::Vec::new(), + rows: wc.rows, + cols: wc.cols, + width: wc.width, + height: wc.height, + modes, }) } diff --git a/sshproto/src/conn.rs b/sshproto/src/conn.rs index 69bcaceed97f66906ca94a61bde00e3650bcba4e..cf9fa13b6104d81fa3c6607bff075a5ff720234b 100644 --- a/sshproto/src/conn.rs +++ b/sshproto/src/conn.rs @@ -178,11 +178,22 @@ impl<'a> Conn<'a> { /// We queue response packets that can be sent (written into the same buffer) /// after `handle_payload()` runs. pub(crate) async fn handle_payload<'p>( - &mut self, payload: &'p [u8], keys: &mut KeyState, b: &mut Behaviour<'_>, + &mut self, payload: &'p [u8], seq: u32, + keys: &mut KeyState, b: &mut Behaviour<'_>, ) -> Result<Dispatched<'_>, Error> { - let p = sshwire::packet_from_bytes(payload, &self.parse_ctx)?; - let r = self.dispatch_packet(p, keys, b).await; - r + let r = sshwire::packet_from_bytes(payload, &self.parse_ctx); + match r { + Ok(p) => self.dispatch_packet(p, keys, b).await, + Err(Error::UnknownPacket { number }) => { + trace!("Unimplemented packet type {number}"); + let p: Packet = packets::Unimplemented { seq }.into(); + let mut resp = RespPackets::new(); + // unwrap is OK, single packet has space + resp.push(p.into()).unwrap(); + Ok(Dispatched { resp, event: None }) + } + Err(e) => return Err(e), + } } async fn dispatch_packet<'p>( diff --git a/sshproto/src/encrypt.rs b/sshproto/src/encrypt.rs index 5806a221fa3601f1b6a2d9cdb25ebca4f6471b8a..0ef9c062695e74e214b05f5bdededfc34f13d3ca 100644 --- a/sshproto/src/encrypt.rs +++ b/sshproto/src/encrypt.rs @@ -62,6 +62,10 @@ impl KeyState { self.keys = keys } + pub fn recv_seq(&self) -> u32 { + self.seq_decrypt.0 + } + /// Decrypts the first block in the buffer, returning the length. pub fn decrypt_first_block(&mut self, buf: &mut [u8]) -> Result<u32, Error> { self.keys.decrypt_first_block(buf, self.seq_decrypt.0) diff --git a/sshproto/src/error.rs b/sshproto/src/error.rs index 51abae2bc2e67ed34d61abaa9a08e1468110a355..421761033f20aa3176705553d2d6daa73ae6f166 100644 --- a/sshproto/src/error.rs +++ b/sshproto/src/error.rs @@ -26,19 +26,20 @@ pub enum Error { /// Not a UTF8 string BadString, - /// Not a valid SSH ascii string + /// Not a valid SSH ASCII string BadName, - /// Decryption failure or integrity mismatch + /// Key exchange incorrect + BadKex, + + /// Decryption failed BadDecrypt, /// Signature is incorrect BadSig, - /// Key exchange incorrect - BadKex, - - // Signature doesn't match key type + // 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 @@ -96,6 +97,7 @@ pub enum Error { /// Program bug Bug, + // TODO remove this OtherBug { location: snafu::Location }, } diff --git a/sshproto/src/kex.rs b/sshproto/src/kex.rs index da4146b3c1005fb485b5524107429fbc5dce4851..cee9f13e83ab6d8ea0fe4a1cd96c33ab12f0e6f6 100644 --- a/sshproto/src/kex.rs +++ b/sshproto/src/kex.rs @@ -128,10 +128,14 @@ impl KexHash { } /// Fill everything except K. - /// q_c and q_s need to be padded as mpint (extra 0x00 if high bit set) - /// for ecdsa and DH modes, but not for curve25519. fn prefinish(&mut self, host_key: &PubKey, q_c: &[u8], q_s: &[u8]) -> Result<()> { hash_ser_length(&mut self.hash_ctx, host_key)?; + + // TODO: q_c and q_s need to be padded as mpint (extra 0x00 if high bit set) + // for ecdsa and DH modes, but not for curve25519. + // Hack test for ed25519 algo + assert_eq!(q_c.len(), 32); + self.hash_slice(q_c); self.hash_slice(q_s); Ok(()) @@ -493,11 +497,10 @@ impl<'a> KexOutput { // server only pub fn make_kexdhreply(&'a self) -> Result<Packet<'a>> { let q_s = BinString(self.pubkey.as_ref().trap()?); - // TODO + // TODO real signature let k_s = Blob(PubKey::Ed25519(packets::Ed25519PubKey{ key: BinString(&[]) })); let sig = Blob(Signature::Ed25519(packets::Ed25519Sig{ sig: BinString(&[]) })); Ok(packets::KexDHReply { k_s, q_s, sig }.into()) - // then sign it. } } @@ -534,7 +537,7 @@ impl KexCurve25519 { algos: &mut Algos, theirs: &[u8], kex_hash: KexHash, sess_id: &Option<SessId>, ) -> Result<KexOutput> { - #[warn(irrefutable_let_patterns)] // until we have other algos + #[allow(irrefutable_let_patterns)] // until we have other algos let kex = if let SharedSecret::KexCurve25519(k) = &mut algos.kex { k } else { diff --git a/sshproto/src/runner.rs b/sshproto/src/runner.rs index 50a2993d11c4b4124e52d7a4f4107069ba940f39..1d952fc3ab330a5fd5781f29894e571a21c11f0c 100644 --- a/sshproto/src/runner.rs +++ b/sshproto/src/runner.rs @@ -74,7 +74,7 @@ impl<'a> Runner<'a> { /// event to the application. /// [`done_payload()`] must be called after any `Ok` result. pub async fn progress<'f>(&'f mut self) -> Result<Option<Event<'f>>, Error> { - let em = if let Some(payload) = self.traffic.payload() { + let em = if let Some((payload, seq)) = self.traffic.payload() { // Lifetimes here are a bit subtle. // `payload` has self.traffic lifetime, used until `handle_payload` // completes. @@ -82,7 +82,7 @@ impl<'a> Runner<'a> { // by the send_packet(). // After that progress() can perform more send_packet() itself. - let d = self.conn.handle_payload(payload, &mut self.keys, &mut self.behaviour).await?; + let d = self.conn.handle_payload(payload, seq, &mut self.keys, &mut self.behaviour).await?; self.traffic.handled_payload()?; if !d.resp.is_empty() || d.event.is_none() { @@ -250,6 +250,11 @@ impl<'a> Runner<'a> { self.conn.channels.send_allowed(chan).map(|s| s.min(buf_space)) } + pub fn term_window_change(&self, chan: u32, wc: packets::WinChange) -> Result<()> { + todo!(); + // self.conn.channels.term_window_change(chan, wc) + } + // pub fn chan_pending(&self) -> bool { // self.conn.chan_pending() // } diff --git a/sshproto/src/traffic.rs b/sshproto/src/traffic.rs index 1cab016fafc1cb28777e62c59e6fb8ab060c21c9..73e05808fa6bba780f47e7eaa67a825fdc9f79ad 100644 --- a/sshproto/src/traffic.rs +++ b/sshproto/src/traffic.rs @@ -41,7 +41,7 @@ enum TrafState { /// Whole encrypted packet has been read ReadComplete { len: usize }, /// Decrypted complete input payload - InPayload { len: usize }, + 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 }, @@ -164,27 +164,21 @@ impl<'a> Traffic<'a> { Ok(inlen) } - /// Returns a reference to the decrypted payload buffer if ready. - /// For a given payload should be called once initially to pass to handle_payload(), - /// with borrow=false. Subsequent calls will only return the payload if borrow=false, - /// used for borrowing the payload for Event. - // TODO: get rid of the bool and make two functions. need to figure naming. - // "payload_reborrow()"? - pub(crate) fn payload(&mut self) -> Option<&[u8]> { - let p = match self.state { - | TrafState::InPayload { len, .. } + /// Returns a reference to the decrypted payload buffer if ready, + /// and the `seq` of that packet. + pub(crate) fn payload(&mut self) -> Option<(&[u8], u32)> { + match self.state { + | TrafState::InPayload { len, seq } => { let payload = &self.buf[SSH_PAYLOAD_START..SSH_PAYLOAD_START + len]; - Some(payload) + Some((payload, seq)) } _ => None, - }; - trace!("traf 2 {:?}", self.state); - p + } } pub(crate) fn payload_reborrow(&mut self) -> Option<&[u8]> { - let p = match self.state { + match self.state { | TrafState::InPayload { len, .. } | TrafState::BorrowPayload { len, .. } => { @@ -192,16 +186,14 @@ impl<'a> Traffic<'a> { Some(payload) } _ => None, - }; - trace!("traf 2 {:?}", self.state); - p + } } /// 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 { - | TrafState::InPayload { len } + | TrafState::InPayload { len, .. } | TrafState::BorrowPayload { len } => { self.state = TrafState::BorrowPayload { len }; @@ -348,8 +340,9 @@ impl<'a> Traffic<'a> { if let TrafState::ReadComplete { len } = self.state { let w = &mut self.buf[0..len]; + let seq = keys.recv_seq(); let payload_len = keys.decrypt(w)?; - self.state = TrafState::InPayload { len: payload_len } + self.state = TrafState::InPayload { len: payload_len, seq } } Ok(buf.len() - r.len())