chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1,227 @@
///
/// Run this example with:
/// cargo run --example client_exec_interactive -- -k <private key path> <host> <command>
///
use std::convert::TryFrom;
use std::env;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
use anyhow::Result;
use async_trait::async_trait;
use clap::Parser;
use log::info;
use russh::keys::*;
use russh::*;
use termion::raw::IntoRawMode;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::ToSocketAddrs;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.init();
// CLI options are defined later in this file
let cli = Cli::parse();
info!("Connecting to {}:{}", cli.host, cli.port);
info!("Key path: {:?}", cli.private_key);
info!("OpenSSH Certificate path: {:?}", cli.openssh_certificate);
// Session is a wrapper around a russh client, defined down below
let mut ssh = Session::connect(
cli.private_key,
cli.username.unwrap_or("root".to_string()),
cli.openssh_certificate,
(cli.host, cli.port),
)
.await?;
info!("Connected");
let code = {
// We're using `termion` to put the terminal into raw mode, so that we can
// display the output of interactive applications correctly
let _raw_term = std::io::stdout().into_raw_mode()?;
ssh.call(
&cli.command
.into_iter()
.map(|x| shell_escape::escape(x.into())) // arguments are escaped manually since the SSH protocol doesn't support quoting
.collect::<Vec<_>>()
.join(" "),
)
.await?
};
println!("Exitcode: {:?}", code);
ssh.close().await?;
Ok(())
}
struct Client {}
// More SSH event handlers
// can be defined in this trait
// In this example, we're only using Channel, so these aren't needed.
#[async_trait]
impl client::Handler for Client {
type Error = russh::Error;
async fn check_server_key(
&mut self,
_server_public_key: &key::PublicKey,
) -> Result<bool, Self::Error> {
Ok(true)
}
}
/// This struct is a convenience wrapper
/// around a russh client
/// that handles the input/output event loop
pub struct Session {
session: client::Handle<Client>,
}
impl Session {
async fn connect<P: AsRef<Path>, A: ToSocketAddrs>(
key_path: P,
user: impl Into<String>,
openssh_cert_path: Option<P>,
addrs: A,
) -> Result<Self> {
let key_pair = load_secret_key(key_path, None)?;
// load ssh certificate
let mut openssh_cert = None;
if openssh_cert_path.is_some() {
openssh_cert = Some(load_openssh_certificate(openssh_cert_path.unwrap())?);
}
let config = client::Config {
inactivity_timeout: Some(Duration::from_secs(5)),
..<_>::default()
};
let config = Arc::new(config);
let sh = Client {};
let mut session = client::connect(config, addrs, sh).await?;
// use publickey authentication, with or without certificate
if openssh_cert.is_none() {
let auth_res = session
.authenticate_publickey(user, Arc::new(key_pair))
.await?;
if !auth_res {
anyhow::bail!("Authentication (with publickey) failed");
}
} else {
let auth_res = session
.authenticate_openssh_cert(user, Arc::new(key_pair), openssh_cert.unwrap())
.await?;
if !auth_res {
anyhow::bail!("Authentication (with publickey+cert) failed");
}
}
Ok(Self { session })
}
async fn call(&mut self, command: &str) -> Result<u32> {
let mut channel = self.session.channel_open_session().await?;
// This example doesn't terminal resizing after the connection is established
let (w, h) = termion::terminal_size()?;
// Request an interactive PTY from the server
channel
.request_pty(
false,
&env::var("TERM").unwrap_or("xterm".into()),
w as u32,
h as u32,
0,
0,
&[], // ideally you want to pass the actual terminal modes here
)
.await?;
channel.exec(true, command).await?;
let code;
let mut stdin = tokio_fd::AsyncFd::try_from(0)?;
let mut stdout = tokio_fd::AsyncFd::try_from(1)?;
let mut buf = vec![0; 1024];
let mut stdin_closed = false;
loop {
// Handle one of the possible events:
tokio::select! {
// There's terminal input available from the user
r = stdin.read(&mut buf), if !stdin_closed => {
match r {
Ok(0) => {
stdin_closed = true;
channel.eof().await?;
},
// Send it to the server
Ok(n) => channel.data(&buf[..n]).await?,
Err(e) => return Err(e.into()),
};
},
// There's an event available on the session channel
Some(msg) = channel.wait() => {
match msg {
// Write data to the terminal
ChannelMsg::Data { ref data } => {
stdout.write_all(data).await?;
stdout.flush().await?;
}
// The command has returned an exit code
ChannelMsg::ExitStatus { exit_status } => {
code = exit_status;
if !stdin_closed {
channel.eof().await?;
}
break;
}
_ => {}
}
},
}
}
Ok(code)
}
async fn close(&mut self) -> Result<()> {
self.session
.disconnect(Disconnect::ByApplication, "", "English")
.await?;
Ok(())
}
}
#[derive(clap::Parser)]
#[clap(trailing_var_arg = true)]
pub struct Cli {
#[clap(index = 1)]
host: String,
#[clap(long, short, default_value_t = 22)]
port: u16,
#[clap(long, short)]
username: Option<String>,
#[clap(long, short = 'k')]
private_key: PathBuf,
#[clap(long, short = 'o')]
openssh_certificate: Option<PathBuf>,
#[clap(multiple = true, index = 2, required = true)]
command: Vec<String>,
}

