//! Elevated One-Shot-Setup: EIN UAC-Prompt erledigt alles — //! DoH-Konfiguration, ProgramData-State, Tamper-Service-Installation. //! Danach läuft alles silent (Service als SYSTEM, Deaktivierung server-gesteuert). use protection_core::{apply_script, ProtectionState, SERVICE_NAME}; use std::net::ToSocketAddrs; use std::path::PathBuf; use protection_core::{ps_run, DOH_FALLBACK_IP, DOH_HOST}; /// Löst die IPv4 des DoH-Hosts auf (solange DNS noch normal funktioniert). /// Fallback auf die bekannte Hetzner-IP wenn Resolve fehlschlägt. pub fn resolve_doh_ip() -> String { if let Ok(addrs) = format!("{DOH_HOST}:443").to_socket_addrs() { for addr in addrs { if let std::net::SocketAddr::V4(v4) = addr { return v4.ip().to_string(); } } } DOH_FALLBACK_IP.to_string() } /// Pfad des Tamper-Service-Binaries (Tauri-Sidecar, liegt neben der Haupt-Exe). pub fn service_exe_path() -> Result { let exe = std::env::current_exe().map_err(|e| e.to_string())?; let dir = exe .parent() .ok_or_else(|| "Exe-Verzeichnis nicht ermittelbar".to_string())?; Ok(dir.join("rebreak-protection-service.exe")) } /// Baut das komplette elevated Setup-Script: /// 1. DoH anwenden 2. State nach %ProgramData%\ReBreak 3. ACL härten 4. Service installieren+starten pub fn build_setup_script(state: &ProtectionState, service_exe: &str) -> String { let mut script = String::new(); script.push_str(&apply_script(&state.server_ip, &state.doh_template)); let state_dir = protection_core::state_dir(); let state_path = protection_core::state_path(); let json = serde_json::to_string_pretty(state).unwrap_or_default(); script.push_str(&format!( r#" # Persistenter State für den Tamper-Service (SYSTEM liest das beim Polling) New-Item -ItemType Directory -Force -Path '{state_dir}' | Out-Null @' {json} '@ | Set-Content -Path '{state_path}' -Encoding UTF8 # ACL härten: nur SYSTEM + Administratoren dürfen lesen/schreiben. # Standard-User verlieren Read-Zugriff → DNS-Token nicht trivial auslesbar. $acl = New-Object System.Security.AccessControl.FileSecurity $acl.SetAccessRuleProtection($true, $false) # Vererbung deaktivieren $ruleSystem = New-Object System.Security.AccessControl.FileSystemAccessRule( "NT AUTHORITY\SYSTEM", "FullControl", "Allow" ) $ruleAdmins = New-Object System.Security.AccessControl.FileSystemAccessRule( "BUILTIN\Administrators", "FullControl", "Allow" ) $acl.AddAccessRule($ruleSystem) $acl.AddAccessRule($ruleAdmins) Set-Acl -Path '{state_path}' -AclObject $acl # Tamper-Protection-Service als SYSTEM installieren (idempotent: alte Instanz weg) sc.exe stop {SERVICE_NAME} 2>$null | Out-Null sc.exe delete {SERVICE_NAME} 2>$null | Out-Null sc.exe create {SERVICE_NAME} binPath= '"{service_exe}"' start= auto obj= LocalSystem DisplayName= "ReBreak Protection" | Out-Null sc.exe description {SERVICE_NAME} "ReBreak DNS-Schutz — Tamper-Monitor (alle 5 Minuten)" | Out-Null sc.exe failure {SERVICE_NAME} reset= 86400 actions= restart/60000/restart/60000/restart/60000 | Out-Null sc.exe start {SERVICE_NAME} | Out-Null exit 0 "#, state_dir = state_dir.display(), state_path = state_path.display(), )); script } /// Schreibt das Script als Temp-.ps1 und startet es elevated (UAC) mit -Wait. /// Gibt Err zurück wenn der User den UAC-Prompt abbricht oder das Script failt. pub fn run_elevated(script: &str) -> Result<(), String> { let tmp = std::env::temp_dir().join(format!( "rebreak-setup-{}.ps1", std::process::id() )); std::fs::write(&tmp, script).map_err(|e| format!("Temp-Script: {e}"))?; // Outer-PS startet Inner-PS elevated; -Wait + -PassThru für Exit-Code let outer = format!( r#"$p = Start-Process powershell.exe -Verb RunAs -Wait -PassThru -WindowStyle Hidden -ArgumentList @('-NonInteractive','-NoProfile','-ExecutionPolicy','Bypass','-File','{}') exit $p.ExitCode"#, tmp.display() ); let result = ps_run(&outer); let _ = std::fs::remove_file(&tmp); match result { Ok(_) => Ok(()), Err(e) if e.contains("canceled") || e.contains("abgebrochen") => { Err("Administrator-Bestätigung wurde abgebrochen.".into()) } Err(e) => Err(format!("Setup fehlgeschlagen: {e}")), } }