diff --git a/.gitignore b/.gitignore
index ea8c4bf7f35f6f77f75d92ad8ce8349f6e81ddba..e2a3069b6ee98740d149140b881d6947483653c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 /target
+*~
diff --git a/async/examples/serv1.rs b/async/examples/serv1.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e8b2156aef999268bb596941e53aca02f24efa34
--- /dev/null
+++ b/async/examples/serv1.rs
@@ -0,0 +1,118 @@
+#[allow(unused_imports)]
+use {
+    // crate::error::Error,
+    log::{debug, error, info, log, trace, warn},
+};
+use anyhow::{Context, Result, Error, bail};
+use pretty_hex::PrettyHex;
+
+use tokio::net::{TcpStream, TcpListener};
+
+use std::{net::Ipv6Addr, io::Read};
+
+use door_sshproto::*;
+use door_async::{SSHServer, raw_pty};
+
+use simplelog::*;
+#[derive(argh::FromArgs)]
+/** con1
+ */
+struct Args {
+    #[argh(switch, short='v')]
+    /// verbose debug logging
+    debug: bool,
+
+    #[argh(switch)]
+    /// more verbose
+    trace: bool,
+
+    #[argh(option, short='p', default="22")]
+    /// port
+    port: u16,
+}
+
+fn parse_args() -> Result<Args> {
+    let mut args: Args = argh::from_env();
+
+    Ok(args)
+}
+
+fn main() -> Result<()> {
+    let args = parse_args()?;
+
+    setup_log(&args)?;
+
+    tokio::runtime::Builder::new_current_thread()
+        .enable_all()
+        .build()
+        .unwrap()
+        .block_on(async {
+            run(&args).await
+        })
+        .map_err(|e| {
+            error!("Exit with error: {e:?}");
+            e
+        });
+    Ok(())
+}
+
+fn setup_log(args: &Args) -> Result<()> {
+    let mut conf = simplelog::ConfigBuilder::new();
+    let conf = conf
+    .add_filter_allow_str("door")
+    .add_filter_allow_str("serv1")
+    // not debugging these bits of the stack at present
+    // .add_filter_ignore_str("door_sshproto::traffic")
+    // .add_filter_ignore_str("door_sshproto::runner")
+    // .add_filter_ignore_str("door_async::async_door")
+    .set_time_offset_to_local().expect("Couldn't get local timezone")
+    .build();
+
+    let level = if args.trace {
+        LevelFilter::Trace
+    } else if args.debug {
+        LevelFilter::Debug
+    } else {
+        LevelFilter::Warn
+    };
+
+    let mut logs: Vec<Box<dyn SharedLogger>> = vec![
+        TermLogger::new(level, conf.clone(), TerminalMode::Mixed, ColorChoice::Auto),
+    ];
+
+    CombinedLogger::init(logs).unwrap();
+    Ok(())
+}
+
+fn run_session<'a, R: Send>(scope: &'a moro::Scope<'a, '_, R>, stream: TcpStream) -> Result<()> {
+    let rxbuf = vec![0; 3000];
+    // TODO: better lifetime rather than leaking
+    let rxbuf = Box::leak(Box::new(rxbuf)).as_mut_slice();
+    let txbuf = vec![0; 3000];
+    let txbuf = Box::leak(Box::new(txbuf)).as_mut_slice();
+
+    let mut serv = SSHServer::new(rxbuf, txbuf)?;
+
+    scope.spawn(async {
+
+    });
+    Ok(())
+
+}
+
+async fn run(args: &Args) -> Result<()> {
+    let listener = TcpListener::bind(("", args.port)).await?;
+    moro::async_scope!(|scope| {
+        scope.spawn(async {
+            loop {
+                let (stream, _) = listener.accept().await?;
+
+                run_session(scope, stream)?
+            }
+            #[allow(unreachable_code)]
+            Ok::<_, anyhow::Error>(())
+        });
+
+    }).await;
+    Ok(())
+}
diff --git a/async/src/lib.rs b/async/src/lib.rs
index 0bd2e20fb9794c352f4543262eb00b4f3b2def27..f802913b7b91609cf1aed93f89065a722ea51855 100644
--- a/async/src/lib.rs
+++ b/async/src/lib.rs
@@ -1,6 +1,7 @@
 #![allow(unused_imports)]
 
 mod client;