View File

@@ -0,0 +1,158 @@
///
/// Run this example with:
/// cargo run --example client_exec_simple -- -k <private key path> <host> <command>
///
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
use anyhow::Result;
use async_trait::async_trait;
use clap::Parser;
use log::info;
use russh::keys::*;
use russh::*;
use tokio::io::AsyncWriteExt;
use tokio::net::ToSocketAddrs;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.init();
// CLI options are defined later in this file
let cli = Cli::parse();
info!("Connecting to {}:{}", cli.host, cli.port);
info!("Key path: {:?}", cli.private_key);
// Session is a wrapper around a russh client, defined down below
let mut ssh = Session::connect(
cli.private_key,
cli.username.unwrap_or("root".to_string()),
(cli.host, cli.port),
)
.await?;
info!("Connected");
let code = ssh
.call(
&cli.command
.into_iter()
.map(|x| shell_escape::escape(x.into())) // arguments are escaped manually since the SSH protocol doesn't support quoting
.collect::<Vec<_>>()
.join(" "),
)
.await?;
println!("Exitcode: {:?}", code);
ssh.close().await?;
Ok(())
}
struct Client {}
// More SSH event handlers
// can be defined in this trait
// In this example, we're only using Channel, so these aren't needed.
#[async_trait]
impl client::Handler for Client {
type Error = russh::Error;
async fn check_server_key(
&mut self,
_server_public_key: &key::PublicKey,
) -> Result<bool, Self::Error> {
Ok(true)
}
}
/// This struct is a convenience wrapper
/// around a russh client
pub struct Session {
session: client::Handle<Client>,
}
impl Session {
async fn connect<P: AsRef<Path>, A: ToSocketAddrs>(
key_path: P,
user: impl Into<String>,
addrs: A,
) -> Result<Self> {
let key_pair = load_secret_key(key_path, None)?;
let config = client::Config {
inactivity_timeout: Some(Duration::from_secs(5)),
..<_>::default()
};
let config = Arc::new(config);
let sh = Client {};
let mut session = client::connect(config, addrs, sh).await?;
let auth_res = session
.authenticate_publickey(user, Arc::new(key_pair))
.await?;
if !auth_res {
anyhow::bail!("Authentication failed");
}
Ok(Self { session })
}
async fn call(&mut self, command: &str) -> Result<u32> {
let mut channel = self.session.channel_open_session().await?;
channel.exec(true, command).await?;
let mut code = None;
let mut stdout = tokio::io::stdout();
loop {
// There's an event available on the session channel
let Some(msg) = channel.wait().await else {
break;
};
match msg {
// Write data to the terminal
ChannelMsg::Data { ref data } => {
stdout.write_all(data).await?;
stdout.flush().await?;
}
// The command has returned an exit code
ChannelMsg::ExitStatus { exit_status } => {
code = Some(exit_status);
// cannot leave the loop immediately, there might still be more data to receive
}
_ => {}
}
}
Ok(code.expect("program did not exit cleanly"))
}
async fn close(&mut self) -> Result<()> {
self.session
.disconnect(Disconnect::ByApplication, "", "English")
.await?;
Ok(())
}
}
#[derive(clap::Parser)]
#[clap(trailing_var_arg = true)]
pub struct Cli {
#[clap(index = 1)]
host: String,
#[clap(long, short, default_value_t = 22)]
port: u16,
#[clap(long, short)]
username: Option<String>,
#[clap(long, short = 'k')]
private_key: PathBuf,
#[clap(multiple = true, index = 2, required = true)]
command: Vec<String>,
}

