use std::io::Write;
use std::sync::Arc;
use async_trait::async_trait;
use unftp_core::auth::{AuthenticationError, Authenticator, Credentials, Principal};
use unftp_sbe_fs::Filesystem;
pub fn random_tls_certificate() -> anyhow::Result<(Vec<u8>, Vec<u8>)> {
use openssl::{rsa::Rsa, x509::X509, pkey::PKey, asn1::{Asn1Integer, Asn1Time}, bn::BigNum};
let key = Rsa::generate(1 << 11)?;
let pkey = PKey::from_rsa(key)?;
let mut builder = X509::builder()?;
let one = BigNum::from_u32(1)?;
let serial_number = Asn1Integer::from_bn(&one)?;
builder.set_serial_number(&serial_number)?;
builder.set_version(2)?;
let mut name = openssl::x509::X509NameBuilder::new()?;
name.append_entry_by_text("C", "CA")?;
name.append_entry_by_text("ST", "ON")?;
name.append_entry_by_text("O", "Inside the house")?;
name.append_entry_by_text("CN", "localhost")?;
name.append_entry_by_text("subjectAltName", "DNS:localhost,IP:127.0.0.1")?;
let name = name.build();
builder.set_issuer_name(&name)?;
builder.set_subject_name(&name)?;
let not_before = Asn1Time::from_unix((chrono::Utc::now() - chrono::Duration::days(1)).timestamp())?;
builder.set_not_before(¬_before)?;
let not_after = Asn1Time::from_unix((chrono::Utc::now() + chrono::Duration::days(366)).timestamp())?;
builder.set_not_after(¬_after)?;
builder.set_pubkey(&pkey)?;
builder.sign(&pkey, openssl::hash::MessageDigest::sha256())?;
let cert = builder.build();
println!("{:?}", cert.subject_alt_names());
Ok((cert.to_pem()?, pkey.rsa()?.private_key_to_pem()?))
}
pub async fn start_temp_ftp_server(tls_cert: Option<(Vec<u8>, Vec<u8>)>) -> String {
#[derive(Debug)]
struct StaticAuth;
#[async_trait]
impl Authenticator for StaticAuth {
async fn authenticate(&self, username: &str, creds: &Credentials) -> Result<Principal, AuthenticationError> {
if username.eq_ignore_ascii_case("bozo") {
if let Some(password) = &creds.password {
if password == "theclown" {
return Ok(Principal {
username: username.to_ascii_lowercase()
})
}
}
}
Err(AuthenticationError::BadUser)
}
}
let dir = tempfile::tempdir().unwrap();
let certs_file = tempfile::NamedTempFile::new().unwrap();
let key_file = tempfile::NamedTempFile::new().unwrap();
if let Some((public, private)) = &tls_cert {
certs_file.as_file().write_all(public).unwrap();
key_file.as_file().write_all(private).unwrap();
}
let listener = tokio::net::TcpListener::bind("0.0.0.0:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
let dir_path = dir.path().to_owned();
loop {
let dir_path = dir_path.clone();
let mut server_builder = libunftp::ServerBuilder::new(Box::new(move || Filesystem::new(dir_path.clone()).unwrap()))
.authenticator(Arc::new(StaticAuth{}))
.passive_ports(50000..=65535);
if tls_cert.is_some() {
server_builder = server_builder.ftps(certs_file.path(), key_file.path());
}
let server = server_builder.build().unwrap();
let (sock, _) = listener.accept().await.unwrap();
println!("test server accepted connection");
tokio::spawn(async move {
server.service(sock).await.unwrap();
});
}
});
return format!("bozo:theclown@127.0.0.1:{}", addr.port())
}