From 87440d5a23f0fd894d949295a7c1ff85043550fc Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 28 Oct 2026 22:55:51 +0100 Subject: v0.4.0: add first-time TLS setup and server help --- src/bin/server/args.rs | 85 ++++++++++++++++++++++++++++++++++++-------------- src/bin/server/main.rs | 21 ++++++++++++- src/bin/server/tls.rs | 34 ++++++++++---------- 3 files changed, 99 insertions(+), 41 deletions(-) (limited to 'src/bin') diff --git a/src/bin/server/args.rs b/src/bin/server/args.rs index fc844ea..1babd1d 100644 --- a/src/bin/server/args.rs +++ b/src/bin/server/args.rs @@ -2,6 +2,7 @@ pub struct Args { pub ip: String, pub port: u16, pub unsafe_mode: bool, + pub init: bool, } pub fn parse_args() -> Args { @@ -9,42 +10,80 @@ pub fn parse_args() -> Args { let mut ip = "127.0.0.1".to_string(); let mut port = 11525; let mut unsafe_mode = false; + let mut init = false; let mut i = 1; + while i < args.len() { - if args[i] == "-p" { - if i + 1 < args.len() { - port = args[i + 1].parse::().unwrap_or_else(|_| { - eprintln!("Invalid port number: {}", args[i + 1]); + match args[i].as_str() { + "-p" => { + if i + 1 < args.len() { + port = args[i + 1].parse::().unwrap_or_else(|_| { + eprintln!("Invalid port number: {}", args[i + 1]); + std::process::exit(1); + }); + i += 2; + } else { + eprintln!("-p requires a port number"); std::process::exit(1); - }); - i += 2; - } else { - eprintln!("-p requires a port number"); - std::process::exit(1); + } } - } else if args[i] == "-i" { - if i + 1 < args.len() { - ip = args[i + 1].clone(); - if ip != "127.0.0.1" && ip != "0.0.0.0" { - eprintln!("Invalid IP address: {}. Must be 127.0.0.1 or 0.0.0.0", ip); + "-i" => { + if i + 1 < args.len() { + ip = args[i + 1].clone(); + if ip != "127.0.0.1" && ip != "0.0.0.0" { + eprintln!( + "Invalid IP address: {}. Must be 127.0.0.1 or 0.0.0.0", + ip + ); + std::process::exit(1); + } + i += 2; + } else { + eprintln!("-i requires an IP address"); std::process::exit(1); } - i += 2; - } else { - eprintln!("-i requires an IP address"); + } + "--unsafe" => { + unsafe_mode = true; + i += 1; + } + "--init" => { + init = true; + i += 1; + } + "-h" | "--help" => { + print_usage(); + std::process::exit(0); + } + other => { + eprintln!("Unknown argument: {other}"); + print_usage(); std::process::exit(1); } - } else if args[i] == "--unsafe" { - unsafe_mode = true; - i += 1; - } else { - eprintln!("Unknown argument: {}", args[i]); - std::process::exit(1); } } + Args { ip, port, unsafe_mode, + init, } } + +fn print_usage() { + println!( + "Usage: server [OPTIONS]\n\ + \n\ + Options:\n\ + \x20 -i Bind address (127.0.0.1 or 0.0.0.0)\n\ + \x20 -p Bind port (default 11525)\n\ + \x20 --init Generate identity.p12, key.pem and cert.pem, then exit\n\ + \x20 --unsafe Run in unsafe mode (warning only)\n\ + \x20 -h, --help Show this help\n\ + \n\ + First run:\n\ + \x20 cargo run --bin server -- --init\n\ + \x20 cargo run --bin server" + ); +} diff --git a/src/bin/server/main.rs b/src/bin/server/main.rs index 8dba415..2770387 100644 --- a/src/bin/server/main.rs +++ b/src/bin/server/main.rs @@ -8,6 +8,25 @@ use std::thread; fn main() { let args = args::parse_args(); + + if args.init { + match zagan_bet::server::setup::init_identity(std::path::Path::new("."), "localhost") { + Ok(report) => { + println!("TLS identity created:"); + println!(" {} (PKCS#12, empty password)", report.identity.display()); + println!(" {} (private key)", report.key.display()); + println!(" {} (certificate)", report.cert.display()); + println!("\nStart the server with:\n cargo run --bin server"); + println!("Clients will trust this certificate on first contact."); + } + Err(error) => { + eprintln!("TLS setup failed:\n{error}"); + std::process::exit(1); + } + } + return; + } + let listener = match TcpListener::bind(format!("{}:{}", args.ip, args.port)) { Ok(l) => l, Err(e) => { @@ -16,7 +35,7 @@ fn main() { } }; - let tls = match tls::TlsServer::new("identity.p12", "key.pem", args.unsafe_mode) { + let tls = match tls::TlsServer::new("identity.p12") { Ok(t) => Arc::new(t), Err(e) => { eprintln!("Failed to initialize TLS: {}", e); diff --git a/src/bin/server/tls.rs b/src/bin/server/tls.rs index 62a25db..292ebf7 100644 --- a/src/bin/server/tls.rs +++ b/src/bin/server/tls.rs @@ -9,25 +9,25 @@ pub struct TlsServer { } impl TlsServer { - pub fn new( - cert_path: &str, - _key_path: &str, - unsafe_mode: bool, - ) -> Result> { - let cert_data = if unsafe_mode { - if std::path::Path::new(cert_path).exists() { - std::fs::read(cert_path)? - } else { + pub fn new(cert_path: &str) -> Result> { + let cert_data = match std::fs::read(cert_path) { + Ok(data) => data, + Err(error) => { return Err(format!( - "Certificate file '{}' not found. In --unsafe mode, you must provide a valid PKCS12 certificate file.\n\ - You can generate one with:\n\ - openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes\n\ - openssl pkcs12 -export -out identity.p12 -inkey key.pem -in cert.pem", - cert_path - ).into()); + "Could not read TLS identity '{}': {}\n\ + \n\ + First-time setup:\n\ + \x20 cargo run --bin server -- --init\n\ + \n\ + Or generate it manually:\n\ + \x20 openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \\\n\ + \x20 -days 3650 -nodes -subj \"/CN=localhost\"\n\ + \x20 openssl pkcs12 -export -out identity.p12 -inkey key.pem -in cert.pem \\\n\ + \x20 -passout pass:", + cert_path, error + ) + .into()); } - } else { - std::fs::read(cert_path)? }; let identity = native_tls::Identity::from_pkcs12(&cert_data, "")?; -- cgit v1.2.3