121
vendor/russh/examples/echoserver.rs vendored Normal file
View File

@@ -0,0 +1,121 @@
use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use russh::keys::*;
use russh::server::{Msg, Server as _, Session};
use russh::*;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
env_logger::builder()
.filter_level(log::LevelFilter::Debug)
.init();
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![russh_keys::key::KeyPair::generate_ed25519()],
..Default::default()
};
let config = Arc::new(config);
let mut sh = Server {
clients: Arc::new(Mutex::new(HashMap::new())),
id: 0,
};
sh.run_on_address(config, ("0.0.0.0", 2222)).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: CryptoVec) {
let mut clients = self.clients.lock().await;
for ((id, channel), ref mut 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);
}
}
#[async_trait]
impl server::Handler for Server {
type Error = russh::Error;
async fn channel_open_session(
&mut self,
channel: Channel<Msg>,
session: &mut Session,
) -> Result<bool, Self::Error> {
{
let mut clients = self.clients.lock().await;
clients.insert((self.id, channel.id()), session.handle());
}
Ok(true)
}
async fn auth_publickey(
&mut self,
_: &str,
key: &key::PublicKey,
) -> Result<server::Auth, Self::Error> {
dbg!(key);
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 = CryptoVec::from(format!("Got data: {}\r\n", String::from_utf8_lossy(data)));
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)
}
}

212
vendor/russh/examples/ratatui_app.rs vendored Normal file
View File

@@ -0,0 +1,212 @@
use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use ratatui::backend::CrosstermBackend;
use ratatui::layout::Rect;
use ratatui::style::{Color, Style};
use ratatui::widgets::{Block, Borders, Clear, Paragraph};
use ratatui::Terminal;
use russh::keys::key::PublicKey;
use russh::server::*;
use russh::{Channel, ChannelId};
use tokio::sync::Mutex;
type SshTerminal = Terminal<CrosstermBackend<TerminalHandle>>;
struct App {
pub counter: usize,
}
impl App {
pub fn new() -> App {
Self { counter: 0 }
}
}
#[derive(Clone)]
struct TerminalHandle {
handle: Handle,
// The sink collects the data which is finally flushed to the handle.
sink: Vec<u8>,
channel_id: ChannelId,
}
// The crossterm backend writes to the terminal handle.
impl std::io::Write for TerminalHandle {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.sink.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
let handle = self.handle.clone();
let channel_id = self.channel_id;
let data = self.sink.clone().into();
futures::executor::block_on(async move {
let result = handle.data(channel_id, data).await;
if result.is_err() {
eprintln!("Failed to send data: {:?}", result);
}
});
self.sink.clear();
Ok(())
}
}
#[derive(Clone)]
struct AppServer {
clients: Arc<Mutex<HashMap<usize, (SshTerminal, App)>>>,
id: usize,
}
impl AppServer {
pub fn new() -> Self {
Self {
clients: Arc::new(Mutex::new(HashMap::new())),
id: 0,
}
}
pub async fn run(&mut self) -> Result<(), anyhow::Error> {
let clients = self.clients.clone();
tokio::spawn(async move {
loop {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
for (_, (terminal, app)) in clients.lock().await.iter_mut() {
app.counter += 1;
terminal
.draw(|f| {
let size = f.size();
f.render_widget(Clear, size);
let style = match app.counter % 3 {
0 => Style::default().fg(Color::Red),
1 => Style::default().fg(Color::Green),
_ => Style::default().fg(Color::Blue),
};
let paragraph = Paragraph::new(format!("Counter: {}", app.counter))
.alignment(ratatui::layout::Alignment::Center)
.style(style);
let block = Block::default()
.title("Press 'c' to reset the counter!")
.borders(Borders::ALL);
f.render_widget(paragraph.block(block), size);
})
.unwrap();
}
}
});
let config = 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![russh_keys::key::KeyPair::generate_ed25519()],
..Default::default()
};
self.run_on_address(Arc::new(config), ("0.0.0.0", 2222))
.await?;
Ok(())
}
}
impl Server for AppServer {
type Handler = Self;
fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self {
let s = self.clone();
self.id += 1;
s
}
}
#[async_trait]
impl Handler for AppServer {
type Error = anyhow::Error;
async fn channel_open_session(
&mut self,
channel: Channel<Msg>,
session: &mut Session,
) -> Result<bool, Self::Error> {
{
let mut clients = self.clients.lock().await;
let terminal_handle = TerminalHandle {
handle: session.handle(),
sink: Vec::new(),
channel_id: channel.id(),
};
let backend = CrosstermBackend::new(terminal_handle.clone());
let terminal = Terminal::new(backend)?;
let app = App::new();
clients.insert(self.id, (terminal, app));
}
Ok(true)
}
async fn auth_publickey(&mut self, _: &str, _: &PublicKey) -> Result<Auth, Self::Error> {
Ok(Auth::Accept)
}
async fn data(
&mut self,
channel: ChannelId,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
match data {
// Pressing 'q' closes the connection.
b"q" => {
self.clients.lock().await.remove(&self.id);
session.close(channel);
}
// Pressing 'c' resets the counter for the app.
// Only the client with the id sees the counter reset.
b"c" => {
let mut clients = self.clients.lock().await;
let (_, app) = clients.get_mut(&self.id).unwrap();
app.counter = 0;
}
_ => {}
}
Ok(())
}
/// The client's window size has changed.
async fn window_change_request(
&mut self,
_: ChannelId,
col_width: u32,
row_height: u32,
_: u32,
_: u32,
_: &mut Session,
) -> Result<(), Self::Error> {
{
let mut clients = self.clients.lock().await;
let (terminal, _) = clients.get_mut(&self.id).unwrap();
let rect = Rect {
x: 0,
y: 0,
width: col_width as u16,
height: row_height as u16,
};
terminal.resize(rect)?;
}
Ok(())
}
}
#[tokio::main]
async fn main() {
let mut server = AppServer::new();
server.run().await.expect("Failed running server");
}

