Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ This is a fork of [Thrussh](https://nest.pijul.com/pijul/thrussh) by Pierre-Éti
* `ecdsa-sha2-nistp256` ✨
* `ecdsa-sha2-nistp384` ✨
* `ecdsa-sha2-nistp521` ✨
* OpenSSH certificates ✨
* Authentication methods:
* `password`
* `publickey`
Expand Down Expand Up @@ -260,4 +261,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
190 changes: 190 additions & 0 deletions russh/examples/echoserver_certificates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use clap::Parser;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;

use russh::keys::{Certificate, *};
use russh::server::{Msg, Server as _, Session};
use russh::*;
use tokio::net::TcpListener;
use tokio::sync::Mutex;

#[derive(Parser, Debug)]
#[clap(
name = "echoserver_custom_keys",
about = "Echo server with custom keys"
)]
struct Cli {
/// Path to the private key file
#[clap(short, long)]
key: PathBuf,

/// Path to the certificate file (optional)
#[clap(short, long)]
cert: Option<PathBuf>,

/// Port to listen on
#[clap(short, long, default_value_t = 2222)]
port: u16,
}

#[tokio::main]
async fn main() {
env_logger::builder()
.filter_level(log::LevelFilter::Debug)
.init();

let args = Cli::parse();

// Load private key
let key = russh::keys::load_secret_key(&args.key, None).expect("Could not load private key");

// Load certificate if provided
let mut certs = Vec::new();
if let Some(cert_path) = args.cert {
let cert =
russh::keys::load_openssh_certificate(&cert_path).expect("Could not load certificate");
certs.push(cert);
}

let config = russh::server::Config {
inactivity_timeout: Some(std::time::Duration::from_secs(3600)),
auth_rejection_time: std::time::Duration::from_secs(3),
auth_rejection_time_initial: Some(std::time::Duration::from_secs(0)),
keys: vec![key],
certificates: certs,
preferred: Preferred {
// kex: std::borrow::Cow::Owned(vec![russh::kex::DH_GEX_SHA256]),
..Preferred::default()
},
..Default::default()
};
let config = Arc::new(config);
let mut sh = Server {
clients: Arc::new(Mutex::new(HashMap::new())),
id: 0,
};

let socket = TcpListener::bind(("0.0.0.0", args.port)).await.unwrap();
let server = sh.run_on_socket(config, &socket);
let handle = server.handle();

tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(600)).await;
handle.shutdown("Server shutting down after 10 minutes".into());
});

println!("Listening on port {}", args.port);
server.await.unwrap()
}

#[derive(Clone)]
struct Server {
clients: Arc<Mutex<HashMap<usize, (ChannelId, russh::server::Handle)>>>,
id: usize,
}

impl Server {
async fn post(&mut self, data: Vec<u8>) {
let mut clients = self.clients.lock().await;
for (id, (channel, s)) in clients.iter_mut() {
if *id != self.id {
let _ = s.data(*channel, data.clone()).await;
}
}
}
}

impl server::Server for Server {
type Handler = Self;
fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self {
let s = self.clone();
self.id += 1;
s
}
fn handle_session_error(&mut self, _error: <Self::Handler as russh::server::Handler>::Error) {
eprintln!("Session error: {_error:#?}");
}
}

impl server::Handler for Server {
type Error = russh::Error;

async fn channel_open_session(
&mut self,
channel: Channel<Msg>,
reply: server::ChannelOpenHandle,
session: &mut Session,
) -> Result<(), Self::Error> {
{
let mut clients = self.clients.lock().await;
clients.insert(self.id, (channel.id(), session.handle()));
}
reply.accept().await;
Ok(())
}

async fn auth_publickey(
&mut self,
_: &str,
_key: &ssh_key::PublicKey,
) -> Result<server::Auth, Self::Error> {
Ok(server::Auth::Accept)
}

async fn auth_openssh_certificate(
&mut self,
_user: &str,
_certificate: &Certificate,
) -> Result<server::Auth, Self::Error> {
Ok(server::Auth::Accept)
}

async fn data(
&mut self,
channel: ChannelId,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
// Sending Ctrl+C ends the session and disconnects the client
if data == [3] {
return Err(russh::Error::Disconnect);
}

let data = format!("Got data: {}\r\n", String::from_utf8_lossy(data)).into_bytes();
self.post(data.clone()).await;
session.data(channel, data)?;
Ok(())
}

async fn tcpip_forward(
&mut self,
address: &str,
port: &mut u32,
session: &mut Session,
) -> Result<bool, Self::Error> {
let handle = session.handle();
let address = address.to_string();
let port = *port;
tokio::spawn(async move {
let channel = handle
.channel_open_forwarded_tcpip(address, port, "1.2.3.4", 1234)
.await
.unwrap();
let _ = channel.data(&b"Hello from a forwarded port"[..]).await;
let _ = channel.eof().await;
});
Ok(true)
}
}

impl Drop for Server {
fn drop(&mut self) {
let id = self.id;
let clients = self.clients.clone();
tokio::spawn(async move {
let mut clients = clients.lock().await;
clients.remove(&id);
});
}
}
1 change: 1 addition & 0 deletions russh/src/client/kex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ impl ClientKex {
&input.buffer,
&self.config.preferred,
None,
None,
&self.cause,
)?
};
Expand Down
Loading
Loading