Add TLS transport support

Connect over TLS (default port 6697) using native-tls with the system
OpenSSL trust store for certificate verification. Unify the plaintext and
TLS transports behind a single Box<dyn Read + Write>, dropping the
read/write socket clone that a TLS session cannot support. Add a `tls`
config key and IRC_TLS override; the port auto-defaults to 6697 when TLS
is enabled.
This commit is contained in:
Jean Chevronnet 2026-07-28 14:57:44 +00:00
parent 05c004d32d
commit ab02a5436e
5 changed files with 78 additions and 28 deletions

View file

@ -44,7 +44,7 @@ fn main() {
/// Run a single connection lifecycle: connect, register, loop until disconnect.
fn run_once(config: &Config) -> std::io::Result<()> {
let conn = Connection::connect(&config.server, config.port)?;
let conn = Connection::connect(&config.server, config.port, config.tls)?;
let mut bot = Bot::new(config.clone(), conn);
bot.run()
}
@ -62,6 +62,12 @@ fn load_config() -> Config {
}
apply_env(&mut c);
// Convention: TLS listens on 6697. If TLS is enabled but the port is still
// the plaintext default, switch to the conventional TLS port.
if c.tls && c.port == 6667 {
c.port = 6697;
}
c
}
@ -101,6 +107,7 @@ fn apply_config_file(c: &mut Config, path: &Path) -> io::Result<()> {
Ok(p) => c.port = p,
Err(_) => log(&format!("{}:{}: invalid port '{value}'", path.display(), i + 1)),
},
"tls" => c.tls = parse_bool(value),
"nick" => c.nick = value.to_string(),
"user" => c.user = value.to_string(),
"realname" => c.realname = value.to_string(),
@ -127,6 +134,9 @@ fn apply_env(c: &mut Config) {
if let Some(p) = std::env::var("IRC_PORT").ok().and_then(|v| v.parse().ok()) {
c.port = p;
}
if let Ok(v) = std::env::var("IRC_TLS") {
c.tls = parse_bool(&v);
}
if let Ok(v) = std::env::var("IRC_NICK") {
c.user = v.clone();
c.nick = v;
@ -151,6 +161,14 @@ fn apply_env(c: &mut Config) {
}
}
/// Parse a boolean config value. Accepts `1`, `true`, `yes`, `on` (any case).
fn parse_bool(value: &str) -> bool {
matches!(
value.trim().to_ascii_lowercase().as_str(),
"1" | "true" | "yes" | "on"
)
}
/// Split a channel list on commas and/or whitespace, dropping empties.
fn split_list(value: &str) -> Vec<String> {
value