feat: upgrade OpenMLS 0.5 → 0.8 for security patches and GREASE support
Migrates all MLS code in quicprochat-core from OpenMLS 0.5 to 0.8: - StorageProvider replaces OpenMlsKeyStore (keystore.rs full rewrite) - HybridCryptoProvider updated for new OpenMlsProvider trait - Group operations updated for new API signatures - MLS state persistence via MemoryStorage serialization - tls_codec 0.3 → 0.4, openmls_traits/rust_crypto 0.2 → 0.5
This commit is contained in:
@@ -28,12 +28,159 @@ use quicprochat_client::{
|
||||
#[cfg(all(feature = "tui", not(feature = "v2")))]
|
||||
use quicprochat_client::client::tui::run_tui;
|
||||
|
||||
// ── Config file loading ──────────────────────────────────────────────────────
|
||||
//
|
||||
// Loads a TOML config file and sets QPQ_* environment variables for values
|
||||
// not already set. This runs BEFORE clap parses, so the natural precedence is:
|
||||
// CLI flags > environment variables > config file > compiled defaults.
|
||||
//
|
||||
// Config file search order:
|
||||
// 1. --config <path> (parsed manually from argv)
|
||||
// 2. $QPC_CONFIG env var
|
||||
// 3. $XDG_CONFIG_HOME/qpc/config.toml (usually ~/.config/qpc/config.toml)
|
||||
// 4. ~/.qpc.toml
|
||||
#[cfg(not(feature = "v2"))]
|
||||
mod client_config {
|
||||
use serde::Deserialize;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct ClientFileConfig {
|
||||
pub server: Option<String>,
|
||||
pub server_name: Option<String>,
|
||||
pub ca_cert: Option<String>,
|
||||
pub username: Option<String>,
|
||||
pub password: Option<String>,
|
||||
pub access_token: Option<String>,
|
||||
pub device_id: Option<String>,
|
||||
pub state_password: Option<String>,
|
||||
pub state: Option<String>,
|
||||
pub danger_accept_invalid_certs: Option<bool>,
|
||||
pub no_server: Option<bool>,
|
||||
}
|
||||
|
||||
/// Find and load the config file. Returns the parsed config (or default if
|
||||
/// no file is found).
|
||||
pub fn load_client_config() -> ClientFileConfig {
|
||||
let path = find_config_path();
|
||||
let path = match path {
|
||||
Some(p) if p.exists() => p,
|
||||
_ => return ClientFileConfig::default(),
|
||||
};
|
||||
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(contents) => match toml::from_str(&contents) {
|
||||
Ok(cfg) => {
|
||||
eprintln!("Loaded config: {}", path.display());
|
||||
cfg
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Warning: failed to parse {}: {e}", path.display());
|
||||
ClientFileConfig::default()
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Warning: failed to read {}: {e}", path.display());
|
||||
ClientFileConfig::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_config_path() -> Option<PathBuf> {
|
||||
// 1. --config <path> from argv (before clap parses).
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
for i in 0..args.len().saturating_sub(1) {
|
||||
if args[i] == "--config" || args[i] == "-c" {
|
||||
return Some(PathBuf::from(&args[i + 1]));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. $QPC_CONFIG env var.
|
||||
if let Ok(p) = std::env::var("QPC_CONFIG") {
|
||||
return Some(PathBuf::from(p));
|
||||
}
|
||||
|
||||
// 3. $XDG_CONFIG_HOME/qpc/config.toml
|
||||
let xdg = std::env::var("XDG_CONFIG_HOME")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| {
|
||||
let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
|
||||
PathBuf::from(home).join(".config")
|
||||
});
|
||||
let xdg_path = xdg.join("qpc").join("config.toml");
|
||||
if xdg_path.exists() {
|
||||
return Some(xdg_path);
|
||||
}
|
||||
|
||||
// 4. ~/.qpc.toml
|
||||
if let Ok(home) = std::env::var("HOME") {
|
||||
let home_path = PathBuf::from(home).join(".qpc.toml");
|
||||
if home_path.exists() {
|
||||
return Some(home_path);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Set QPQ_* env vars from config values, but only if they're not already set.
|
||||
pub fn apply_config_to_env(cfg: &ClientFileConfig) {
|
||||
fn set_if_empty(key: &str, val: &str) {
|
||||
if std::env::var(key).is_err() {
|
||||
std::env::set_var(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref v) = cfg.server {
|
||||
set_if_empty("QPQ_SERVER", v);
|
||||
}
|
||||
if let Some(ref v) = cfg.server_name {
|
||||
set_if_empty("QPQ_SERVER_NAME", v);
|
||||
}
|
||||
if let Some(ref v) = cfg.ca_cert {
|
||||
set_if_empty("QPQ_CA_CERT", v);
|
||||
}
|
||||
if let Some(ref v) = cfg.username {
|
||||
set_if_empty("QPQ_USERNAME", v);
|
||||
}
|
||||
if let Some(ref v) = cfg.password {
|
||||
set_if_empty("QPQ_PASSWORD", v);
|
||||
}
|
||||
if let Some(ref v) = cfg.access_token {
|
||||
set_if_empty("QPQ_ACCESS_TOKEN", v);
|
||||
}
|
||||
if let Some(ref v) = cfg.device_id {
|
||||
set_if_empty("QPQ_DEVICE_ID", v);
|
||||
}
|
||||
if let Some(ref v) = cfg.state_password {
|
||||
set_if_empty("QPQ_STATE_PASSWORD", v);
|
||||
}
|
||||
if let Some(ref v) = cfg.state {
|
||||
set_if_empty("QPQ_STATE", v);
|
||||
}
|
||||
if let Some(v) = cfg.danger_accept_invalid_certs {
|
||||
if v {
|
||||
set_if_empty("QPQ_DANGER_ACCEPT_INVALID_CERTS", "true");
|
||||
}
|
||||
}
|
||||
if let Some(v) = cfg.no_server {
|
||||
if v {
|
||||
set_if_empty("QPQ_NO_SERVER", "true");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── CLI ───────────────────────────────────────────────────────────────────────
|
||||
#[cfg(not(feature = "v2"))]
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "qpc", about = "quicprochat CLI client", version)]
|
||||
struct Args {
|
||||
/// Path to a TOML config file (auto-detected from ~/.config/qpc/config.toml or ~/.qpc.toml).
|
||||
#[arg(long, short = 'c', global = true, env = "QPC_CONFIG")]
|
||||
config: Option<PathBuf>,
|
||||
|
||||
/// Path to the server's TLS certificate (self-signed by default).
|
||||
#[arg(
|
||||
long,
|
||||
@@ -540,6 +687,13 @@ async fn main() -> anyhow::Result<()> {
|
||||
)
|
||||
.init();
|
||||
|
||||
// Load config file and apply to env BEFORE clap parses (so config values
|
||||
// act as defaults that env vars and CLI flags can override).
|
||||
{
|
||||
let cfg = client_config::load_client_config();
|
||||
client_config::apply_config_to_env(&cfg);
|
||||
}
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
if args.danger_accept_invalid_certs {
|
||||
|
||||
Reference in New Issue
Block a user