View File

@@ -0,0 +1,211 @@
use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use ratatui::backend::CrosstermBackend;
use ratatui::layout::Rect;
use ratatui::style::{Color, Style};
use ratatui::widgets::{Block, Borders, Clear, Paragraph};
use ratatui::Terminal;
use russh::keys::key::PublicKey;
use russh::server::*;
use russh::{Channel, ChannelId};
use tokio::sync::Mutex;
type SshTerminal = Terminal<CrosstermBackend<TerminalHandle>>;
struct App {
pub counter: usize,
}
impl App {
pub fn new() -> App {
Self { counter: 0 }
}
}
#[derive(Clone)]
struct TerminalHandle {
handle: Handle,
// The sink collects the data which is finally flushed to the handle.
sink: Vec<u8>,
channel_id: ChannelId,
}
// The crossterm backend writes to the terminal handle.
impl std::io::Write for TerminalHandle {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.sink.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
let handle = self.handle.clone();
let channel_id = self.channel_id;
let data = self.sink.clone().into();
futures::executor::block_on(async move {
let result = handle.data(channel_id, data).await;
if result.is_err() {
eprintln!("Failed to send data: {:?}", result);
}
});
self.sink.clear();
Ok(())
}
}
#[derive(Clone)]
struct AppServer {
clients: Arc<Mutex<HashMap<usize, SshTerminal>>>,
id: usize,
app: Arc<Mutex<App>>,
}
impl AppServer {
pub fn new() -> Self {
Self {
clients: Arc::new(Mutex::new(HashMap::new())),
id: 0,
app: Arc::new(Mutex::new(App::new())),
}
}
pub async fn run(&mut self) -> Result<(), anyhow::Error> {
let app = self.app.clone();
let clients = self.clients.clone();
tokio::spawn(async move {
loop {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
app.lock().await.counter += 1;
let counter = app.lock().await.counter;
for (_, terminal) in clients.lock().await.iter_mut() {
terminal
.draw(|f| {
let size = f.size();
f.render_widget(Clear, size);
let style = match counter % 3 {
0 => Style::default().fg(Color::Red),
1 => Style::default().fg(Color::Green),
_ => Style::default().fg(Color::Blue),
};
let paragraph = Paragraph::new(format!("Counter: {counter}"))
.alignment(ratatui::layout::Alignment::Center)
.style(style);
let block = Block::default()
.title("Press 'c' to reset the counter!")
.borders(Borders::ALL);
f.render_widget(paragraph.block(block), size);
})
.unwrap();
}
}
});
let config = 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![russh_keys::key::KeyPair::generate_ed25519()],
..Default::default()
};
self.run_on_address(Arc::new(config), ("0.0.0.0", 2222))
.await?;
Ok(())
}
}
impl Server for AppServer {
type Handler = Self;
fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self {
let s = self.clone();
self.id += 1;
s
}
}
#[async_trait]
impl Handler for AppServer {
type Error = anyhow::Error;
async fn channel_open_session(
&mut self,
channel: Channel<Msg>,
session: &mut Session,
) -> Result<bool, Self::Error> {
{
let mut clients = self.clients.lock().await;
let terminal_handle = TerminalHandle {
handle: session.handle(),
sink: Vec::new(),
channel_id: channel.id(),
};
let backend = CrosstermBackend::new(terminal_handle.clone());
let terminal = Terminal::new(backend)?;
clients.insert(self.id, terminal);
}
Ok(true)
}
async fn auth_publickey(&mut self, _: &str, _: &PublicKey) -> Result<Auth, Self::Error> {
Ok(Auth::Accept)
}
async fn data(
&mut self,
channel: ChannelId,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
let app = self.app.clone();
match data {
// Pressing 'q' closes the connection.
b"q" => {
self.clients.lock().await.remove(&self.id);
session.close(channel);
}
// Pressing 'c' resets the counter for the app.
// Every client sees the counter reset.
b"c" => {
app.lock().await.counter = 0;
}
_ => {}
}
Ok(())
}
/// The client's pseudo-terminal window size has changed.
async fn window_change_request(
&mut self,
_: ChannelId,
col_width: u32,
row_height: u32,
_: u32,
_: u32,
_: &mut Session,
) -> Result<(), Self::Error> {
let mut terminal = {
let clients = self.clients.lock().await;
clients.get(&self.id).unwrap().clone()
};
let rect = Rect {
x: 0,
y: 0,
width: col_width as u16,
height: row_height as u16,
};
terminal.resize(rect)?;
Ok(())
}
}
#[tokio::main]
async fn main() {
let mut server = AppServer::new();
server.run().await.expect("Failed running server");
}

