Add advanced monitoring and release infrastructure
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import Foundation
|
||||
import PuterHelperProtocol
|
||||
import Security
|
||||
|
||||
private final class HelperService: NSObject, PuterPrivilegedHelperProtocol {
|
||||
func version(withReply reply: @escaping (Int) -> Void) {
|
||||
reply(PuterHelperConstants.protocolVersion)
|
||||
}
|
||||
|
||||
func setFanTargets(_ targets: [NSNumber: NSNumber], withReply reply: @escaping (Bool, String?) -> Void) {
|
||||
guard let validated = PuterFanTargetValidator.validate(targets) else {
|
||||
reply(false, "Fan targets failed validation.")
|
||||
return
|
||||
}
|
||||
do {
|
||||
for (fan, rpm) in validated {
|
||||
try runSMC(["fan", "\(fan)", "-m", "1"])
|
||||
try runSMC(["fan", "\(fan)", "-v", "\(rpm)"])
|
||||
}
|
||||
reply(true, nil)
|
||||
} catch { reply(false, error.localizedDescription) }
|
||||
}
|
||||
|
||||
func restoreAutomaticFanControl(withReply reply: @escaping (Bool, String?) -> Void) {
|
||||
do {
|
||||
try runSMC(["reset"])
|
||||
reply(true, nil)
|
||||
} catch { reply(false, error.localizedDescription) }
|
||||
}
|
||||
|
||||
private func runSMC(_ arguments: [String]) throws {
|
||||
let helperURL = URL(fileURLWithPath: CommandLine.arguments[0]).standardizedFileURL
|
||||
let toolURL = helperURL.deletingLastPathComponent().appendingPathComponent("smc")
|
||||
guard FileManager.default.isExecutableFile(atPath: toolURL.path) else {
|
||||
throw HelperError.smcUnavailable
|
||||
}
|
||||
let process = Process()
|
||||
let errorPipe = Pipe()
|
||||
process.executableURL = toolURL
|
||||
process.arguments = arguments
|
||||
process.standardOutput = FileHandle.nullDevice
|
||||
process.standardError = errorPipe
|
||||
try process.run()
|
||||
let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
|
||||
process.waitUntilExit()
|
||||
guard process.terminationStatus == 0 else {
|
||||
throw HelperError.commandFailed(String(decoding: errorData, as: UTF8.self).trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum HelperError: LocalizedError {
|
||||
case smcUnavailable
|
||||
case commandFailed(String)
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .smcUnavailable: "The bundled SMC backend is unavailable."
|
||||
case .commandFailed(let detail): detail.isEmpty ? "The SMC command failed." : detail
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class HelperDelegate: NSObject, NSXPCListenerDelegate {
|
||||
private let service = HelperService()
|
||||
|
||||
func listener(_ listener: NSXPCListener, shouldAcceptNewConnection connection: NSXPCConnection) -> Bool {
|
||||
guard ClientCodeValidator.isTrusted(pid: connection.processIdentifier) else { return false }
|
||||
connection.exportedInterface = NSXPCInterface(with: PuterPrivilegedHelperProtocol.self)
|
||||
connection.exportedObject = service
|
||||
connection.resume()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private enum ClientCodeValidator {
|
||||
static func isTrusted(pid: pid_t) -> Bool {
|
||||
var clientCode: SecCode?
|
||||
let attributes = [kSecGuestAttributePid: NSNumber(value: pid)] as CFDictionary
|
||||
guard SecCodeCopyGuestWithAttributes(nil, attributes, [], &clientCode) == errSecSuccess,
|
||||
let clientCode,
|
||||
let clientInfo = signingInfo(for: clientCode),
|
||||
clientInfo.identifier == "dev.soconnor.puter" else { return false }
|
||||
|
||||
var helperCode: SecCode?
|
||||
guard SecCodeCopySelf([], &helperCode) == errSecSuccess,
|
||||
let helperCode,
|
||||
let helperInfo = signingInfo(for: helperCode),
|
||||
let clientTeam = clientInfo.team,
|
||||
let helperTeam = helperInfo.team,
|
||||
clientTeam == helperTeam else { return false }
|
||||
|
||||
var requirement: SecRequirement?
|
||||
let text = "anchor apple generic and identifier \"dev.soconnor.puter\""
|
||||
guard SecRequirementCreateWithString(text as CFString, [], &requirement) == errSecSuccess,
|
||||
let requirement else { return false }
|
||||
return SecCodeCheckValidity(clientCode, [], requirement) == errSecSuccess
|
||||
}
|
||||
|
||||
private static func signingInfo(for code: SecCode) -> (identifier: String, team: String?)? {
|
||||
var staticCode: SecStaticCode?
|
||||
guard SecCodeCopyStaticCode(code, [], &staticCode) == errSecSuccess, let staticCode else { return nil }
|
||||
var information: CFDictionary?
|
||||
guard SecCodeCopySigningInformation(staticCode, SecCSFlags(rawValue: kSecCSSigningInformation), &information) == errSecSuccess,
|
||||
let values = information as? [CFString: Any],
|
||||
let identifier = values[kSecCodeInfoIdentifier] as? String else { return nil }
|
||||
return (identifier, values[kSecCodeInfoTeamIdentifier] as? String)
|
||||
}
|
||||
}
|
||||
|
||||
private let delegate = HelperDelegate()
|
||||
private let listener = NSXPCListener(machServiceName: PuterHelperConstants.machServiceName)
|
||||
listener.delegate = delegate
|
||||
listener.resume()
|
||||
RunLoop.current.run()
|
||||
Reference in New Issue
Block a user