Skip to main content

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}