112
vendor/russh/examples/sftp_client.rs vendored Normal file
View File

@@ -0,0 +1,112 @@
use async_trait::async_trait;
use log::{error, info, LevelFilter};
use russh::*;
use russh_keys::*;
use russh_sftp::{client::SftpSession, protocol::OpenFlags};
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
struct Client;
#[async_trait]
impl client::Handler for Client {
type Error = anyhow::Error;
async fn check_server_key(
&mut self,
server_public_key: &key::PublicKey,
) -> Result<bool, Self::Error> {
info!("check_server_key: {:?}", server_public_key);
Ok(true)
}
async fn data(
&mut self,
channel: ChannelId,
data: &[u8],
_session: &mut client::Session,
) -> Result<(), Self::Error> {
info!("data on channel {:?}: {}", channel, data.len());
Ok(())
}
}
#[tokio::main]
async fn main() {
env_logger::builder()
.filter_level(LevelFilter::Debug)
.init();
let config = russh::client::Config::default();
let sh = Client {};
let mut session = russh::client::connect(Arc::new(config), ("localhost", 22), sh)
.await
.unwrap();
if session
.authenticate_password("root", "password")
.await
.unwrap()
{
let channel = session.channel_open_session().await.unwrap();
channel.request_subsystem(true, "sftp").await.unwrap();
let sftp = SftpSession::new(channel.into_stream()).await.unwrap();
info!("current path: {:?}", sftp.canonicalize(".").await.unwrap());
// create dir and symlink
let path = "./some_kind_of_dir";
let symlink = "./symlink";
sftp.create_dir(path).await.unwrap();
sftp.symlink(path, symlink).await.unwrap();
info!("dir info: {:?}", sftp.metadata(path).await.unwrap());
info!(
"symlink info: {:?}",
sftp.symlink_metadata(path).await.unwrap()
);
// scanning directory
for entry in sftp.read_dir(".").await.unwrap() {
info!("file in directory: {:?}", entry.file_name());
}
sftp.remove_file(symlink).await.unwrap();
sftp.remove_dir(path).await.unwrap();
// interaction with i/o
let filename = "test_new.txt";
let mut file = sftp
.open_with_flags(
filename,
OpenFlags::CREATE | OpenFlags::TRUNCATE | OpenFlags::WRITE | OpenFlags::READ,
)
.await
.unwrap();
info!("metadata by handle: {:?}", file.metadata().await.unwrap());
file.write_all(b"magic text").await.unwrap();
info!("flush: {:?}", file.flush().await); // or file.sync_all()
info!(
"current cursor position: {:?}",
file.stream_position().await
);
let mut str = String::new();
file.rewind().await.unwrap();
file.read_to_string(&mut str).await.unwrap();
file.rewind().await.unwrap();
info!(
"our magical contents: {}, after rewind: {:?}",
str,
file.stream_position().await
);
file.shutdown().await.unwrap();
sftp.remove_file(filename).await.unwrap();
// should fail because handle was closed
error!("should fail: {:?}", file.read_u8().await);
}
}

