use std::io;
use async_std::{
channel::Sender,
net::{TcpStream, ToSocketAddrs},
};
use futures::AsyncWriteExt;
use log::*;
use rustls_acme::{futures_rustls::server::TlsStream, AccountCache, CertCache};
pub async fn acme(
prod: bool,
on: impl ToSocketAddrs + std::fmt::Debug,
domains: impl IntoIterator<Item = impl AsRef<str>>,
contact: Option<impl AsRef<str>>,
cert_tx: Sender<Option<(Vec<String>, Vec<u8>)>>,
conns_tx: Option<Sender<TlsStream<TcpStream>>>,
) {
let res = acme_handle(prod, on, domains, contact, cert_tx.clone(), conns_tx).await;
if let Err(e) = res {
warn!("ACME failed: {e}")
}
cert_tx.send(None).await.expect("no certs to send");
}
async fn acme_handle(
prod: bool,
on: impl ToSocketAddrs + std::fmt::Debug,
domains: impl IntoIterator<Item = impl AsRef<str>>,
contact: Option<impl AsRef<str>>,
cert_tx: Sender<Option<(Vec<String>, Vec<u8>)>>,
conns_tx: Option<Sender<TlsStream<TcpStream>>>,
) -> std::io::Result<()> {
use futures::prelude::stream::StreamExt;
use rustls_acme::{caches::DirCache, AcmeConfig};
let contact = contact
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "No ACME without contact (mailto:)"))?;
let cfg = AcmeConfig::new(domains)
.contact_push(contact)
.cache(SharingCache(DirCache::new("./rustls_acme_cache"), cert_tx))
.directory_lets_encrypt(prod);
info!("Running ACME on {on:?}: {cfg:?}");
let tcp_listener = async_std::net::TcpListener::bind(on).await?;
let mut tls_incoming = cfg.incoming(tcp_listener.incoming(), Vec::new());
while let Some(tls) = tls_incoming.next().await {
let mut tls = tls?;
if let Some(tx) = &conns_tx {
tx.send(tls).await;
} else {
tls.write_all(HELLO).await?;
tls.close().await?;
}
}
const HELLO: &'static [u8] = br#"HTTP/1.1 200 OK
Content-Length: 10
Content-Type: text/plain; charset=utf-8
Hello Tls!"#;
Ok(())
}
struct SharingCache<T>(T, Sender<Option<(Vec<String>, Vec<u8>)>>);
#[async_trait::async_trait]
impl<T> AccountCache for SharingCache<T>
where
T: AccountCache,
{
type EA = T::EA;
#[must_use]
#[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)]
async fn load_account(
&self,
contact: &[String],
directory_url: &str,
) -> Result<Option<Vec<u8>>, Self::EA> {
self.0.load_account(contact, directory_url).await
}
#[must_use]
#[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)]
async fn store_account(
&self,
contact: &[String],
directory_url: &str,
account: &[u8],
) -> Result<(), Self::EA> {
self.0.store_account(contact, directory_url, account).await
}
}
#[async_trait::async_trait]
impl<T> CertCache for SharingCache<T>
where
T: CertCache + std::marker::Sync + std::marker::Send,
T::EC: std::marker::Sync + std::marker::Send,
{
type EC = T::EC;
#[must_use]
#[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)]
async fn load_cert(
&self,
domains: &[String],
directory_url: &str,
) -> Result<Option<Vec<u8>>, Self::EC> {
let loaded = self.0.load_cert(domains, directory_url).await;
match loaded {
Ok(Some(cert)) => {
self.1
.send(Some((domains.to_vec(), cert.clone())))
.await
.ok();
Ok(Some(cert))
}
other => other,
}
}
#[must_use]
#[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)]
async fn store_cert(
&self,
domains: &[String],
directory_url: &str,
cert: &[u8],
) -> Result<(), Self::EC> {
self.1
.send(Some((domains.to_vec(), cert.to_vec())))
.await
.ok();
self.0.store_cert(domains, directory_url, cert).await
}
}