-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHelperToolService.swift
More file actions
129 lines (116 loc) · 4.72 KB
/
Copy pathHelperToolService.swift
File metadata and controls
129 lines (116 loc) · 4.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import Foundation
final class HelperToolListenerDelegate: NSObject, NSXPCListenerDelegate {
func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
// 1. Validate the connecting process — must be signed by the same
// Team ID as us. SMAppService daemons are *typically* only reachable
// from the main app, but we belt-and-suspenders this anyway.
guard CodeSignValidator.validateConnection(newConnection) else {
NSLog("[privacycommandHelper] Rejecting connection: signature mismatch")
return false
}
let exportedInterface = NSXPCInterface(with: HelperToolProtocol.self)
let remoteInterface = NSXPCInterface(with: HelperToolEventReceiver.self)
let service = HelperToolService(connection: newConnection)
newConnection.exportedInterface = exportedInterface
newConnection.exportedObject = service
newConnection.remoteObjectInterface = remoteInterface
newConnection.invalidationHandler = { [weak service] in service?.invalidate() }
newConnection.interruptionHandler = { [weak service] in service?.invalidate() }
newConnection.resume()
return true
}
}
final class HelperToolService: NSObject, HelperToolProtocol {
private let connection: NSXPCConnection
private var fsUsageRunner: FsUsageRunner?
init(connection: NSXPCConnection) {
self.connection = connection
super.init()
}
func invalidate() {
fsUsageRunner?.stop()
fsUsageRunner = nil
}
private var remoteReceiver: HelperToolEventReceiver? {
connection.remoteObjectProxy as? HelperToolEventReceiver
}
// MARK: - HelperToolProtocol
func helperVersion(reply: @escaping (String, Int) -> Void) {
reply("privacycommandHelper 0.1.0", HelperToolID.protocolVersion)
}
func startFileMonitor(forPID pid: Int32, reply: @escaping (Bool, String?) -> Void) {
guard fsUsageRunner == nil else {
reply(false, "Already monitoring")
return
}
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let runner = FsUsageRunner(pid: pid,
onEvent: { [weak self] event in
guard let self else { return }
do {
let data = try encoder.encode(event)
self.remoteReceiver?.helperDidEmitFileEvent(data)
} catch {
self.remoteReceiver?.helperDidEmitLog("encode error: \(error)")
}
}, onLog: { [weak self] msg in
self?.remoteReceiver?.helperDidEmitLog(msg)
})
fsUsageRunner = runner
do {
try runner.start()
reply(true, nil)
} catch {
fsUsageRunner = nil
reply(false, error.localizedDescription)
}
}
func stopFileMonitor(reply: @escaping () -> Void) {
fsUsageRunner?.stop()
fsUsageRunner = nil
reply()
}
func uninstall(reply: @escaping () -> Void) {
// GUI is responsible for SMAppService.unregister(); we just stop work.
invalidate()
reply()
}
func runSfltoolDumpBTM(reply: @escaping (String?, String?) -> Void) {
// We're already root inside the helper, so `sfltool dumpbtm`
// executes without triggering Authorization Services.
let path = "/usr/bin/sfltool"
guard FileManager.default.isExecutableFile(atPath: path) else {
reply(nil, "sfltool not present at \(path) (pre-macOS-13?)")
return
}
let task = Process()
task.executableURL = URL(fileURLWithPath: path)
task.arguments = ["dumpbtm"]
let outPipe = Pipe()
let errPipe = Pipe()
task.standardOutput = outPipe
task.standardError = errPipe
do {
try task.run()
} catch {
reply(nil, "sfltool launch failed: \(error.localizedDescription)")
return
}
let deadline = Date().addingTimeInterval(8)
while task.isRunning {
if Date() > deadline { task.terminate(); break }
Thread.sleep(forTimeInterval: 0.05)
}
let outData = (try? outPipe.fileHandleForReading.readToEnd()) ?? Data()
if let s = String(data: outData, encoding: .utf8), !s.isEmpty {
reply(s, nil)
} else {
let errData = (try? errPipe.fileHandleForReading.readToEnd()) ?? Data()
let errStr = String(data: errData, encoding: .utf8) ?? ""
reply(nil, errStr.isEmpty
? "sfltool produced no output (status \(task.terminationStatus))"
: "sfltool failed: \(errStr)")
}
}
}