chat_system/servers/tls_irc.rs
1//! TLS-enabled IRC listener implementation.
2
3use crate::server::{ChatListener, MessageHandler};
4use anyhow::Result;
5use async_trait::async_trait;
6use std::sync::Arc;
7use tokio::net::TcpListener;
8use tokio_rustls::TlsAcceptor;
9
10/// A TLS-enabled TCP listener that speaks the IRC protocol.
11///
12/// Wraps a [`rustls::ServerConfig`] and performs TLS termination before handing
13/// off to the same IRC message-handling logic used by
14/// [`IrcListener`](super::IrcListener).
15///
16/// # Example
17///
18/// ```rust,no_run
19/// use chat_system::server::Server;
20/// use chat_system::servers::TlsIrcListener;
21/// use chat_system::ChatServer;
22/// use std::sync::Arc;
23///
24/// # #[tokio::main] async fn main() -> anyhow::Result<()> {
25/// let tls_config = rustls::ServerConfig::builder()
26/// .with_no_client_auth()
27/// .with_single_cert(/* certs */ vec![], /* key */ todo!())?;
28///
29/// let mut server = Server::new("secure-echo")
30/// .add_listener(TlsIrcListener::new("0.0.0.0:6697", Arc::new(tls_config)));
31///
32/// server.run(|msg| async move {
33/// Ok(Some(format!("echo: {}", msg.content)))
34/// }).await?;
35/// # Ok(()) }
36/// ```
37pub struct TlsIrcListener {
38 address: String,
39 acceptor: TlsAcceptor,
40 shutdown_tx: Option<tokio::sync::watch::Sender<bool>>,
41}
42
43impl TlsIrcListener {
44 /// Create a new [`TlsIrcListener`] that will bind to `address` and terminate
45 /// TLS using the provided [`rustls::ServerConfig`].
46 pub fn new(address: impl Into<String>, config: Arc<rustls::ServerConfig>) -> Self {
47 Self {
48 address: address.into(),
49 acceptor: TlsAcceptor::from(config),
50 shutdown_tx: None,
51 }
52 }
53}
54
55#[async_trait]
56impl ChatListener for TlsIrcListener {
57 fn address(&self) -> &str {
58 &self.address
59 }
60
61 fn protocol(&self) -> &str {
62 "irc+tls"
63 }
64
65 async fn start(
66 &mut self,
67 handler: MessageHandler,
68 alive: tokio::sync::mpsc::Sender<()>,
69 ) -> Result<()> {
70 let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(false);
71 let listener = TcpListener::bind(&self.address).await?;
72 // Update to the actual bound address (useful when port 0 is requested).
73 self.address = listener.local_addr()?.to_string();
74 tracing::info!(address = %self.address, "IRC+TLS listener bound");
75 self.shutdown_tx = Some(shutdown_tx);
76
77 let acceptor = self.acceptor.clone();
78
79 tokio::spawn(async move {
80 let _alive = alive;
81
82 loop {
83 tokio::select! {
84 result = listener.accept() => {
85 match result {
86 Ok((stream, peer)) => {
87 tracing::debug!(%peer, "IRC+TLS listener: new connection");
88 let h = Arc::clone(&handler);
89 let acc = acceptor.clone();
90 tokio::spawn(async move {
91 match acc.accept(stream).await {
92 Ok(tls_stream) => {
93 if let Err(e) = super::irc::handle_connection(tls_stream, h).await {
94 tracing::warn!("IRC+TLS connection error: {e}");
95 }
96 }
97 Err(e) => {
98 tracing::warn!("TLS handshake error from {peer}: {e}");
99 }
100 }
101 });
102 }
103 Err(e) => {
104 tracing::warn!("IRC+TLS listener accept error: {e}");
105 break;
106 }
107 }
108 }
109 _ = shutdown_rx.changed() => {
110 if *shutdown_rx.borrow() {
111 break;
112 }
113 }
114 }
115 }
116 });
117
118 Ok(())
119 }
120
121 async fn shutdown(&mut self) -> Result<()> {
122 if let Some(tx) = &self.shutdown_tx {
123 let _ = tx.send(true);
124 }
125 Ok(())
126 }
127}