use std::{
env,
io::{stderr, stdout},
sync::Arc,
};
use anyhow::{Context, Result};
use clap::Parser;
use rustls::{
ServerConfig,
pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject},
};
use tokio::{io::AsyncWriteExt, net::TcpListener};
use tokio_rustls::{TlsAcceptor, rustls};
use tracing::{debug, error, subscriber};
use tracing_appender::rolling;
use tracing_subscriber::fmt::format::FmtSpan;
use crate::cli::{Args, Trace};
mod cli;
mod gmi;
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse().resolve()?;
let (non_blocking_writer, _guard) = match args.trace {
Trace::Stdout => tracing_appender::non_blocking(stdout()),
Trace::Stderr => tracing_appender::non_blocking(stderr()),
Trace::Tmp => {
let temp_dir = env::temp_dir().join("zhuque-gemini");
let file_appender = rolling::hourly(temp_dir, "zq.gmi");
tracing_appender::non_blocking(file_appender)
}
};
let sub = tracing_subscriber::fmt()
.compact()
.with_file(true)
.with_line_number(true)
.with_target(false)
.with_span_events(FmtSpan::NEW)
.with_writer(non_blocking_writer)
.with_ansi(false)
.with_max_level(args.level)
.finish();
subscriber::set_global_default(sub).expect("Could not set global default subscriber");
debug!("with args: {:?}", args);
let certs = CertificateDer::from_pem_file(&args.cert)
.with_context(|| format!("failed to read cert pem file: {}", args.cert.display()))
.map(|c| vec![c])?;
let key = PrivateKeyDer::from_pem_file(&args.key)
.with_context(|| format!("failed to read key pem file: {}", args.key.display()))?;
let config = ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)?;
let addr = format!("{}:{}", args.addr, args.port);
if let Some(badge) = &args.badge {
println!("{badge}");
}
let acceptor = TlsAcceptor::from(Arc::new(config));
let listener = TcpListener::bind(&addr).await?;
loop {
let (tcp_stream, from) = listener.accept().await?;
let acceptor = acceptor.clone();
let root = args.root.clone();
let index = args.index.clone();
let footer = args.footer.clone();
tokio::spawn(async move {
match acceptor.accept(tcp_stream).await {
Ok(mut tls_stream) => {
let _ =
gmi::handle(from, &mut tls_stream, root, index, footer.as_deref()).await;
if let Err(e) = tls_stream.flush().await {
error!("Could not flush stream: {e}");
};
if let Err(e) = tls_stream.shutdown().await {
error!("Could not shut down connection: {e}");
};
}
Err(e) => {
error!("TLS handshake failed from {}: {}", from, e);
}
}
});
}
}