Files
remmina-mac/Sources/RemminaMac/Services/ConnectionServices.swift
T

121 lines
5.2 KiB
Swift

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)
}
}
}