201
vendor/russh/examples/sftp_server.rs vendored Normal file
View File

@@ -0,0 +1,201 @@
use async_trait::async_trait;
use log::{error, info, LevelFilter};
use russh::{
server::{Auth, Msg, Server as _, Session},
Channel, ChannelId,
};
use russh_keys::key::KeyPair;
use russh_sftp::protocol::{File, FileAttributes, Handle, Name, Status, StatusCode, Version};
use std::{collections::HashMap, net::SocketAddr, sync::Arc, time::Duration};
use tokio::sync::Mutex;
#[derive(Clone)]
struct Server;
impl russh::server::Server for Server {
type Handler = SshSession;
fn new_client(&mut self, _: Option<SocketAddr>) -> Self::Handler {
SshSession::default()
}
}
struct SshSession {
clients: Arc<Mutex<HashMap<ChannelId, Channel<Msg>>>>,
}
impl Default for SshSession {
fn default() -> Self {
Self {
clients: Arc::new(Mutex::new(HashMap::new())),
}
}
}
impl SshSession {
pub async fn get_channel(&mut self, channel_id: ChannelId) -> Channel<Msg> {
let mut clients = self.clients.lock().await;
clients.remove(&channel_id).unwrap()
}
}
#[async_trait]
impl russh::server::Handler for SshSession {
type Error = anyhow::Error;
async fn auth_password(&mut self, user: &str, password: &str) -> Result<Auth, Self::Error> {
info!("credentials: {}, {}", user, password);
Ok(Auth::Accept)
}
async fn auth_publickey(
&mut self,
user: &str,
public_key: &russh_keys::key::PublicKey,
) -> Result<Auth, Self::Error> {
info!("credentials: {}, {:?}", user, public_key);
Ok(Auth::Accept)
}
async fn channel_open_session(
&mut self,
channel: Channel<Msg>,
_session: &mut Session,
) -> Result<bool, Self::Error> {
{
let mut clients = self.clients.lock().await;
clients.insert(channel.id(), channel);
}
Ok(true)
}
async fn channel_eof(
&mut self,
channel: ChannelId,
session: &mut Session,
) -> Result<(), Self::Error> {
// After a client has sent an EOF, indicating that they don't want
// to send more data in this session, the channel can be closed.
session.close(channel);
Ok(())
}
async fn subsystem_request(
&mut self,
channel_id: ChannelId,
name: &str,
session: &mut Session,
) -> Result<(), Self::Error> {
info!("subsystem: {}", name);
if name == "sftp" {
let channel = self.get_channel(channel_id).await;
let sftp = SftpSession::default();
session.channel_success(channel_id);
russh_sftp::server::run(channel.into_stream(), sftp).await;
} else {
session.channel_failure(channel_id);
}
Ok(())
}
}
#[derive(Default)]
struct SftpSession {
version: Option<u32>,
root_dir_read_done: bool,
}
#[async_trait]
impl russh_sftp::server::Handler for SftpSession {
type Error = StatusCode;
fn unimplemented(&self) -> Self::Error {
StatusCode::OpUnsupported
}
async fn init(
&mut self,
version: u32,
extensions: HashMap<String, String>,
) -> Result<Version, Self::Error> {
if self.version.is_some() {
error!("duplicate SSH_FXP_VERSION packet");
return Err(StatusCode::ConnectionLost);
}
self.version = Some(version);
info!("version: {:?}, extensions: {:?}", self.version, extensions);
Ok(Version::new())
}
async fn close(&mut self, id: u32, _handle: String) -> Result<Status, Self::Error> {
Ok(Status {
id,
status_code: StatusCode::Ok,
error_message: "Ok".to_string(),
language_tag: "en-US".to_string(),
})
}
async fn opendir(&mut self, id: u32, path: String) -> Result<Handle, Self::Error> {
info!("opendir: {}", path);
self.root_dir_read_done = false;
Ok(Handle { id, handle: path })
}
async fn readdir(&mut self, id: u32, handle: String) -> Result<Name, Self::Error> {
info!("readdir handle: {}", handle);
if handle == "/" && !self.root_dir_read_done {
self.root_dir_read_done = true;
return Ok(Name {
id,
files: vec![
File::new("foo", FileAttributes::default()),
File::new("bar", FileAttributes::default()),
],
});
}
// If all files have been sent to the client, respond with an EOF
Err(StatusCode::Eof)
}
async fn realpath(&mut self, id: u32, path: String) -> Result<Name, Self::Error> {
info!("realpath: {}", path);
Ok(Name {
id,
files: vec![File::dummy("/")],
})
}
}
#[tokio::main]
async fn main() {
env_logger::builder()
.filter_level(LevelFilter::Debug)
.init();
let config = russh::server::Config {
auth_rejection_time: Duration::from_secs(3),
auth_rejection_time_initial: Some(Duration::from_secs(0)),
keys: vec![KeyPair::generate_ed25519()],
..Default::default()
};
let mut server = Server;
server
.run_on_address(
Arc::new(config),
(
"0.0.0.0",
std::env::var("PORT")
.unwrap_or("22".to_string())
.parse()
.unwrap(),
),
)
.await
.unwrap();
}

