import Foundation import AppKit enum SSHService { static func sshArguments(for profile: ConnectionProfile, key: StoredSSHKey?) -> [String] { var args: [String] = [] args += ["-p", "\(profile.port)"] if let key, !key.privateKeyPath.isEmpty { args += ["-i", key.privateKeyPath] } if !profile.extraArgs.isEmpty { args += profile.extraArgs.split(separator: " ").map(String.init) } args.append("\(profile.username)@\(profile.host)") return args } static func sshCommandString(for profile: ConnectionProfile, key: StoredSSHKey?) -> String { let escaped = sshArguments(for: profile, key: key).map { $0.contains(" ") ? "'\($0)'" : $0 } return "ssh " + escaped.joined(separator: " ") } /// Öffnet die SSH-Session in Terminal.app — ohne AppleScript (das scheiterte /// still an fehlender Automation-Berechtigung), sondern über eine .command-Datei. static func openInTerminal(profile: ConnectionProfile, key: StoredSSHKey?) { let cmd = sshCommandString(for: profile, key: key) let dir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! .appendingPathComponent("RemminaMac", isDirectory: true) try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) let url = dir.appendingPathComponent("launch-\(profile.id.uuidString).command") // exec bash hält das Fenster nach Sitzungsende offen try? "#!/bin/bash\n\(cmd)\nexec bash\n".write(to: url, atomically: true, encoding: .utf8) try? FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: url.path) NSWorkspace.shared.open(url) } static func copyCommandToClipboard(profile: ConnectionProfile, key: StoredSSHKey?) { let cmd = sshCommandString(for: profile, key: key) NSPasteboard.general.clearContents() NSPasteboard.general.setString(cmd, forType: .string) } // MARK: - Passwort / Auto-Login /// Gespeichertes Login-Secret: erst Profil-Passwort, dann Key-Passphrase (Keychain, nie JSON). static func loginSecret(for profile: ConnectionProfile, key: StoredSSHKey?) -> String? { if let acct = profile.passwordAccount, let pw = KeychainHelper.load(account: acct), !pw.isEmpty { return pw } if let key, let acct = key.passphraseAccount, let pp = KeychainHelper.load(account: acct), !pp.isEmpty { return pp } return nil } static func findSshpass() -> String? { for p in ["/opt/homebrew/bin/sshpass", "/usr/local/bin/sshpass", "/usr/bin/sshpass"] { if FileManager.default.isExecutableFile(atPath: p) { return p } } return nil } /// true = beim Verbinden wird ohne Tippen eingeloggt (sshpass + Keychain-Secret). static func willAutoLogin(profile: ConnectionProfile, key: StoredSSHKey?) -> Bool { loginSecret(for: profile, key: key) != nil && findSshpass() != nil } /// Reine Funktion (testbar): mit Secret+sshpass -> sshpass -e … + SSHPASS-Env, /// sonst direkter Aufruf. Passwort steht NIE in der Befehlszeile (nur im Env). static func sessionConfig(executable: String, args: [String], password: String?, sshpassPath: String?) -> (executable: String, arguments: [String], extraEnv: [String: String], autoLogin: Bool) { if let password, !password.isEmpty, let sshpass = sshpassPath, !sshpass.isEmpty { return (sshpass, ["-e", executable] + args, ["SSHPASS": password], true) } return (executable, args, [:], false) } } enum RDPService { static var freeRDPPath: String? { for candidate in ["/opt/homebrew/bin/xfreerdp", "/usr/local/bin/xfreerdp", "/usr/bin/xfreerdp"] { if FileManager.default.isExecutableFile(atPath: candidate) { return candidate } } return nil } static func connect(profile: ConnectionProfile) { if let freerdp = freeRDPPath { let task = Process() task.executableURL = URL(fileURLWithPath: freerdp) var args = ["/v:\(profile.host):\(profile.port)", "/u:\(profile.username)"] if profile.resolution.lowercased() != "automatisch" { let size = profile.resolution.replacingOccurrences(of: " ", with: "") args.append("/size:\(size)") } else { args.append("/f") } if !profile.extraArgs.isEmpty { args += profile.extraArgs.split(separator: " ").map(String.init) } task.arguments = args try? task.run() } else { // Fallback: Microsoft Remote Desktop URL-Scheme let addr = "full address=s:\(profile.host):\(profile.port)" if let encoded = addr.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: "rdc://?\(encoded)") { NSWorkspace.shared.open(url) } } } static func connectVNC(profile: ConnectionProfile) { if let url = URL(string: "vnc://\(profile.host):\(profile.port)") { NSWorkspace.shared.open(url) } } }