Start implementing SFTP

This commit is contained in:
nora 2024-08-30 22:25:09 +02:00
parent a9e2edc572
commit 2ad87d3a14
11 changed files with 549 additions and 32 deletions

View file

@ -7,6 +7,9 @@ edition = "2021"
eyre.workspace = true
tracing.workspace = true
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
cluelessh-sftp = { path = "../../lib/cluelessh-sftp" }
tokio = "1.40.0"
rustix = { version = "0.38.35", features = ["stdio"] }
[lints]
workspace = true

View file

@ -1,8 +1,18 @@
use eyre::Result;
use tracing::info;
use std::{
fs::File,
io,
os::fd::OwnedFd,
pin::Pin,
task::{ready, Poll},
};
use eyre::{Context, Result};
use tokio::io::{unix::AsyncFd, AsyncRead, AsyncWrite};
use tracing::debug;
use tracing_subscriber::EnvFilter;
fn main() -> Result<()> {
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let env_filter =
EnvFilter::try_from_env("SFTP_LOG").unwrap_or_else(|_| EnvFilter::new("debug"));
@ -11,7 +21,90 @@ fn main() -> Result<()> {
.with_env_filter(env_filter)
.init();
info!("mroooow!");
let stdin = rustix::stdio::stdin().try_clone_to_owned()?;
let stdout = rustix::stdio::stdout().try_clone_to_owned()?;
Ok(())
// Ensure that writing to stdout fails
if let Ok(full) = File::open("/dev/full") {
let _ = rustix::stdio::dup2_stdout(&full);
}
let input = AsyncFdWrapper::from_fd(stdin)?;
let output = AsyncFdWrapper::from_fd(stdout)?;
debug!("Starting SFTP server");
let mut server = cluelessh_sftp::SftpServer::new(input, output);
server.serve().await
}
// TODO: Share with cluelesshd
struct AsyncFdWrapper {
fd: AsyncFd<OwnedFd>,
}
impl AsyncFdWrapper {
fn from_fd(fd: OwnedFd) -> Result<Self> {
rustix::io::ioctl_fionbio(&fd, true).wrap_err("putting fd into nonblocking mode")?;
Ok(Self {
fd: AsyncFd::new(fd).wrap_err("failed to register async event")?,
})
}
}
impl AsyncRead for AsyncFdWrapper {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> Poll<Result<(), io::Error>> {
loop {
let mut guard = ready!(self.fd.poll_read_ready(cx))?;
let unfilled = buf.initialize_unfilled();
match guard.try_io(|inner| {
rustix::io::read(inner.get_ref(), unfilled).map_err(io::Error::from)
}) {
Ok(Ok(len)) => {
buf.advance(len);
return Poll::Ready(Ok(()));
}
Ok(Err(err)) => return Poll::Ready(Err(err)),
Err(_would_block) => continue,
}
}
}
}
impl AsyncWrite for AsyncFdWrapper {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, io::Error>> {
loop {
let mut guard = ready!(self.fd.poll_write_ready(cx))?;
match guard
.try_io(|inner| rustix::io::write(inner.get_ref(), buf).map_err(io::Error::from))
{
Ok(result) => return Poll::Ready(result),
Err(_would_block) => continue,
}
}
}
fn poll_flush(
self: Pin<&mut Self>,
_: &mut std::task::Context<'_>,
) -> Poll<Result<(), io::Error>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(
self: Pin<&mut Self>,
_: &mut std::task::Context<'_>,
) -> Poll<Result<(), io::Error>> {
Poll::Ready(Ok(()))
}
}

View file

@ -376,7 +376,10 @@ impl SessionState {
if let Some(writer) = &mut self.writer {
writer.shutdown().await?;
}
// TODO: somehow this isn't enough to close an SFTP connection....
self.writer = None;
self.reader = None;
self.reader_ext = None;
}
ChannelUpdateKind::Open(_)
| ChannelUpdateKind::Closed
@ -409,7 +412,11 @@ impl SessionState {
Ok(())
}
async fn shell(&mut self, shell_command: Option<String>, subsystem: Option<String>) -> Result<()> {
async fn shell(
&mut self,
shell_command: Option<String>,
subsystem: Option<String>,
) -> Result<()> {
let mut fds = self
.rpc_client
.shell(