95
vendor/russh/examples/test.rs vendored Normal file
View File

@@ -0,0 +1,95 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use log::debug;
use russh::keys::*;
use russh::server::{Auth, Msg, Server as _, Session};
use russh::*;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
env_logger::init();
let mut config = russh::server::Config::default();
config.auth_rejection_time = std::time::Duration::from_secs(3);
config
.keys
.push(russh_keys::key::KeyPair::generate_ed25519());
let config = Arc::new(config);
let mut sh = Server {
clients: Arc::new(Mutex::new(HashMap::new())),
id: 0,
};
tokio::time::timeout(
std::time::Duration::from_secs(60),
sh.run_on_address(config, ("0.0.0.0", 2222)),
)
.await
.unwrap_or(Ok(()))?;
Ok(())
}
#[derive(Clone)]
struct Server {
clients: Arc<Mutex<HashMap<(usize, ChannelId), Channel<Msg>>>>,
id: usize,
}
impl server::Server for Server {
type Handler = Self;
fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self {
debug!("new client");
let s = self.clone();
self.id += 1;
s
}
}
#[async_trait]
impl server::Handler for Server {
type Error = anyhow::Error;
async fn channel_open_session(
&mut self,
channel: Channel<Msg>,
_session: &mut Session,
) -> Result<bool, Self::Error> {
{
debug!("channel open session");
let mut clients = self.clients.lock().unwrap();
clients.insert((self.id, channel.id()), channel);
}
Ok(true)
}
/// The client requests a shell.
#[allow(unused_variables)]
async fn shell_request(
&mut self,
channel: ChannelId,
session: &mut Session,
) -> Result<(), Self::Error> {
session.request_success();
Ok(())
}
async fn auth_publickey(&mut self, _: &str, _: &key::PublicKey) -> Result<Auth, Self::Error> {
Ok(server::Auth::Accept)
}
async fn data(
&mut self,
_channel: ChannelId,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
debug!("data: {data:?}");
{
let mut clients = self.clients.lock().unwrap();
for ((_, _channel_id), ref mut channel) in clients.iter_mut() {
session.data(channel.id(), CryptoVec::from(data.to_vec()));
}
}
Ok(())
}
}