diff --git a/src/cache.rs b/src/cache.rs index 6907551..a120525 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -68,7 +68,7 @@ impl Cache { Err(e) => { crate::log::warn( "cache", - &format!("Cache-Lesefehler (trusted_networks): {e}"), + &format!("Cache read error (trusted_networks): {e}"), ); false } @@ -90,7 +90,7 @@ impl Cache { { crate::log::warn( "cache", - &format!("Cache-Schreibfehler (trusted_networks, ignoriert): {e}"), + &format!("Cache write error (trusted_networks, ignored): {e}"), ); } } @@ -108,7 +108,7 @@ impl Cache { { Ok(rows) => rows, Err(e) => { - crate::log::warn("cache", &format!("Cache-Lesefehler: {e}")); + crate::log::warn("cache", &format!("Cache read error: {e}")); return None; } }; @@ -126,7 +126,7 @@ impl Cache { } Ok(None) => None, Err(e) => { - crate::log::warn("cache", &format!("Cache-Lesefehler: {e}")); + crate::log::warn("cache", &format!("Cache read error: {e}")); None } } @@ -145,7 +145,7 @@ impl Cache { ) .await { - crate::log::warn("cache", &format!("Cache-Schreibfehler (ignoriert): {e}")); + crate::log::warn("cache", &format!("Cache write error (ignored): {e}")); } } } @@ -158,10 +158,7 @@ pub fn is_expired(updated_at: i64, now: i64, ttl_seconds: u64) -> bool { fn ensure_cache_dir(dir: &Path) -> Result<(), String> { if !dir.exists() { std::fs::create_dir_all(dir).map_err(|e| { - format!( - "Verzeichnis '{}' konnte nicht angelegt werden: {e}", - dir.display() - ) + format!("could not create directory '{}': {e}", dir.display()) })?; // create_dir_all() unterliegt dem Prozess-Umask; explizites chmod ist nötig, um // wirklich 0777 zu erreichen (Verzeichnis-Rechte steuern nicht die Umask neuer Dateien). @@ -169,7 +166,7 @@ fn ensure_cache_dir(dir: &Path) -> Result<(), String> { // unprivilegierter Aufruf auf root-eigenen Pfaden mit EPERM fehlschlagen. #[cfg(unix)] std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o777)) - .map_err(|e| format!("Verzeichnis-Rechte konnten nicht gesetzt werden: {e}"))?; + .map_err(|e| format!("could not set directory permissions: {e}"))?; } Ok(()) } @@ -191,7 +188,7 @@ pub async fn try_open_cache(db_path: &Path) -> Option { if let Some(dir) = db_path.parent() && let Err(reason) = ensure_cache_dir(dir) { - crate::log::warn("cache", &format!("Cache deaktiviert: {reason}")); + crate::log::warn("cache", &format!("Cache disabled: {reason}")); return None; } @@ -203,7 +200,7 @@ pub async fn try_open_cache(db_path: &Path) -> Option { Err(e) => { crate::log::warn( "cache", - &format!("Cache-Datenbank konnte nicht geöffnet werden: {e}"), + &format!("Could not open cache database: {e}"), ); return None; } @@ -212,7 +209,7 @@ pub async fn try_open_cache(db_path: &Path) -> Option { let conn = match db.connect() { Ok(c) => c, Err(e) => { - crate::log::warn("cache", &format!("Cache-Verbindung fehlgeschlagen: {e}")); + crate::log::warn("cache", &format!("Cache connection failed: {e}")); return None; } }; @@ -221,7 +218,7 @@ pub async fn try_open_cache(db_path: &Path) -> Option { if let Err(e) = cache.init_schema().await { crate::log::warn( "cache", - &format!("Cache-Schema konnte nicht initialisiert werden: {e}"), + &format!("Could not initialize cache schema: {e}"), ); return None; } diff --git a/src/cli.rs b/src/cli.rs index 024e104..6083af1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -11,50 +11,87 @@ use crate::mac::MacAddress; name = "mac2ip", author, version, - about = "Findet zuverlässig die aktuelle IP-Adresse zu einer MAC-Adresse im lokalen Netzwerk", + about = "Reliably finds the current IP address for a MAC address on the local network", long_about = None )] pub struct Cli { /// MAC-Adresse, deren aktuelle IP-Adresse ermittelt werden soll (z. B. aa:bb:cc:dd:ee:ff) + #[arg(help = "MAC address to look up the current IP address for (e.g. aa:bb:cc:dd:ee:ff)")] pub mac: MacAddress, /// Gibt das Ergebnis als einzeiliges JSON-Objekt aus; unterdrückt alle sonstigen Log-Ausgaben - #[arg(long)] + #[arg( + long, + help = "Print the result as a single-line JSON object; suppresses all other log output" + )] pub json: bool, /// Benutzerdefinierter Pfad zur Konfigurationsdatei - #[arg(long)] + #[arg(long, help = "Custom path to the configuration file")] pub config: Option, /// Logging-Level (error, warn, info, debug) - #[arg(long, value_enum, env = "MAC2IP_LOG_LEVEL")] + #[arg( + long, + value_enum, + env = "MAC2IP_LOG_LEVEL", + help = "Log level (error, warn, info, debug)" + )] pub log_level: Option, /// Cache-TTL in Sekunden (Standard: 1800) - #[arg(long, env = "MAC2IP_CACHE_TTL_SECONDS")] + #[arg( + long, + env = "MAC2IP_CACHE_TTL_SECONDS", + help = "Cache TTL in seconds (default: 1800)" + )] pub cache_ttl_seconds: Option, /// Pfad zur globalen Cache-Datenbankdatei (Standard: /var/lib/mac2ip/cache.db) - #[arg(long, env = "MAC2IP_CACHE_DB_PATH")] + #[arg( + long, + env = "MAC2IP_CACHE_DB_PATH", + help = "Path to the global cache database file (default: /var/lib/mac2ip/cache.db)" + )] pub cache_db_path: Option, /// Timeout in Sekunden für einen einzelnen nmap-Subnetz-Scan (Standard: 120) - #[arg(long, env = "MAC2IP_NMAP_TIMEOUT_SECONDS")] + #[arg( + long, + env = "MAC2IP_NMAP_TIMEOUT_SECONDS", + help = "Timeout in seconds for a single nmap subnet scan (default: 120)" + )] pub nmap_timeout_seconds: Option, /// Kommagetrennte Liste von CIDR-Subnetzen für den nmap-Scan (überschreibt Auto-Erkennung) - #[arg(long, env = "MAC2IP_NETWORKS", value_delimiter = ',')] + #[arg( + long, + env = "MAC2IP_NETWORKS", + value_delimiter = ',', + help = "Comma-separated list of CIDR subnets for the nmap scan (overrides auto-detection)" + )] pub networks: Option>, /// Kommagetrennte Liste von Gateway-MAC-Adressen, deren Netzwerke ohne Rückfrage für /// nmap-Scans (Schritt 3) vertraut werden (überschreibt die Konfigurationsdatei vollständig) - #[arg(long, env = "MAC2IP_TRUSTED_NETWORKS", value_delimiter = ',')] + #[arg( + long, + env = "MAC2IP_TRUSTED_NETWORKS", + value_delimiter = ',', + help = "Comma-separated list of gateway MAC addresses whose networks are trusted for \ + nmap scans (step 3) without confirmation (fully overrides the config file)" + )] pub trusted_networks: Option>, /// Beantwortet die "nmap-Scan in diesem Netzwerk erlauben?"-Rückfrage vor Schritt 3 /// automatisch mit Ja (und merkt sich das Netzwerk dauerhaft im Cache), statt /// interaktiv nachzufragen bzw. im --json-Modus den Scan abzulehnen - #[arg(long)] + #[arg( + long, + help = "Automatically answers yes to the \"allow nmap scan on this network?\" prompt \ + before step 3 (and remembers the network permanently in the cache), instead of \ + asking interactively or rejecting the scan in --json mode" + )] pub auto_trust_networks: bool, } diff --git a/src/error.rs b/src/error.rs index ed8c7c7..1f20957 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,15 +4,15 @@ use crate::mac::MacAddressError; #[derive(Debug, thiserror::Error)] pub enum Mac2IpError { - #[error("keine IP-Adresse für MAC '{mac}' gefunden")] + #[error("no IP address found for MAC '{mac}'")] NotFound { mac: String }, #[error( - "nmap-Scan abgelehnt: Netzwerk nicht vertrauenswürdig{}", - .gateway_mac.as_ref().map(|m| format!(" (Gateway-MAC {m})")).unwrap_or_default() + "nmap scan rejected: network not trusted{}", + .gateway_mac.as_ref().map(|m| format!(" (gateway MAC {m})")).unwrap_or_default() )] UntrustedNetwork { gateway_mac: Option }, - #[error("MAC-Adresse ungültig: {0}")] + #[error("invalid MAC address: {0}")] InvalidMac(#[from] MacAddressError), - #[error("E/A-Fehler: {0}")] + #[error("I/O error: {0}")] Io(#[from] std::io::Error), } diff --git a/src/mac.rs b/src/mac.rs index 7e81c30..86854aa 100644 --- a/src/mac.rs +++ b/src/mac.rs @@ -9,7 +9,7 @@ pub struct MacAddress([u8; 6]); #[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)] pub enum MacAddressError { - #[error("ungültige MAC-Adresse: '{0}'")] + #[error("invalid MAC address: '{0}'")] InvalidFormat(String), } diff --git a/src/network.rs b/src/network.rs index 2b06331..2475539 100644 --- a/src/network.rs +++ b/src/network.rs @@ -86,11 +86,11 @@ pub enum SudoMode { #[derive(Debug, thiserror::Error)] pub enum NmapRunError { - #[error("sudo-Zugangsdaten nicht verfügbar (nicht-interaktiver Modus)")] + #[error("sudo credentials unavailable (non-interactive mode)")] SudoUnavailable, - #[error("nmap-Scan hat das Zeitlimit überschritten")] + #[error("nmap scan exceeded the time limit")] Timeout, - #[error("E/A-Fehler: {0}")] + #[error("I/O error: {0}")] Io(#[from] std::io::Error), } diff --git a/src/output.rs b/src/output.rs index 522d960..1e2d1a3 100644 --- a/src/output.rs +++ b/src/output.rs @@ -42,12 +42,11 @@ pub fn print_success(result: &ResolveResult, json: bool) { if json { println!( "{}", - serde_json::to_string(&success_json(result)) - .expect("JSON-Serialisierung schlägt nicht fehl") + serde_json::to_string(&success_json(result)).expect("JSON serialization never fails") ); } else { println!( - "{} -> {} (Quelle: {})", + "{} -> {} (source: {})", result.mac, result.ip, result.source.as_str() @@ -59,8 +58,7 @@ pub fn print_failure(mac: &MacAddress, err: &Mac2IpError, json: bool) { if json { println!( "{}", - serde_json::to_string(&failure_json(mac, err)) - .expect("JSON-Serialisierung schlägt nicht fehl") + serde_json::to_string(&failure_json(mac, err)).expect("JSON serialization never fails") ); } else { crate::log::error("mac2ip", &err.to_string()); diff --git a/src/resolver.rs b/src/resolver.rs index 1995611..be09ceb 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -57,7 +57,7 @@ pub async fn resolve( { crate::log::info( "resolver", - &format!("Treffer im Cache für {mac}: {}", entry.ip), + &format!("Cache hit for {mac}: {}", entry.ip), ); return Ok(ResolveResult { mac: *mac, @@ -69,13 +69,13 @@ pub async fn resolve( // Schritt 2: ip neigh crate::log::debug( "resolver", - "Cache-Treffer nicht verfügbar, prüfe 'ip neigh show'", + "No cache hit available, checking 'ip neigh show'", ); if let Ok(output) = network::run_ip_neigh_show().await && let Some(ip) = network::parse_ip_neigh_output(&output, mac) && network::ping_check(ip).await { - crate::log::info("resolver", &format!("Treffer via ip neigh für {mac}: {ip}")); + crate::log::info("resolver", &format!("Hit via ip neigh for {mac}: {ip}")); if let Some(cache) = cache { cache.upsert(mac, ip, MatchSource::Arp.as_str(), now).await; } @@ -87,14 +87,14 @@ pub async fn resolve( } // Schritt 3: nmap - crate::log::debug("resolver", "Kein Treffer via ip neigh, starte nmap-Scan"); + crate::log::debug("resolver", "No hit via ip neigh, starting nmap scan"); match trust::ensure_network_trusted(config, cache, auto_trust_networks, json_mode, now).await { TrustDecision::Allowed => {} TrustDecision::Denied { gateway_mac } => { crate::log::warn( "resolver", - "nmap-Scan übersprungen: Netzwerk nicht als vertrauenswürdig bestätigt", + "nmap scan skipped: network not confirmed as trusted", ); return Err(Mac2IpError::UntrustedNetwork { gateway_mac: gateway_mac.map(|m| m.to_string()), @@ -125,7 +125,7 @@ pub async fn resolve( if let Some(ip) = network::parse_nmap_output(&output, mac) && network::ping_check(ip).await { - crate::log::info("resolver", &format!("Treffer via nmap für {mac}: {ip}")); + crate::log::info("resolver", &format!("Hit via nmap for {mac}: {ip}")); if let Some(cache) = cache { cache.upsert(mac, ip, MatchSource::Nmap.as_str(), now).await; } @@ -139,12 +139,12 @@ pub async fn resolve( Err(NmapRunError::SudoUnavailable) => { crate::log::warn( "nmap", - "sudo -n fehlgeschlagen (keine Zugangsdaten) - Schritt 3 wird abgebrochen", + "sudo -n failed (no credentials available) - aborting step 3", ); break; // sudo-Session-Status ist global, ein Retry pro Subnetz bringt nichts } Err(e) => { - crate::log::warn("nmap", &format!("nmap-Scan für {cidr} fehlgeschlagen: {e}")); + crate::log::warn("nmap", &format!("nmap scan for {cidr} failed: {e}")); continue; // Fehler pro Subnetz (z. B. Timeout) - nächstes Subnetz versuchen } } diff --git a/src/trust.rs b/src/trust.rs index 7dd7075..de7968e 100644 --- a/src/trust.rs +++ b/src/trust.rs @@ -35,12 +35,12 @@ pub async fn ensure_network_trusted( let Some(gateway_mac) = network::detect_default_gateway_mac().await else { crate::log::warn( "trust", - "Gateway-MAC konnte nicht ermittelt werden, Netzwerk kann nicht identifiziert werden", + "Could not determine gateway MAC, network cannot be identified", ); if auto_trust { crate::log::warn( "trust", - "--auto-trust-networks gesetzt: nmap-Scan wird trotz unbekannter Gateway-MAC ausgeführt", + "--auto-trust-networks set: running nmap scan despite unknown gateway MAC", ); return TrustDecision::Allowed; } @@ -60,9 +60,7 @@ pub async fn ensure_network_trusted( if auto_trust { crate::log::info( "trust", - &format!( - "Netzwerk (Gateway-MAC {gateway_mac}) automatisch als vertrauenswürdig markiert" - ), + &format!("Network (gateway MAC {gateway_mac}) automatically marked as trusted"), ); if let Some(cache) = cache { cache.trust_network(&gateway_mac, now).await; @@ -95,7 +93,7 @@ pub async fn ensure_network_trusted( async fn prompt_trust_confirmation(gateway_mac: MacAddress) -> bool { tokio::task::spawn_blocking(move || { eprint!( - "Unbekanntes Netzwerk (Gateway-MAC {gateway_mac}). nmap-Scan in diesem Netzwerk erlauben und dauerhaft merken? [y/N]: " + "Unknown network (gateway MAC {gateway_mac}). Allow nmap scan on this network and remember it permanently? [y/N]: " ); let _ = std::io::stderr().flush();