From ab692bc9c0b067bcb070ee079bcef8e474436de5 Mon Sep 17 00:00:00 2001
From: Matt Johnston <matt@ucc.asn.au>
Date: Sun, 28 Aug 2022 00:10:48 +0800
Subject: [PATCH] progress on servauth

---
 .rustfmt.toml               |   5 +-
 async/examples/con1.rs      |  15 ++--
 async/examples/serv1.rs     |  12 ++--
 async/src/async_door.rs     |   1 -
 async/src/cmdline_client.rs | 133 ++++++++++++++++++------------------
 async/src/lib.rs            |   3 +-
 async/src/pty.rs            |   2 +-
 sshproto/src/behaviour.rs   |  15 ++--
 sshproto/src/client.rs      |  29 ++++----
 sshproto/src/config.rs      |   2 +-
 sshproto/src/conn.rs        |  28 +++++---
 sshproto/src/error.rs       |  14 ++--
 sshproto/src/kex.rs         |  32 ++++-----
 sshproto/src/lib.rs         |   4 +-
 sshproto/src/packets.rs     |   6 +-
 sshproto/src/runner.rs      |   6 +-
 sshproto/src/servauth.rs    | 125 ++++++++++++++++++++++++++++++++-
 sshproto/src/server.rs      |  31 +++++++--
 sshproto/src/sign.rs        |   7 +-
 sshproto/src/sshnames.rs    |   1 +
 sshproto/src/traffic.rs     |  44 ++----------
 21 files changed, 325 insertions(+), 190 deletions(-)

diff --git a/.rustfmt.toml b/.rustfmt.toml
index 54026e1..f5ac0a7 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 673e673..e903f9c 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 031e822..663b1bd 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 4306c51..c1231e9 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 a1e40ce..4564e72 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 f802913..bda500e 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 10a3fa2..b1004ff 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 75f67af..71e580b 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 2b17b8f..8226692 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 03ff3bb..3c61839 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 b11396f..e646783 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 bfbb14b..e519c3e 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 aceeb22..6e6f65d 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 9dac0a9..b77e37a 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 1b3292f..e4bd7c8 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 a1954d2..3c26752 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 364658d..325660c 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 a507787..cc1bfa4 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 7ec846c..d156893 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 97b179b..5a73a73 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 0fae0f5..110a17e 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()),
-- 
GitLab