+mod server;
 mod async_door;
 mod async_channel;
 mod cmdline_client;
@@ -8,6 +9,7 @@ mod pty;
 
 pub use async_door::AsyncDoor;
 pub use client::SSHClient;
+pub use server::SSHServer;
 pub use cmdline_client::CmdlineClient;
 
 #[cfg(unix)]
diff --git a/sshproto/src/behaviour.rs b/sshproto/src/behaviour.rs
index f0ad10888a147f93a217be65602f4fe28ea3f809..de627f9a837f2f0c705df3cbe283fb4437d1e377 100644
--- a/sshproto/src/behaviour.rs
+++ b/sshproto/src/behaviour.rs
@@ -42,6 +42,10 @@ pub enum BhError {
 // Permit impl Trait in type aliases
 // https://github.com/rust-lang/rust/issues/63063
 
+// TODO: another interim option would to split the async trait methods
+// into a separate trait (which impls the non-async trait) for a bit more
+// DRY.
+
 pub struct Behaviour<'a> {
     #[cfg(feature = "std")]
     inner: async_behaviour::AsyncCliServ<'a>,
diff --git a/sshproto/src/conn.rs b/sshproto/src/conn.rs
index de77fe7fc92c90d6133827f1bea14fcf926308e3..da464d5b8ccd934e83c2df40bf2683b63e71b80d 100644
--- a/sshproto/src/conn.rs
+++ b/sshproto/src/conn.rs
@@ -110,6 +110,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()))
+    }
+
     fn new(cliserv: ClientServer) -> Result<Self, Error> {
         Ok(Conn {
             kex: kex::Kex::new()?,
diff --git a/sshproto/src/runner.rs b/sshproto/src/runner.rs
index ddae3a0e1a9b9477e16ac437659cf4841ce12713..19ade4a165282d83ad6fcd804a7c78ff5d852f35 100644
--- a/sshproto/src/runner.rs
+++ b/sshproto/src/runner.rs
@@ -46,6 +46,22 @@ impl<'a> Runner<'a> {
         Ok(runner)
     }
 
+    pub fn new_server(
+        inbuf: &'a mut [u8],
+        outbuf: &'a mut [u8],
+    ) -> Result<Runner<'a>, Error> {
+        let conn = Conn::new_server()?;
+        let runner = Runner {
+            conn,
+            traffic: traffic::Traffic::new(outbuf, inbuf),
+            keys: KeyState::new_cleartext(),
+            output_waker: None,
+            input_waker: None,
+        };
+
+        Ok(runner)
+    }
+
     pub fn input(&mut self, buf: &[u8]) -> Result<usize, Error> {
         self.traffic.input(
             &mut self.keys,
diff --git a/sshproto/src/servauth.rs b/sshproto/src/servauth.rs
index 5919e4d9740091e3057856d4e0f2362f49c3c211..364658dbe636c4d746a75bb4b8957bba27cedd70 100644
--- a/sshproto/src/servauth.rs
+++ b/sshproto/src/servauth.rs
@@ -2,3 +2,10 @@ use crate::*;
 
 pub(crate) struct ServAuth {
 }
+
+
+impl ServAuth {
+    pub fn new() -> Self {
+        Self {}
+    }
+}
diff --git a/sshproto/src/server.rs b/sshproto/src/server.rs
index f16f2e3c2aac61fdaf994bcb9164d91604d97d03..a5077872ee1596b3975d812c8e752dbae71ebcde 100644
--- a/sshproto/src/server.rs
+++ b/sshproto/src/server.rs
@@ -4,3 +4,11 @@ use crate::servauth::ServAuth;
 pub(crate) struct Server {
     auth: ServAuth,
 }
+
+impl Server {
+    pub fn new() -> Self {
+        Server {
+            auth: ServAuth::new(),
+        }
+    }
+}