@skylandnpm/octopus-proxy 0.1.39 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-BYXUILAK.js → chunk-WSL4LYYW.js} +10 -5
- package/dist/cli.js +1 -1
- package/dist/main.js +1 -1
- package/native/desktop-macos.m +44 -17
- package/native/screen-unlock-macos.m +32 -190
- package/package.json +1 -1
|
@@ -12359,8 +12359,8 @@ var ScreenLockError = class extends Error {
|
|
|
12359
12359
|
var nativeErrors = {
|
|
12360
12360
|
permission: "\u8BF7\u5728 Proxy \u4E3B\u673A\u201C\u7CFB\u7EDF\u8BBE\u7F6E \u2192 \u9690\u79C1\u4E0E\u5B89\u5168\u6027 \u2192 \u8F85\u52A9\u529F\u80FD\u201D\u4E2D\u6388\u6743\u5B9E\u9645\u8FD0\u884C Proxy \u7684\u7A0B\u5E8F\uFF0C\u5E76\u91CD\u542F Proxy\u3002",
|
|
12361
12361
|
session: "\u65E0\u6CD5\u8BBF\u95EE Proxy \u7528\u6237\u7684\u6D3B\u52A8\u56FE\u5F62\u4F1A\u8BDD\uFF1B\u4E0D\u652F\u6301\u767B\u5F55\u524D\u3001FileVault \u542F\u52A8\u89E3\u9501\u6216\u5207\u6362\u5230\u5176\u4ED6\u7528\u6237\u7684\u5C4F\u5E55\u3002",
|
|
12362
|
-
focus: "\
|
|
12363
|
-
changed: "\u4E3B\u673A\u9501\u5C4F\u72B6\u6001\u6216\
|
|
12362
|
+
focus: "\u65E0\u6CD5\u5411\u4E3B\u673A\u9501\u5C4F\u754C\u9762\u53D1\u9001\u5524\u9192\u6309\u952E\uFF0C\u5C1A\u672A\u8F93\u5165\u5BC6\u7801\u3002\u8BF7\u68C0\u67E5\u4E3B\u673A\u6D3B\u52A8\u56FE\u5F62\u4F1A\u8BDD\u540E\u91CD\u8BD5\u3002",
|
|
12363
|
+
changed: "\u4E3B\u673A\u9501\u5C4F\u72B6\u6001\u6216\u6D3B\u52A8\u4F1A\u8BDD\u5DF2\u53D8\u5316\uFF0C\u5DF2\u505C\u6B62\u8F93\u5165\uFF0C\u8BF7\u91CD\u8BD5\u3002",
|
|
12364
12364
|
timeout: "\u4E3B\u673A\u5C4F\u5E55\u4ECD\u9501\u5B9A\uFF0C\u7CFB\u7EDF\u672A\u63A5\u53D7\u89E3\u9501\u8F93\u5165\uFF1B\u8BF7\u68C0\u67E5\u8F85\u52A9\u529F\u80FD\u6743\u9650\u548C\u9501\u5C4F\u754C\u9762\u540E\u91CD\u8BD5\u3002",
|
|
12365
12365
|
password: "\u4E3B\u673A\u5BC6\u7801\u683C\u5F0F\u65E0\u6548\u3002"
|
|
12366
12366
|
};
|
|
@@ -12467,12 +12467,12 @@ var PlatformScreenLock = class {
|
|
|
12467
12467
|
if (signal.aborted) throw new ScreenLockError("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u5C4F\u5E55\u89E3\u9501\u5DF2\u53D6\u6D88\u3002");
|
|
12468
12468
|
};
|
|
12469
12469
|
check();
|
|
12470
|
-
if (!password || Buffer.byteLength(password) > 1024 || /[\x00-\x1f\x7f]/.test(password)) throw new ScreenLockError("\u4E3B\u673A\u5BC6\u7801\u683C\u5F0F\u65E0\u6548\u3002");
|
|
12471
|
-
if (Date.now() < this.blockedUntil) throw new ScreenLockError("\u5BC6\u7801\u9A8C\u8BC1\u5931\u8D25\u6B21\u6570\u8FC7\u591A\uFF0C\u8BF7\u7B49\u5F85 30 \u79D2\u540E\u91CD\u8BD5\u3002");
|
|
12472
12470
|
const status = await this.status();
|
|
12473
12471
|
check();
|
|
12474
12472
|
if (!status.locked) return status;
|
|
12475
12473
|
if (!status.canUnlock) throw new ScreenLockError(status.message ?? "\u4E3B\u673A\u4E0D\u652F\u6301\u5C4F\u5E55\u89E3\u9501\u3002");
|
|
12474
|
+
if (!password || Buffer.byteLength(password) > 1024 || /[\x00-\x1f\x7f]/.test(password)) throw new ScreenLockError("\u4E3B\u673A\u5BC6\u7801\u683C\u5F0F\u65E0\u6548\u3002");
|
|
12475
|
+
if (Date.now() < this.blockedUntil) throw new ScreenLockError("\u5BC6\u7801\u9A8C\u8BC1\u5931\u8D25\u6B21\u6570\u8FC7\u591A\uFF0C\u8BF7\u7B49\u5F85 30 \u79D2\u540E\u91CD\u8BD5\u3002");
|
|
12476
12476
|
let valid;
|
|
12477
12477
|
try {
|
|
12478
12478
|
valid = await this.authenticate(this.stateDirectory, this.account.username, password);
|
|
@@ -12489,6 +12489,10 @@ var PlatformScreenLock = class {
|
|
|
12489
12489
|
}
|
|
12490
12490
|
this.failures = 0;
|
|
12491
12491
|
if (this.system === "darwin") {
|
|
12492
|
+
const current = await this.status();
|
|
12493
|
+
check();
|
|
12494
|
+
if (!current.locked) return current;
|
|
12495
|
+
if (!current.canUnlock) throw new ScreenLockError(current.message ?? "\u4E3B\u673A\u4E0D\u652F\u6301\u5C4F\u5E55\u89E3\u9501\u3002");
|
|
12492
12496
|
const helper = await this.macHelper();
|
|
12493
12497
|
check();
|
|
12494
12498
|
const bytes = Buffer.from(password, "utf8"), input = Buffer.alloc(bytes.length + 2);
|
|
@@ -12507,7 +12511,8 @@ var PlatformScreenLock = class {
|
|
|
12507
12511
|
}
|
|
12508
12512
|
const session = await this.linuxSession(signal);
|
|
12509
12513
|
check();
|
|
12510
|
-
if (session.locked)
|
|
12514
|
+
if (!session.locked) return { ...status, locked: false };
|
|
12515
|
+
await this.execute("loginctl", ["--no-ask-password", "unlock-session", session.id], void 0, signal);
|
|
12511
12516
|
for (let i = 0; i < 20; i++) {
|
|
12512
12517
|
check();
|
|
12513
12518
|
const current = await this.linuxSession(signal);
|
package/dist/cli.js
CHANGED
package/dist/main.js
CHANGED
package/native/desktop-macos.m
CHANGED
|
@@ -29,6 +29,39 @@ static NSDictionary *displayInfo(CGDirectDisplayID displayId) {
|
|
|
29
29
|
@"width": @(CGDisplayPixelsWide(displayId)), @"height": @(CGDisplayPixelsHigh(displayId))};
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
static BOOL saveImage(CGImageRef image, NSString *path) {
|
|
33
|
+
if (!image) return NO;
|
|
34
|
+
CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:path], CFSTR("public.png"), 1, NULL);
|
|
35
|
+
if (!destination) return NO;
|
|
36
|
+
CGImageDestinationAddImage(destination, image, NULL);
|
|
37
|
+
BOOL saved = CGImageDestinationFinalize(destination);
|
|
38
|
+
CFRelease(destination);
|
|
39
|
+
return saved;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static void finishImage(BOOL saved) {
|
|
43
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
44
|
+
if (finished) return;
|
|
45
|
+
if (saved) { resultCode = 0; finished = YES; }
|
|
46
|
+
else fail(@"无法保存桌面 PNG 截图,请检查 Proxy 状态目录。" );
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
|
|
51
|
+
static void captureImage(SCContentFilter *filter, SCStreamConfiguration *configuration, NSString *path) API_AVAILABLE(macos(14.0));
|
|
52
|
+
static void captureImage(SCContentFilter *filter, SCStreamConfiguration *configuration, NSString *path) {
|
|
53
|
+
// A stream may start successfully without delivering frames while the screen
|
|
54
|
+
// is locked. The screenshot API completes a single capture without that wait.
|
|
55
|
+
[SCScreenshotManager captureImageWithFilter:filter configuration:configuration completionHandler:^(CGImageRef image, NSError *error) {
|
|
56
|
+
if (error || !image) {
|
|
57
|
+
dispatch_async(dispatch_get_main_queue(), ^{ if (!finished) fail(permissionMessage()); });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
finishImage(saveImage(image, path));
|
|
61
|
+
}];
|
|
62
|
+
}
|
|
63
|
+
#endif
|
|
64
|
+
|
|
32
65
|
@interface DesktopFrame : NSObject <SCStreamOutput, SCStreamDelegate>
|
|
33
66
|
@property(nonatomic, strong) SCStream *stream;
|
|
34
67
|
@property(nonatomic, copy) NSString *path;
|
|
@@ -49,23 +82,11 @@ static NSDictionary *displayInfo(CGDirectDisplayID displayId) {
|
|
|
49
82
|
self.received = YES;
|
|
50
83
|
CGImageRef image = NULL;
|
|
51
84
|
OSStatus conversion = VTCreateCGImageFromCVPixelBuffer(pixel, NULL, &image);
|
|
52
|
-
BOOL saved =
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
saved = CGImageDestinationFinalize(destination);
|
|
58
|
-
CFRelease(destination);
|
|
59
|
-
}
|
|
60
|
-
CGImageRelease(image);
|
|
61
|
-
}
|
|
62
|
-
[stream stopCaptureWithCompletionHandler:^(NSError *error) {
|
|
63
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
64
|
-
if (finished) return;
|
|
65
|
-
if (saved) { resultCode = 0; finished = YES; }
|
|
66
|
-
else fail(@"无法保存桌面 PNG 截图,请检查 Proxy 状态目录。" );
|
|
67
|
-
});
|
|
68
|
-
}];
|
|
85
|
+
BOOL saved = conversion == noErr && saveImage(image, self.path);
|
|
86
|
+
if (image) CGImageRelease(image);
|
|
87
|
+
// Stopping a stream is cleanup; a saved PNG must not depend on its callback.
|
|
88
|
+
[stream stopCaptureWithCompletionHandler:nil];
|
|
89
|
+
finishImage(saved);
|
|
69
90
|
}
|
|
70
91
|
@end
|
|
71
92
|
|
|
@@ -121,6 +142,12 @@ int main(int argc, const char *argv[]) {
|
|
|
121
142
|
configuration.showsCursor = YES;
|
|
122
143
|
configuration.queueDepth = 3;
|
|
123
144
|
configuration.minimumFrameInterval = CMTimeMake(1, 30);
|
|
145
|
+
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
|
|
146
|
+
if (@available(macOS 14.0, *)) {
|
|
147
|
+
captureImage(filter, configuration, frame.path);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
#endif
|
|
124
151
|
frame.stream = [[SCStream alloc] initWithFilter:filter configuration:configuration delegate:frame];
|
|
125
152
|
NSError *outputError = nil;
|
|
126
153
|
if (![frame.stream addStreamOutput:frame type:SCStreamOutputTypeScreen sampleHandlerQueue:dispatch_queue_create("octopus.desktop.frame", DISPATCH_QUEUE_SERIAL) error:&outputError]) {
|
|
@@ -57,182 +57,41 @@ static pid_t loginWindowPID(void) {
|
|
|
57
57
|
return cached;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
static CFTypeRef copyAttribute(AXUIElementRef element, CFStringRef attribute) {
|
|
61
|
-
CFTypeRef value = NULL;
|
|
62
|
-
AXUIElementSetMessagingTimeout(element, 0.1);
|
|
63
|
-
if (AXUIElementCopyAttributeValue(element, attribute, &value) != kAXErrorSuccess) {
|
|
64
|
-
if (value) CFRelease(value);
|
|
65
|
-
return NULL;
|
|
66
|
-
}
|
|
67
|
-
return value;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
static BOOL isSecureField(AXUIElementRef field) {
|
|
71
|
-
if (!field || CFGetTypeID(field) != AXUIElementGetTypeID()) return NO;
|
|
72
|
-
pid_t pid = 0;
|
|
73
|
-
if (AXUIElementGetPid(field, &pid) != kAXErrorSuccess || !isLoginWindow(pid)) return NO;
|
|
74
|
-
CFTypeRef role = copyAttribute(field, kAXRoleAttribute);
|
|
75
|
-
CFTypeRef subrole = copyAttribute(field, kAXSubroleAttribute);
|
|
76
|
-
BOOL secure = (subrole && CFEqual(subrole, kAXSecureTextFieldSubrole)) ||
|
|
77
|
-
(role && CFEqual(role, kAXSecureTextFieldSubrole));
|
|
78
|
-
if (role) CFRelease(role);
|
|
79
|
-
if (subrole) CFRelease(subrole);
|
|
80
|
-
CFTypeRef enabled = copyAttribute(field, kAXEnabledAttribute);
|
|
81
|
-
if (enabled && CFEqual(enabled, kCFBooleanFalse)) secure = NO;
|
|
82
|
-
if (enabled) CFRelease(enabled);
|
|
83
|
-
CFTypeRef hidden = copyAttribute(field, CFSTR("AXHidden"));
|
|
84
|
-
if (hidden && CFEqual(hidden, kCFBooleanTrue)) secure = NO;
|
|
85
|
-
if (hidden) CFRelease(hidden);
|
|
86
|
-
return secure;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
static AXUIElementRef copySecureFocus(AXUIElementRef root) {
|
|
90
|
-
CFTypeRef focused = copyAttribute(root, kAXFocusedUIElementAttribute);
|
|
91
|
-
if (focused && isSecureField((AXUIElementRef)focused)) return (AXUIElementRef)focused;
|
|
92
|
-
if (focused) CFRelease(focused);
|
|
93
|
-
return NULL;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
static void collectSecureFields(AXUIElementRef root, CFMutableArrayRef fields,
|
|
97
|
-
CFMutableSetRef visited, int depth, CFAbsoluteTime deadline) {
|
|
98
|
-
if (!root || CFGetTypeID(root) != AXUIElementGetTypeID() || depth > 10 ||
|
|
99
|
-
CFSetGetCount(visited) >= 128 || CFAbsoluteTimeGetCurrent() >= deadline || CFSetContainsValue(visited, root)) return;
|
|
100
|
-
CFSetAddValue(visited, root);
|
|
101
|
-
if (isSecureField(root)) { CFArrayAppendValue(fields, root); return; }
|
|
102
|
-
// Some loginwindow versions expose windows separately from AXChildren.
|
|
103
|
-
CFStringRef attributes[] = { kAXChildrenAttribute, kAXWindowsAttribute };
|
|
104
|
-
for (int i = 0; i < 2; i++) {
|
|
105
|
-
CFTypeRef children = copyAttribute(root, attributes[i]);
|
|
106
|
-
if (children && CFGetTypeID(children) == CFArrayGetTypeID()) {
|
|
107
|
-
for (CFIndex j = 0; j < CFArrayGetCount(children); j++)
|
|
108
|
-
collectSecureFields((AXUIElementRef)CFArrayGetValueAtIndex(children, j), fields, visited, depth + 1, deadline);
|
|
109
|
-
}
|
|
110
|
-
if (children) CFRelease(children);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
static CFArrayRef copySecureFields(AXUIElementRef app) {
|
|
115
|
-
CFMutableArrayRef fields = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
|
|
116
|
-
CFMutableSetRef visited = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
|
|
117
|
-
collectSecureFields(app, fields, visited, 0, CFAbsoluteTimeGetCurrent() + 2);
|
|
118
|
-
CFRelease(visited);
|
|
119
|
-
return fields;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
static AXUIElementRef passwordFocus(void) {
|
|
123
|
-
AXUIElementRef system = AXUIElementCreateSystemWide();
|
|
124
|
-
AXUIElementRef field = copySecureFocus(system);
|
|
125
|
-
CFRelease(system);
|
|
126
|
-
if (field) return field;
|
|
127
|
-
pid_t pid = loginWindowPID();
|
|
128
|
-
if (!pid) return NULL;
|
|
129
|
-
AXUIElementRef app = AXUIElementCreateApplication(pid);
|
|
130
|
-
field = copySecureFocus(app);
|
|
131
|
-
if (!field) {
|
|
132
|
-
// A locked console can lack the system-wide focused element while the
|
|
133
|
-
// password field itself or loginwindow still reports its local focus.
|
|
134
|
-
CFArrayRef fields = copySecureFields(app);
|
|
135
|
-
for (CFIndex i = 0; i < CFArrayGetCount(fields); i++) {
|
|
136
|
-
AXUIElementRef candidate = (AXUIElementRef)CFArrayGetValueAtIndex(fields, i);
|
|
137
|
-
CFTypeRef focused = copyAttribute(candidate, kAXFocusedAttribute);
|
|
138
|
-
BOOL active = focused && CFEqual(focused, kCFBooleanTrue);
|
|
139
|
-
if (focused) CFRelease(focused);
|
|
140
|
-
if (!active) continue;
|
|
141
|
-
if (field) { CFRelease(field); field = NULL; break; }
|
|
142
|
-
field = (AXUIElementRef)CFRetain(candidate);
|
|
143
|
-
}
|
|
144
|
-
CFRelease(fields);
|
|
145
|
-
}
|
|
146
|
-
CFRelease(app);
|
|
147
|
-
return field;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
static AXUIElementRef focusPasswordField(void) {
|
|
151
|
-
BOOL locked = NO;
|
|
152
|
-
if (!lockState(&locked) || !locked) return NULL;
|
|
153
|
-
AXUIElementRef focused = passwordFocus();
|
|
154
|
-
if (focused) return focused;
|
|
155
|
-
pid_t pid = loginWindowPID();
|
|
156
|
-
if (!pid) return NULL;
|
|
157
|
-
AXUIElementRef app = AXUIElementCreateApplication(pid);
|
|
158
|
-
CFArrayRef fields = copySecureFields(app);
|
|
159
|
-
CFRelease(app);
|
|
160
|
-
// Never guess between multiple password fields or use a generic text field.
|
|
161
|
-
if (CFArrayGetCount(fields) != 1) { CFRelease(fields); return NULL; }
|
|
162
|
-
AXUIElementRef field = (AXUIElementRef)CFArrayGetValueAtIndex(fields, 0);
|
|
163
|
-
if (!lockState(&locked) || !locked || !isSecureField(field)) { CFRelease(fields); return NULL; }
|
|
164
|
-
AXError activated = AXUIElementSetAttributeValue(field, kAXFocusedAttribute, kCFBooleanTrue);
|
|
165
|
-
if (activated != kAXErrorSuccess) AXUIElementPerformAction(field, kAXPressAction);
|
|
166
|
-
usleep(50000);
|
|
167
|
-
focused = passwordFocus();
|
|
168
|
-
if (focused && !CFEqual(field, focused)) { CFRelease(focused); focused = NULL; }
|
|
169
|
-
CFRelease(fields);
|
|
170
|
-
return focused;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
static BOOL stillFocused(AXUIElementRef field) {
|
|
174
|
-
BOOL locked = NO;
|
|
175
|
-
if (!lockState(&locked) || !locked || !isSecureField(field)) return NO;
|
|
176
|
-
AXUIElementRef system = AXUIElementCreateSystemWide();
|
|
177
|
-
AXUIElementRef current = copySecureFocus(system);
|
|
178
|
-
CFRelease(system);
|
|
179
|
-
if (!current) {
|
|
180
|
-
pid_t pid = 0;
|
|
181
|
-
if (AXUIElementGetPid(field, &pid) != kAXErrorSuccess) return NO;
|
|
182
|
-
AXUIElementRef app = AXUIElementCreateApplication(pid);
|
|
183
|
-
current = copySecureFocus(app);
|
|
184
|
-
CFRelease(app);
|
|
185
|
-
}
|
|
186
|
-
BOOL same = current && CFEqual(current, field);
|
|
187
|
-
if (current) { CFRelease(current); return same; }
|
|
188
|
-
// Avoid traversing the entire AX tree per character on systems that only
|
|
189
|
-
// expose field-local focus. A false or unavailable focus is never accepted.
|
|
190
|
-
CFTypeRef focused = copyAttribute(field, kAXFocusedAttribute);
|
|
191
|
-
same = focused && CFEqual(focused, kCFBooleanTrue);
|
|
192
|
-
if (focused) CFRelease(focused);
|
|
193
|
-
return same;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
60
|
static BOOL key(CGKeyCode code, CGEventFlags flags, const UniChar *text, size_t length) {
|
|
197
61
|
CGEventRef down = CGEventCreateKeyboardEvent(NULL, code, true);
|
|
198
62
|
CGEventRef up = CGEventCreateKeyboardEvent(NULL, code, false);
|
|
199
63
|
if (!down || !up) { if (down) CFRelease(down); if (up) CFRelease(up); return NO; }
|
|
200
64
|
CGEventSetFlags(down, flags); CGEventSetFlags(up, flags);
|
|
201
65
|
if (text) { CGEventKeyboardSetUnicodeString(down, length, text); CGEventKeyboardSetUnicodeString(up, length, text); }
|
|
202
|
-
CGEventPost(kCGHIDEventTap, down);
|
|
66
|
+
CGEventPost(kCGHIDEventTap, down);
|
|
67
|
+
usleep(50000);
|
|
68
|
+
CGEventPost(kCGHIDEventTap, up);
|
|
203
69
|
CFRelease(down); CFRelease(up);
|
|
204
|
-
usleep(
|
|
70
|
+
usleep(50000);
|
|
205
71
|
return YES;
|
|
206
72
|
}
|
|
207
73
|
|
|
74
|
+
// Space reveals and focuses the locked console's password prompt. Allow the
|
|
75
|
+
// transition to finish before typing; no Accessibility discovery is required.
|
|
76
|
+
static BOOL wakePasswordPrompt(void) {
|
|
77
|
+
BOOL locked = NO;
|
|
78
|
+
if (!lockState(&locked)) return NO;
|
|
79
|
+
if (!locked) return YES;
|
|
80
|
+
if (!key(49, 0, NULL, 0)) return NO;
|
|
81
|
+
usleep(1000000);
|
|
82
|
+
return YES;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static BOOL lockedKey(CGKeyCode code, CGEventFlags flags, const UniChar *text, size_t length) {
|
|
86
|
+
BOOL locked = NO;
|
|
87
|
+
return lockState(&locked) && locked && key(code, flags, text, length);
|
|
88
|
+
}
|
|
89
|
+
|
|
208
90
|
static void printDiagnostics(BOOL locked) {
|
|
209
|
-
// Read-only
|
|
210
|
-
|
|
211
|
-
pid_t pid = loginWindowPID();
|
|
212
|
-
AXUIElementRef system = AXUIElementCreateSystemWide();
|
|
213
|
-
CFTypeRef global = NULL;
|
|
214
|
-
AXError globalError = AXUIElementCopyAttributeValue(system, kAXFocusedUIElementAttribute, &global);
|
|
215
|
-
CFRelease(system);
|
|
216
|
-
BOOL globalSecure = global && isSecureField((AXUIElementRef)global);
|
|
217
|
-
if (global) CFRelease(global);
|
|
218
|
-
AXError appError = kAXErrorNoValue;
|
|
219
|
-
CFIndex count = 0;
|
|
220
|
-
BOOL appSecure = NO;
|
|
221
|
-
if (pid && AXIsProcessTrusted()) {
|
|
222
|
-
AXUIElementRef app = AXUIElementCreateApplication(pid);
|
|
223
|
-
AXUIElementSetMessagingTimeout(app, 0.1);
|
|
224
|
-
CFTypeRef local = NULL;
|
|
225
|
-
appError = AXUIElementCopyAttributeValue(app, kAXFocusedUIElementAttribute, &local);
|
|
226
|
-
appSecure = local && isSecureField((AXUIElementRef)local);
|
|
227
|
-
if (local) CFRelease(local);
|
|
228
|
-
CFArrayRef fields = copySecureFields(app);
|
|
229
|
-
count = CFArrayGetCount(fields);
|
|
230
|
-
CFRelease(fields); CFRelease(app);
|
|
231
|
-
}
|
|
232
|
-
printf("{\"locked\":%s,\"canUnlock\":%s,\"loginwindowFound\":%s,\"globalFocusError\":%d,\"globalSecureFocus\":%s,\"applicationFocusError\":%d,\"applicationSecureFocus\":%s,\"secureFieldCount\":%ld}",
|
|
91
|
+
// Read-only; no wake, focus, field contents, screenshots, or input events.
|
|
92
|
+
printf("{\"locked\":%s,\"canUnlock\":%s,\"loginwindowFound\":%s,\"inputMethod\":\"space-password\"}",
|
|
233
93
|
locked ? "true" : "false", AXIsProcessTrusted() && CGPreflightPostEventAccess() ? "true" : "false",
|
|
234
|
-
|
|
235
|
-
appSecure ? "true" : "false", (long)count);
|
|
94
|
+
loginWindowPID() ? "true" : "false");
|
|
236
95
|
}
|
|
237
96
|
|
|
238
97
|
int main(int argc, const char *argv[]) {
|
|
@@ -252,42 +111,25 @@ int main(int argc, const char *argv[]) {
|
|
|
252
111
|
NSString *password = [[NSString alloc] initWithBytes:bytes length:count encoding:NSUTF8StringEncoding];
|
|
253
112
|
wipe(bytes, sizeof(bytes));
|
|
254
113
|
if (!password || [password rangeOfCharacterFromSet:NSCharacterSet.controlCharacterSet].location != NSNotFound) return fail("password");
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (!
|
|
259
|
-
AXUIElementRef field = NULL;
|
|
260
|
-
field = focusPasswordField();
|
|
261
|
-
// Shift wakes the display but can leave the clock/wallpaper layer visible.
|
|
262
|
-
// Space reveals the sign-in controls without submitting a password.
|
|
263
|
-
if (!field) {
|
|
264
|
-
if (!lockState(&locked) || !locked) return fail("changed");
|
|
265
|
-
if (!key(49, 0, NULL, 0)) return fail("focus");
|
|
266
|
-
NSDate *deadline = [NSDate dateWithTimeIntervalSinceNow:6];
|
|
267
|
-
while (!field && deadline.timeIntervalSinceNow > 0) {
|
|
268
|
-
usleep(100000);
|
|
269
|
-
field = focusPasswordField();
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
if (!field) return fail("focus");
|
|
273
|
-
if (!stillFocused(field) || !key(0, kCGEventFlagMaskCommand, NULL, 0) ||
|
|
274
|
-
!stillFocused(field) || !key(51, 0, NULL, 0)) { CFRelease(field); return fail("changed"); }
|
|
114
|
+
if (!wakePasswordPrompt()) return fail(lockState(&locked) ? "focus" : "session");
|
|
115
|
+
if (!lockState(&locked)) return fail("session");
|
|
116
|
+
// Saver dismissal, Touch ID or a manual unlock may finish during wake.
|
|
117
|
+
if (!locked) { printStatus(NO); return 0; }
|
|
275
118
|
UniChar characters[1024] = {0};
|
|
276
119
|
NSUInteger length = password.length;
|
|
277
|
-
if (length > 1024)
|
|
120
|
+
if (length > 1024) return fail("password");
|
|
278
121
|
[password getCharacters:characters range:NSMakeRange(0, length)];
|
|
279
122
|
password = nil;
|
|
280
123
|
BOOL sent = YES;
|
|
281
|
-
// One Unicode scalar per event avoids event-string truncation.
|
|
282
|
-
//
|
|
124
|
+
// One Unicode scalar per event avoids event-string truncation. Verify the
|
|
125
|
+
// same user's locked console immediately before every password event.
|
|
283
126
|
for (NSUInteger i = 0; i < length;) {
|
|
284
127
|
NSUInteger units = CFStringIsSurrogateHighCharacter(characters[i]) && i + 1 < length && CFStringIsSurrogateLowCharacter(characters[i + 1]) ? 2 : 1;
|
|
285
|
-
if (!
|
|
128
|
+
if (!lockedKey(0, 0, &characters[i], units)) { sent = NO; break; }
|
|
286
129
|
i += units;
|
|
287
130
|
}
|
|
288
131
|
wipe(characters, sizeof(characters));
|
|
289
|
-
if (!sent || !
|
|
290
|
-
CFRelease(field);
|
|
132
|
+
if (!sent || !lockedKey(36, 0, NULL, 0)) return fail("changed");
|
|
291
133
|
for (int i = 0; i < 50; i++) {
|
|
292
134
|
usleep(100000);
|
|
293
135
|
if (!lockState(&locked)) return fail("session");
|