Files
smartmta/rust/crates/mailer-bin/src/main.rs

1055 lines
35 KiB
Rust
Raw Normal View History

//! mailer-bin: CLI and IPC binary for the @push.rocks/smartmta Rust crates.
//!
//! Supports two modes:
//! 1. **CLI mode** — traditional subcommands for testing and standalone use
//! 2. **Management mode** (`--management`) — JSON-over-stdin/stdout IPC for
//! integration with `@push.rocks/smartrust` from TypeScript
use clap::{Parser, Subcommand};
use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use std::io::{self, BufRead, Write};
use std::net::IpAddr;
use std::sync::Arc;
use tokio::sync::oneshot;
use mailer_smtp::connection::{
AuthResult, CallbackRegistry, ConnectionEvent, EmailProcessingResult,
};
/// mailer-bin: Rust-powered email security tools
#[derive(Parser)]
#[command(name = "mailer-bin", version, about)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
/// Run in management/IPC mode (JSON-over-stdin/stdout for smartrust bridge)
#[arg(long)]
management: bool,
}
#[derive(Subcommand)]
enum Commands {
/// Print version information
Version,
/// Validate an email address
Validate {
/// The email address to validate
email: String,
},
/// Detect bounce type from an SMTP response
Bounce {
/// The SMTP response or diagnostic message
message: String,
},
/// Check IP reputation via DNSBL
CheckIp {
/// The IP address to check
ip: String,
},
/// Verify DKIM/SPF/DMARC for an email (reads raw message from stdin)
VerifyEmail {
/// Sender IP address for SPF check
#[arg(long)]
ip: Option<String>,
/// HELO domain for SPF check
#[arg(long)]
helo: Option<String>,
/// Receiving server hostname
#[arg(long, default_value = "localhost")]
hostname: String,
/// MAIL FROM address for SPF check
#[arg(long)]
mail_from: Option<String>,
},
/// Sign an email with DKIM (reads raw message from stdin)
DkimSign {
/// Signing domain
#[arg(long)]
domain: String,
/// DKIM selector
#[arg(long, default_value = "mta")]
selector: String,
/// Path to RSA private key PEM file
#[arg(long)]
key: String,
},
}
// --- IPC types for smartrust bridge ---
#[derive(Deserialize)]
struct IpcRequest {
id: String,
method: String,
params: serde_json::Value,
}
#[derive(Serialize)]
struct IpcResponse {
id: String,
success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
result: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>,
}
#[derive(Serialize)]
struct IpcEvent {
event: String,
data: serde_json::Value,
}
// --- Pending callbacks for correlation-ID based reverse calls ---
/// Stores oneshot senders for pending email processing and auth callbacks.
struct PendingCallbacks {
email: DashMap<String, oneshot::Sender<EmailProcessingResult>>,
auth: DashMap<String, oneshot::Sender<AuthResult>>,
}
impl PendingCallbacks {
fn new() -> Self {
Self {
email: DashMap::new(),
auth: DashMap::new(),
}
}
}
impl CallbackRegistry for PendingCallbacks {
fn register_email_callback(
&self,
correlation_id: &str,
) -> oneshot::Receiver<EmailProcessingResult> {
let (tx, rx) = oneshot::channel();
self.email.insert(correlation_id.to_string(), tx);
rx
}
fn register_auth_callback(
&self,
correlation_id: &str,
) -> oneshot::Receiver<AuthResult> {
let (tx, rx) = oneshot::channel();
self.auth.insert(correlation_id.to_string(), tx);
rx
}
}
fn main() {
let cli = Cli::parse();
if cli.management {
run_management_mode();
return;
}
match cli.command {
Some(Commands::Version) | None => {
println!(
"mailer-bin v{} (core: {}, smtp: {}, security: {})",
env!("CARGO_PKG_VERSION"),
mailer_core::version(),
mailer_smtp::version(),
mailer_security::version(),
);
}
Some(Commands::Validate { email }) => {
let result = mailer_core::validate_email(&email);
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"email": email,
"valid": result.is_valid,
"formatValid": result.format_valid,
"score": result.score,
"error": result.error_message,
}))
.unwrap()
);
}
Some(Commands::Bounce { message }) => {
let detection = mailer_core::detect_bounce_type(Some(&message), None, None);
println!(
"{}",
serde_json::to_string_pretty(&detection).unwrap()
);
}
Some(Commands::CheckIp { ip }) => {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let ip_addr: IpAddr = match ip.parse() {
Ok(addr) => addr,
Err(e) => {
eprintln!("Invalid IP address: {}", e);
std::process::exit(1);
}
};
let resolver = hickory_resolver::TokioResolver::builder_tokio().map(|b| b.build())
.expect("Failed to create DNS resolver");
match mailer_security::check_reputation(
ip_addr,
mailer_security::DEFAULT_DNSBL_SERVERS,
&resolver,
)
.await
{
Ok(result) => {
println!("{}", serde_json::to_string_pretty(&result).unwrap());
}
Err(e) => {
eprintln!("Error: {}", e);
std::process::exit(1);
}
}
});
}
Some(Commands::VerifyEmail {
ip,
helo,
hostname,
mail_from,
}) => {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
// Read raw message from stdin
let mut raw_message = Vec::new();
io::stdin()
.lock()
.read_to_end(&mut raw_message)
.expect("Failed to read from stdin");
let authenticator = mailer_security::default_authenticator()
.expect("Failed to create authenticator");
// DKIM verification
let dkim_results = mailer_security::verify_dkim(&raw_message, &authenticator)
.await
.unwrap_or_else(|e| {
vec![mailer_security::DkimVerificationResult {
is_valid: false,
domain: None,
selector: None,
status: "error".to_string(),
details: Some(e.to_string()),
}]
});
let mut output = serde_json::json!({
"dkim": dkim_results,
});
// SPF verification (if IP provided)
if let (Some(ip_str), Some(helo_domain), Some(sender)) =
(&ip, &helo, &mail_from)
{
if let Ok(ip_addr) = ip_str.parse::<IpAddr>() {
match mailer_security::check_spf(
ip_addr,
helo_domain,
&hostname,
sender,
&authenticator,
)
.await
{
Ok(spf_result) => {
output["spf"] = serde_json::to_value(&spf_result).unwrap();
}
Err(e) => {
output["spf"] =
serde_json::json!({"error": e.to_string()});
}
}
}
}
println!("{}", serde_json::to_string_pretty(&output).unwrap());
});
}
Some(Commands::DkimSign {
domain,
selector,
key,
}) => {
// Read private key
let key_pem = std::fs::read_to_string(&key).unwrap_or_else(|e| {
eprintln!("Failed to read key file '{}': {}", key, e);
std::process::exit(1);
});
// Read raw message from stdin
let mut raw_message = Vec::new();
io::stdin()
.lock()
.read_to_end(&mut raw_message)
.expect("Failed to read from stdin");
match mailer_security::sign_dkim(&raw_message, &domain, &selector, &key_pem) {
Ok(header) => {
// Output signed message: DKIM header + original message
print!("{}", header);
io::stdout().write_all(&raw_message).unwrap();
}
Err(e) => {
eprintln!("DKIM signing failed: {}", e);
std::process::exit(1);
}
}
}
}
}
use std::io::Read;
/// Shared state for the management mode.
struct ManagementState {
callbacks: Arc<PendingCallbacks>,
smtp_handle: Option<mailer_smtp::server::SmtpServerHandle>,
smtp_event_rx: Option<tokio::sync::mpsc::Receiver<ConnectionEvent>>,
}
/// Run in management/IPC mode for smartrust bridge.
///
/// This mode supports both request/response IPC (existing commands) and
/// long-running SMTP server with event-based callbacks.
fn run_management_mode() {
// Signal readiness
let ready_event = IpcEvent {
event: "ready".to_string(),
data: serde_json::json!({
"version": env!("CARGO_PKG_VERSION"),
"core_version": mailer_core::version(),
"security_version": mailer_security::version(),
}),
};
println!("{}", serde_json::to_string(&ready_event).unwrap());
io::stdout().flush().unwrap();
let rt = tokio::runtime::Runtime::new().unwrap();
let callbacks = Arc::new(PendingCallbacks::new());
let mut state = ManagementState {
callbacks: callbacks.clone(),
smtp_handle: None,
smtp_event_rx: None,
};
// We need to read stdin in a separate thread (blocking I/O)
// and process commands + SMTP events in the tokio runtime.
let (cmd_tx, mut cmd_rx) = tokio::sync::mpsc::channel::<String>(256);
// Spawn stdin reader thread
std::thread::spawn(move || {
let stdin = io::stdin();
for line in stdin.lock().lines() {
match line {
Ok(l) if !l.trim().is_empty() => {
if cmd_tx.blocking_send(l).is_err() {
break;
}
}
Ok(_) => continue,
Err(_) => break,
}
}
});
rt.block_on(async {
loop {
// Select between stdin commands and SMTP server events
tokio::select! {
cmd = cmd_rx.recv() => {
match cmd {
Some(line) => {
let req: IpcRequest = match serde_json::from_str(&line) {
Ok(r) => r,
Err(e) => {
let resp = IpcResponse {
id: "unknown".to_string(),
success: false,
result: None,
error: Some(format!("Invalid request: {}", e)),
};
emit_line(&serde_json::to_string(&resp).unwrap());
continue;
}
};
let response = handle_ipc_request(&req, &mut state).await;
emit_line(&serde_json::to_string(&response).unwrap());
}
None => {
// stdin closed — shut down
if let Some(handle) = state.smtp_handle.take() {
handle.shutdown().await;
}
break;
}
}
}
event = async {
if let Some(rx) = &mut state.smtp_event_rx {
rx.recv().await
} else {
// No SMTP server running — wait forever (yields to other branch)
std::future::pending::<Option<ConnectionEvent>>().await
}
} => {
if let Some(event) = event {
handle_smtp_event(event);
}
}
}
}
});
}
/// Emit a line to stdout and flush.
fn emit_line(line: &str) {
let stdout = io::stdout();
let mut handle = stdout.lock();
let _ = writeln!(handle, "{}", line);
let _ = handle.flush();
}
/// Emit an IPC event to stdout.
fn emit_event(event_name: &str, data: serde_json::Value) {
let event = IpcEvent {
event: event_name.to_string(),
data,
};
emit_line(&serde_json::to_string(&event).unwrap());
}
/// Handle a connection event from the SMTP server.
fn handle_smtp_event(event: ConnectionEvent) {
match event {
ConnectionEvent::EmailReceived {
correlation_id,
session_id,
mail_from,
rcpt_to,
data,
remote_addr,
client_hostname,
secure,
authenticated_user,
security_results,
} => {
emit_event(
"emailReceived",
serde_json::json!({
"correlationId": correlation_id,
"sessionId": session_id,
"mailFrom": mail_from,
"rcptTo": rcpt_to,
"data": data,
"remoteAddr": remote_addr,
"clientHostname": client_hostname,
"secure": secure,
"authenticatedUser": authenticated_user,
"securityResults": security_results,
}),
);
}
ConnectionEvent::AuthRequest {
correlation_id,
session_id,
username,
password,
remote_addr,
} => {
emit_event(
"authRequest",
serde_json::json!({
"correlationId": correlation_id,
"sessionId": session_id,
"username": username,
"password": password,
"remoteAddr": remote_addr,
}),
);
}
}
}
async fn handle_ipc_request(req: &IpcRequest, state: &mut ManagementState) -> IpcResponse {
match req.method.as_str() {
"ping" => IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::json!({"pong": true})),
error: None,
},
"version" => IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::json!({
"bin": env!("CARGO_PKG_VERSION"),
"core": mailer_core::version(),
"security": mailer_security::version(),
"smtp": mailer_smtp::version(),
})),
error: None,
},
"validateEmail" => {
let email = req.params.get("email").and_then(|v| v.as_str()).unwrap_or("");
let result = mailer_core::validate_email(email);
IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::json!({
"valid": result.is_valid,
"formatValid": result.format_valid,
"score": result.score,
"error": result.error_message,
})),
error: None,
}
}
"detectBounce" => {
let smtp_response = req.params.get("smtpResponse").and_then(|v| v.as_str());
let diagnostic = req.params.get("diagnosticCode").and_then(|v| v.as_str());
let status = req.params.get("statusCode").and_then(|v| v.as_str());
let detection = mailer_core::detect_bounce_type(smtp_response, diagnostic, status);
IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::to_value(&detection).unwrap()),
error: None,
}
}
"checkIpReputation" => {
let ip_str = req.params.get("ip").and_then(|v| v.as_str()).unwrap_or("");
match ip_str.parse::<IpAddr>() {
Ok(ip_addr) => {
let resolver = match hickory_resolver::TokioResolver::builder_tokio().map(|b| b.build()) {
Ok(r) => r,
Err(e) => {
return IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(format!("DNS resolver error: {}", e)),
};
}
};
match mailer_security::check_reputation(
ip_addr,
mailer_security::DEFAULT_DNSBL_SERVERS,
&resolver,
)
.await
{
Ok(result) => IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::to_value(&result).unwrap()),
error: None,
},
Err(e) => IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(e.to_string()),
},
}
}
Err(e) => IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(format!("Invalid IP address: {}", e)),
},
}
}
"verifyDkim" => {
let raw_message = req
.params
.get("rawMessage")
.and_then(|v| v.as_str())
.unwrap_or("");
let authenticator = match mailer_security::default_authenticator() {
Ok(a) => a,
Err(e) => {
return IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(format!("Authenticator error: {}", e)),
};
}
};
match mailer_security::verify_dkim(raw_message.as_bytes(), &authenticator).await {
Ok(results) => IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::to_value(&results).unwrap()),
error: None,
},
Err(e) => IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(e.to_string()),
},
}
}
"signDkim" => {
let raw_message = req
.params
.get("rawMessage")
.and_then(|v| v.as_str())
.unwrap_or("");
let domain = req.params.get("domain").and_then(|v| v.as_str()).unwrap_or("");
let selector = req
.params
.get("selector")
.and_then(|v| v.as_str())
.unwrap_or("mta");
let private_key = req
.params
.get("privateKey")
.and_then(|v| v.as_str())
.unwrap_or("");
match mailer_security::sign_dkim(raw_message.as_bytes(), domain, selector, private_key) {
Ok(header) => IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::json!({
"header": header,
"signedMessage": format!("{}{}", header, raw_message),
})),
error: None,
},
Err(e) => IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(e.to_string()),
},
}
}
"verifyEmail" => {
let raw_message = req
.params
.get("rawMessage")
.and_then(|v| v.as_str())
.unwrap_or("");
let ip_str = req.params.get("ip").and_then(|v| v.as_str()).unwrap_or("");
let helo = req
.params
.get("heloDomain")
.and_then(|v| v.as_str())
.unwrap_or("");
let hostname = req
.params
.get("hostname")
.and_then(|v| v.as_str())
.unwrap_or("localhost");
let mail_from = req
.params
.get("mailFrom")
.and_then(|v| v.as_str())
.unwrap_or("");
match ip_str.parse::<IpAddr>() {
Ok(ip_addr) => {
let authenticator = match mailer_security::default_authenticator() {
Ok(a) => a,
Err(e) => {
return IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(format!("Authenticator error: {}", e)),
};
}
};
match mailer_security::verify_email_security(
raw_message.as_bytes(),
ip_addr,
helo,
hostname,
mail_from,
&authenticator,
)
.await
{
Ok(result) => IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::to_value(&result).unwrap()),
error: None,
},
Err(e) => IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(e.to_string()),
},
}
}
Err(e) => IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(format!("Invalid IP address: {}", e)),
},
}
}
"scanContent" => {
let subject = req.params.get("subject").and_then(|v| v.as_str());
let text_body = req.params.get("textBody").and_then(|v| v.as_str());
let html_body = req.params.get("htmlBody").and_then(|v| v.as_str());
let attachment_names: Vec<String> = req.params.get("attachmentNames")
.and_then(|v| v.as_array())
.map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect())
.unwrap_or_default();
let result = mailer_security::content_scanner::scan_content(
subject, text_body, html_body, &attachment_names
);
IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::to_value(&result).unwrap()),
error: None,
}
}
"checkSpf" => {
let ip_str = req.params.get("ip").and_then(|v| v.as_str()).unwrap_or("");
let helo = req
.params
.get("heloDomain")
.and_then(|v| v.as_str())
.unwrap_or("");
let hostname = req
.params
.get("hostname")
.and_then(|v| v.as_str())
.unwrap_or("localhost");
let mail_from = req
.params
.get("mailFrom")
.and_then(|v| v.as_str())
.unwrap_or("");
match ip_str.parse::<IpAddr>() {
Ok(ip_addr) => {
let authenticator = match mailer_security::default_authenticator() {
Ok(a) => a,
Err(e) => {
return IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(format!("Authenticator error: {}", e)),
};
}
};
match mailer_security::check_spf(ip_addr, helo, hostname, mail_from, &authenticator)
.await
{
Ok(result) => IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::to_value(&result).unwrap()),
error: None,
},
Err(e) => IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(e.to_string()),
},
}
}
Err(e) => IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(format!("Invalid IP address: {}", e)),
},
}
}
// --- SMTP Server lifecycle commands ---
"startSmtpServer" => {
handle_start_smtp_server(req, state).await
}
"stopSmtpServer" => {
handle_stop_smtp_server(req, state).await
}
"emailProcessingResult" => {
handle_email_processing_result(req, state)
}
"authResult" => {
handle_auth_result(req, state)
}
"configureRateLimits" => {
// Rate limit configuration is set at startSmtpServer time.
// This command allows runtime updates, but for now we acknowledge it.
IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::json!({"configured": true})),
error: None,
}
}
_ => IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(format!("Unknown method: {}", req.method)),
},
}
}
/// Handle startSmtpServer IPC command.
async fn handle_start_smtp_server(req: &IpcRequest, state: &mut ManagementState) -> IpcResponse {
// Stop existing server if running
if let Some(handle) = state.smtp_handle.take() {
handle.shutdown().await;
}
// Parse config from params
let config = match parse_smtp_config(&req.params) {
Ok(c) => c,
Err(e) => {
return IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(format!("Invalid config: {}", e)),
};
}
};
// Parse optional rate limit config
let rate_config = req.params.get("rateLimits").and_then(|v| {
serde_json::from_value::<mailer_smtp::rate_limiter::RateLimitConfig>(v.clone()).ok()
});
match mailer_smtp::server::start_server(config, state.callbacks.clone(), rate_config).await {
Ok((handle, event_rx)) => {
state.smtp_handle = Some(handle);
state.smtp_event_rx = Some(event_rx);
IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::json!({"started": true})),
error: None,
}
}
Err(e) => IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(format!("Failed to start SMTP server: {}", e)),
},
}
}
/// Handle stopSmtpServer IPC command.
async fn handle_stop_smtp_server(req: &IpcRequest, state: &mut ManagementState) -> IpcResponse {
if let Some(handle) = state.smtp_handle.take() {
handle.shutdown().await;
state.smtp_event_rx = None;
IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::json!({"stopped": true})),
error: None,
}
} else {
IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::json!({"stopped": true, "wasRunning": false})),
error: None,
}
}
}
/// Handle emailProcessingResult IPC command — resolves a pending email callback.
fn handle_email_processing_result(req: &IpcRequest, state: &ManagementState) -> IpcResponse {
let correlation_id = req
.params
.get("correlationId")
.and_then(|v| v.as_str())
.unwrap_or("");
let result = EmailProcessingResult {
accepted: req.params.get("accepted").and_then(|v| v.as_bool()).unwrap_or(false),
smtp_code: req.params.get("smtpCode").and_then(|v| v.as_u64()).map(|v| v as u16),
smtp_message: req
.params
.get("smtpMessage")
.and_then(|v| v.as_str())
.map(String::from),
};
if let Some((_, tx)) = state.callbacks.email.remove(correlation_id) {
let _ = tx.send(result);
IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::json!({"resolved": true})),
error: None,
}
} else {
IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(format!(
"No pending callback for correlationId: {}",
correlation_id
)),
}
}
}
/// Handle authResult IPC command — resolves a pending auth callback.
fn handle_auth_result(req: &IpcRequest, state: &ManagementState) -> IpcResponse {
let correlation_id = req
.params
.get("correlationId")
.and_then(|v| v.as_str())
.unwrap_or("");
let result = AuthResult {
success: req.params.get("success").and_then(|v| v.as_bool()).unwrap_or(false),
message: req
.params
.get("message")
.and_then(|v| v.as_str())
.map(String::from),
};
if let Some((_, tx)) = state.callbacks.auth.remove(correlation_id) {
let _ = tx.send(result);
IpcResponse {
id: req.id.clone(),
success: true,
result: Some(serde_json::json!({"resolved": true})),
error: None,
}
} else {
IpcResponse {
id: req.id.clone(),
success: false,
result: None,
error: Some(format!(
"No pending auth callback for correlationId: {}",
correlation_id
)),
}
}
}
/// Parse SmtpServerConfig from IPC params JSON.
fn parse_smtp_config(
params: &serde_json::Value,
) -> Result<mailer_smtp::config::SmtpServerConfig, String> {
let mut config = mailer_smtp::config::SmtpServerConfig::default();
if let Some(hostname) = params.get("hostname").and_then(|v| v.as_str()) {
config.hostname = hostname.to_string();
}
if let Some(ports) = params.get("ports").and_then(|v| v.as_array()) {
config.ports = ports
.iter()
.filter_map(|v| v.as_u64().map(|p| p as u16))
.collect();
}
if let Some(secure_port) = params.get("securePort").and_then(|v| v.as_u64()) {
config.secure_port = Some(secure_port as u16);
}
if let Some(cert) = params.get("tlsCertPem").and_then(|v| v.as_str()) {
config.tls_cert_pem = Some(cert.to_string());
}
if let Some(key) = params.get("tlsKeyPem").and_then(|v| v.as_str()) {
config.tls_key_pem = Some(key.to_string());
}
if let Some(size) = params.get("maxMessageSize").and_then(|v| v.as_u64()) {
config.max_message_size = size;
}
if let Some(conns) = params.get("maxConnections").and_then(|v| v.as_u64()) {
config.max_connections = conns as u32;
}
if let Some(rcpts) = params.get("maxRecipients").and_then(|v| v.as_u64()) {
config.max_recipients = rcpts as u32;
}
if let Some(timeout) = params.get("connectionTimeoutSecs").and_then(|v| v.as_u64()) {
config.connection_timeout_secs = timeout;
}
if let Some(timeout) = params.get("dataTimeoutSecs").and_then(|v| v.as_u64()) {
config.data_timeout_secs = timeout;
}
if let Some(auth) = params.get("authEnabled").and_then(|v| v.as_bool()) {
config.auth_enabled = auth;
}
if let Some(failures) = params.get("maxAuthFailures").and_then(|v| v.as_u64()) {
config.max_auth_failures = failures as u32;
}
if let Some(timeout) = params.get("socketTimeoutSecs").and_then(|v| v.as_u64()) {
config.socket_timeout_secs = timeout;
}
if let Some(timeout) = params.get("processingTimeoutSecs").and_then(|v| v.as_u64()) {
config.processing_timeout_secs = timeout;
}
Ok(config)
}